Skip to content

feat: add CodeAct plugin for code-based agent interaction#2034

Closed
agent-of-mkmeral wants to merge 1 commit intostrands-agents:mainfrom
mkmeral:feat/codeact
Closed

feat: add CodeAct plugin for code-based agent interaction#2034
agent-of-mkmeral wants to merge 1 commit intostrands-agents:mainfrom
mkmeral:feat/codeact

Conversation

@agent-of-mkmeral
Copy link
Copy Markdown
Contributor

Summary

Implements the CodeAct paradigm as a vended Plugin (P2 from the sandbox design doc). Instead of JSON tool calling, the agent writes Python code that calls tools as async functions.

No sandbox dependency — runs locally in host process (Phase 1 per design doc). Sandbox integration can be layered on later.

What's in this PR

Source (2 new files, ~570 lines)

  • src/strands/vended_plugins/codeact/__init__.py — module exports
  • src/strands/vended_plugins/codeact/codeact_plugin.py — full implementation

Tests (2 new files, ~740 lines)

  • tests/strands/vended_plugins/codeact/__init__.py
  • tests/strands/vended_plugins/codeact/test_codeact_plugin.py — 64 tests

How it works

from strands import Agent
from strands.vended_plugins.codeact import CodeActPlugin

agent = Agent(
    tools=[shell, calculator],
    plugins=[CodeActPlugin()],
)

result = agent("Calculate squares of 1-10 and sum them")

The plugin registers two hooks:

  1. BeforeInvocationEvent — injects CodeAct system prompt + tool function signatures
  2. AfterInvocationEvent — parses code blocks from response, executes locally, feeds results back via event.resume

Key features

  • Plugin architecture — uses @hook decorators on a Plugin subclass
  • Tool wrappers — generates async wrappers for each agent tool (agent.tool.X())
  • Persistent namespace — variables persist across code execution turns
  • AST validation — blocks exec(), eval(), compile(), dangerous __dunder__ access
  • Import restrictions — configurable allowlist (defaults to safe stdlib modules)
  • Max iterations — safety limit (default: 10 rounds)
  • Error feedback — errors feed back as observations for self-correction
  • final_answer() — clean termination mechanism

Design decisions

Decision Rationale
Vended plugin (not core) Follows existing pattern (vended_plugins/skills/, vended_plugins/steering/)
@hook decorators Uses the Plugin system's auto-discovery, no manual hook registration
event.resume for loop Native to the hook system — no custom agent loop needed
record_direct_tool_call=False Tool calls within CodeAct code shouldn't pollute message history
asyncio.new_event_loop() Creates fresh loop per execution to avoid event loop conflicts
Namespace __ns__ update pattern Copies locals back to namespace for cross-turn persistence

Test results

  • 64 new tests
  • 1828 existing tests ✅ (0 regressions)

Related

cc @mkmeral

Implements the CodeAct paradigm as a vended Plugin that replaces standard
JSON tool calling with code-based orchestration. The model generates Python
code that calls tools as async functions, and the plugin executes the code
locally and feeds results back via AfterInvocationEvent.resume.

Key features:
- Plugin-based architecture using @hook decorators
- BeforeInvocationEvent: injects CodeAct instructions + tool signatures
- AfterInvocationEvent: parses code blocks, executes, sets resume
- Persistent namespace across turns for state accumulation
- Tool wrappers that route through agent.tool.X() (record_direct_tool_call=False)
- final_answer() termination mechanism
- AST-level code validation (blocks exec/eval/compile/dangerous dunders)
- Import restrictions with configurable allowed modules
- Max iteration safety limit (default: 10)
- Error feedback loop for self-correction

No sandbox dependency - executes locally in host process (Phase 1).
Sandbox integration can be added later.

64 new tests, all passing. 1828 existing tests, 0 regressions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant