From 5ac73aa56d05fab10fdcad6a5b9028595996b9c9 Mon Sep 17 00:00:00 2001 From: Fabro Date: Fri, 22 May 2026 15:23:34 -0400 Subject: [PATCH] =?UTF-8?q?checkpoint=20=E2=9A=92=EF=B8=8F=20Generated=20w?= =?UTF-8?q?ith=20[Fabro](https://fabro.sh)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- run.json | 207 +++++++- stages/004-preflight_lint@1/output.log | 1 + .../004-preflight_lint@1/script_timing.json | 8 + stages/004-preflight_lint@1/status.json | 6 + stages/005-implement@1/prompt.md | 456 ++++++++++++++++++ stages/005-implement@1/provider_used.json | 5 + stages/005-implement@1/response.md | 23 + 7 files changed, 696 insertions(+), 10 deletions(-) create mode 100644 stages/004-preflight_lint@1/output.log create mode 100644 stages/004-preflight_lint@1/script_timing.json create mode 100644 stages/004-preflight_lint@1/status.json create mode 100644 stages/005-implement@1/prompt.md create mode 100644 stages/005-implement@1/provider_used.json create mode 100644 stages/005-implement@1/response.md diff --git a/run.json b/run.json index bfd1d9ab7..82cb04dfe 100644 --- a/run.json +++ b/run.json @@ -517,7 +517,7 @@ "kind": "running" }, "status_updated_at": "2026-05-22T18:57:01.494747Z", - "last_event_at": "2026-05-22T18:59:32.199561Z", + "last_event_at": "2026-05-22T19:23:33.678929Z", "pending_control": null, "checkpoints": [ { @@ -684,9 +684,9 @@ } }, { - "seq": 0, + "seq": 47, "checkpoint": { - "timestamp": "2026-05-22T19:02:02.740826Z", + "timestamp": "2026-05-22T19:02:15.510709Z", "current_node": "preflight_lint", "completed_nodes": [ "start", @@ -695,24 +695,111 @@ "preflight_lint" ], "node_retries": {}, + "context_values": { + "internal.retry_count.start": 0, + "internal.work_dir": "/home/daytona/workspace/fabro", + "command.output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126", + "outcome": "succeeded", + "thread.preflight_compile.current_node": "preflight_lint", + "graph.model_stylesheet": "\n * { model: claude-opus-4-7; }\n ", + "failure_signature": "", + "thread.toolchain.current_node": "preflight_compile", + "internal.thread_id": "preflight_compile", + "failure_class": "", + "graph.rankdir": "LR", + "internal.run_id": "01KS8GR1P97NYP93VVXWYCZHYZ", + "internal.retry_count.preflight_compile": 0, + "graph.goal": "# Agent Context Observability Events Implementation Plan\n\n> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.\n\n**Goal:** Add durable API/backend events that report loaded memory files, discovered and activated skills, and per-server MCP tool names for agent runs.\n\n**Architecture:** Keep this API-backend scoped. Emit typed `AgentEvent` variants from the existing `fabro-agent` initialization and skill activation paths, convert them through `fabro-workflow` into durable `fabro-types` run events, and document the event contracts. Do not add run projection fields in this pass; consumers can read the event stream/history.\n\n**Tech Stack:** Rust, Serde, Fabro agent/session events, Fabro workflow event conversion, Fabro MCP connection manager, `cargo nextest`.\n\n---\n\n## Scope\n\nImplement these event changes:\n\n- Add `agent.memory.loaded` with memory file paths, byte counts, loaded byte counts, truncation flags, provider profile, total loaded bytes, and budget bytes.\n- Add `agent.skills.discovered` with source directories, provider profile, and sorted skill summaries.\n- Add persisted `agent.skill.activated` for slash skill expansion and successful `use_skill` tool calls.\n- Enrich `agent.mcp.ready` with names-only tool summaries: qualified tool name and original server tool name.\n\nDo not implement ACP-native equivalents in this pass. Do not include memory file contents in any event payload. Do not include MCP tool descriptions or schemas.\n\n## Existing Patterns To Follow\n\n- Read `docs/internal/events-strategy.md` before changing event variants, names, conversion, or progress JSONL behavior.\n- Read `docs/internal/testing-strategy.md` before adding or reorganizing tests.\n- Follow the current `AgentEvent` flow:\n - `lib/crates/fabro-agent/src/types.rs`\n - `lib/crates/fabro-agent/src/session.rs`\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-types/src/run_event/agent.rs`\n - `lib/crates/fabro-types/src/run_event/mod.rs`\n- Follow Rust import style from `AGENTS.md`: import types by name, import functions through their parent module, and avoid glob imports in production code.\n\n## File Map\n\n- Modify `lib/crates/fabro-types/src/run_event/agent.rs`: add new prop structs and extend `AgentMcpReadyProps`.\n- Modify `lib/crates/fabro-types/src/run_event/mod.rs`: add `EventBody` variants for the new event names.\n- Modify `lib/crates/fabro-agent/src/types.rs`: add internal `AgentEvent` variants, trace output, and noise filtering decisions.\n- Modify `lib/crates/fabro-agent/src/memory.rs`: return memory content plus metadata instead of bare strings.\n- Modify `lib/crates/fabro-agent/src/session.rs`: emit memory, skills, skill activation, and enriched MCP events.\n- Modify `lib/crates/fabro-agent/src/skills.rs`: emit tool-sourced skill activation from `use_skill`.\n- Modify `lib/crates/fabro-mcp/src/connection_manager.rs`: expose or support deterministic names-only tool summaries per server.\n- Modify `lib/crates/fabro-workflow/src/event/convert.rs`: convert new agent events to durable event bodies.\n- Modify `lib/crates/fabro-workflow/src/event/names.rs`: add event names.\n- Modify `lib/crates/fabro-workflow/src/event/events.rs` only if the agent event name mapping also lives there for these variants.\n- Modify `lib/crates/fabro-workflow/src/event/stored_fields.rs` only if a new event needs non-standard stored fields; otherwise rely on existing `Event::Agent` handling.\n- Modify `docs/internal/events.md`: document new event shapes and the richer MCP payload.\n- Add or update tests in `lib/crates/fabro-agent`, `lib/crates/fabro-mcp`, `lib/crates/fabro-types`, and `lib/crates/fabro-workflow`.\n\n---\n\n### Task 1: Add Typed Durable Event Contracts\n\n**Files:**\n- Modify: `lib/crates/fabro-types/src/run_event/agent.rs`\n- Modify: `lib/crates/fabro-types/src/run_event/mod.rs`\n- Test: existing `fabro-types` run event serde tests, or add focused coverage near the existing run event tests.\n\n- [ ] **Step 1: Add agent memory props**\n\nAdd event prop structs with this shape:\n\n```rust\npub struct AgentMemoryLoadedProps {\n pub provider_profile: String,\n pub files: Vec,\n pub total_loaded_bytes: usize,\n pub budget_bytes: usize,\n pub visit: u32,\n}\n\npub struct AgentMemoryFileProps {\n pub path: String,\n pub byte_count: usize,\n pub loaded_bytes: usize,\n pub truncated: bool,\n}\n```\n\n- [ ] **Step 2: Add skill props**\n\nAdd skill discovery and activation props:\n\n```rust\npub struct AgentSkillsDiscoveredProps {\n pub provider_profile: String,\n pub source_dirs: Vec,\n pub skills: Vec,\n pub visit: u32,\n}\n\npub struct AgentSkillSummary {\n pub name: String,\n pub description: String,\n}\n\npub enum AgentSkillActivationSource {\n Slash,\n Tool,\n}\n\npub struct AgentSkillActivatedProps {\n pub skill_name: String,\n pub source: AgentSkillActivationSource,\n pub visit: u32,\n}\n```\n\nUse serde names `slash` and `tool` for `AgentSkillActivationSource`. If a local enum string pattern already exists, follow that pattern.\n\n- [ ] **Step 3: Extend MCP ready props**\n\nExtend `AgentMcpReadyProps` with a backwards-compatible field:\n\n```rust\n#[serde(default, skip_serializing_if = \"Vec::is_empty\")]\npub tools: Vec,\n```\n\nAdd:\n\n```rust\npub struct AgentMcpToolSummary {\n pub name: String,\n pub original_name: String,\n}\n```\n\n- [ ] **Step 4: Add EventBody variants**\n\nAdd `EventBody` variants using these serialized event names:\n\n- `agent.memory.loaded`\n- `agent.skills.discovered`\n- `agent.skill.activated`\n\nKeep existing `agent.mcp.ready` name unchanged and only enrich its props.\n\n- [ ] **Step 5: Add serde tests**\n\nCover:\n\n- New event names serialize to the expected dot names.\n- `AgentSkillActivationSource` serializes as `slash` and `tool`.\n- Old `agent.mcp.ready` JSON without `tools` still deserializes with `tools == []`.\n\n---\n\n### Task 2: Add Internal Agent Events And Conversion\n\n**Files:**\n- Modify: `lib/crates/fabro-agent/src/types.rs`\n- Modify: `lib/crates/fabro-workflow/src/event/convert.rs`\n- Modify: `lib/crates/fabro-workflow/src/event/names.rs`\n- Modify: `lib/crates/fabro-workflow/src/event/events.rs` if needed by the existing name mapping.\n- Test: `lib/crates/fabro-workflow` event conversion tests.\n\n- [ ] **Step 1: Add internal AgentEvent variants**\n\nAdd variants equivalent to:\n\n```rust\nMemoryLoaded {\n provider_profile: String,\n files: Vec,\n total_loaded_bytes: usize,\n budget_bytes: usize,\n}\n\nSkillsDiscovered {\n provider_profile: String,\n source_dirs: Vec,\n skills: Vec,\n}\n\nSkillActivated {\n skill_name: String,\n source: SkillActivationSource,\n}\n\nMcpServerReady {\n server_name: String,\n tool_count: usize,\n tools: Vec,\n}\n```\n\nPrefer small shared internal structs near `AgentEvent` if that matches the existing file organization.\n\n- [ ] **Step 2: Persist skill activation**\n\nDo not classify `SkillActivated` as streaming noise. The existing `SkillExpanded` event is currently filtered before persistence; replace slash expansion emissions with `SkillActivated { source: Slash }` or keep `SkillExpanded` internal-only if removing it would create unnecessary churn.\n\n- [ ] **Step 3: Add trace behavior**\n\nUpdate `AgentEvent::trace` so the new events emit concise tracing summaries:\n\n- memory loaded: profile, file count, total loaded bytes, budget bytes\n- skills discovered: profile, skill count, source dir count\n- skill activated: name and source\n- MCP ready: server, count, and summary count\n\n- [ ] **Step 4: Convert to durable events**\n\nUpdate `fabro-workflow` event conversion so the new agent events map to the new `fabro-types` props and include `visit`.\n\n- [ ] **Step 5: Add conversion tests**\n\nCover each new event with a focused conversion assertion that checks:\n\n- durable event name\n- `visit`\n- core fields\n- no memory content in the converted payload\n\n---\n\n### Task 3: Emit Memory Loaded Metadata\n\n**Files:**\n- Modify: `lib/crates/fabro-agent/src/memory.rs`\n- Modify: `lib/crates/fabro-agent/src/session.rs`\n- Test: relevant `fabro-agent` memory/session tests.\n\n- [ ] **Step 1: Change memory discovery return type**\n\nChange memory discovery from bare `Vec` to a document type carrying both prompt content and event metadata:\n\n```rust\npub struct MemoryDocument {\n pub path: String,\n pub content: String,\n pub byte_count: usize,\n pub loaded_bytes: usize,\n pub truncated: bool,\n}\n```\n\nKeep existing behavior unchanged:\n\n- provider profile filename candidates stay the same\n- root-to-working-dir walk stays the same\n- content dedupe stays the same\n- empty files are skipped\n- total budget remains 32 KiB\n- truncated content keeps the existing truncation marker\n\n- [ ] **Step 2: Preserve prompt assembly behavior**\n\nAdjust session/profile prompt assembly to pass only memory contents where prompt assembly expects memory text. The system prompt should be byte-for-byte equivalent except where existing tests allow non-semantic ordering differences.\n\n- [ ] **Step 3: Emit agent.memory.loaded**\n\nIn `Session::initialize()`, emit `AgentEvent::MemoryLoaded` immediately after memory discovery, before skills and MCP initialization.\n\nEmit the event even when no memory files are loaded. That lets consumers distinguish \"no memory\" from \"not reported.\"\n\n- [ ] **Step 4: Add memory tests**\n\nCover:\n\n- loaded file path appears in event metadata\n- `byte_count` is the original file byte count\n- `loaded_bytes` reflects bytes actually loaded into the prompt budget\n- `truncated` is true only for truncated files\n- event payload never contains memory file contents\n- empty discovery still emits a memory-loaded event with `files == []`\n\n---\n\n### Task 4: Emit Skills Discovered And Skill Activated\n\n**Files:**\n- Modify: `lib/crates/fabro-agent/src/session.rs`\n- Modify: `lib/crates/fabro-agent/src/skills.rs`\n- Test: relevant `fabro-agent` skill/session tests.\n\n- [ ] **Step 1: Emit skills discovered**\n\nAfter `discover_skills(...)`, emit `AgentEvent::SkillsDiscovered` with:\n\n- `provider_profile`\n- `source_dirs`\n- sorted `skills: [{ name, description }]`\n\nEmit the event even when no skills are discovered.\n\n- [ ] **Step 2: Emit slash activation**\n\nWhere slash skill expansion currently emits or creates `SkillExpanded`, emit:\n\n```rust\nAgentEvent::SkillActivated {\n skill_name,\n source: SkillActivationSource::Slash,\n}\n```\n\n- [ ] **Step 3: Emit tool activation**\n\nIn `make_use_skill_tool`, use `ToolContext::emit_agent_event(...)` after a requested skill is found and before returning the skill template. Emit:\n\n```rust\nAgentEvent::SkillActivated {\n skill_name: name.to_string(),\n source: SkillActivationSource::Tool,\n}\n```\n\nDo not emit activation for failed `use_skill` lookups.\n\n- [ ] **Step 4: Add skill tests**\n\nCover:\n\n- discovery event includes all discovered skills sorted by name\n- discovery event includes configured source directories\n- empty discovery emits `skills == []`\n- slash expansion emits `source == slash`\n- successful `use_skill` emits `source == tool`\n- failed `use_skill` does not emit activation\n\n---\n\n### Task 5: Enrich agent.mcp.ready With Names-Only Tool Summaries\n\n**Files:**\n- Modify: `lib/crates/fabro-mcp/src/connection_manager.rs`\n- Modify: `lib/crates/fabro-agent/src/session.rs`\n- Test: relevant `fabro-mcp` or `fabro-agent` MCP tests.\n\n- [ ] **Step 1: Add deterministic tool summaries**\n\nExpose a helper on `McpConnectionManager` or compute in `Session` from `all_tools()`:\n\n- filter tools by `server_name`\n- return qualified tool name as `name`\n- return server-provided tool name as `original_name`\n- sort by qualified `name`\n\n- [ ] **Step 2: Enrich ready emissions**\n\nWhen emitting `AgentEvent::McpServerReady`, include the tool summaries for that server. Keep existing `server_name` and `tool_count`.\n\n- [ ] **Step 3: Add MCP tests**\n\nCover:\n\n- ready event includes only tools from the ready server\n- summaries are sorted by qualified name\n- `name` is the Fabro-qualified MCP tool name\n- `original_name` is the server-provided tool name\n- descriptions and input schemas are not included\n\n---\n\n### Task 6: Update Event Documentation\n\n**Files:**\n- Modify: `docs/internal/events.md`\n\n- [ ] **Step 1: Document new events**\n\nAdd sections for:\n\n- `agent.memory.loaded`\n- `agent.skills.discovered`\n- `agent.skill.activated`\n\nFor `agent.memory.loaded`, explicitly state that file contents are excluded.\n\n- [ ] **Step 2: Update MCP ready docs**\n\nUpdate `agent.mcp.ready` to show:\n\n```json\n{\n \"server_name\": \"github\",\n \"tool_count\": 2,\n \"tools\": [\n {\n \"name\": \"mcp__github__create_issue\",\n \"original_name\": \"create_issue\"\n }\n ],\n \"visit\": 1\n}\n```\n\n- [ ] **Step 3: Record skill event replacement**\n\nIf `agent.skill.expanded` remains in internal code or docs, mark it internal-only or replaced by `agent.skill.activated`.\n\n---\n\n### Task 7: Verify\n\n**Files:**\n- No new files unless test placement requires it.\n\n- [ ] **Step 1: Run focused tests**\n\nRun:\n\n```bash\ncargo nextest run -p fabro-agent -p fabro-workflow -p fabro-types -p fabro-mcp\n```\n\n- [ ] **Step 2: Run formatting**\n\nRun:\n\n```bash\ncargo +nightly-2026-04-14 fmt --all\n```\n\n- [ ] **Step 3: Run clippy for touched crates or workspace**\n\nPrefer the workspace command if time permits:\n\n```bash\ncargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings\n```\n\n- [ ] **Step 4: Final sanity checks**\n\nConfirm:\n\n- memory events never contain file contents\n- skills discovered and memory loaded are emitted even for empty lists\n- skill activation is persisted rather than filtered as streaming noise\n- `agent.mcp.ready` remains backwards-compatible for old events without `tools`\n- docs match the serialized event names and payload shapes\n\n", + "internal.node_visit_count": 1, + "thread.start.current_node": "toolchain", + "internal.retry_count.toolchain": 0, + "current_node": "preflight_lint", + "internal.fidelity": "compact", + "internal.retry_count.preflight_lint": 0 + }, + "node_outcomes": { + "preflight_compile": { + "status": "succeeded", + "context_updates": { + "command.output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126" + }, + "notes": "Script completed: cargo check -q --workspace 2>&1", + "usage": null + }, + "start": { + "status": "succeeded", + "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 + }, + "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": "implement", + "git_commit_sha": "288445381365f0199d831d1d0baeb322a29cd4b3", + "node_visits": { + "preflight_lint": 1, + "toolchain": 1, + "preflight_compile": 1, + "start": 1 + } + }, + "diff": { + "summary": { + "files_changed": 0, + "additions": 0, + "deletions": 0 + } + } + }, + { + "seq": 0, + "checkpoint": { + "timestamp": "2026-05-22T19:23:33.870050Z", + "current_node": "implement", + "completed_nodes": [ + "start", + "toolchain", + "preflight_compile", + "preflight_lint", + "implement" + ], + "node_retries": {}, "context_values": { "internal.run_id": "01KS8GR1P97NYP93VVXWYCZHYZ", "thread.toolchain.current_node": "preflight_compile", "graph.model_stylesheet": "\n * { model: claude-opus-4-7; }\n ", + "response.implement": "Let me do a quick sanity check on the plan's \"final sanity checks\":\n\n1. ✅ Memory events never contain file contents — verified by test `agent_memory_loaded_payload_excludes_file_contents` and structural design.\n2. ✅ Skills discovered and memory loaded emitted even for empty lists — verified by tests `initialize_emits_memory_loaded_event_with_empty_files_when_no_memory` and `initialize_emits_skills_discovered_event_when_no_skills`.\n3. ✅ Skill activation is persisted rather than filtered as streaming noise — `SkillActivated` is not in `is_streaming_noise()` and has conversion + name mapping.\n4. ✅ `agent.mcp.ready` backwards-compatible — verified by `agent_mcp_ready_deserializes_legacy_payload_without_tools` test and `#[serde(default, skip_serializing_if = \"Vec::is_empty\")]`.\n5. ✅ Docs match the serialized event names and payload shapes.\n\nSummary of changes:\n\n**Task 1 (fabro-types):** Added `AgentMemoryLoadedProps`, `AgentMemoryFileProps`, `AgentSkillsDiscoveredProps`, `AgentSkillSummary`, `AgentSkillActivationSource`, `AgentSkillActivatedProps`, `AgentMcpToolSummary`. Extended `AgentMcpReadyProps` with a backwards-compatible `tools` field. Added 3 new `EventBody` variants (`AgentMemoryLoaded`, `AgentSkillsDiscovered`, `AgentSkillActivated`) plus the legacy whitelist entries. Added 5 serde tests.\n\n**Task 2 (fabro-agent / fabro-workflow):** Added internal `AgentEvent::MemoryLoaded`, `SkillsDiscovered`, `SkillActivated` variants plus support structs (`MemoryFileSummary`, `SkillSummary`, `McpToolSummary`, `SkillActivationSource`). Extended `AgentEvent::McpServerReady` with `tools`. Added tracing for the new variants. Updated workflow `convert.rs` and `names.rs`. Added 4 conversion tests.\n\n**Task 3 (memory):** Changed `discover_memory` to return `Vec` with `{path, content, byte_count, loaded_bytes, truncated}`. `Session::initialize` emits `MemoryLoaded` immediately after discovery, always (even for empty results). The prompt assembly receives content-only strings. Memory test added for truncation metadata.\n\n**Task 4 (skills):** `Session::initialize` emits `SkillsDiscovered` (always). Slash expansion now emits `SkillActivated { Slash }` instead of `SkillExpanded`. `make_use_skill_tool` emits `SkillActivated { Tool }` only on successful lookup. Failed lookups do not emit. Added 4 session tests.\n\n**Task 5 (MCP):** Added `McpConnectionManager::tool_summaries_for_server` that filters/sorts by qualified name. `Session::initialize` includes the summaries in `McpServerReady`. Existing MCP e2e test now asserts the names-only summary content. Added an mcp unit test.\n\n**Task 6 (docs):** Updated `docs/internal/events.md` with the enriched `agent.mcp.ready` payload, three new sections for `agent.memory.loaded`, `agent.skills.discovered`, `agent.skill.activated`, and a note about the retired `agent.skill.expanded` notification.\n\n**Task 7:** All 1841 tests in `fabro-agent`, `fabro-workflow`, `fabro-types`, `fabro-mcp` pass; workspace clippy clean under nightly-2026-04-14; formatting checked.", "command.output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126", + "last_response": "Let me do a quick sanity check on the plan's \"final sanity checks\":\n\n1. ✅ Memory events never contain file contents — verified by test `agent_memory_loaded_payload_excludes_file_contents` and stru", "failure_class": "", "internal.work_dir": "/home/daytona/workspace/fabro", "failure_signature": "", - "internal.thread_id": "preflight_compile", + "internal.thread_id": "preflight_lint", "internal.retry_count.preflight_compile": 0, "internal.node_visit_count": 1, "internal.retry_count.start": 0, "internal.retry_count.toolchain": 0, "internal.fidelity": "compact", "internal.retry_count.preflight_lint": 0, + "last_stage": "implement", "outcome": "succeeded", - "current_node": "preflight_lint", + "internal.retry_count.implement": 0, + "current_node": "implement", "thread.start.current_node": "toolchain", + "thread.preflight_lint.current_node": "implement", "thread.preflight_compile.current_node": "preflight_lint", "graph.goal": "# Agent Context Observability Events Implementation Plan\n\n> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.\n\n**Goal:** Add durable API/backend events that report loaded memory files, discovered and activated skills, and per-server MCP tool names for agent runs.\n\n**Architecture:** Keep this API-backend scoped. Emit typed `AgentEvent` variants from the existing `fabro-agent` initialization and skill activation paths, convert them through `fabro-workflow` into durable `fabro-types` run events, and document the event contracts. Do not add run projection fields in this pass; consumers can read the event stream/history.\n\n**Tech Stack:** Rust, Serde, Fabro agent/session events, Fabro workflow event conversion, Fabro MCP connection manager, `cargo nextest`.\n\n---\n\n## Scope\n\nImplement these event changes:\n\n- Add `agent.memory.loaded` with memory file paths, byte counts, loaded byte counts, truncation flags, provider profile, total loaded bytes, and budget bytes.\n- Add `agent.skills.discovered` with source directories, provider profile, and sorted skill summaries.\n- Add persisted `agent.skill.activated` for slash skill expansion and successful `use_skill` tool calls.\n- Enrich `agent.mcp.ready` with names-only tool summaries: qualified tool name and original server tool name.\n\nDo not implement ACP-native equivalents in this pass. Do not include memory file contents in any event payload. Do not include MCP tool descriptions or schemas.\n\n## Existing Patterns To Follow\n\n- Read `docs/internal/events-strategy.md` before changing event variants, names, conversion, or progress JSONL behavior.\n- Read `docs/internal/testing-strategy.md` before adding or reorganizing tests.\n- Follow the current `AgentEvent` flow:\n - `lib/crates/fabro-agent/src/types.rs`\n - `lib/crates/fabro-agent/src/session.rs`\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-types/src/run_event/agent.rs`\n - `lib/crates/fabro-types/src/run_event/mod.rs`\n- Follow Rust import style from `AGENTS.md`: import types by name, import functions through their parent module, and avoid glob imports in production code.\n\n## File Map\n\n- Modify `lib/crates/fabro-types/src/run_event/agent.rs`: add new prop structs and extend `AgentMcpReadyProps`.\n- Modify `lib/crates/fabro-types/src/run_event/mod.rs`: add `EventBody` variants for the new event names.\n- Modify `lib/crates/fabro-agent/src/types.rs`: add internal `AgentEvent` variants, trace output, and noise filtering decisions.\n- Modify `lib/crates/fabro-agent/src/memory.rs`: return memory content plus metadata instead of bare strings.\n- Modify `lib/crates/fabro-agent/src/session.rs`: emit memory, skills, skill activation, and enriched MCP events.\n- Modify `lib/crates/fabro-agent/src/skills.rs`: emit tool-sourced skill activation from `use_skill`.\n- Modify `lib/crates/fabro-mcp/src/connection_manager.rs`: expose or support deterministic names-only tool summaries per server.\n- Modify `lib/crates/fabro-workflow/src/event/convert.rs`: convert new agent events to durable event bodies.\n- Modify `lib/crates/fabro-workflow/src/event/names.rs`: add event names.\n- Modify `lib/crates/fabro-workflow/src/event/events.rs` only if the agent event name mapping also lives there for these variants.\n- Modify `lib/crates/fabro-workflow/src/event/stored_fields.rs` only if a new event needs non-standard stored fields; otherwise rely on existing `Event::Agent` handling.\n- Modify `docs/internal/events.md`: document new event shapes and the richer MCP payload.\n- Add or update tests in `lib/crates/fabro-agent`, `lib/crates/fabro-mcp`, `lib/crates/fabro-types`, and `lib/crates/fabro-workflow`.\n\n---\n\n### Task 1: Add Typed Durable Event Contracts\n\n**Files:**\n- Modify: `lib/crates/fabro-types/src/run_event/agent.rs`\n- Modify: `lib/crates/fabro-types/src/run_event/mod.rs`\n- Test: existing `fabro-types` run event serde tests, or add focused coverage near the existing run event tests.\n\n- [ ] **Step 1: Add agent memory props**\n\nAdd event prop structs with this shape:\n\n```rust\npub struct AgentMemoryLoadedProps {\n pub provider_profile: String,\n pub files: Vec,\n pub total_loaded_bytes: usize,\n pub budget_bytes: usize,\n pub visit: u32,\n}\n\npub struct AgentMemoryFileProps {\n pub path: String,\n pub byte_count: usize,\n pub loaded_bytes: usize,\n pub truncated: bool,\n}\n```\n\n- [ ] **Step 2: Add skill props**\n\nAdd skill discovery and activation props:\n\n```rust\npub struct AgentSkillsDiscoveredProps {\n pub provider_profile: String,\n pub source_dirs: Vec,\n pub skills: Vec,\n pub visit: u32,\n}\n\npub struct AgentSkillSummary {\n pub name: String,\n pub description: String,\n}\n\npub enum AgentSkillActivationSource {\n Slash,\n Tool,\n}\n\npub struct AgentSkillActivatedProps {\n pub skill_name: String,\n pub source: AgentSkillActivationSource,\n pub visit: u32,\n}\n```\n\nUse serde names `slash` and `tool` for `AgentSkillActivationSource`. If a local enum string pattern already exists, follow that pattern.\n\n- [ ] **Step 3: Extend MCP ready props**\n\nExtend `AgentMcpReadyProps` with a backwards-compatible field:\n\n```rust\n#[serde(default, skip_serializing_if = \"Vec::is_empty\")]\npub tools: Vec,\n```\n\nAdd:\n\n```rust\npub struct AgentMcpToolSummary {\n pub name: String,\n pub original_name: String,\n}\n```\n\n- [ ] **Step 4: Add EventBody variants**\n\nAdd `EventBody` variants using these serialized event names:\n\n- `agent.memory.loaded`\n- `agent.skills.discovered`\n- `agent.skill.activated`\n\nKeep existing `agent.mcp.ready` name unchanged and only enrich its props.\n\n- [ ] **Step 5: Add serde tests**\n\nCover:\n\n- New event names serialize to the expected dot names.\n- `AgentSkillActivationSource` serializes as `slash` and `tool`.\n- Old `agent.mcp.ready` JSON without `tools` still deserializes with `tools == []`.\n\n---\n\n### Task 2: Add Internal Agent Events And Conversion\n\n**Files:**\n- Modify: `lib/crates/fabro-agent/src/types.rs`\n- Modify: `lib/crates/fabro-workflow/src/event/convert.rs`\n- Modify: `lib/crates/fabro-workflow/src/event/names.rs`\n- Modify: `lib/crates/fabro-workflow/src/event/events.rs` if needed by the existing name mapping.\n- Test: `lib/crates/fabro-workflow` event conversion tests.\n\n- [ ] **Step 1: Add internal AgentEvent variants**\n\nAdd variants equivalent to:\n\n```rust\nMemoryLoaded {\n provider_profile: String,\n files: Vec,\n total_loaded_bytes: usize,\n budget_bytes: usize,\n}\n\nSkillsDiscovered {\n provider_profile: String,\n source_dirs: Vec,\n skills: Vec,\n}\n\nSkillActivated {\n skill_name: String,\n source: SkillActivationSource,\n}\n\nMcpServerReady {\n server_name: String,\n tool_count: usize,\n tools: Vec,\n}\n```\n\nPrefer small shared internal structs near `AgentEvent` if that matches the existing file organization.\n\n- [ ] **Step 2: Persist skill activation**\n\nDo not classify `SkillActivated` as streaming noise. The existing `SkillExpanded` event is currently filtered before persistence; replace slash expansion emissions with `SkillActivated { source: Slash }` or keep `SkillExpanded` internal-only if removing it would create unnecessary churn.\n\n- [ ] **Step 3: Add trace behavior**\n\nUpdate `AgentEvent::trace` so the new events emit concise tracing summaries:\n\n- memory loaded: profile, file count, total loaded bytes, budget bytes\n- skills discovered: profile, skill count, source dir count\n- skill activated: name and source\n- MCP ready: server, count, and summary count\n\n- [ ] **Step 4: Convert to durable events**\n\nUpdate `fabro-workflow` event conversion so the new agent events map to the new `fabro-types` props and include `visit`.\n\n- [ ] **Step 5: Add conversion tests**\n\nCover each new event with a focused conversion assertion that checks:\n\n- durable event name\n- `visit`\n- core fields\n- no memory content in the converted payload\n\n---\n\n### Task 3: Emit Memory Loaded Metadata\n\n**Files:**\n- Modify: `lib/crates/fabro-agent/src/memory.rs`\n- Modify: `lib/crates/fabro-agent/src/session.rs`\n- Test: relevant `fabro-agent` memory/session tests.\n\n- [ ] **Step 1: Change memory discovery return type**\n\nChange memory discovery from bare `Vec` to a document type carrying both prompt content and event metadata:\n\n```rust\npub struct MemoryDocument {\n pub path: String,\n pub content: String,\n pub byte_count: usize,\n pub loaded_bytes: usize,\n pub truncated: bool,\n}\n```\n\nKeep existing behavior unchanged:\n\n- provider profile filename candidates stay the same\n- root-to-working-dir walk stays the same\n- content dedupe stays the same\n- empty files are skipped\n- total budget remains 32 KiB\n- truncated content keeps the existing truncation marker\n\n- [ ] **Step 2: Preserve prompt assembly behavior**\n\nAdjust session/profile prompt assembly to pass only memory contents where prompt assembly expects memory text. The system prompt should be byte-for-byte equivalent except where existing tests allow non-semantic ordering differences.\n\n- [ ] **Step 3: Emit agent.memory.loaded**\n\nIn `Session::initialize()`, emit `AgentEvent::MemoryLoaded` immediately after memory discovery, before skills and MCP initialization.\n\nEmit the event even when no memory files are loaded. That lets consumers distinguish \"no memory\" from \"not reported.\"\n\n- [ ] **Step 4: Add memory tests**\n\nCover:\n\n- loaded file path appears in event metadata\n- `byte_count` is the original file byte count\n- `loaded_bytes` reflects bytes actually loaded into the prompt budget\n- `truncated` is true only for truncated files\n- event payload never contains memory file contents\n- empty discovery still emits a memory-loaded event with `files == []`\n\n---\n\n### Task 4: Emit Skills Discovered And Skill Activated\n\n**Files:**\n- Modify: `lib/crates/fabro-agent/src/session.rs`\n- Modify: `lib/crates/fabro-agent/src/skills.rs`\n- Test: relevant `fabro-agent` skill/session tests.\n\n- [ ] **Step 1: Emit skills discovered**\n\nAfter `discover_skills(...)`, emit `AgentEvent::SkillsDiscovered` with:\n\n- `provider_profile`\n- `source_dirs`\n- sorted `skills: [{ name, description }]`\n\nEmit the event even when no skills are discovered.\n\n- [ ] **Step 2: Emit slash activation**\n\nWhere slash skill expansion currently emits or creates `SkillExpanded`, emit:\n\n```rust\nAgentEvent::SkillActivated {\n skill_name,\n source: SkillActivationSource::Slash,\n}\n```\n\n- [ ] **Step 3: Emit tool activation**\n\nIn `make_use_skill_tool`, use `ToolContext::emit_agent_event(...)` after a requested skill is found and before returning the skill template. Emit:\n\n```rust\nAgentEvent::SkillActivated {\n skill_name: name.to_string(),\n source: SkillActivationSource::Tool,\n}\n```\n\nDo not emit activation for failed `use_skill` lookups.\n\n- [ ] **Step 4: Add skill tests**\n\nCover:\n\n- discovery event includes all discovered skills sorted by name\n- discovery event includes configured source directories\n- empty discovery emits `skills == []`\n- slash expansion emits `source == slash`\n- successful `use_skill` emits `source == tool`\n- failed `use_skill` does not emit activation\n\n---\n\n### Task 5: Enrich agent.mcp.ready With Names-Only Tool Summaries\n\n**Files:**\n- Modify: `lib/crates/fabro-mcp/src/connection_manager.rs`\n- Modify: `lib/crates/fabro-agent/src/session.rs`\n- Test: relevant `fabro-mcp` or `fabro-agent` MCP tests.\n\n- [ ] **Step 1: Add deterministic tool summaries**\n\nExpose a helper on `McpConnectionManager` or compute in `Session` from `all_tools()`:\n\n- filter tools by `server_name`\n- return qualified tool name as `name`\n- return server-provided tool name as `original_name`\n- sort by qualified `name`\n\n- [ ] **Step 2: Enrich ready emissions**\n\nWhen emitting `AgentEvent::McpServerReady`, include the tool summaries for that server. Keep existing `server_name` and `tool_count`.\n\n- [ ] **Step 3: Add MCP tests**\n\nCover:\n\n- ready event includes only tools from the ready server\n- summaries are sorted by qualified name\n- `name` is the Fabro-qualified MCP tool name\n- `original_name` is the server-provided tool name\n- descriptions and input schemas are not included\n\n---\n\n### Task 6: Update Event Documentation\n\n**Files:**\n- Modify: `docs/internal/events.md`\n\n- [ ] **Step 1: Document new events**\n\nAdd sections for:\n\n- `agent.memory.loaded`\n- `agent.skills.discovered`\n- `agent.skill.activated`\n\nFor `agent.memory.loaded`, explicitly state that file contents are excluded.\n\n- [ ] **Step 2: Update MCP ready docs**\n\nUpdate `agent.mcp.ready` to show:\n\n```json\n{\n \"server_name\": \"github\",\n \"tool_count\": 2,\n \"tools\": [\n {\n \"name\": \"mcp__github__create_issue\",\n \"original_name\": \"create_issue\"\n }\n ],\n \"visit\": 1\n}\n```\n\n- [ ] **Step 3: Record skill event replacement**\n\nIf `agent.skill.expanded` remains in internal code or docs, mark it internal-only or replaced by `agent.skill.activated`.\n\n---\n\n### Task 7: Verify\n\n**Files:**\n- No new files unless test placement requires it.\n\n- [ ] **Step 1: Run focused tests**\n\nRun:\n\n```bash\ncargo nextest run -p fabro-agent -p fabro-workflow -p fabro-types -p fabro-mcp\n```\n\n- [ ] **Step 2: Run formatting**\n\nRun:\n\n```bash\ncargo +nightly-2026-04-14 fmt --all\n```\n\n- [ ] **Step 3: Run clippy for touched crates or workspace**\n\nPrefer the workspace command if time permits:\n\n```bash\ncargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings\n```\n\n- [ ] **Step 4: Final sanity checks**\n\nConfirm:\n\n- memory events never contain file contents\n- skills discovered and memory loaded are emitted even for empty lists\n- skill activation is persisted rather than filtered as streaming noise\n- `agent.mcp.ready` remains backwards-compatible for old events without `tools`\n- docs match the serialized event names and payload shapes\n\n", "graph.rankdir": "LR" @@ -745,14 +832,61 @@ }, "notes": "Script completed: cargo check -q --workspace 2>&1", "usage": null + }, + "implement": { + "status": "succeeded", + "context_updates": { + "last_response": "Let me do a quick sanity check on the plan's \"final sanity checks\":\n\n1. ✅ Memory events never contain file contents — verified by test `agent_memory_loaded_payload_excludes_file_contents` and stru", + "response.implement": "Let me do a quick sanity check on the plan's \"final sanity checks\":\n\n1. ✅ Memory events never contain file contents — verified by test `agent_memory_loaded_payload_excludes_file_contents` and structural design.\n2. ✅ Skills discovered and memory loaded emitted even for empty lists — verified by tests `initialize_emits_memory_loaded_event_with_empty_files_when_no_memory` and `initialize_emits_skills_discovered_event_when_no_skills`.\n3. ✅ Skill activation is persisted rather than filtered as streaming noise — `SkillActivated` is not in `is_streaming_noise()` and has conversion + name mapping.\n4. ✅ `agent.mcp.ready` backwards-compatible — verified by `agent_mcp_ready_deserializes_legacy_payload_without_tools` test and `#[serde(default, skip_serializing_if = \"Vec::is_empty\")]`.\n5. ✅ Docs match the serialized event names and payload shapes.\n\nSummary of changes:\n\n**Task 1 (fabro-types):** Added `AgentMemoryLoadedProps`, `AgentMemoryFileProps`, `AgentSkillsDiscoveredProps`, `AgentSkillSummary`, `AgentSkillActivationSource`, `AgentSkillActivatedProps`, `AgentMcpToolSummary`. Extended `AgentMcpReadyProps` with a backwards-compatible `tools` field. Added 3 new `EventBody` variants (`AgentMemoryLoaded`, `AgentSkillsDiscovered`, `AgentSkillActivated`) plus the legacy whitelist entries. Added 5 serde tests.\n\n**Task 2 (fabro-agent / fabro-workflow):** Added internal `AgentEvent::MemoryLoaded`, `SkillsDiscovered`, `SkillActivated` variants plus support structs (`MemoryFileSummary`, `SkillSummary`, `McpToolSummary`, `SkillActivationSource`). Extended `AgentEvent::McpServerReady` with `tools`. Added tracing for the new variants. Updated workflow `convert.rs` and `names.rs`. Added 4 conversion tests.\n\n**Task 3 (memory):** Changed `discover_memory` to return `Vec` with `{path, content, byte_count, loaded_bytes, truncated}`. `Session::initialize` emits `MemoryLoaded` immediately after discovery, always (even for empty results). The prompt assembly receives content-only strings. Memory test added for truncation metadata.\n\n**Task 4 (skills):** `Session::initialize` emits `SkillsDiscovered` (always). Slash expansion now emits `SkillActivated { Slash }` instead of `SkillExpanded`. `make_use_skill_tool` emits `SkillActivated { Tool }` only on successful lookup. Failed lookups do not emit. Added 4 session tests.\n\n**Task 5 (MCP):** Added `McpConnectionManager::tool_summaries_for_server` that filters/sorts by qualified name. `Session::initialize` includes the summaries in `McpServerReady`. Existing MCP e2e test now asserts the names-only summary content. Added an mcp unit test.\n\n**Task 6 (docs):** Updated `docs/internal/events.md` with the enriched `agent.mcp.ready` payload, three new sections for `agent.memory.loaded`, `agent.skills.discovered`, `agent.skill.activated`, and a note about the retired `agent.skill.expanded` notification.\n\n**Task 7:** All 1841 tests in `fabro-agent`, `fabro-workflow`, `fabro-types`, `fabro-mcp` pass; workspace clippy clean under nightly-2026-04-14; formatting checked.", + "last_stage": "implement" + }, + "notes": "Stage completed: implement", + "usage": { + "input": { + "usage": { + "model": { + "provider": "anthropic", + "model_id": "claude-opus-4-7" + }, + "tokens": { + "input_tokens": 220090, + "output_tokens": 51681, + "reasoning_tokens": 0, + "cache_read_tokens": 24871894, + "cache_write_tokens": 231090 + } + }, + "facts": { + "algorithm": "anthropic", + "cache_write_5m_tokens": 231090, + "cache_write_1h_tokens": 0 + } + }, + "total_usd_micros": 16272734 + }, + "files_touched": [ + "/home/daytona/workspace/fabro/docs/internal/events.md", + "/home/daytona/workspace/fabro/lib/crates/fabro-agent/src/lib.rs", + "/home/daytona/workspace/fabro/lib/crates/fabro-agent/src/memory.rs", + "/home/daytona/workspace/fabro/lib/crates/fabro-agent/src/session.rs", + "/home/daytona/workspace/fabro/lib/crates/fabro-agent/src/skills.rs", + "/home/daytona/workspace/fabro/lib/crates/fabro-agent/src/types.rs", + "/home/daytona/workspace/fabro/lib/crates/fabro-mcp/src/connection_manager.rs", + "/home/daytona/workspace/fabro/lib/crates/fabro-types/src/run_event/agent.rs", + "/home/daytona/workspace/fabro/lib/crates/fabro-types/src/run_event/mod.rs", + "/home/daytona/workspace/fabro/lib/crates/fabro-workflow/src/event/convert.rs", + "/home/daytona/workspace/fabro/lib/crates/fabro-workflow/src/event/names.rs", + "/home/daytona/workspace/fabro/lib/crates/fabro-workflow/src/handler/prompt.rs" + ] } }, - "next_node_id": "implement", + "next_node_id": "simplify_opus", "node_visits": { "start": 1, "toolchain": 1, "preflight_compile": 1, - "preflight_lint": 1 + "preflight_lint": 1, + "implement": 1 } }, "diff": {} @@ -909,11 +1043,48 @@ }, "state": "succeeded" }, + "implement@1": { + "first_event_seq": 50, + "prompt": null, + "response": null, + "completion": null, + "provider_used": { + "mode": "agent", + "provider": "anthropic", + "model": "claude-opus-4-7" + }, + "diff": null, + "script_invocation": null, + "script_timing": null, + "parallel_results": null, + "output": null, + "started_at": "2026-05-22T19:02:15.512182Z", + "handler": "agent", + "usage": { + "input_tokens": 220090, + "output_tokens": 51681, + "total_tokens": 25374755, + "reasoning_tokens": 0, + "cache_read_tokens": 24871894, + "cache_write_tokens": 231090, + "total_usd_micros": 16272734 + }, + "model": { + "provider": "anthropic", + "model_id": "claude-opus-4-7" + }, + "state": "running" + }, "preflight_lint@1": { "first_event_seq": 40, "prompt": null, "response": null, - "completion": null, + "completion": { + "outcome": "succeeded", + "notes": "Script completed: cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", + "failure_reason": null, + "timestamp": "2026-05-22T19:02:02.739980Z" + }, "provider_used": null, "diff": null, "script_invocation": { @@ -921,11 +1092,27 @@ "command": "exec 2>&1\ncargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", "language": "shell" }, - "script_timing": null, + "script_timing": { + "output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126", + "exit_code": 0, + "duration_ms": 150532, + "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-22T18:59:32.199220Z", "handler": "command", + "timing": { + "wall_time_ms": 150539, + "inference_time_ms": 0, + "tool_time_ms": 0, + "active_time_ms": 0 + }, "usage": { "input_tokens": 0, "output_tokens": 0, @@ -934,7 +1121,7 @@ "cache_read_tokens": 0, "cache_write_tokens": 0 }, - "state": "running" + "state": "succeeded" } } } \ No newline at end of file diff --git a/stages/004-preflight_lint@1/output.log b/stages/004-preflight_lint@1/output.log new file mode 100644 index 000000000..d87ba9545 --- /dev/null +++ b/stages/004-preflight_lint@1/output.log @@ -0,0 +1 @@ +blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126 \ No newline at end of file diff --git a/stages/004-preflight_lint@1/script_timing.json b/stages/004-preflight_lint@1/script_timing.json new file mode 100644 index 000000000..5c6c643d8 --- /dev/null +++ b/stages/004-preflight_lint@1/script_timing.json @@ -0,0 +1,8 @@ +{ + "output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126", + "exit_code": 0, + "duration_ms": 150532, + "termination": "exited", + "output_bytes": 0, + "live_streaming": false +} \ No newline at end of file diff --git a/stages/004-preflight_lint@1/status.json b/stages/004-preflight_lint@1/status.json new file mode 100644 index 000000000..b33945bf1 --- /dev/null +++ b/stages/004-preflight_lint@1/status.json @@ -0,0 +1,6 @@ +{ + "outcome": "succeeded", + "notes": "Script completed: cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", + "failure_reason": null, + "timestamp": "2026-05-22T19:02:02.739980Z" +} \ No newline at end of file diff --git a/stages/005-implement@1/prompt.md b/stages/005-implement@1/prompt.md new file mode 100644 index 000000000..b5b347b23 --- /dev/null +++ b/stages/005-implement@1/prompt.md @@ -0,0 +1,456 @@ +Goal: # Agent Context Observability Events Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add durable API/backend events that report loaded memory files, discovered and activated skills, and per-server MCP tool names for agent runs. + +**Architecture:** Keep this API-backend scoped. Emit typed `AgentEvent` variants from the existing `fabro-agent` initialization and skill activation paths, convert them through `fabro-workflow` into durable `fabro-types` run events, and document the event contracts. Do not add run projection fields in this pass; consumers can read the event stream/history. + +**Tech Stack:** Rust, Serde, Fabro agent/session events, Fabro workflow event conversion, Fabro MCP connection manager, `cargo nextest`. + +--- + +## Scope + +Implement these event changes: + +- Add `agent.memory.loaded` with memory file paths, byte counts, loaded byte counts, truncation flags, provider profile, total loaded bytes, and budget bytes. +- Add `agent.skills.discovered` with source directories, provider profile, and sorted skill summaries. +- Add persisted `agent.skill.activated` for slash skill expansion and successful `use_skill` tool calls. +- Enrich `agent.mcp.ready` with names-only tool summaries: qualified tool name and original server tool name. + +Do not implement ACP-native equivalents in this pass. Do not include memory file contents in any event payload. Do not include MCP tool descriptions or schemas. + +## Existing Patterns To Follow + +- Read `docs/internal/events-strategy.md` before changing event variants, names, conversion, or progress JSONL behavior. +- Read `docs/internal/testing-strategy.md` before adding or reorganizing tests. +- Follow the current `AgentEvent` flow: + - `lib/crates/fabro-agent/src/types.rs` + - `lib/crates/fabro-agent/src/session.rs` + - `lib/crates/fabro-workflow/src/handler/llm/api.rs` + - `lib/crates/fabro-workflow/src/event/convert.rs` + - `lib/crates/fabro-workflow/src/event/names.rs` + - `lib/crates/fabro-types/src/run_event/agent.rs` + - `lib/crates/fabro-types/src/run_event/mod.rs` +- Follow Rust import style from `AGENTS.md`: import types by name, import functions through their parent module, and avoid glob imports in production code. + +## File Map + +- Modify `lib/crates/fabro-types/src/run_event/agent.rs`: add new prop structs and extend `AgentMcpReadyProps`. +- Modify `lib/crates/fabro-types/src/run_event/mod.rs`: add `EventBody` variants for the new event names. +- Modify `lib/crates/fabro-agent/src/types.rs`: add internal `AgentEvent` variants, trace output, and noise filtering decisions. +- Modify `lib/crates/fabro-agent/src/memory.rs`: return memory content plus metadata instead of bare strings. +- Modify `lib/crates/fabro-agent/src/session.rs`: emit memory, skills, skill activation, and enriched MCP events. +- Modify `lib/crates/fabro-agent/src/skills.rs`: emit tool-sourced skill activation from `use_skill`. +- Modify `lib/crates/fabro-mcp/src/connection_manager.rs`: expose or support deterministic names-only tool summaries per server. +- Modify `lib/crates/fabro-workflow/src/event/convert.rs`: convert new agent events to durable event bodies. +- Modify `lib/crates/fabro-workflow/src/event/names.rs`: add event names. +- Modify `lib/crates/fabro-workflow/src/event/events.rs` only if the agent event name mapping also lives there for these variants. +- Modify `lib/crates/fabro-workflow/src/event/stored_fields.rs` only if a new event needs non-standard stored fields; otherwise rely on existing `Event::Agent` handling. +- Modify `docs/internal/events.md`: document new event shapes and the richer MCP payload. +- Add or update tests in `lib/crates/fabro-agent`, `lib/crates/fabro-mcp`, `lib/crates/fabro-types`, and `lib/crates/fabro-workflow`. + +--- + +### Task 1: Add Typed Durable Event Contracts + +**Files:** +- Modify: `lib/crates/fabro-types/src/run_event/agent.rs` +- Modify: `lib/crates/fabro-types/src/run_event/mod.rs` +- Test: existing `fabro-types` run event serde tests, or add focused coverage near the existing run event tests. + +- [ ] **Step 1: Add agent memory props** + +Add event prop structs with this shape: + +```rust +pub struct AgentMemoryLoadedProps { + pub provider_profile: String, + pub files: Vec, + pub total_loaded_bytes: usize, + pub budget_bytes: usize, + pub visit: u32, +} + +pub struct AgentMemoryFileProps { + pub path: String, + pub byte_count: usize, + pub loaded_bytes: usize, + pub truncated: bool, +} +``` + +- [ ] **Step 2: Add skill props** + +Add skill discovery and activation props: + +```rust +pub struct AgentSkillsDiscoveredProps { + pub provider_profile: String, + pub source_dirs: Vec, + pub skills: Vec, + pub visit: u32, +} + +pub struct AgentSkillSummary { + pub name: String, + pub description: String, +} + +pub enum AgentSkillActivationSource { + Slash, + Tool, +} + +pub struct AgentSkillActivatedProps { + pub skill_name: String, + pub source: AgentSkillActivationSource, + pub visit: u32, +} +``` + +Use serde names `slash` and `tool` for `AgentSkillActivationSource`. If a local enum string pattern already exists, follow that pattern. + +- [ ] **Step 3: Extend MCP ready props** + +Extend `AgentMcpReadyProps` with a backwards-compatible field: + +```rust +#[serde(default, skip_serializing_if = "Vec::is_empty")] +pub tools: Vec, +``` + +Add: + +```rust +pub struct AgentMcpToolSummary { + pub name: String, + pub original_name: String, +} +``` + +- [ ] **Step 4: Add EventBody variants** + +Add `EventBody` variants using these serialized event names: + +- `agent.memory.loaded` +- `agent.skills.discovered` +- `agent.skill.activated` + +Keep existing `agent.mcp.ready` name unchanged and only enrich its props. + +- [ ] **Step 5: Add serde tests** + +Cover: + +- New event names serialize to the expected dot names. +- `AgentSkillActivationSource` serializes as `slash` and `tool`. +- Old `agent.mcp.ready` JSON without `tools` still deserializes with `tools == []`. + +--- + +### Task 2: Add Internal Agent Events And Conversion + +**Files:** +- Modify: `lib/crates/fabro-agent/src/types.rs` +- Modify: `lib/crates/fabro-workflow/src/event/convert.rs` +- Modify: `lib/crates/fabro-workflow/src/event/names.rs` +- Modify: `lib/crates/fabro-workflow/src/event/events.rs` if needed by the existing name mapping. +- Test: `lib/crates/fabro-workflow` event conversion tests. + +- [ ] **Step 1: Add internal AgentEvent variants** + +Add variants equivalent to: + +```rust +MemoryLoaded { + provider_profile: String, + files: Vec, + total_loaded_bytes: usize, + budget_bytes: usize, +} + +SkillsDiscovered { + provider_profile: String, + source_dirs: Vec, + skills: Vec, +} + +SkillActivated { + skill_name: String, + source: SkillActivationSource, +} + +McpServerReady { + server_name: String, + tool_count: usize, + tools: Vec, +} +``` + +Prefer small shared internal structs near `AgentEvent` if that matches the existing file organization. + +- [ ] **Step 2: Persist skill activation** + +Do not classify `SkillActivated` as streaming noise. The existing `SkillExpanded` event is currently filtered before persistence; replace slash expansion emissions with `SkillActivated { source: Slash }` or keep `SkillExpanded` internal-only if removing it would create unnecessary churn. + +- [ ] **Step 3: Add trace behavior** + +Update `AgentEvent::trace` so the new events emit concise tracing summaries: + +- memory loaded: profile, file count, total loaded bytes, budget bytes +- skills discovered: profile, skill count, source dir count +- skill activated: name and source +- MCP ready: server, count, and summary count + +- [ ] **Step 4: Convert to durable events** + +Update `fabro-workflow` event conversion so the new agent events map to the new `fabro-types` props and include `visit`. + +- [ ] **Step 5: Add conversion tests** + +Cover each new event with a focused conversion assertion that checks: + +- durable event name +- `visit` +- core fields +- no memory content in the converted payload + +--- + +### Task 3: Emit Memory Loaded Metadata + +**Files:** +- Modify: `lib/crates/fabro-agent/src/memory.rs` +- Modify: `lib/crates/fabro-agent/src/session.rs` +- Test: relevant `fabro-agent` memory/session tests. + +- [ ] **Step 1: Change memory discovery return type** + +Change memory discovery from bare `Vec` to a document type carrying both prompt content and event metadata: + +```rust +pub struct MemoryDocument { + pub path: String, + pub content: String, + pub byte_count: usize, + pub loaded_bytes: usize, + pub truncated: bool, +} +``` + +Keep existing behavior unchanged: + +- provider profile filename candidates stay the same +- root-to-working-dir walk stays the same +- content dedupe stays the same +- empty files are skipped +- total budget remains 32 KiB +- truncated content keeps the existing truncation marker + +- [ ] **Step 2: Preserve prompt assembly behavior** + +Adjust session/profile prompt assembly to pass only memory contents where prompt assembly expects memory text. The system prompt should be byte-for-byte equivalent except where existing tests allow non-semantic ordering differences. + +- [ ] **Step 3: Emit agent.memory.loaded** + +In `Session::initialize()`, emit `AgentEvent::MemoryLoaded` immediately after memory discovery, before skills and MCP initialization. + +Emit the event even when no memory files are loaded. That lets consumers distinguish "no memory" from "not reported." + +- [ ] **Step 4: Add memory tests** + +Cover: + +- loaded file path appears in event metadata +- `byte_count` is the original file byte count +- `loaded_bytes` reflects bytes actually loaded into the prompt budget +- `truncated` is true only for truncated files +- event payload never contains memory file contents +- empty discovery still emits a memory-loaded event with `files == []` + +--- + +### Task 4: Emit Skills Discovered And Skill Activated + +**Files:** +- Modify: `lib/crates/fabro-agent/src/session.rs` +- Modify: `lib/crates/fabro-agent/src/skills.rs` +- Test: relevant `fabro-agent` skill/session tests. + +- [ ] **Step 1: Emit skills discovered** + +After `discover_skills(...)`, emit `AgentEvent::SkillsDiscovered` with: + +- `provider_profile` +- `source_dirs` +- sorted `skills: [{ name, description }]` + +Emit the event even when no skills are discovered. + +- [ ] **Step 2: Emit slash activation** + +Where slash skill expansion currently emits or creates `SkillExpanded`, emit: + +```rust +AgentEvent::SkillActivated { + skill_name, + source: SkillActivationSource::Slash, +} +``` + +- [ ] **Step 3: Emit tool activation** + +In `make_use_skill_tool`, use `ToolContext::emit_agent_event(...)` after a requested skill is found and before returning the skill template. Emit: + +```rust +AgentEvent::SkillActivated { + skill_name: name.to_string(), + source: SkillActivationSource::Tool, +} +``` + +Do not emit activation for failed `use_skill` lookups. + +- [ ] **Step 4: Add skill tests** + +Cover: + +- discovery event includes all discovered skills sorted by name +- discovery event includes configured source directories +- empty discovery emits `skills == []` +- slash expansion emits `source == slash` +- successful `use_skill` emits `source == tool` +- failed `use_skill` does not emit activation + +--- + +### Task 5: Enrich agent.mcp.ready With Names-Only Tool Summaries + +**Files:** +- Modify: `lib/crates/fabro-mcp/src/connection_manager.rs` +- Modify: `lib/crates/fabro-agent/src/session.rs` +- Test: relevant `fabro-mcp` or `fabro-agent` MCP tests. + +- [ ] **Step 1: Add deterministic tool summaries** + +Expose a helper on `McpConnectionManager` or compute in `Session` from `all_tools()`: + +- filter tools by `server_name` +- return qualified tool name as `name` +- return server-provided tool name as `original_name` +- sort by qualified `name` + +- [ ] **Step 2: Enrich ready emissions** + +When emitting `AgentEvent::McpServerReady`, include the tool summaries for that server. Keep existing `server_name` and `tool_count`. + +- [ ] **Step 3: Add MCP tests** + +Cover: + +- ready event includes only tools from the ready server +- summaries are sorted by qualified name +- `name` is the Fabro-qualified MCP tool name +- `original_name` is the server-provided tool name +- descriptions and input schemas are not included + +--- + +### Task 6: Update Event Documentation + +**Files:** +- Modify: `docs/internal/events.md` + +- [ ] **Step 1: Document new events** + +Add sections for: + +- `agent.memory.loaded` +- `agent.skills.discovered` +- `agent.skill.activated` + +For `agent.memory.loaded`, explicitly state that file contents are excluded. + +- [ ] **Step 2: Update MCP ready docs** + +Update `agent.mcp.ready` to show: + +```json +{ + "server_name": "github", + "tool_count": 2, + "tools": [ + { + "name": "mcp__github__create_issue", + "original_name": "create_issue" + } + ], + "visit": 1 +} +``` + +- [ ] **Step 3: Record skill event replacement** + +If `agent.skill.expanded` remains in internal code or docs, mark it internal-only or replaced by `agent.skill.activated`. + +--- + +### Task 7: Verify + +**Files:** +- No new files unless test placement requires it. + +- [ ] **Step 1: Run focused tests** + +Run: + +```bash +cargo nextest run -p fabro-agent -p fabro-workflow -p fabro-types -p fabro-mcp +``` + +- [ ] **Step 2: Run formatting** + +Run: + +```bash +cargo +nightly-2026-04-14 fmt --all +``` + +- [ ] **Step 3: Run clippy for touched crates or workspace** + +Prefer the workspace command if time permits: + +```bash +cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings +``` + +- [ ] **Step 4: Final sanity checks** + +Confirm: + +- memory events never contain file contents +- skills discovered and memory loaded are emitted even for empty lists +- skill activation is persisted rather than filtered as streaming noise +- `agent.mcp.ready` remains backwards-compatible for old events without `tools` +- docs match the serialized event names and payload shapes + + + +## Completed stages +- **toolchain**: succeeded + - Script: `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` + - Output: + ``` + cargo 1.95.0 (f2d3ce0bd 2026-03-21) + ``` +- **preflight_compile**: succeeded + - Script: `cargo check -q --workspace 2>&1` + - Output: (empty) +- **preflight_lint**: succeeded + - Script: `cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1` + - Output: (empty) + + +Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD. \ No newline at end of file diff --git a/stages/005-implement@1/provider_used.json b/stages/005-implement@1/provider_used.json new file mode 100644 index 000000000..672cc50e5 --- /dev/null +++ b/stages/005-implement@1/provider_used.json @@ -0,0 +1,5 @@ +{ + "mode": "agent", + "provider": "anthropic", + "model": "claude-opus-4-7" +} \ No newline at end of file diff --git a/stages/005-implement@1/response.md b/stages/005-implement@1/response.md new file mode 100644 index 000000000..89e783506 --- /dev/null +++ b/stages/005-implement@1/response.md @@ -0,0 +1,23 @@ +Let me do a quick sanity check on the plan's "final sanity checks": + +1. ✅ Memory events never contain file contents — verified by test `agent_memory_loaded_payload_excludes_file_contents` and structural design. +2. ✅ Skills discovered and memory loaded emitted even for empty lists — verified by tests `initialize_emits_memory_loaded_event_with_empty_files_when_no_memory` and `initialize_emits_skills_discovered_event_when_no_skills`. +3. ✅ Skill activation is persisted rather than filtered as streaming noise — `SkillActivated` is not in `is_streaming_noise()` and has conversion + name mapping. +4. ✅ `agent.mcp.ready` backwards-compatible — verified by `agent_mcp_ready_deserializes_legacy_payload_without_tools` test and `#[serde(default, skip_serializing_if = "Vec::is_empty")]`. +5. ✅ Docs match the serialized event names and payload shapes. + +Summary of changes: + +**Task 1 (fabro-types):** Added `AgentMemoryLoadedProps`, `AgentMemoryFileProps`, `AgentSkillsDiscoveredProps`, `AgentSkillSummary`, `AgentSkillActivationSource`, `AgentSkillActivatedProps`, `AgentMcpToolSummary`. Extended `AgentMcpReadyProps` with a backwards-compatible `tools` field. Added 3 new `EventBody` variants (`AgentMemoryLoaded`, `AgentSkillsDiscovered`, `AgentSkillActivated`) plus the legacy whitelist entries. Added 5 serde tests. + +**Task 2 (fabro-agent / fabro-workflow):** Added internal `AgentEvent::MemoryLoaded`, `SkillsDiscovered`, `SkillActivated` variants plus support structs (`MemoryFileSummary`, `SkillSummary`, `McpToolSummary`, `SkillActivationSource`). Extended `AgentEvent::McpServerReady` with `tools`. Added tracing for the new variants. Updated workflow `convert.rs` and `names.rs`. Added 4 conversion tests. + +**Task 3 (memory):** Changed `discover_memory` to return `Vec` with `{path, content, byte_count, loaded_bytes, truncated}`. `Session::initialize` emits `MemoryLoaded` immediately after discovery, always (even for empty results). The prompt assembly receives content-only strings. Memory test added for truncation metadata. + +**Task 4 (skills):** `Session::initialize` emits `SkillsDiscovered` (always). Slash expansion now emits `SkillActivated { Slash }` instead of `SkillExpanded`. `make_use_skill_tool` emits `SkillActivated { Tool }` only on successful lookup. Failed lookups do not emit. Added 4 session tests. + +**Task 5 (MCP):** Added `McpConnectionManager::tool_summaries_for_server` that filters/sorts by qualified name. `Session::initialize` includes the summaries in `McpServerReady`. Existing MCP e2e test now asserts the names-only summary content. Added an mcp unit test. + +**Task 6 (docs):** Updated `docs/internal/events.md` with the enriched `agent.mcp.ready` payload, three new sections for `agent.memory.loaded`, `agent.skills.discovered`, `agent.skill.activated`, and a note about the retired `agent.skill.expanded` notification. + +**Task 7:** All 1841 tests in `fabro-agent`, `fabro-workflow`, `fabro-types`, `fabro-mcp` pass; workspace clippy clean under nightly-2026-04-14; formatting checked. \ No newline at end of file