Code Support Agent¶
Delegate coding work to Claude Code and Codex — the two coding CLIs —
from inside a LazyBridge agent, then let them collaborate.
lazytools.connectors.code_support gives each CLI two integration modes and a
collaboration pipeline that combines them.
| Agent | CLI mode | MCP mode | Guide |
|---|---|---|---|
| Claude Code | claude_code |
claude_code_mcp |
Claude Code |
| Codex | codex |
codex_mcp |
Codex |
| Collaboration | — | — | Collaboration (build_cli_collaboration) |
Status & install
Status: alpha. The CLI-mode tools (claude_code, codex,
build_cli_collaboration) are stdlib-only (subprocess, json, shutil):
claude_code_mcp / codex_mcp) additionally
need the mcp extra:
The package is lazytoolkit (installed from GitHub — see Install); the import root is lazytools. The two
CLIs are not Python packages — install them separately and make sure
claude / codex are on PATH:
- Claude Code — https://docs.anthropic.com/claude-code
- Codex — https://github.com/openai/codex
Two modes, one idea¶
Both agents run as subprocesses — not Python libraries, not HTTP APIs. What differs is the relationship between your agent and the CLI:
CLI mode (claude_code, codex) |
MCP mode (claude_code_mcp, codex_mcp) |
|
|---|---|---|
| Relationship | the CLI is the agent | the CLI exposes a tool surface; your agent orchestrates it |
| One call | a whole delegated task → result string | one MCP tool invocation |
| Built on | subprocess.run (stdlib) |
the MCP connector — needs [mcp] |
| Use when | you want to delegate a task | you want the CLI's primitives inside your own loop |
Pick CLI mode to hand off "do this task and tell me the result". Pick MCP mode
when you want your own agent to drive the CLI's individual tools (Claude's
Read/Edit/Bash, or Codex's codex/codex-reply) step by step.
At a glance¶
from lazybridge import Agent, LLMEngine
from lazytools.connectors.code_support import claude_code_mcp
# allow= is REQUIRED (deny-by-default). Patterns match the namespaced name.
reader = claude_code_mcp(allow=["claude_code.Read", "claude_code.Glob"])
agent = Agent(engine=LLMEngine("claude-opus-4-8"), tools=[reader])
Startup check¶
from lazytools.connectors.code_support import check_clis_available
check_clis_available() # {"claude": True, "codex": False}
shutil.which for both CLIs — call it at startup to fail fast on a missing CLI
instead of discovering it at the first tool call.
Timeouts — set tool_timeout=None¶
LLMEngine's tool_timeout wraps each tool call in asyncio.wait_for. But the
CLI-mode functions run subprocess.run(...) in a thread pool, and cancelling
the coroutine does not interrupt that thread — the subprocess keeps running
(orphaned) until its own timeout fires. So let the subprocess own the
deadline:
If you do want an engine-level ceiling, set tool_timeout strictly greater
than the per-call timeout (e.g. tool_timeout=320 with timeout=300).
Security & safety¶
- Read-only by construction. The CLI-mode tools
claude_code/codexare read/plan-only — write mode is not an argument an orchestrating LLM could choose (Claude:Read,Grep,Glob— no Bash; Codex:-s read-only).
Writes: CodeWriteTools¶
Writes (file edits + command execution) live behind a gated provider you must construct explicitly — the same safety model as the Gmail send tools:
from lazytools.connectors.code_support import CodeWriteTools, claude_code
writer = CodeWriteTools(base_dir="/path/to/project") # mandatory sandbox root
agent = Agent(engine=..., tools=[claude_code, writer])
writer.confirm_write() # a human approves exactly ONE write call
agent("fix the failing test") # the agent may now call claude_code_write once
- Capability, not argument — if you don't pass the writer in
tools=, the LLM cannot write, full stop. base_dirsandbox — every write call'scwdmust resolve inside it; escapes raiseCodeWriteBlocked(and never burn a confirmation grant).- One-shot confirmation (default) — each
confirm_write()authorizes exactly one write call; grants can be task-scoped (task_id=). For autonomous pipelines passrequire_confirmation=Falseand rely on the sandbox plus a git checkout. - Codex writes keep the git rail —
codex_skip_git_checkdefaults toFalsefor the writer (unlike the harmless read-only default). - Output is labelled — all CLI results return
{"result": ..., "content_is_untrusted": true}; treat repo-derived text as third-party content (prompt-injection surface). - MCP mode is deny-by-default.
claude_code_mcp/codex_mcprequire anallow=/deny=filter, exactly likeMCP.stdio— and Claude Code's MCP server has no per-call confirmation of its own, so the allow-list is your primary control. - Secrets stay with the CLIs. Auth is owned by each CLI's own login
(
~/.claude/.credentials.json/ANTHROPIC_API_KEYfor Claude,codex loginfor Codex), inherited via the environment; the connector never reads, rewrites, or echoes tokens. - Errors are returned, not raised (CLI mode). Missing CLI, non-zero exit, and
timeouts come back as
"[claude_code] …"/"[codex] …"strings so the model can recover instead of crashing the run.
See also¶
- Claude Code — CLI + MCP modes, full reference.
- Codex — CLI + MCP modes, full reference.
- Collaboration — the two agents working together.
- Tools overview — every connector at a glance.
- MCP — the connector the MCP mode is built on.
- Safety — gating dangerous tools.