Strands Code
Strands Code is a conversational coding CLI in the spirit of Claude Code and Codex CLI, built on the Strands harness. The user issues a series of asks; the CLI plans tasks and orchestrates subagents and tool calls to fulfill them. Its home turf is software engineering: speccing features, implementing them, writing and executing tests — with the agent able to "talk" to source code and do deep research when needed.
Core Value: A single ask — spec it, build it, test it, open the PR — completes end to end without the user leaving the conversation.
- Tech stack: Python >=3.10,
uvtoolchain, Strands SDK + harness — pad the CLI, don't fork the platform - AWS posture: AWS-optional, never AWS-required;
agentcorestays an optional extra with lazy imports - Compatibility: no upper pins during the experimental phase; resync upstream deliberately, not continuously
- Python >=3.10 - All library code in
strands_code_agent/, all tests intests/, example inexamples/ - None - No TypeScript, JavaScript, or other languages detected in the repo
- Python >=3.10 (classifiers cover 3.10–3.13; observed dev interpreter is Python 3.14.7)
- uv
- Lockfile: present (
uv.lock, ~755 KB, pinsstrands-agents 1.57.0,strands-harness 0.1.2,smolagents 1.26.0,boto3 1.43.100,pytest 9.1.1)
- strands-agents 1.57.0 - Agent runtime;
CodeAgentsubclassesstrands.Agent(strands_code_agent/code_agent.py),@tooldecorator definespython_repl(strands_code_agent/python_environments/base.py) - strands-harness 0.1.2 - CLI direction only (planned
create_harness()composition perREADME.md); not imported by library code yet - smolagents 1.26.0 - Sandboxed execution backend;
SandboxedPythonInterpreterwrapssmolagents.local_python_executor.LocalPythonExecutor(strands_code_agent/python_environments/local_sandboxed.py) - Jinja2 3.1.6 - System-prompt templating (
CODE_PREAMBLE_TEMPLATE,TEMP_DIR_TEMPLATE,DOMAIN_SPECIFIC_DOC_TEMPLATEinstrands_code_agent/code_agent.py) - Rich 15.0.0 - Terminal rendering in
CodeAgentCallbackHandler(strands_code_agent/callback_handler.py:Syntax,JSON,Markdown,Pretty,Console) - PyYAML 6.0.3 - YAML strategy, capability, provider, and memory config parse (
strands_code_cli/routing/core.py,strands_code_cli/model/model_capabilities.py,strands_code_cli/entry/provider_config.py) - pytest >=8 (locked 9.1.1) - Test runner;
integrationmarker deselected by default via[tool.pytest.ini_options]inpyproject.toml - numpy / pandas / matplotlib / seaborn / scipy (dev dependency group) - Required at test runtime because toolkit wiring tests execute real plotting/analysis code (noted in
pyproject.tomlcomment) - hatchling - Build backend (
[build-system]inpyproject.toml) - uv 0.9.26 - Sync/install workflow (
uv syncperREADME.md) - GitHub Actions
publish.yml-pip install build+python -m buildon release
- strands-agents >=0.1.0 (locked 1.57.0) - Without it
CodeAgentand thepython_repltool cannot exist - smolagents >=1.0.0 (locked 1.26.0) - Powers the default
SandboxedPythonInterpreter(import allowlisting, timeouts) - Jinja2 >=3.0 - System-prompt assembly; removing it breaks
CodeAgent.__init__ - Rich >=13.0 - Callback handler rendering only; safe to make optional for headless use
- PyYAML >=6.0 - YAML strategy, capability, provider, and memory config parse in
strands_code_cli/ - boto3 >=1.35.0 (locked 1.43.100, optional
agentcoreextra) - Only import isstrands_code_agent/python_environments/agentcore.py; local-only installs never touch it - botocore (via boto3) - Monkey-patched in
strands_code_agent/solution_user_agent.pyto appendAWSSOLUTION/SO0353/v0.4.0to User-Agent; imported at package init (strands_code_agent/__init__.py)
- No
.envfiles, noos.environ/os.getenvreads anywhere instrands_code_agent/- all tuning is constructor kwargs (region,timeout_seconds,tmp_dir,toolkits,modelvia**kwargstostrands.Agent) - AWS credentials resolve through the standard boto3 chain (env vars, shared config, IAM role) - implicit, never read directly in code
CodeAgent(tmp_dir=True)creates a per-agent directory viatempfile.mkdtemp(dir='/tmp')and advertises it in the system prompt (strands_code_agent/code_agent.py)pyproject.toml- Project metadata, dependencies, optionalagentcoreextra, dev group, pytest config; no[tool.hatch]overrides (hatchling defaults)uv.lock- Pinned dependency tree.github/workflows/publish.yml- Release-to-PyPI build pipeline (checkout, setup-python 3.11,python -m build,pypa/gh-action-pypi-publish)- No
tsconfig.json,.nvmrc,.python-version, or Dockerfiles detected; lint/format config lives in[tool.ruff]inpyproject.toml
- Python >=3.10 with
uv(uv syncreproduces project-local.venv) - Dev/test extras:
numpy,pandas,matplotlib,seaborn,scipy(test-only runtime needs) - Optional:
pip install .[agentcore]+ AWS credentials forAgentCorePythonInterpreter - Published as a PyPI library (
strands-code-agent 0.4.0,hatchling.build); consumers embedCodeAgentin their own process - No server, container, or hosting target in this repo; no deployment manifests
snake_case.pyfor all modules:code_agent.py,callback_handler.py,document_code.py,local_sandboxed.py,local_exec.py- Test files mirror the module under test:
test_<module>.pyin mirroredtests/subpackages (e.g.tests/agent/test_code_agent.pyforstrands_code_agent/code_agent.py). - Use
snake_casefor all functions and methods:get_documentation,format_function,extract_imports,get_import_string,execute_code,clear_state,format_message - Private module helpers use a leading underscore:
_parse_frontmatter,_extract_links,_get_source,_make_stream(test helper intests/agent/test_agentcore_python_interpreter.py),_make_agent(test helper intests/agent/test_code_agent.py) - Private methods on classes use a leading underscore:
PythonInterpreter.get_toolinternals,SandboxedPythonInterpreter._init_executor,AgentCorePythonInterpreter._ensure_session,_run_code,_stop_session,_build_initialization - Use
snake_casefor locals and parameters:authorized_imports,initialization_code,domain_specific_code,timeout_seconds,stdout_label,stderr_label - Use
UPPER_SNAKE_CASEfor module-level constants:CODE_AGENT_INSTRUCTIONSinstrands_code_agent/code_agent.py,CODE_PREAMBLE_TEMPLATE,TEMP_DIR_TEMPLATE,DOMAIN_SPECIFIC_DOC_TEMPLATE,DEFAULT_CODE_AGENT_CALLBACK_HANDLER,VISUALIZATION_TOOLKIT,DATA_ANALYSIS_TOOLKITinstrands_code_agent/toolkits.py,STDOUT_LABEL,STDERR_LABELinstrands_code_agent/python_environments/base.py,EXTRA_BUILTINSinstrands_code_agent/python_environments/local_sandboxed.py,SOLUTION_UAinstrands_code_agent/solution_user_agent.py - Test-local helper names use leading underscore plus descriptive name:
_sample_func,_SampleClassintests/agent/test_code_agent.py - Use
PascalCasefor all classes:CodeAgent(strands_code_agent/code_agent.py),Toolkit(strands_code_agent/toolkits.py),PythonInterpreter(strands_code_agent/python_environments/base.py),SandboxedPythonInterpreter,ExecPythonInterpreter,AgentCorePythonInterpreter,CodeAgentCallbackHandler - Use
PascalCasefor test classes grouping a unit under test:TestCodeAgentSystemPrompt,TestPythonInterpreterExecution,TestGetImportString— one class per behavior area, not per method
- Ruff governs lint + format: run
uv run ruff check strands_code_cli tests scriptsanduv run ruff format --check strands_code_cli tests scripts. Config lives in[tool.ruff]inpyproject.toml(target py310,force-exclude,strands_code_agent/excluded for upstream-merge safety). Style is ruff defaults: 88 columns, double quotes. - Observed style: 4-space indentation, double quotes for strings, single quotes accepted inside strings, blank line between top-level definitions, section banners in tests (
# ---...---comment blocks). - Keep imports at the top of the file, one import per line;
from __future__ import annotationsfirst where present. - Ruff is enforced with
select = ["E", "F", "I"](pyflakes + pycodestyle errors + isort) andignore = ["E501"](the formatter owns line length); complexity stays with radon/xenon. Waivers are per-line# noqawith a reason only — no file-level ignores. - The
# noqa: F401re-export precedent instrands_code_agent/__init__.pystill applies; the rignoqa: E402sibling-import precedent inscripts/uattest/shows the reason-citing form.
- None. Use absolute imports rooted at the package everywhere (e.g.
from strands_code_agent.toolkits import Toolkit). Do not use relative imports. Exception:scripts/uattest/rig scripts insert their own directory viasys.pathand use absolute sibling imports (loader-safe: the contract tests load the driver viaimportlibwith no package path; rig scripts have no package path).
- Return
(stdout, stderr)tuples from interpreters instead of raising: bothSandboxedPythonInterpreter.execute_code(strands_code_agent/python_environments/local_sandboxed.py) andExecPythonInterpreter.execute_code(strands_code_agent/python_environments/local_exec.py) catchExceptionand putstr(e)in the stderr slot. Thepython_repltool instrands_code_agent/python_environments/base.pythen formats the tuple intoSTDOUT:/STDERR:observation text, or"Code executed successfully."when both are empty. - Salvage partial output on failure:
SandboxedPythonInterpreter.execute_codereadsself.executor.state["_print_outputs"]for print output captured before the exception. - Raise
TypeErrorfor wrong input types where the caller is a programmer, not the agent:PythonInterpreter()cannot be instantiated (abstractABCwith@abstractmethodinstrands_code_agent/python_environments/base.py);get_documentationraisesTypeError(f"Expected a function or class, got {type(obj)}")(strands_code_agent/document_code.py). - Convert low-level introspection failures into actionable
ValueErrorwith chaining:_get_sourceinstrands_code_agent/python_environments/agentcore.pycatches(OSError, TypeError)frominspect.getsourceand raisesValueError(... remote execution ...) from e. - Fail soft on missing data in read paths: return formatted "not found" strings with suggestions instead of raising; return empty defaults (
{},[]) when optional data is absent. - Log-and-continue only for best-effort cleanup:
AgentCorePythonInterpreter._stop_sessioncatchesExceptionand callslogger.warning(...)soclear_state/close/__del__never raise.
- Use module-level
logger = logging.getLogger(__name__)and%s-style args:logger.info("Started AgentCore session: %s", self._session_id)(strands_code_agent/python_environments/agentcore.py). This is currently the only library module that logs. - Use
warnings.catch_warnings()withsimplefilter("ignore")around noisy third-party introspection, not logging:SandboxedPythonInterpreter._init_executor(strands_code_agent/python_environments/local_sandboxed.py). - Use
rich(Syntax,JSON,Markdown,Pretty,Console) only instrands_code_agent/callback_handler.pyfor human-facing REPL transcript rendering. Never useprint()in library code;printappears only inside test snippets and example strings executed by the interpreters. - Do not add new logging to the
python_replobservation path — agent-visible output flows through the(stdout, stderr)tuple andSTDOUT_LABEL/STDERR_LABELprefixes.
- Explain why a non-obvious workaround exists, not what the code does:
# smolagents' LocalPythonExecutor introspects all attributes ... via getattr(), which triggers DeprecationWarnings(strands_code_agent/python_environments/local_sandboxed.py);# Python builtins that smolagents' executor doesn't allow-list by defaultaboveEXTRA_BUILTINS;# Salvage any print output captured before the error. - Mark test intent with Given/When-free one-liners only where the assertion is not self-evident:
# Should still have a working REPL,# extra_tool + python_repl,# SandboxedPythonInterpreter always includes EXTRA_BUILTINS. - Do not add narrative or design-deliberation comments; keep comments to one or two lines.
- Use triple-double-quoted docstrings on every public module-adjacent function, class, and method. First line is an imperative summary; longer units add
Args:/Returns:sections in plain text (not Sphinx or Google style strictly). - Document the agent-tool contract in the tool docstring itself:
python_repl(code: str) -> strinstrands_code_agent/python_environments/base.pydocumentscodeas "The Python code to execute". - Keep
Toolkitfield docs in the class docstring (strands_code_agent/toolkits.py) and interpreter session semantics in the class docstring (AgentCorePythonInterpreter); mirror that pattern for new public classes.
| Component | Responsibility | File |
|---|---|---|
| CodeAgent | strands.Agent subclass; merges toolkits into authorized imports, preamble, system prompt, and additional_functions; registers python_repl tool |
strands_code_agent/code_agent.py |
| Toolkit | Dataclass bundling libraries, initialization_code, usage_instructions, domain_specific_code for the REPL |
strands_code_agent/toolkits.py |
| PythonInterpreter (ABC) | clear_state / execute_code contract plus get_tool() that exposes the python_repl tool with STDOUT/STDERR observation formatting |
strands_code_agent/python_environments/base.py |
| ExecPythonInterpreter | Unrestricted in-process exec() interpreter with dict state |
strands_code_agent/python_environments/local_exec.py |
| SandboxedPythonInterpreter | Default interpreter; allowlisted imports + timeout via smolagents LocalPythonExecutor |
strands_code_agent/python_environments/local_sandboxed.py |
| AgentCorePythonInterpreter | Remote Bedrock AgentCore code-interpreter sessions (lazy boto3 session, user-code serialization) | strands_code_agent/python_environments/agentcore.py |
| Prompt builders | get_documentation (signature + docstring rendering) and get_import_string / extract_imports (AST-based) feed the system prompt and import allowlist |
strands_code_agent/document_code.py, strands_code_agent/imports.py |
| CodeAgentCallbackHandler | Rich terminal rendering of messages, code tool uses, and tool results | strands_code_agent/callback_handler.py |
| Solution user-agent hook | Monkeypatches botocore.session.Session.__init__ to append AWSSOLUTION/SO0353 tag; imported for side effect on package init |
strands_code_agent/solution_user_agent.py |
| Helpers | image_to_base64, get_response_metrics (Bedrock token/cost summary) |
strands_code_agent/utils.py |
CodeAgentdoes not orchestrate tool chains; it gives the model one primary action interface (python_repl) and injects domain capability as importable Python symbols.- Toolkits are declarative capability bundles: allowlist entries, startup preamble, prompt guidance, and documented callables are merged at construction time (
strands_code_agent/code_agent.py:87-137). - Execution backends are swappable behind the
PythonInterpreterABC via thepython_interpreter_classconstructor argument.
- Purpose: Owns the public API (
CodeAgent,Toolkit) and builds the agent: system prompt assembly, import authorization, REPL tool registration. - Location:
strands_code_agent/code_agent.py,strands_code_agent/toolkits.py,strands_code_agent/__init__.py - Contains: Prompt templates (
CODE_AGENT_INSTRUCTIONS,CODE_PREAMBLE_TEMPLATE,TEMP_DIR_TEMPLATE,DOMAIN_SPECIFIC_DOC_TEMPLATE), toolkit merge logic,tools=[python_repl]wiring. - Depends on: Execution layer (interpreters), prompt-builder helpers, upstream
strands.Agent. - Used by: the
strands_code_cliconversational CLI (thestrands-codeconsole script). - Purpose: Runs model-generated Python and returns STDOUT/STDERR observations; controls isolation (unrestricted vs allowlisted vs remote).
- Location:
strands_code_agent/python_environments/ - Contains:
PythonInterpreterABC plus three implementations (local_exec.py,local_sandboxed.py,agentcore.py). - Depends on:
smolagents(sandboxed),boto3(AgentCore, optional extra),strands.tooldecorator for tool exposure. - Used by:
CodeAgentonly (constructed incode_agent.py:131-138). - Purpose: Human-facing terminal output and cost telemetry; no control-flow role.
- Location:
strands_code_agent/callback_handler.py,strands_code_agent/utils.py - Contains: Rich-based message/code/result rendering;
get_response_metricstoken/cost helper;image_to_base64. - Depends on:
rich. - Used by:
CodeAgentdefaultcallback_handler; example scripts.
- REPL state persists across tool calls within one interpreter lifetime but
CodeAgentdocuments reset between user messages;clear_state()re-runs the preamble (local) or drops the remote session (AgentCore). - Per-agent
tmp_dircreated under/tmpwhentmp_dir=Trueand advertised in the system prompt (strands_code_agent/code_agent.py:118-120).
- Purpose: Single public entry point that turns declarative toolkits into a capable coding agent.
- Examples:
strands_code_agent/code_agent.py - Pattern: Constructor-assembled system prompt + tool injection; extra behavior via
tools=[...]and Strands**kwargs(model, etc.). - Purpose: The unit of domain capability: allowlist + preamble + prompt guidance + documented symbols.
- Examples:
strands_code_agent/toolkits.py(VISUALIZATION_TOOLKIT,DATA_ANALYSIS_TOOLKIT) - Pattern: Plain data merged by
CodeAgent.__init__;domain_specific_codesymbols are auto-imported (get_import_string) and auto-documented (get_documentation) into the prompt. - Purpose: Uniform execution seam:
execute_code(code) -> (stdout, stderr)plus Strands@tool-wrappedpython_repl. - Examples:
strands_code_agent/python_environments/base.py,local_exec.py,local_sandboxed.py,agentcore.py - Pattern: Strategy —
CodeAgent(python_interpreter_class=...)selects the backend;python_interpreter_kwargsforwarded.
- Location:
strands_code_agent/__init__.py - Triggers: Any consumer
import(also executessolution_user_agentside effect). - Responsibilities: Public surface; keep
__all__in sync when adding top-level exports. - Location:
strands_code_agent/code_agent.py:77-149 - Triggers: Consumer code (e.g.
agent = CodeAgent(toolkits=[...])thenagent("...")viastrands.Agent.__call__). - Responsibilities: Toolkit merge, prompt assembly, interpreter construction, tool registration.
- There is no
__main__.pyor service handler; the CLI ships as thestrands-codeconsole script (strands_code_cli.entry.main:main).
- Threading: Single-threaded synchronous model. No threads, asyncio, or workers in this repo; long runs are bounded by per-call
timeout_seconds(default 180 inCodeAgent, 60 in interpreters) and AgentCoresession_timeout_seconds(default 900). - Global state:
DEFAULT_CODE_AGENT_CALLBACK_HANDLERmodule-level singleton shared by allCodeAgent()instances that omitcallback_handler(strands_code_agent/code_agent.py:36);botocore.session.Session.__init__monkeypatched at import (strands_code_agent/solution_user_agent.py:18). - Circular imports: None detected. Dependency direction is one-way:
code_agent.py→ interpreters/helpers;python_environments/*→base.py. - AWS-optional:
boto3is an optionalagentcoreextra;AgentCorePythonInterpreteris importable from the top level but only usable with credentials. Defaults stay local.
- Local:
exec()errors →str(e)in stderr (local_exec.py); sandbox errors → stderr plus salvaged_print_outputsstdout (local_sandboxed.py:39-49). - Remote: session-stop failures only logged as warnings, never raised (
agentcore.py:143-147); unserializable domain symbols raiseValueErrorwith guidance at build time (agentcore.py:11-20). - Prompt builders:
extract_importsreturns empty set onSyntaxError;format_functionfalls back to(...)signatures (imports.py:6-22,document_code.py:5-21).
No project skills found. Add skills to any of: .claude/skills/, .agents/skills/, .cursor/skills/, .github/skills/, or .codex/skills/ with a SKILL.md index file.
Before using Edit, Write, or other file-changing tools, start work through a GSD command so planning artifacts and execution context stay in sync.
Use these entry points:
$gsd-quickfor small fixes, doc updates, and ad-hoc tasks$gsd-debugfor investigation and bug fixing$gsd-execute-phasefor planned phase work
Do not make direct repo edits outside a GSD workflow unless the user explicitly asks to bypass it.
Profile not yet configured. Run
$gsd-profile-userto generate your developer profile. This section is managed bygenerate-claude-profile-- do not edit manually.