Skip to content

An agent node on the Claude CLI now delegates instead of refusing - #58

Merged
Shashankss1205 merged 1 commit into
mainfrom
feat/claude-cli-agent-node-delegation
Aug 3, 2026
Merged

An agent node on the Claude CLI now delegates instead of refusing#58
Shashankss1205 merged 1 commit into
mainfrom
feat/claude-cli-agent-node-delegation

Conversation

@Shashankss1205

Copy link
Copy Markdown
Collaborator

AgentNode used to raise AgentConfigError when handed the Claude CLI: the CLI has no tool-calling wire format, so GraphARC cannot run its own gated loop over it. That was accurate and useless — the CLI is a complete agent, and refusing to use it meant the subscription backend could do no agentic work inside a graph at all.

It now hands the whole loop to Claude Code's headless agent, reusing the same machinery grapharc agent --executor claude-cli already used. That path's subprocess-and-parse core is extracted as delegate_task(), so the CLI command and the node cannot drift on what actually gets spawned.

This is a real widening of the trust boundary and is treated as one:

  • DelegatedToolUseWarning at construction, naming each thing given up — every tool Claude Code has, not checked by this graph's policy, not confined by the sandbox, token figure self-reported. Its own category, so -W error::grapharc.harness.agent.DelegatedToolUseWarning restores the old refusal for anyone who wants it.
  • Every trace event from a delegated node carries executor=delegated, plus the permission mode and governed_by. A warning is gone by the time someone reads the run back; without this, a JSONL reader six months later sees an agent node that completed and cannot tell that its tool calls never reached the permission engine. That is the project's own claim about its traces, so the marking is the part that keeps it true.

One thing found by running it rather than reasoning about it: omitting --allowedTools does not mean "every tool". It leaves Claude Code's own gating in place, and headless there is nobody to approve a Write — the first delegated node came back reporting it could not create the file. Only --permission-mode bypassPermissions means what "everything Claude Code has" was meant to mean, so that is what a delegated node runs under, named in the warning and recorded in the trace. delegate_task takes the two as separate arguments because conflating them is the trap.

Detection is on _llm_type == "grapharc-claude-cli", not on "does this model lack bind_tools" — ScriptedChatModel lacks it too, and matching that way would have silently delegated every mocked agent in the suite to a real subprocess. A test pins that.

Verified live end to end: a node given get_model("claude-cli") created a file in its workspace using Claude Code's own tools (2 turns, target_met), and the trace shows executor=delegated with permission_mode=bypassPermissions on every event. Docs updated in README's limits and stdlib's module docstring, both of which asserted the old refusal. Full suite green on 3.12; ruff clean.

`AgentNode` used to raise `AgentConfigError` when handed the Claude CLI: the
CLI has no tool-calling wire format, so GraphARC cannot run its own gated loop
over it. That was accurate and useless — the CLI *is* a complete agent, and
refusing to use it meant the subscription backend could do no agentic work
inside a graph at all.

It now hands the whole loop to Claude Code's headless agent, reusing the same
machinery `grapharc agent --executor claude-cli` already used. That path's
subprocess-and-parse core is extracted as `delegate_task()`, so the CLI command
and the node cannot drift on what actually gets spawned.

This is a real widening of the trust boundary and is treated as one:

- `DelegatedToolUseWarning` at construction, naming each thing given up — every
  tool Claude Code has, not checked by this graph's policy, not confined by the
  sandbox, token figure self-reported. Its own category, so
  `-W error::grapharc.harness.agent.DelegatedToolUseWarning` restores the old
  refusal for anyone who wants it.
- **Every trace event from a delegated node carries `executor=delegated`**, plus
  the permission mode and `governed_by`. A warning is gone by the time someone
  reads the run back; without this, a JSONL reader six months later sees an
  agent node that completed and cannot tell that its tool calls never reached
  the permission engine. That is the project's own claim about its traces, so
  the marking is the part that keeps it true.

One thing found by running it rather than reasoning about it: omitting
`--allowedTools` does **not** mean "every tool". It leaves Claude Code's own
gating in place, and headless there is nobody to approve a Write — the first
delegated node came back reporting it could not create the file. Only
`--permission-mode bypassPermissions` means what "everything Claude Code has"
was meant to mean, so that is what a delegated node runs under, named in the
warning and recorded in the trace. `delegate_task` takes the two as separate
arguments because conflating them is the trap.

Detection is on `_llm_type == "grapharc-claude-cli"`, not on "does this model
lack `bind_tools`" — `ScriptedChatModel` lacks it too, and matching that way
would have silently delegated every mocked agent in the suite to a real
subprocess. A test pins that.

Verified live end to end: a node given `get_model("claude-cli")` created a file
in its workspace using Claude Code's own tools (2 turns, target_met), and the
trace shows `executor=delegated` with `permission_mode=bypassPermissions` on
every event. Docs updated in README's limits and `stdlib`'s module docstring,
both of which asserted the old refusal. Full suite green on 3.12; ruff clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Shashankss1205
Shashankss1205 merged commit c9dfb98 into main Aug 3, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants