Skip to content

Claude Code as a provider, a task program and a crew seat - #2505

Merged
santoshkumarradha merged 16 commits into
app/v3from
app/claude-code-agent
Nov 8, 2026
Merged

santoshkumarradha merged 16 commits into
app/v3from
app/claude-code-agent

Conversation

@santoshkumarradha

@santoshkumarradha santoshkumarradha commented Nov 7, 2026 •

Copy link
Copy Markdown
Member

What

The person's own signed-in Claude Code, driven the way T3 Code drives it (claude over stream-json with the control protocol, CLAUDE_CODE_ENTRYPOINT), in three places:

  • Task program claude (internal/claudecode): a task handed to Claude Code on a branch; messages typed on the task reach it mid-run; outward commands (push, PR, release, publish) refused on the wire via --disallowedTools, over the person's own hooks.
  • Provider claude-code (internal/claudemodel, kind agent, no key): a conversation or crew member on claude-code/opus|sonnet|haiku is a live Claude Code session in its folder. Toolless small jobs (titles, summaries) are one-shot claude -p, so codeaf with only Claude Code connected works with no OpenRouter key.
  • Worker seat: a Worker pinned to Claude Code hands tasks to the claude program.

T3 Code parity, checked live:

  • Mid-turn messages: codeaf cuts the request on a steer; the transport now keeps the cut turn running for 3s and writes the correction into it. Live: a steer 7s into sleep 12 && … did not stop it; both files made, one answer.
  • Resume: the session id is kept in the conversation folder and --resumed after an engine restart (live: code word recalled across two engine processes).
  • Switching models: moving off Claude Code and back hands it what it missed.

Wire: providers.list gains kind agent; keys.check reports Found Claude Code <version> · signed in · <plan>; keys.set connects with no key.

Desk: Agent-Field/codeaf-desk#325.

Verified live (this laptop, Claude Max plan)

connect → models.list → conversation turn (file written) → crew member started on claude-code/haiku reports finished → steer mid-turn → resume across restart → Claude Code calls mcp__codeaf__manual and mcp__codeaf__propose_task; the task is proposed and queued.

codeaf's tools on Claude Code

codeaf's belt is served to the Claude Code session as an in-process SDK MCP server (--mcp-config type sdk, answered over the control protocol's mcp_message), less the tools it has its own of. A call becomes a tool_calls delta, so codeaf's turn loop runs it with its own row; the next request answers the waiting MCP call and the same Claude turn continues. Pictures on the newest message go as image blocks (unit-tested, not yet live).

Claude Code's own steps, in codeaf's format

Each step Claude Code runs itself (Bash, Read, Edit, MultiEdit, Write, Grep, Glob, LS, WebFetch, WebSearch) is handed up the stream when its result arrives, as a codeaf_steps delta → provider.StreamStepsRan → session.recordStepsRan, and recorded as codeaf's own call and result (bash, read, edit, …) with its took line. So the page draws rows, a reopened chat redraws them, and the next model reads them in its history. Nothing is run twice. Ids carry claude_code_, so the transport never re-tells Claude Code its own history.

Canvas and skills

Conversations and tasks on Claude Code load the skills kept for codeaf and other programs (.codeaf/skills, .agents/skills, Codex, Cursor…) as a session plugin (--plugin-dir, links, not copies), shown as codeaf:<name>. The turn's request context carries CODEAF_AGENT_DID to the session, so a canvas it makes belongs to the conversation. Live: "mock up a landing page for Ember on a canvas" → codeaf:workaf-canvas loaded → canvas new/frame add/put/look → canvas member is the conversation's did:key:….

Fixed along the way (found live)

  • Every turn after the first was mis-addressed: the last user-role message (codeaf's session note) was sent as "the person now says". Now every user-role message since the last answer goes, notes as notes.
  • False compaction: Claude Code's result usage is the turn's sum (4 steps ≈ 199k) and codeaf read it as context fill. Now the last call's context is reported.
  • A correction delivered twice: the 10s first-reply watch cut the adopting request during a long command and re-asked. An adopting stream now speaks at once, and a re-ask with nothing new only watches.
  • codeaf connect claude-code / chat /connect treated it as a key service (bare row, 128k window, local chat refused turns). Both now run the app's connect.
  • First-run setup in the chat lists Claude Code only where claude is installed.

Verified live

  • App wire: steps rows + turn 2 reads them; canvas with DID.
  • Terminal (codeaf chat): multi-turn, steering lands once (queued_command ×1), file-change line.
  • Relay (codeaf serve + chat --at, own relay): turns 1–3, steering, manual row, context ≈ 51k.

Not done / limits

  • No approval UI: edits are accepted; only outward commands are refused.
  • Effort setting does not reach Claude Code; its tool and skill lists are fixed at session start.
  • /rewind does not put back files Claude Code changed (stated in the manual).
  • --host over ssh not exercised (the box's ssh codeaf predates this).

Checks

make pr-ready BASE=origin/app/v3 — pass on c6b458a22 (laws + touched packages incl. tui3, session, provider, codeaf).

🤖 Generated with Claude Code

Adds the claude program (internal/claudecode): codeaf starts the installed,
signed-in claude CLI in the run's worktree with stream-json output, turns its
tool calls into steps and its result into the ending. codeaf holds no Claude
credential and sends nothing to Anthropic itself.
internal/claudeagent speaks the Agent SDK's control protocol to the person's
own claude CLI: stream-json both ways, initialize, can_use_tool, interrupt,
set_model, resume. The claude program now hears the task's messages mid-run,
interrupts on stop, refuses pushes and publishes even over a user hook, and
ends when Claude Code has answered everything and gone quiet.
… on the person's own sign-in

A conversation or crew member on claude-code/* is a live Claude Code
session in its folder, driven over stream-json as T3 Code does: a
correction typed mid-turn is written into the running turn instead of
cutting it, the session id is kept so a restart resumes it, and a
conversation moved off and back hands Claude Code what it missed.
Toolless small jobs are one-shot claude -p calls, so a codeaf with only
Claude Code connected still names and summarises. A Worker pinned to
Claude Code hands tasks to the claude program. providers.list gains the
agent kind; keys.check reports the version and plan.
… fits the fixed prefix

Reading Claude Code's sign-in starts the program, and providers.list sits
in front of models.list, so the reading now happens in the background
and provider.changed redraws the row when it lands. The claude program's
guide moves to programguide beside the others and is cut, with the
sec guide's description, so four programs fit the prefix budget.
A conversation seated on Claude Code now holds codeaf's belt as
mcp__codeaf__* tools answered through the control protocol, so it can
propose a task, reach crew members and read the manual. Pictures on the
newest message go to it as image blocks. The manual says what is still
missing: approvals, effort, a tool list fixed at session start.
…ix its context weight

Each step Claude Code runs itself is handed up the stream when it finishes
and recorded as codeaf's own call and result, so the page draws it, the
conversation keeps it and the next model reads it. codeaf's skills reach
it as a session plugin, so it makes canvases, and its commands carry the
conversation's agent identity. Its input is reported as what the turn's
last call read, not the turn's sum, which had made codeaf compact a
three-line conversation.
A turn now gives Claude Code every user-role message since the last answer,
so codeaf's session note is no longer read as the person's words and their
real message is no longer dropped. `codeaf connect claude-code` and the
chat's /connect row run the app's connect: no key, three models with
Claude's window.
A request that takes over a running turn says so at once, so codeaf's
first-reply watch does not cut it during a long command; and one that
carries nothing new only watches the turn again, rather than sending the
person's newest message a second time.
It needs no key, so on a machine without the claude program its row could
only say it is missing; /connect still lists it everywhere. This keeps the
setup list flat at 40x10.
@santoshkumarradha
santoshkumarradha merged commit ba8d049 into app/v3 Nov 8, 2026
9 checks passed
@santoshkumarradha
santoshkumarradha deleted the app/claude-code-agent branch November 8, 2026 15:41
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.

1 participant