Skip to content

Session replay: Claude Code mapper — Task/Agent/Workflow fanout, inline sidechains, nested delegation #4815

Description

@vivekchand

Context

Claude Code is the highest-fanout runtime we support — memory + observed data confirm sessions with 20+ Agent/Task calls in this repo alone (top-level pendingWorkflowCount and pendingBackgroundAgentCount counters prove the harness tracks concurrent spawns).

Today the transcript viewer shows the parent turn with a single collapsed "Task" tool chip — the child's actual work (thinking, tools, turns) is never inlined. Users have to jump to the Subagents modal and open a second transcript. Nested delegation collapses entirely.

Substrate

  • Parent JSONL: ~/.claude/projects/<cwd-slug>/<session-uuid>.jsonl
  • Sub-agent JSONLs: <parent-dir>/<parent-uuid>/subagents/agent-*.jsonl, one file per child (../clawmetry-pro/clawmetry_pro/adapters/claude_code.py:78, :199)
  • Meta: <parent-dir>/<parent-uuid>/subagents/agent-<uuid>.meta.json — has description, agentType

Sub-agent model

Invoked via the Task tool (legacy) or the newer Agent tool. Context passed = the caller's input.prompt string. Reply comes back as a tool_result whose text is the child's final message. Child linkage back to parent via parentUuid, logicalParentUuid, sourceToolAssistantUUID, sourceToolUseID, isSidechain: true.

subagent_type: "fork" inherits the parent's full context (verified in Anthropic Agent tool docs).

Workflow model

The Workflow tool persists a workflow script; each agent it fans out shows up as a normal Task/Agent tool_use in the parent, plus its own sidechain file. No separate DAG table. Reconstruct DAG by grouping sidechains by sourceToolAssistantUUID.

Mode + approvals

  • Mode: {"type":"permission-mode","permissionMode":"default|acceptEdits|plan|bypassPermissions"} on every session, plus a per-turn permissionMode field on turns. bypassPermissions == YOLO. Separate mode events (normal/plan) reflect Plan Mode toggles mid-session.
  • Approvals: no discrete permission_request record. A tool_use followed by a tool_result = approved. A tool_use followed by a user message with toolDenialKind and no tool_result = denied. Denial reasons appear as top-level toolDenialKind, refusedUserMessageUuid. Model refusals appear as apiRefusalCategory / apiRefusalExplanation.
  • Edit-approve: capture as edit_diff when a second tool_use appears with different args after the same sourceToolUseID (see OSS-02 for schema).

Deliverable

Implement iter_replay_events(session_id) → Iterator[ReplayEvent] for the Claude family adapter (the family adapter is in OSS — Pro's claude_code.py extends it).

Output events per the canonical schema (OSS-01):

  • mode.changed on every permission-mode / mode event
  • llm.call / llm.response / thinking / tool.call / tool.result for the normal event stream
  • agent.spawn on every Task/Agent tool_use — payload includes description, subagent_type, prompt, child_session_id (from enumerating subagents/agent-*.jsonl and matching sourceToolAssistantUUID)
  • agent.return when the child's tool_result lands, with token_count, duration_ms, final_message_summary
  • workflow.start / workflow.stage / workflow.end when the Workflow tool is invoked — payload includes the workflow name + phases from the script's meta block
  • approval.requested / approval.decided per the OSS-02 mapping rules

Acceptance

  • 3 recorded fixtures replay in the new UI:
    1. A session with ≥1 Task spawn (child renders inline, expandable)
    2. A session with a Workflow fanout ≥5 agents (workflow swimlane renders above the transcript)
    3. A session with nested delegation depth ≥2 (child spawns grandchild — indent tree renders correctly)
  • pendingBackgroundAgentCount from the parent JSONL is surfaced as a workflow badge
  • Mode chip shows the correct value for a bypassPermissions fixture (red YOLO)
  • Approvals rail shows denied approvals with toolDenialKind as decision_reason

Depends on

  • OSS-01 (schema + endpoint + renderer skeleton)
  • OSS-02 (mode + approvals ingest + UI wiring)

Gotchas

  • Sidechain 40:1 fanout — enumerate lazily on the endpoint side; do not eagerly parse all sidechain JSONLs at ingest time. Store a child_session_ids: [] reference on the parent; parse-on-expand.
  • sourceToolAssistantUUID linking — the ONLY reliable way to tie a sidechain to the exact tool_use that spawned it. parentUuid chain within one file is not enough.
  • fork subagents inherit context — the child's first message is not the entire history; the UI should NOT re-render the parent context when opening a fork child.

References

  • Substrate + linking: ../clawmetry-pro/clawmetry_pro/adapters/claude_code.py:78-90, :199-210, :642-644
  • Existing Task cost-split logic: routes/sessions.py:1308 (query_cost_split_with_subagents), :4486 (query_events_with_subagents)
  • run_ledger.parent_task_id: clawmetry/local_store.py:545
  • Depth-1 grouping bug (nested spawns collapse): routes/sessions.py:2984 (api_delegation_tree)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions