From a160aa0bf243fcca0ca439ff02654d46d93f339a Mon Sep 17 00:00:00 2001 From: Noel Moreno Lemus Date: Wed, 18 Feb 2026 23:38:50 -0500 Subject: [PATCH 1/4] fix: resolve model before validation to avoid NoneType in CLI (fixes #47) Co-authored-by: Cursor --- src/dsagent/cli.py | 11 +++++++---- src/dsagent/cli/repl.py | 20 +++++++++++++------- src/dsagent/cli/run.py | 11 +++++++---- src/dsagent/utils/validation.py | 10 ++++++++-- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/dsagent/cli.py b/src/dsagent/cli.py index 0c5d3db..de374af 100644 --- a/src/dsagent/cli.py +++ b/src/dsagent/cli.py @@ -154,9 +154,12 @@ def main(): args = parser.parse_args() - # Validate model and API key configuration + # Resolve model (CLI flag, env, or fallback) before validation + from dsagent.config import get_default_model + effective_model = get_default_model(explicit=args.model) + try: - validate_configuration(args.model) + validate_configuration(effective_model) except ConfigurationError as e: print(f"Configuration Error: {e}", file=sys.stderr) sys.exit(1) @@ -189,7 +192,7 @@ def main(): if data_info: print(f"Data: {data_info}") print(f"Run Path: {context.run_path}") - print(f"Model: {args.model}") + print(f"Model: {effective_model}") if hitl_mode != HITLMode.NONE: print(f"HITL Mode: {hitl_mode.value}") if mcp_config_path: @@ -209,7 +212,7 @@ def main(): # Create and run agent with context agent = PlannerAgent( - model=args.model, + model=effective_model, workspace=context.run_path, # Use run-specific path max_rounds=args.max_rounds, verbose=not args.quiet, diff --git a/src/dsagent/cli/repl.py b/src/dsagent/cli/repl.py index 4f1e1fa..1112d4d 100644 --- a/src/dsagent/cli/repl.py +++ b/src/dsagent/cli/repl.py @@ -671,10 +671,13 @@ def main(): args = parser.parse_args() - # Validate configuration + # Resolve model (CLI flag, env, or fallback) before validation + from dsagent.config import get_default_model + from dsagent.utils.validation import validate_configuration + effective_model = get_default_model(explicit=args.model) + try: - from dsagent.utils.validation import validate_configuration - validate_configuration(args.model) + validate_configuration(effective_model) except Exception as e: console = Console() console.print(f"[red]Configuration Error: {e}[/red]") @@ -702,7 +705,7 @@ def main(): # Run the CLI cli = ConversationalCLI( workspace=Path(args.workspace), - model=args.model, + model=effective_model, session_id=args.session, hitl_mode=hitl_mode, enable_live_notebook=args.live_notebook, @@ -727,13 +730,16 @@ def run_chat(args) -> int: Returns: Exit code (0 for success) """ + from dsagent.config import get_default_model from dsagent.utils.validation import validate_configuration console = Console() - # Validate configuration + # Resolve model (CLI flag, env, or fallback) before validation + effective_model = get_default_model(explicit=args.model) + try: - validate_configuration(args.model) + validate_configuration(effective_model) except Exception as e: console.print(f"[red]Configuration Error: {e}[/red]") return 1 @@ -759,7 +765,7 @@ def run_chat(args) -> int: # Run the CLI cli = ConversationalCLI( workspace=Path(args.workspace), - model=args.model, + model=effective_model, session_id=getattr(args, 'session', None), hitl_mode=hitl_mode, enable_live_notebook=getattr(args, 'live_notebook', False), diff --git a/src/dsagent/cli/run.py b/src/dsagent/cli/run.py index 6d7ac90..e7d41fe 100644 --- a/src/dsagent/cli/run.py +++ b/src/dsagent/cli/run.py @@ -55,9 +55,12 @@ def run_task(args) -> int: """ console = Console() - # Validate model configuration + # Resolve model (CLI flag, env, or fallback) before validation + from dsagent.config import get_default_model + effective_model = get_default_model(explicit=args.model) + try: - validate_configuration(args.model) + validate_configuration(effective_model) except ConfigurationError as e: console.print(f"[red]Configuration Error: {e}[/red]") return 1 @@ -91,7 +94,7 @@ def run_task(args) -> int: if data_info: console.print(f"[cyan]Data:[/cyan] {data_info}") console.print(f"[cyan]Run Path:[/cyan] {context.run_path}") - console.print(f"[cyan]Model:[/cyan] {args.model}") + console.print(f"[cyan]Model:[/cyan] {effective_model}") if hitl_mode != HITLMode.NONE: console.print(f"[cyan]HITL Mode:[/cyan] {hitl_mode.value}") if mcp_config_path: @@ -111,7 +114,7 @@ def run_task(args) -> int: # Create and run agent agent = PlannerAgent( - model=args.model, + model=effective_model, workspace=context.run_path, max_rounds=args.max_rounds, verbose=not args.quiet, diff --git a/src/dsagent/utils/validation.py b/src/dsagent/utils/validation.py index e966a7a..1533450 100644 --- a/src/dsagent/utils/validation.py +++ b/src/dsagent/utils/validation.py @@ -238,15 +238,21 @@ def validate_model_name(model: str) -> None: ) -def validate_configuration(model: str) -> None: +def validate_configuration(model: Optional[str]) -> None: """Validate all configuration for running with the given model. Args: - model: The model name/identifier + model: The model name/identifier (must be non-empty; use get_default_model() + before calling if the value can be None) Raises: ConfigurationError: If any configuration is invalid """ + if not model or not isinstance(model, str): + raise ConfigurationError( + "Model name must be set. Use DSAGENT_DEFAULT_MODEL or LLM_MODEL, " + "or pass --model. Run 'dsagent init' to configure." + ) apply_llm_api_base(model) # validate_model_name(model) validate_api_key(model) From 3b1c26e92bfe0566935f981f5b21757729faabcd Mon Sep 17 00:00:00 2001 From: Noel Moreno Lemus Date: Wed, 18 Feb 2026 23:38:55 -0500 Subject: [PATCH 2/4] =?UTF-8?q?docs:=20add=20workflow=20rule=20and=20AGENT?= =?UTF-8?q?S.md=20(issue=20=E2=86=92=20branch=20=E2=86=92=20PR)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .cursor/rules/workflow-branch-pr.mdc | 29 ++++++++++++++++++++++++++++ AGENTS.md | 5 +++-- 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 .cursor/rules/workflow-branch-pr.mdc diff --git a/.cursor/rules/workflow-branch-pr.mdc b/.cursor/rules/workflow-branch-pr.mdc new file mode 100644 index 0000000..8170914 --- /dev/null +++ b/.cursor/rules/workflow-branch-pr.mdc @@ -0,0 +1,29 @@ +--- +description: Never commit directly to main; use issues, branch, fix, test, PR +alwaysApply: true +--- + +# Workflow: Issues, Branch, PR + +**Never make changes or commits directly on `main`.** + +For every bug or feature: + +1. **Document in GitHub** + Create or reference an issue in the project’s GitHub repo (bug report or feature request). + +2. **Create a branch** + From `main` (or `develop` if the project uses Git Flow): + `feature/*`, `bugfix/*`, `fix/*`, or `release/vX.Y.Z` as appropriate. + +3. **Implement and test** + Do all edits and commits on that branch. Run the test suite (`uv run pytest`) and fix any failures. + +4. **Push and open a PR** + Push the branch and open a Pull Request targeting `main` (or `develop`). + Link the PR to the issue (e.g. “Fixes #123”). + +5. **Merge after review** + Do not merge yourself unless the project allows it; wait for review/approval if required. + +For hotfixes or release bumps, use a dedicated branch (e.g. `fix/description` or `release/0.9.1`) and follow the same flow. diff --git a/AGENTS.md b/AGENTS.md index 34d8059..c31ce0b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,8 +26,9 @@ - New features and fixes should include or update tests; no explicit coverage target is defined. ## Commit & Pull Request Guidelines -- Follow Git Flow: branch from `develop` and PR back to `develop`. -- Branch names: `feature/*`, `bugfix/*`, `release/vX.Y.Z`, `hotfix/*`. +- **Do not commit directly to `main`.** For every change: document in a GitHub issue → create a branch → implement → test → push → open a PR. See `.cursor/rules/workflow-branch-pr.mdc`. +- Follow Git Flow: branch from `develop` (or `main`) and PR back to `develop`/`main`. +- Branch names: `feature/*`, `bugfix/*`, `fix/*`, `release/vX.Y.Z`, `hotfix/*`. - Commit messages use `type: short description` (e.g., `feat: add streaming API endpoint`). - PRs should include a clear description, pass tests, and update docs when needed. From 9cd51c104d3a386b3655a2a579086bead1f05c29 Mon Sep 17 00:00:00 2001 From: Noel Moreno Lemus Date: Wed, 18 Feb 2026 23:39:04 -0500 Subject: [PATCH 3/4] chore: bump version to 0.9.1 Co-authored-by: Cursor --- Dockerfile | 2 +- pyproject.toml | 2 +- src/dsagent/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0b2052e..315e4ec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ ARG INSTALL_LATEX=false # Labels LABEL maintainer="DSAgent Contributors" -LABEL version="0.9.0" +LABEL version="0.9.1" LABEL description="AI-powered autonomous agent for data science" # Set environment variables diff --git a/pyproject.toml b/pyproject.toml index cbbdba6..7803024 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "datascience-agent" -version = "0.9.0" +version = "0.9.1" description = "AI Agent with dynamic planning and persistent Jupyter kernel execution for data analysis" readme = "README.md" license = "MIT" diff --git a/src/dsagent/__init__.py b/src/dsagent/__init__.py index c3f2aea..a3591c2 100644 --- a/src/dsagent/__init__.py +++ b/src/dsagent/__init__.py @@ -62,7 +62,7 @@ MCPServerConfig = None # type: ignore _MCP_AVAILABLE = False -__version__ = "0.9.0" +__version__ = "0.9.1" __all__ = [ # Main classes From 88744b3dd8afbab94fb42c4d3eb30f63ba15b98a Mon Sep 17 00:00:00 2001 From: Noel Moreno Lemus Date: Wed, 18 Feb 2026 23:40:27 -0500 Subject: [PATCH 4/4] test: accept model=None default in ConversationalAgentConfig Co-authored-by: Cursor --- tests/test_conversational.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_conversational.py b/tests/test_conversational.py index 61d00b3..d7a699a 100644 --- a/tests/test_conversational.py +++ b/tests/test_conversational.py @@ -19,10 +19,10 @@ class TestConversationalAgentConfig: """Tests for ConversationalAgentConfig.""" def test_default_config(self): - """Test default configuration values.""" + """Test default configuration values. Model is resolved at runtime via get_default_model().""" config = ConversationalAgentConfig() - assert config.model == "gpt-4o" + assert config.model is None # resolved at runtime assert config.temperature == 0.3 assert config.max_tokens == 4096 assert config.code_timeout == 300