Background
Issue #28 landed the trajectory_events table, the TrajectoryRecorder type, the orchestrator hook, and the /trace + /resume slash commands. But the recorder is never actually constructed in the production code path — it sits as Option<Arc<TrajectoryRecorder>> defaulting to None on every Orchestrator built in `crates/forge_app/src/app.rs`.
Net effect: `/trace` always reports "No trajectory events recorded" because nothing populates the table.
What's blocking
The recorder needs an `Arc`. The natural place to build it is at the same site where the Orchestrator is constructed (`crates/forge_app/src/app.rs:175-184`). But `ForgeApp` is bounded on `S: Services + EnvironmentInfra<...>`, not `+ TrajectoryRepo`. Adding the bound has too much blast radius — `agent_executor.rs` and other call sites of `ForgeApp::chat` would each need updating.
Proposed fix
Inject the recorder from one layer up — `crates/forge_api/src/forge_api.rs`, where `self.infra: Arc` is already bounded on `+ TrajectoryRepo` (added in #28). Add a builder method on ForgeApp that takes `Option<Arc>`, and have ForgeAPI::chat construct it before calling ForgeApp::chat.
Sketch:
```rust
// In forge_api.rs::API::chat:
let trajectory_repo: Arc = self.infra.clone();
let initial_seq = trajectory_repo
.next_seq_for(&chat.conversation_id.to_string(), agent_id.as_str())
.await
.unwrap_or(0);
let recorder = Arc::new(TrajectoryRecorder::new(
trajectory_repo,
chat.conversation_id.to_string(),
agent_id.as_str().to_string(),
None,
initial_seq,
));
self.app().chat_with_recorder(agent_id, chat, Some(recorder)).await
```
Acceptance
Background
Issue #28 landed the trajectory_events table, the TrajectoryRecorder type, the orchestrator hook, and the /trace + /resume slash commands. But the recorder is never actually constructed in the production code path — it sits as
Option<Arc<TrajectoryRecorder>>defaulting to None on every Orchestrator built in `crates/forge_app/src/app.rs`.Net effect: `/trace` always reports "No trajectory events recorded" because nothing populates the table.
What's blocking
The recorder needs an `Arc`. The natural place to build it is at the same site where the Orchestrator is constructed (`crates/forge_app/src/app.rs:175-184`). But `ForgeApp
` is bounded on `S: Services + EnvironmentInfra<...>`, not `+ TrajectoryRepo`. Adding the bound has too much blast radius — `agent_executor.rs` and other call sites of `ForgeApp::chat` would each need updating.Proposed fix
Inject the recorder from one layer up — `crates/forge_api/src/forge_api.rs`, where `self.infra: Arc` is already bounded on `+ TrajectoryRepo` (added in #28). Add a builder method on ForgeApp that takes `Option<Arc>`, and have ForgeAPI::chat construct it before calling ForgeApp::chat.
Sketch:
```rust
// In forge_api.rs::API::chat:
let trajectory_repo: Arc = self.infra.clone();
let initial_seq = trajectory_repo
.next_seq_for(&chat.conversation_id.to_string(), agent_id.as_str())
.await
.unwrap_or(0);
let recorder = Arc::new(TrajectoryRecorder::new(
trajectory_repo,
chat.conversation_id.to_string(),
agent_id.as_str().to_string(),
None,
initial_seq,
));
self.app().chat_with_recorder(agent_id, chat, Some(recorder)).await
```
Acceptance
` (callers of `ForgeApp::chat` unchanged)