Background
#29 wired `TrajectoryRecorder` into the production code path via `ForgeAPI::chat` — every top-level chat now constructs a recorder and threads it to the orchestrator. #30 wired the parent orchestrator's view of subagent dispatches (Task tool calls produce `tool_call` + `tool_result` rows on the parent's trajectory).
But child agents themselves don't go through `ForgeAPI::chat`. The Task tool path is:
```
orch.execute_tool_calls
→ services.call(...)
→ ToolRegistry::execute_tool_call
→ AgentExecutor::execute // crates/forge_app/src/agent_executor.rs:50
→ let app = crate::ForgeApp::new(self.services.clone()); // line 79
→ app.chat(...) // line 80
```
`AgentExecutor` calls `ForgeApp::new` directly with no `with_trajectory_recorder`. So the child's internal tool calls (and any further nested subagents) never make it into `trajectory_events`.
What `/trace` of a parent conversation shows today:
- ✅ Parent's tool calls
- ✅ Parent's view of Task dispatches (`tool_call` / `tool_result` for the Task itself)
- ❌ Child's internal tool calls under the child's own `agent_id`
Proposed fix
Build a child recorder inside `AgentExecutor::execute` and pass it via `with_trajectory_recorder` before calling `chat`. Two design questions:
-
Where does AgentExecutor get an `Arc`?
- Option A: add a `+ TrajectoryRepo` bound to `AgentExecutor
` impl. Was rejected for `ForgeApp` due to blast radius (multiple callers); for `AgentExecutor` the only caller is `ToolRegistry` which is itself bounded on Services. Probably tractable here.
- Option B: thread the `Arc` through AgentExecutor's constructor, set by ForgeApp when it builds its tool registry. No new bound, but extra plumbing.
-
What's the `parent_agent_id` for the child recorder?
- The dispatching agent's id. Currently AgentExecutor doesn't know it — it's the agent that invoked the Task tool, which is the parent orchestrator's `self.agent.id`. Would need to be passed through `AgentExecutor::execute`.
Acceptance
Related
Background
#29 wired `TrajectoryRecorder` into the production code path via `ForgeAPI::chat` — every top-level chat now constructs a recorder and threads it to the orchestrator. #30 wired the parent orchestrator's view of subagent dispatches (Task tool calls produce `tool_call` + `tool_result` rows on the parent's trajectory).
But child agents themselves don't go through `ForgeAPI::chat`. The Task tool path is:
```
orch.execute_tool_calls
→ services.call(...)
→ ToolRegistry::execute_tool_call
→ AgentExecutor::execute // crates/forge_app/src/agent_executor.rs:50
→ let app = crate::ForgeApp::new(self.services.clone()); // line 79
→ app.chat(...) // line 80
```
`AgentExecutor` calls `ForgeApp::new` directly with no `with_trajectory_recorder`. So the child's internal tool calls (and any further nested subagents) never make it into `trajectory_events`.
What `/trace` of a parent conversation shows today:
Proposed fix
Build a child recorder inside `AgentExecutor::execute` and pass it via `with_trajectory_recorder` before calling `chat`. Two design questions:
Where does AgentExecutor get an `Arc`?
` impl. Was rejected for `ForgeApp` due to blast radius (multiple callers); for `AgentExecutor` the only caller is `ToolRegistry` which is itself bounded on Services. Probably tractable here.What's the `parent_agent_id` for the child recorder?
Acceptance
Related