diff --git a/run.json b/run.json index 571817525..5e29b7cdf 100644 --- a/run.json +++ b/run.json @@ -517,7 +517,7 @@ "kind": "running" }, "status_updated_at": "2026-05-22T19:21:12.018648Z", - "last_event_at": "2026-05-22T19:21:21.206770Z", + "last_event_at": "2026-05-22T19:23:32.954425Z", "pending_control": null, "checkpoints": [ { @@ -615,9 +615,9 @@ } }, { - "seq": 0, + "seq": 37, "checkpoint": { - "timestamp": "2026-05-22T19:23:27.472712Z", + "timestamp": "2026-05-22T19:23:32.951142Z", "current_node": "preflight_compile", "completed_nodes": [ "start", @@ -625,6 +625,76 @@ "preflight_compile" ], "node_retries": {}, + "context_values": { + "graph.goal": "# Unified Agent Transcript Events Implementation Plan\n\n> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` or `superpowers:executing-plans` to implement this plan task-by-task.\n\n**Goal:** Make the ordered Fabro event stream sufficient to recreate an API-mode agent session without adding a parallel transcript event family.\n\n**Architecture:** Extend existing `agent.message`, `agent.tool.started`, and `agent.tool.completed` event semantics. Messages are communication (`system`, `user`, `reasoning`, `agent`). Tool calls and tool results are actions, not messages. Persist only committed events; partial stream deltas, retries, and interrupted output are not replay sources.\n\n**Out of scope:** Request metadata, compaction semantics, and broad store refactors.\n\n---\n\n## Key Decisions\n\n- Use one shared Fabro transcript model in `fabro-types`; do not create parallel DTOs for events, API, store projection, and runtime history.\n- Treat reasoning as a first-class message kind, not a tool call and not part of the visible agent answer.\n- Keep model-role semantics (`kind`) separate from audit/source semantics (`source`).\n- Keep tool calls/results as enriched action lifecycle records.\n- Use event `seq` as the ordering source of truth.\n- Keep run/session lifecycle events for lifecycle only; transcript replay comes from `agent.message` and `agent.tool.*`.\n- Preserve provider replay payloads as structured parts, not strings.\n\n## Type Ownership\n\nPromote provider-neutral replay primitives from `fabro-llm` into `fabro-types`, then make `fabro-llm` import or re-export the canonical types.\n\nCanonical shared types:\n\n- `ContentPart`\n- `ThinkingData`\n- `ToolCall`\n- `ToolResult`\n- `TranscriptMessage`\n- `MessageKind`\n- `MessageSource`\n- `PairMessageRef`\n- existing `Principal` for actor attribution\n\nName the durable transcript type `TranscriptMessage`, not bare `Message`, to avoid import ambiguity with `fabro_agent::Message` and `fabro_llm::types::Message`. Do not add `AgentTranscriptPart` as a second `{ kind, data }` model if `ContentPart` can own the role. Event props must embed the canonical `ToolCall`, `ToolResult`, and `ContentPart` types directly. OpenAPI replacements should point generated API types at these canonical Rust types and include type identity / JSON parity tests.\n\n## Interface Changes\n\nAdd shared transcript types in `fabro-types`:\n\n```rust\nTranscriptMessage {\n id,\n turn_id,\n kind, // system | user | reasoning | agent\n source, // system_prompt | turn_input | followup | steer | pair | injected_system | injected_user | loop_detection\n actor: Option,\n pair: Option,\n content: Vec,\n provider,\n model,\n response_id,\n usage,\n}\n\nPairMessageRef {\n pair_id,\n message_id,\n client_message_id,\n}\n```\n\n`kind` captures provider/model-role semantics for replay. `source` captures audit/UI origin. Steering is a source, not a role: steering that currently replays to the LLM as user-role input must be stored as `kind=user, source=steer`.\n\nExtend existing durable events:\n\n- `agent.message`\n - Add `message: TranscriptMessage`.\n - This becomes the canonical replay source for committed system, user, reasoning, and agent messages.\n - Keep narrow `text`, `model`, `billing`, and `tool_call_count` fields until web/server/client consumers are migrated.\n- `agent.tool.started`\n - Add `tool_call: ToolCall`.\n - Add `turn_id` and `parent_message_id`.\n - Keep narrow `tool_name`, `tool_call_id`, and `arguments` fields until consumers are migrated.\n- `agent.tool.completed`\n - Add `tool_result: ToolResult`.\n - Add `turn_id`.\n - Keep narrow `tool_name`, `tool_call_id`, `output`, and `is_error` fields until consumers are migrated.\n\nProvider replay requirements:\n\n- OpenAI `openai_reasoning` and `openai_message` opaque items remain exact `ContentPart::Other` payloads.\n- Anthropic thinking and redacted thinking remain `ContentPart::Thinking` payloads with signatures preserved.\n- Gemini `thoughtSignature` remains `ToolCall.provider_metadata`.\n- Reasoning messages can contain cleartext, redacted, signed, encrypted, or opaque provider parts, but implementation must not collapse these into plain strings.\n\nIdentity requirements:\n\n- Add a canonical `MessageId` in `fabro-types`.\n- `fabro-agent::Session` mints a `TurnId` for every `run_single_input()` invocation unless the caller supplies one.\n- Ask Fabro passes its existing API `TurnId` into the agent session before processing.\n- Workflow API-mode stages let the agent session mint a `TurnId`.\n- The assistant/agent message id is minted before emitting tool calls. Tool calls emitted from that response use `parent_message_id = agent_message.id`.\n\n## Implementation Tasks\n\n### 1. Add Typed Event Contracts\n\nModify:\n\n- `lib/crates/fabro-types/src/run_event/agent.rs`\n- `lib/crates/fabro-types/src/run_event/session.rs`\n- `lib/crates/fabro-types/src/run_event/mod.rs`\n- `docs/public/api-reference/fabro-api.yaml` if exposed wire shapes change\n\nTasks:\n\n- Move or re-home provider-neutral `ContentPart`, `ThinkingData`, `ToolCall`, and `ToolResult` into `fabro-types`.\n- Add canonical `TranscriptMessage`, `MessageKind`, `MessageSource`, and `PairMessageRef` types in `fabro-types`.\n- Extend `AgentMessageProps` to carry the canonical message payload.\n- Extend tool started/completed props to carry canonical tool call/result payloads plus turn/message linkage.\n- Keep serde defaults where needed so old event payloads continue to deserialize.\n- Add `fabro-api` replacement tests for type identity and JSON parity when OpenAPI schemas map to canonical Rust types.\n\n### 2. Emit Committed Messages From `fabro-agent`\n\nModify:\n\n- `lib/crates/fabro-agent/src/types.rs`\n- `lib/crates/fabro-agent/src/session.rs`\n- `lib/crates/fabro-agent/src/history.rs`\n\nTasks:\n\n- Replace or extend the narrow assistant-only `AgentEvent::AssistantMessage` path with a general committed `AgentEvent::Message`.\n- Emit `kind=system, source=system_prompt` after the exact rendered system prompt is assembled.\n- Emit `kind=user, source=turn_input` after skill expansion/wrapping, using the exact user message sent to the model.\n- Emit `kind=user, source=followup` for follow-up inputs.\n- Emit `kind=user, source=steer` for steering-as-user.\n- Emit `kind=user, source=loop_detection` for loop-detection steering.\n- Emit `kind=system, source=injected_system` for injected system messages.\n- Emit `kind=user, source=injected_user` for injected user-role messages.\n- Emit `kind=user, source=pair` for pair chat messages that enter LLM history, with `pair` populated.\n- Emit `kind=system, source=pair` for pair join/leave or other pair system messages that enter LLM history, with `pair` populated.\n- Emit `kind=reasoning` only for completed provider reasoning blocks that must be preserved for replay, preserving exact structured parts.\n- Emit `kind=agent` after provider `Finish`, using the completed response content.\n- Do not emit committed messages for deltas, retries, or interrupted partial output.\n- Ensure all message events carry `turn_id`, `source`, and optional `actor`/`pair` metadata where applicable.\n\n### 3. Enrich Tool Action Events\n\nModify:\n\n- `lib/crates/fabro-agent/src/session.rs`\n- `lib/crates/fabro-agent/src/tool_execution.rs`\n- provider adapters only where extra metadata is not currently surfaced\n\nTasks:\n\n- Preserve `ToolCall.tool_type`, `raw_arguments`, and `provider_metadata`.\n- Preserve `ToolResult` structured output, error state, and supported media/artifact fields.\n- Link every tool call to the owning agent message with `parent_message_id`.\n- Mint the agent message id before tool execution so tool events can link correctly.\n- Keep tool calls/results out of message events.\n\n### 4. Persist Unified Events In Both API Paths\n\nModify:\n\n- `lib/crates/fabro-workflow/src/handler/llm/api.rs`\n- `lib/crates/fabro-workflow/src/event/convert.rs`\n- `lib/crates/fabro-workflow/src/event/names.rs`\n- `lib/crates/fabro-server/src/server/handler/sessions.rs`\n\nTasks:\n\n- Convert the unified agent message event through the existing workflow `Event::Agent` path.\n- Convert Ask Fabro/server session agent events into the same durable `agent.message` and `agent.tool.*` shapes.\n- Keep `run.session.created`, `run.session.turn.started`, and terminal turn events as lifecycle events.\n- Keep old `run.session.user_message`, `run.session.assistant_message`, and `run.session.tool_call.*` projection support until all producers and consumers are migrated.\n- Prefer a shared event persistence helper for workflow and server session paths so redaction behavior is consistent.\n- Avoid creating new transcript-specific event families.\n\nMigration order:\n\n1. Add canonical types and event deserialization support.\n2. Update projection to read both old narrow run-session events and new unified agent events.\n3. Switch workflow and Ask Fabro producers to emit unified events while retaining compatibility fields.\n4. Update web/server/client consumers to prefer unified payloads with narrow-field fallback.\n5. Only then consider deprecating narrow transcript-bearing run-session events.\n\n### 5. Define Pair Transcript Relationship\n\nModify:\n\n- `lib/crates/fabro-workflow/src/steering_hub.rs`\n- `lib/crates/fabro-types/src/pair.rs`\n- `lib/crates/fabro-server/src/server/handler/sessions.rs`\n- web consumers of pair transcript events\n\nTasks:\n\n- Treat `agent.pair.user_message` and `agent.pair.system_message` as UI/audit projection events only.\n- Do not use pair transcript events as replay-authoritative session history.\n- For any pair message that affects LLM history, emit the corresponding canonical `agent.message` event with `source=pair` and a populated `PairMessageRef`.\n- Store pair user chat as `kind=user, source=pair`.\n- Store pair join/leave or other pair system items that enter model context as `kind=system, source=pair`.\n- Keep existing pair API transcript types as projections over pair events and canonical message references, not as a second replay model.\n\n### 6. Rebuild Session Projection From Events\n\nModify:\n\n- `lib/crates/fabro-store/src/run_sessions.rs`\n- `lib/crates/fabro-types/src/session.rs`\n- `lib/crates/fabro-agent/src/history.rs`\n\nTasks:\n\n- Project runtime context from ordered `agent.message` and `agent.tool.*` events scoped by envelope `session_id`.\n- Preserve provider-specific reasoning, opaque provider items, response ids, usage, and tool metadata.\n- Keep best-effort fallback projection for legacy narrow session events.\n- Ignore pair transcript events for replay except as a legacy fallback path; canonical `agent.message` with `source=pair` is the replay source.\n- Ensure `Session::from_record()` can hydrate without dropping provider parts needed for same-provider replay.\n- Preserve injected history sources: rendered system prompt, wrapped user input, follow-up input, steering, injected system messages, injected user-role messages, and loop-detection steering.\n\n### 7. Redaction And Security Policy\n\nModify:\n\n- workflow event persistence path\n- server session event persistence path\n- event redaction utilities\n\nTasks:\n\n- Define raw replay fields explicitly: provider opaque parts, raw tool arguments, provider metadata, and structured tool outputs.\n- Apply one shared redaction policy before durable storage for both workflow and server sessions.\n- Preserve replay-critical opaque provider fields unless they match an existing secret redaction rule.\n- Do not omit fields needed for same-provider replay silently; if a field must be redacted, preserve the shape and mark the value redacted.\n- Add tests covering raw tool arguments and provider metadata through both persistence paths.\n\n### 8. Consumer Compatibility\n\nModify:\n\n- web event consumers that currently read narrow `properties.text`\n- web pair transcript consumers that read `agent.pair.*`\n- server/API projections that expose session detail or event detail\n- generated clients if OpenAPI changes\n\nTasks:\n\n- Keep narrow compatibility fields in emitted events until consumers are updated.\n- Update consumers to prefer `properties.message` and fall back to narrow fields.\n- Keep pair transcript rendering backed by pair projection events, while ensuring session replay and hydration consume canonical `agent.message` events.\n- Add web/server tests that render both old and new event shapes.\n- Document the deprecation path for narrow transcript fields after consumer migration.\n\n## Test Plan\n\n- `fabro-types`: serde round trips for `agent.message`, enriched `agent.tool.started`, and enriched `agent.tool.completed`.\n- Type ownership:\n - canonical `ToolCall`, `ToolResult`, `ContentPart`, `TranscriptMessage`, usage, and event prop types are reused rather than duplicated\n - OpenAPI replacement tests prove type identity and JSON parity where API schemas expose these shapes\n- `fabro-agent`: committed system/user/reasoning/agent messages emit once, while partial deltas and interrupted streams do not create committed messages.\n- `fabro-agent`: followups, steering-as-user, injected system messages, and loop-detection steering emit committed messages with the correct `kind`, `source`, and `turn_id`.\n- Role/source mapping:\n - steering-as-user emits `kind=user, source=steer`\n - loop-detection steering emits `kind=user, source=loop_detection`\n - injected user-role messages emit `kind=user, source=injected_user`\n - pair user chat emits `kind=user, source=pair` with `PairMessageRef`\n - pair join/leave context emits `kind=system, source=pair` with `PairMessageRef`\n- Identity/linkage: tool calls include the parent agent message id minted before tool execution.\n- Provider replay:\n - OpenAI encrypted reasoning and opaque message items survive event replay.\n - Anthropic thinking signatures survive event replay.\n - Gemini thought signatures survive enriched tool call replay.\n- `fabro-store`: session projection from event `seq` order recreates runtime history including provider parts and tool metadata.\n- Pair projection: pair transcript events render in the pair UI/audit surface but do not create duplicate replay history when the canonical `source=pair` message exists.\n- Migration: old narrow run-session events and new unified events both hydrate session detail without duplicate transcript entries.\n- `fabro-server`: Ask Fabro stores the wrapped model input, not only the raw UI question.\n- Redaction: workflow and server session persistence apply the same redaction behavior to raw arguments, provider metadata, and tool outputs.\n- Consumer compatibility: existing UI/server consumers render old narrow fields and new unified message payloads.\n- API conformance: OpenAPI-generated Rust/TypeScript clients still match the spec after schema updates.\n\n## Acceptance Criteria\n\n- A completed API-mode session can be reconstructed from the event stream without losing committed system, user, reasoning, agent, tool call, or tool result state.\n- New transcript state is stored through existing semantic events, not a separate transcript event family.\n- Tool calls remain actions, not messages.\n- Partial output remains non-authoritative for replay.\n- The implementation introduces one canonical set of replay types, not duplicated event/API/runtime DTOs.\n- Steering, pair, and injected inputs preserve provider-role semantics in `kind` and audit/source semantics in `source`.\n- Pair transcript events are UI/audit projection events, not a replay-authoritative transcript source.\n- Ask Fabro migration is backward compatible for existing session events and projections.\n", + "outcome": "succeeded", + "thread.toolchain.current_node": "preflight_compile", + "current_node": "preflight_compile", + "internal.fidelity": "compact", + "command.output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126", + "failure_signature": "", + "internal.node_visit_count": 1, + "internal.retry_count.preflight_compile": 0, + "internal.retry_count.start": 0, + "internal.work_dir": "/home/daytona/workspace/fabro", + "thread.start.current_node": "toolchain", + "internal.thread_id": "toolchain", + "internal.retry_count.toolchain": 0, + "graph.model_stylesheet": "\n * { model: claude-opus-4-7; }\n ", + "failure_class": "", + "internal.run_id": "01KS8J49FYC6K2VTG89FR8479E", + "graph.rankdir": "LR" + }, + "node_outcomes": { + "start": { + "status": "succeeded", + "usage": null + }, + "preflight_compile": { + "status": "succeeded", + "context_updates": { + "command.output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126" + }, + "notes": "Script completed: cargo check -q --workspace 2>&1", + "usage": null + }, + "toolchain": { + "status": "succeeded", + "context_updates": { + "command.output": "blob://sha256/fc14b2ba2d770e5cd3169df7a29525c962adfc4cfa3097b9098c63ebd61a748c" + }, + "notes": "Script completed: command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", + "usage": null + } + }, + "next_node_id": "preflight_lint", + "git_commit_sha": "ba7577ffcc45f7baa2a6094c5692cde4556d435d", + "node_visits": { + "start": 1, + "toolchain": 1, + "preflight_compile": 1 + } + }, + "diff": { + "summary": { + "files_changed": 0, + "additions": 0, + "deletions": 0 + } + } + }, + { + "seq": 0, + "checkpoint": { + "timestamp": "2026-05-22T19:25:54.003577Z", + "current_node": "preflight_lint", + "completed_nodes": [ + "start", + "toolchain", + "preflight_compile", + "preflight_lint" + ], + "node_retries": {}, "context_values": { "internal.retry_count.toolchain": 0, "internal.fidelity": "compact", @@ -632,18 +702,20 @@ "graph.goal": "# Unified Agent Transcript Events Implementation Plan\n\n> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` or `superpowers:executing-plans` to implement this plan task-by-task.\n\n**Goal:** Make the ordered Fabro event stream sufficient to recreate an API-mode agent session without adding a parallel transcript event family.\n\n**Architecture:** Extend existing `agent.message`, `agent.tool.started`, and `agent.tool.completed` event semantics. Messages are communication (`system`, `user`, `reasoning`, `agent`). Tool calls and tool results are actions, not messages. Persist only committed events; partial stream deltas, retries, and interrupted output are not replay sources.\n\n**Out of scope:** Request metadata, compaction semantics, and broad store refactors.\n\n---\n\n## Key Decisions\n\n- Use one shared Fabro transcript model in `fabro-types`; do not create parallel DTOs for events, API, store projection, and runtime history.\n- Treat reasoning as a first-class message kind, not a tool call and not part of the visible agent answer.\n- Keep model-role semantics (`kind`) separate from audit/source semantics (`source`).\n- Keep tool calls/results as enriched action lifecycle records.\n- Use event `seq` as the ordering source of truth.\n- Keep run/session lifecycle events for lifecycle only; transcript replay comes from `agent.message` and `agent.tool.*`.\n- Preserve provider replay payloads as structured parts, not strings.\n\n## Type Ownership\n\nPromote provider-neutral replay primitives from `fabro-llm` into `fabro-types`, then make `fabro-llm` import or re-export the canonical types.\n\nCanonical shared types:\n\n- `ContentPart`\n- `ThinkingData`\n- `ToolCall`\n- `ToolResult`\n- `TranscriptMessage`\n- `MessageKind`\n- `MessageSource`\n- `PairMessageRef`\n- existing `Principal` for actor attribution\n\nName the durable transcript type `TranscriptMessage`, not bare `Message`, to avoid import ambiguity with `fabro_agent::Message` and `fabro_llm::types::Message`. Do not add `AgentTranscriptPart` as a second `{ kind, data }` model if `ContentPart` can own the role. Event props must embed the canonical `ToolCall`, `ToolResult`, and `ContentPart` types directly. OpenAPI replacements should point generated API types at these canonical Rust types and include type identity / JSON parity tests.\n\n## Interface Changes\n\nAdd shared transcript types in `fabro-types`:\n\n```rust\nTranscriptMessage {\n id,\n turn_id,\n kind, // system | user | reasoning | agent\n source, // system_prompt | turn_input | followup | steer | pair | injected_system | injected_user | loop_detection\n actor: Option,\n pair: Option,\n content: Vec,\n provider,\n model,\n response_id,\n usage,\n}\n\nPairMessageRef {\n pair_id,\n message_id,\n client_message_id,\n}\n```\n\n`kind` captures provider/model-role semantics for replay. `source` captures audit/UI origin. Steering is a source, not a role: steering that currently replays to the LLM as user-role input must be stored as `kind=user, source=steer`.\n\nExtend existing durable events:\n\n- `agent.message`\n - Add `message: TranscriptMessage`.\n - This becomes the canonical replay source for committed system, user, reasoning, and agent messages.\n - Keep narrow `text`, `model`, `billing`, and `tool_call_count` fields until web/server/client consumers are migrated.\n- `agent.tool.started`\n - Add `tool_call: ToolCall`.\n - Add `turn_id` and `parent_message_id`.\n - Keep narrow `tool_name`, `tool_call_id`, and `arguments` fields until consumers are migrated.\n- `agent.tool.completed`\n - Add `tool_result: ToolResult`.\n - Add `turn_id`.\n - Keep narrow `tool_name`, `tool_call_id`, `output`, and `is_error` fields until consumers are migrated.\n\nProvider replay requirements:\n\n- OpenAI `openai_reasoning` and `openai_message` opaque items remain exact `ContentPart::Other` payloads.\n- Anthropic thinking and redacted thinking remain `ContentPart::Thinking` payloads with signatures preserved.\n- Gemini `thoughtSignature` remains `ToolCall.provider_metadata`.\n- Reasoning messages can contain cleartext, redacted, signed, encrypted, or opaque provider parts, but implementation must not collapse these into plain strings.\n\nIdentity requirements:\n\n- Add a canonical `MessageId` in `fabro-types`.\n- `fabro-agent::Session` mints a `TurnId` for every `run_single_input()` invocation unless the caller supplies one.\n- Ask Fabro passes its existing API `TurnId` into the agent session before processing.\n- Workflow API-mode stages let the agent session mint a `TurnId`.\n- The assistant/agent message id is minted before emitting tool calls. Tool calls emitted from that response use `parent_message_id = agent_message.id`.\n\n## Implementation Tasks\n\n### 1. Add Typed Event Contracts\n\nModify:\n\n- `lib/crates/fabro-types/src/run_event/agent.rs`\n- `lib/crates/fabro-types/src/run_event/session.rs`\n- `lib/crates/fabro-types/src/run_event/mod.rs`\n- `docs/public/api-reference/fabro-api.yaml` if exposed wire shapes change\n\nTasks:\n\n- Move or re-home provider-neutral `ContentPart`, `ThinkingData`, `ToolCall`, and `ToolResult` into `fabro-types`.\n- Add canonical `TranscriptMessage`, `MessageKind`, `MessageSource`, and `PairMessageRef` types in `fabro-types`.\n- Extend `AgentMessageProps` to carry the canonical message payload.\n- Extend tool started/completed props to carry canonical tool call/result payloads plus turn/message linkage.\n- Keep serde defaults where needed so old event payloads continue to deserialize.\n- Add `fabro-api` replacement tests for type identity and JSON parity when OpenAPI schemas map to canonical Rust types.\n\n### 2. Emit Committed Messages From `fabro-agent`\n\nModify:\n\n- `lib/crates/fabro-agent/src/types.rs`\n- `lib/crates/fabro-agent/src/session.rs`\n- `lib/crates/fabro-agent/src/history.rs`\n\nTasks:\n\n- Replace or extend the narrow assistant-only `AgentEvent::AssistantMessage` path with a general committed `AgentEvent::Message`.\n- Emit `kind=system, source=system_prompt` after the exact rendered system prompt is assembled.\n- Emit `kind=user, source=turn_input` after skill expansion/wrapping, using the exact user message sent to the model.\n- Emit `kind=user, source=followup` for follow-up inputs.\n- Emit `kind=user, source=steer` for steering-as-user.\n- Emit `kind=user, source=loop_detection` for loop-detection steering.\n- Emit `kind=system, source=injected_system` for injected system messages.\n- Emit `kind=user, source=injected_user` for injected user-role messages.\n- Emit `kind=user, source=pair` for pair chat messages that enter LLM history, with `pair` populated.\n- Emit `kind=system, source=pair` for pair join/leave or other pair system messages that enter LLM history, with `pair` populated.\n- Emit `kind=reasoning` only for completed provider reasoning blocks that must be preserved for replay, preserving exact structured parts.\n- Emit `kind=agent` after provider `Finish`, using the completed response content.\n- Do not emit committed messages for deltas, retries, or interrupted partial output.\n- Ensure all message events carry `turn_id`, `source`, and optional `actor`/`pair` metadata where applicable.\n\n### 3. Enrich Tool Action Events\n\nModify:\n\n- `lib/crates/fabro-agent/src/session.rs`\n- `lib/crates/fabro-agent/src/tool_execution.rs`\n- provider adapters only where extra metadata is not currently surfaced\n\nTasks:\n\n- Preserve `ToolCall.tool_type`, `raw_arguments`, and `provider_metadata`.\n- Preserve `ToolResult` structured output, error state, and supported media/artifact fields.\n- Link every tool call to the owning agent message with `parent_message_id`.\n- Mint the agent message id before tool execution so tool events can link correctly.\n- Keep tool calls/results out of message events.\n\n### 4. Persist Unified Events In Both API Paths\n\nModify:\n\n- `lib/crates/fabro-workflow/src/handler/llm/api.rs`\n- `lib/crates/fabro-workflow/src/event/convert.rs`\n- `lib/crates/fabro-workflow/src/event/names.rs`\n- `lib/crates/fabro-server/src/server/handler/sessions.rs`\n\nTasks:\n\n- Convert the unified agent message event through the existing workflow `Event::Agent` path.\n- Convert Ask Fabro/server session agent events into the same durable `agent.message` and `agent.tool.*` shapes.\n- Keep `run.session.created`, `run.session.turn.started`, and terminal turn events as lifecycle events.\n- Keep old `run.session.user_message`, `run.session.assistant_message`, and `run.session.tool_call.*` projection support until all producers and consumers are migrated.\n- Prefer a shared event persistence helper for workflow and server session paths so redaction behavior is consistent.\n- Avoid creating new transcript-specific event families.\n\nMigration order:\n\n1. Add canonical types and event deserialization support.\n2. Update projection to read both old narrow run-session events and new unified agent events.\n3. Switch workflow and Ask Fabro producers to emit unified events while retaining compatibility fields.\n4. Update web/server/client consumers to prefer unified payloads with narrow-field fallback.\n5. Only then consider deprecating narrow transcript-bearing run-session events.\n\n### 5. Define Pair Transcript Relationship\n\nModify:\n\n- `lib/crates/fabro-workflow/src/steering_hub.rs`\n- `lib/crates/fabro-types/src/pair.rs`\n- `lib/crates/fabro-server/src/server/handler/sessions.rs`\n- web consumers of pair transcript events\n\nTasks:\n\n- Treat `agent.pair.user_message` and `agent.pair.system_message` as UI/audit projection events only.\n- Do not use pair transcript events as replay-authoritative session history.\n- For any pair message that affects LLM history, emit the corresponding canonical `agent.message` event with `source=pair` and a populated `PairMessageRef`.\n- Store pair user chat as `kind=user, source=pair`.\n- Store pair join/leave or other pair system items that enter model context as `kind=system, source=pair`.\n- Keep existing pair API transcript types as projections over pair events and canonical message references, not as a second replay model.\n\n### 6. Rebuild Session Projection From Events\n\nModify:\n\n- `lib/crates/fabro-store/src/run_sessions.rs`\n- `lib/crates/fabro-types/src/session.rs`\n- `lib/crates/fabro-agent/src/history.rs`\n\nTasks:\n\n- Project runtime context from ordered `agent.message` and `agent.tool.*` events scoped by envelope `session_id`.\n- Preserve provider-specific reasoning, opaque provider items, response ids, usage, and tool metadata.\n- Keep best-effort fallback projection for legacy narrow session events.\n- Ignore pair transcript events for replay except as a legacy fallback path; canonical `agent.message` with `source=pair` is the replay source.\n- Ensure `Session::from_record()` can hydrate without dropping provider parts needed for same-provider replay.\n- Preserve injected history sources: rendered system prompt, wrapped user input, follow-up input, steering, injected system messages, injected user-role messages, and loop-detection steering.\n\n### 7. Redaction And Security Policy\n\nModify:\n\n- workflow event persistence path\n- server session event persistence path\n- event redaction utilities\n\nTasks:\n\n- Define raw replay fields explicitly: provider opaque parts, raw tool arguments, provider metadata, and structured tool outputs.\n- Apply one shared redaction policy before durable storage for both workflow and server sessions.\n- Preserve replay-critical opaque provider fields unless they match an existing secret redaction rule.\n- Do not omit fields needed for same-provider replay silently; if a field must be redacted, preserve the shape and mark the value redacted.\n- Add tests covering raw tool arguments and provider metadata through both persistence paths.\n\n### 8. Consumer Compatibility\n\nModify:\n\n- web event consumers that currently read narrow `properties.text`\n- web pair transcript consumers that read `agent.pair.*`\n- server/API projections that expose session detail or event detail\n- generated clients if OpenAPI changes\n\nTasks:\n\n- Keep narrow compatibility fields in emitted events until consumers are updated.\n- Update consumers to prefer `properties.message` and fall back to narrow fields.\n- Keep pair transcript rendering backed by pair projection events, while ensuring session replay and hydration consume canonical `agent.message` events.\n- Add web/server tests that render both old and new event shapes.\n- Document the deprecation path for narrow transcript fields after consumer migration.\n\n## Test Plan\n\n- `fabro-types`: serde round trips for `agent.message`, enriched `agent.tool.started`, and enriched `agent.tool.completed`.\n- Type ownership:\n - canonical `ToolCall`, `ToolResult`, `ContentPart`, `TranscriptMessage`, usage, and event prop types are reused rather than duplicated\n - OpenAPI replacement tests prove type identity and JSON parity where API schemas expose these shapes\n- `fabro-agent`: committed system/user/reasoning/agent messages emit once, while partial deltas and interrupted streams do not create committed messages.\n- `fabro-agent`: followups, steering-as-user, injected system messages, and loop-detection steering emit committed messages with the correct `kind`, `source`, and `turn_id`.\n- Role/source mapping:\n - steering-as-user emits `kind=user, source=steer`\n - loop-detection steering emits `kind=user, source=loop_detection`\n - injected user-role messages emit `kind=user, source=injected_user`\n - pair user chat emits `kind=user, source=pair` with `PairMessageRef`\n - pair join/leave context emits `kind=system, source=pair` with `PairMessageRef`\n- Identity/linkage: tool calls include the parent agent message id minted before tool execution.\n- Provider replay:\n - OpenAI encrypted reasoning and opaque message items survive event replay.\n - Anthropic thinking signatures survive event replay.\n - Gemini thought signatures survive enriched tool call replay.\n- `fabro-store`: session projection from event `seq` order recreates runtime history including provider parts and tool metadata.\n- Pair projection: pair transcript events render in the pair UI/audit surface but do not create duplicate replay history when the canonical `source=pair` message exists.\n- Migration: old narrow run-session events and new unified events both hydrate session detail without duplicate transcript entries.\n- `fabro-server`: Ask Fabro stores the wrapped model input, not only the raw UI question.\n- Redaction: workflow and server session persistence apply the same redaction behavior to raw arguments, provider metadata, and tool outputs.\n- Consumer compatibility: existing UI/server consumers render old narrow fields and new unified message payloads.\n- API conformance: OpenAPI-generated Rust/TypeScript clients still match the spec after schema updates.\n\n## Acceptance Criteria\n\n- A completed API-mode session can be reconstructed from the event stream without losing committed system, user, reasoning, agent, tool call, or tool result state.\n- New transcript state is stored through existing semantic events, not a separate transcript event family.\n- Tool calls remain actions, not messages.\n- Partial output remains non-authoritative for replay.\n- The implementation introduces one canonical set of replay types, not duplicated event/API/runtime DTOs.\n- Steering, pair, and injected inputs preserve provider-role semantics in `kind` and audit/source semantics in `source`.\n- Pair transcript events are UI/audit projection events, not a replay-authoritative transcript source.\n- Ask Fabro migration is backward compatible for existing session events and projections.\n", "internal.work_dir": "/home/daytona/workspace/fabro", "graph.model_stylesheet": "\n * { model: claude-opus-4-7; }\n ", + "thread.preflight_compile.current_node": "preflight_lint", "failure_class": "", "graph.rankdir": "LR", "command.output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126", "thread.start.current_node": "toolchain", "thread.toolchain.current_node": "preflight_compile", - "internal.thread_id": "toolchain", + "internal.thread_id": "preflight_compile", "internal.retry_count.preflight_compile": 0, "internal.run_id": "01KS8J49FYC6K2VTG89FR8479E", "outcome": "succeeded", - "current_node": "preflight_compile", + "current_node": "preflight_lint", "internal.node_visit_count": 1, - "internal.retry_count.start": 0 + "internal.retry_count.start": 0, + "internal.retry_count.preflight_lint": 0 }, "node_outcomes": { "start": { @@ -658,6 +730,14 @@ "notes": "Script completed: command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", "usage": null }, + "preflight_lint": { + "status": "succeeded", + "context_updates": { + "command.output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126" + }, + "notes": "Script completed: cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", + "usage": null + }, "preflight_compile": { "status": "succeeded", "context_updates": { @@ -667,11 +747,12 @@ "usage": null } }, - "next_node_id": "preflight_lint", + "next_node_id": "implement", "node_visits": { - "start": 1, + "preflight_lint": 1, "toolchain": 1, - "preflight_compile": 1 + "preflight_compile": 1, + "start": 1 } }, "diff": {} @@ -732,22 +813,22 @@ }, "state": "succeeded" }, - "preflight_compile@1": { - "first_event_seq": 30, + "preflight_lint@1": { + "first_event_seq": 40, "prompt": null, "response": null, "completion": null, "provider_used": null, "diff": null, "script_invocation": { - "script": "cargo check -q --workspace 2>&1", - "command": "exec 2>&1\ncargo check -q --workspace 2>&1", + "script": "cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", + "command": "exec 2>&1\ncargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", "language": "shell" }, "script_timing": null, "parallel_results": null, "output": null, - "started_at": "2026-05-22T19:21:21.206395Z", + "started_at": "2026-05-22T19:23:32.953973Z", "handler": "command", "usage": { "input_tokens": 0, @@ -806,6 +887,54 @@ "cache_write_tokens": 0 }, "state": "succeeded" + }, + "preflight_compile@1": { + "first_event_seq": 30, + "prompt": null, + "response": null, + "completion": { + "outcome": "succeeded", + "notes": "Script completed: cargo check -q --workspace 2>&1", + "failure_reason": null, + "timestamp": "2026-05-22T19:23:27.471417Z" + }, + "provider_used": null, + "diff": null, + "script_invocation": { + "script": "cargo check -q --workspace 2>&1", + "command": "exec 2>&1\ncargo check -q --workspace 2>&1", + "language": "shell" + }, + "script_timing": { + "output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126", + "exit_code": 0, + "duration_ms": 126253, + "termination": "exited", + "output_bytes": 0, + "live_streaming": false + }, + "parallel_results": null, + "output": null, + "output_bytes": 0, + "live_streaming": false, + "termination": "exited", + "started_at": "2026-05-22T19:21:21.206395Z", + "handler": "command", + "timing": { + "wall_time_ms": 126264, + "inference_time_ms": 0, + "tool_time_ms": 0, + "active_time_ms": 0 + }, + "usage": { + "input_tokens": 0, + "output_tokens": 0, + "total_tokens": 0, + "reasoning_tokens": 0, + "cache_read_tokens": 0, + "cache_write_tokens": 0 + }, + "state": "succeeded" } } } \ No newline at end of file diff --git a/stages/003-preflight_compile@1/output.log b/stages/003-preflight_compile@1/output.log new file mode 100644 index 000000000..d87ba9545 --- /dev/null +++ b/stages/003-preflight_compile@1/output.log @@ -0,0 +1 @@ +blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126 \ No newline at end of file diff --git a/stages/003-preflight_compile@1/script_timing.json b/stages/003-preflight_compile@1/script_timing.json new file mode 100644 index 000000000..89a501e50 --- /dev/null +++ b/stages/003-preflight_compile@1/script_timing.json @@ -0,0 +1,8 @@ +{ + "output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126", + "exit_code": 0, + "duration_ms": 126253, + "termination": "exited", + "output_bytes": 0, + "live_streaming": false +} \ No newline at end of file diff --git a/stages/003-preflight_compile@1/status.json b/stages/003-preflight_compile@1/status.json new file mode 100644 index 000000000..98a157681 --- /dev/null +++ b/stages/003-preflight_compile@1/status.json @@ -0,0 +1,6 @@ +{ + "outcome": "succeeded", + "notes": "Script completed: cargo check -q --workspace 2>&1", + "failure_reason": null, + "timestamp": "2026-05-22T19:23:27.471417Z" +} \ No newline at end of file diff --git a/stages/004-preflight_lint@1/script_invocation.json b/stages/004-preflight_lint@1/script_invocation.json new file mode 100644 index 000000000..0cb6a9faa --- /dev/null +++ b/stages/004-preflight_lint@1/script_invocation.json @@ -0,0 +1,5 @@ +{ + "script": "cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", + "command": "exec 2>&1\ncargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", + "language": "shell" +} \ No newline at end of file