diff --git a/checkpoint.json b/checkpoint.json index 5b613c744..9517c3cfb 100644 --- a/checkpoint.json +++ b/checkpoint.json @@ -1,21 +1,26 @@ { - "timestamp": "2026-03-20T21:32:41.816764Z", - "current_node": "preflight_lint", + "timestamp": "2026-03-20T22:20:17.082010Z", + "current_node": "implement", "completed_nodes": [ "start", "toolchain", "preflight_compile", - "preflight_lint" + "preflight_lint", + "implement" ], "node_retries": { "preflight_compile": 1, "toolchain": 1, + "implement": 1, "start": 1, "preflight_lint": 1 }, "context_values": { - "internal.thread_id": "preflight_compile", - "current_node": "preflight_lint", + "internal.thread_id": "preflight_lint", + "current_node": "implement", + "internal.retry_count.implement": 1, + "last_response": "All verification passes. Let me summarize all the changes made:\n\n## Summary of Changes\n\n### 1. Foundation: `lib/crates/fabro-llm/src/types.rs`\n- Added `speed: Option` to `Request` struct (afte", + "response.implement": "All verification passes. Let me summarize all the changes made:\n\n## Summary of Changes\n\n### 1. Foundation: `lib/crates/fabro-llm/src/types.rs`\n- Added `speed: Option` to `Request` struct (after `reasoning_effort`)\n- Added `speed: Option` to `Usage` struct (with `serde(default, skip_serializing_if)`, before `raw`)\n- Added `speed: self.speed` to `Usage::Add` impl (left-hand side wins)\n- Updated all test struct literals to include `speed: None`\n\n### 2. Graph layer: `lib/crates/fabro-graphviz/src/graph/types.rs`\n- Added `speed()` method to `Node` returning `Option<&str>` (no default, unlike `reasoning_effort` which defaults to \"high\")\n- Added `assert_eq!(node.speed(), None)` to `node_defaults` test\n\n### 3. Stylesheet: `lib/crates/fabro-workflows/src/stylesheet.rs`\n- Added `\"speed\"` to `STYLESHEET_PROPERTIES` array\n- Added `apply_speed_property` test\n\n### 4. Agent config: `lib/crates/fabro-agent/src/config.rs`\n- Added `pub speed: Option` to `SessionConfig`\n- Added `speed: None` to `Default` impl\n\n### 5. Session wiring: `lib/crates/fabro-agent/src/session.rs`\n- Added `speed: self.config.speed.clone()` to Request construction\n- Added `set_speed()` method\n\n### 6. Anthropic provider: `lib/crates/fabro-llm/src/providers/anthropic.rs`\n- **6a.** Added `speed: Option` to `ApiRequest` struct\n- **6b.** Added `FAST_MODE_BETA_HEADER` constant (`\"fast-mode-2026-02-01\"`)\n- **6c.** Extended `build_beta_header()` with `include_fast_mode_header: bool` parameter\n- **6d.** In `build_api_request()`: set `speed` on ApiRequest, compute `is_fast`, pass to `build_beta_header()`\n- **6e.** Added `speed: Option` to `ApiUsage` struct\n- **6f.** Map `api_resp.usage.speed` → `Usage.speed` in both non-streaming and streaming paths\n- Added 3 new tests: `build_api_request_sets_speed`, `build_api_request_injects_fast_mode_beta_header`, `beta_header_includes_both_cache_and_fast_mode`\n\n### 7. Usage tracking: `lib/crates/fabro-workflows/src/outcome.rs`\n- Added `speed: Option` to `StageUsage` (with `serde(default, skip_serializing_if)`)\n- Updated `From<&StageUsage> for Usage` to include `speed`\n\n### 8. Cost multiplier: `lib/crates/fabro-workflows/src/cost.rs`\n- Applied 6x multiplier when `speed == \"fast\"`\n- Added `compute_stage_cost_fast_mode_6x_multiplier` test\n\n### 9. Backend wiring: `lib/crates/fabro-workflows/src/backend/api.rs`\n- SessionConfig construction: added `speed: node.speed().map(String::from)`\n- Prompt-mode Request construction: added `speed: node.speed().map(String::from)`\n- Both StageUsage constructions: added `speed: response.usage.speed.clone()` / `total_usage.speed.clone()`\n\n### 10. Other files updated with `speed: None` in struct literals:\n- `fabro-agent/src/compaction.rs`, `fabro-agent/src/tools.rs`, `fabro-agent/src/types.rs`\n- `fabro-hooks/src/executor.rs`\n- `fabro-api/src/server.rs`\n- `fabro-cli/src/doctor.rs`\n- `fabro-llm/src/generate.rs` (also added `speed` to `GenerateParams`)\n- `fabro-llm/src/client.rs`, `fabro-llm/src/providers/fabro_server.rs`, `fabro-llm/src/providers/gemini.rs`, `fabro-llm/src/providers/openai.rs`, `fabro-llm/src/providers/openai_compatible.rs`\n- `fabro-workflows/src/backend/cli.rs`, `fabro-workflows/src/event.rs`, `fabro-workflows/src/preamble.rs`\n- `fabro-llm/tests/integration.rs`, `fabro-workflows/tests/integration.rs`", "graph.goal": "# Plan: Support Anthropic fast mode (`speed: fast`) — Issue #120\n\n## Context\n\nAnthropic's API supports [fast mode](https://platform.claude.com/docs/en/build-with-claude/fast-mode) (beta) for Claude Opus 4.6 — up to 2.5x faster output at 6x pricing ($30/$150 per MTok vs $5/$25 standard). The API requires:\n- `\"speed\": \"fast\"` top-level field in request body\n- `anthropic-beta: fast-mode-2026-02-01` header\n- Response includes `usage.speed` confirming actual speed used\n\nThis follows the same pattern as `reasoning_effort`, which is already implemented end-to-end.\n\n## Implementation\n\n### 1. Foundation: `lib/crates/fabro-llm/src/types.rs`\n\n- Add `speed: Option` to `Request` (after `reasoning_effort`, line ~454)\n- Add `speed: Option` to `Usage` (before `raw`, line ~372)\n- In `Usage::Add` impl (line ~389): propagate `speed` from `self` (left-hand side wins, since all requests in a session use the same speed)\n\n### 2. Graph layer: `lib/crates/fabro-graphviz/src/graph/types.rs`\n\nAdd after `reasoning_effort()` (~line 211):\n```rust\n#[must_use]\npub fn speed(&self) -> Option<&str> {\n self.str_attr(\"speed\")\n}\n```\nReturns `Option` (no default) — unlike `reasoning_effort` which defaults to \"high\", speed defaults to None (standard) since fast mode is 6x cost.\n\n### 3. Stylesheet: `lib/crates/fabro-workflows/src/stylesheet.rs`\n\nAdd `\"speed\"` to `STYLESHEET_PROPERTIES` array (line 5):\n```rust\nconst STYLESHEET_PROPERTIES: &[&str] = &[\"model\", \"provider\", \"reasoning_effort\", \"speed\", \"backend\"];\n```\n\n### 4. Agent config: `lib/crates/fabro-agent/src/config.rs`\n\n- Add `pub speed: Option` field to `SessionConfig` (line ~66)\n- Add `speed: None` to `Default` impl (line ~135)\n\n### 5. Session wiring: `lib/crates/fabro-agent/src/session.rs`\n\n- Add `speed: self.config.speed.clone()` to Request construction (~line 908)\n- Add `set_speed()` method analogous to `set_reasoning_effort()` (~line 458)\n\n### 6. Anthropic provider: `lib/crates/fabro-llm/src/providers/anthropic.rs`\n\n**6a. `ApiRequest` struct** (~line 87): Add `speed: Option` field\n\n**6b. Beta header constant** (~line 510):\n```rust\nconst FAST_MODE_BETA_HEADER: &str = \"fast-mode-2026-02-01\";\n```\n\n**6c. `build_beta_header()`** (~line 582): Add `include_fast_mode_header: bool` parameter. Inject fast-mode header (same pattern as cache header).\n\n**6d. `build_api_request()`** (~line 1082):\n- Set `speed: request.speed.clone()` on `ApiRequest`\n- Compute `let is_fast = request.speed.as_deref() == Some(\"fast\")`\n- Pass `is_fast` to `build_beta_header()`\n\n**6e. `ApiUsage`** (~line 160): Add `speed: Option` field\n\n**6f. Response parsing**: Map `api_resp.usage.speed` → `Usage.speed` in both:\n- Non-streaming path (~line 1192)\n- Streaming `message_start` handler (~line 690)\n\n### 7. Usage tracking: `lib/crates/fabro-workflows/src/outcome.rs`\n\n- Add `speed: Option` to `StageUsage` (with `serde(default, skip_serializing_if)`)\n- Update `From<&StageUsage> for Usage` to include `speed`\n\n### 8. Cost multiplier: `lib/crates/fabro-workflows/src/cost.rs`\n\nApply 6x multiplier when `speed == \"fast\"`:\n```rust\nlet multiplier = if usage.speed.as_deref() == Some(\"fast\") { 6.0 } else { 1.0 };\n// Apply multiplier to final cost\n```\n\n### 9. Backend wiring: `lib/crates/fabro-workflows/src/backend/api.rs`\n\n- SessionConfig construction (~line 195): add `speed: node.speed().map(String::from)`\n- Prompt-mode Request construction (~line 285): add `speed: node.speed().map(String::from)`\n- Both StageUsage constructions (~lines 389, 589): add `speed: response.usage.speed.clone()` / `total_usage.speed.clone()`\n\n### 10. Tests\n\n- `anthropic.rs`: `build_api_request_sets_speed`, `build_api_request_injects_fast_mode_beta_header`, `beta_header_includes_both_cache_and_fast_mode`\n- `cost.rs`: `compute_stage_cost_fast_mode_6x_multiplier`\n- `stylesheet.rs`: `apply_speed_property`\n- `session.rs`: `set_speed_mid_session`\n- Update all existing struct literals (`Request`, `Usage`, `SessionConfig`, `StageUsage`, `ApiRequest`, `ApiUsage`) in tests to include `speed: None`\n\n## Implementation order\n\n1. `types.rs` (Request + Usage) — everything depends on this\n2. `graph/types.rs` + `stylesheet.rs` — graph layer\n3. `config.rs` + `session.rs` — agent layer\n4. `anthropic.rs` — provider (ApiRequest, beta header, response parsing)\n5. `outcome.rs` + `cost.rs` — usage tracking\n6. `backend/api.rs` — final wiring\n7. Tests throughout\n\n## Verification\n\n```\ncargo build --workspace\ncargo test --workspace\ncargo fmt --check --all\ncargo clippy --workspace -- -D warnings\n```\n", "internal.node_visit_count": 1, "thread.start.current_node": "toolchain", @@ -27,11 +32,13 @@ "internal.retry_count.preflight_lint": 1, "internal.retry_count.preflight_compile": 1, "graph.model_stylesheet": "\n * { backend: api; model: claude-opus-4-6;}\n ", - "current.preamble": "Goal: # Plan: Support Anthropic fast mode (`speed: fast`) — Issue #120\n\n## Context\n\nAnthropic's API supports [fast mode](https://platform.claude.com/docs/en/build-with-claude/fast-mode) (beta) for Claude Opus 4.6 — up to 2.5x faster output at 6x pricing ($30/$150 per MTok vs $5/$25 standard). The API requires:\n- `\"speed\": \"fast\"` top-level field in request body\n- `anthropic-beta: fast-mode-2026-02-01` header\n- Response includes `usage.speed` confirming actual speed used\n\nThis follows the same pattern as `reasoning_effort`, which is already implemented end-to-end.\n\n## Implementation\n\n### 1. Foundation: `lib/crates/fabro-llm/src/types.rs`\n\n- Add `speed: Option` to `Request` (after `reasoning_effort`, line ~454)\n- Add `speed: Option` to `Usage` (before `raw`, line ~372)\n- In `Usage::Add` impl (line ~389): propagate `speed` from `self` (left-hand side wins, since all requests in a session use the same speed)\n\n### 2. Graph layer: `lib/crates/fabro-graphviz/src/graph/types.rs`\n\nAdd after `reasoning_effort()` (~line 211):\n```rust\n#[must_use]\npub fn speed(&self) -> Option<&str> {\n self.str_attr(\"speed\")\n}\n```\nReturns `Option` (no default) — unlike `reasoning_effort` which defaults to \"high\", speed defaults to None (standard) since fast mode is 6x cost.\n\n### 3. Stylesheet: `lib/crates/fabro-workflows/src/stylesheet.rs`\n\nAdd `\"speed\"` to `STYLESHEET_PROPERTIES` array (line 5):\n```rust\nconst STYLESHEET_PROPERTIES: &[&str] = &[\"model\", \"provider\", \"reasoning_effort\", \"speed\", \"backend\"];\n```\n\n### 4. Agent config: `lib/crates/fabro-agent/src/config.rs`\n\n- Add `pub speed: Option` field to `SessionConfig` (line ~66)\n- Add `speed: None` to `Default` impl (line ~135)\n\n### 5. Session wiring: `lib/crates/fabro-agent/src/session.rs`\n\n- Add `speed: self.config.speed.clone()` to Request construction (~line 908)\n- Add `set_speed()` method analogous to `set_reasoning_effort()` (~line 458)\n\n### 6. Anthropic provider: `lib/crates/fabro-llm/src/providers/anthropic.rs`\n\n**6a. `ApiRequest` struct** (~line 87): Add `speed: Option` field\n\n**6b. Beta header constant** (~line 510):\n```rust\nconst FAST_MODE_BETA_HEADER: &str = \"fast-mode-2026-02-01\";\n```\n\n**6c. `build_beta_header()`** (~line 582): Add `include_fast_mode_header: bool` parameter. Inject fast-mode header (same pattern as cache header).\n\n**6d. `build_api_request()`** (~line 1082):\n- Set `speed: request.speed.clone()` on `ApiRequest`\n- Compute `let is_fast = request.speed.as_deref() == Some(\"fast\")`\n- Pass `is_fast` to `build_beta_header()`\n\n**6e. `ApiUsage`** (~line 160): Add `speed: Option` field\n\n**6f. Response parsing**: Map `api_resp.usage.speed` → `Usage.speed` in both:\n- Non-streaming path (~line 1192)\n- Streaming `message_start` handler (~line 690)\n\n### 7. Usage tracking: `lib/crates/fabro-workflows/src/outcome.rs`\n\n- Add `speed: Option` to `StageUsage` (with `serde(default, skip_serializing_if)`)\n- Update `From<&StageUsage> for Usage` to include `speed`\n\n### 8. Cost multiplier: `lib/crates/fabro-workflows/src/cost.rs`\n\nApply 6x multiplier when `speed == \"fast\"`:\n```rust\nlet multiplier = if usage.speed.as_deref() == Some(\"fast\") { 6.0 } else { 1.0 };\n// Apply multiplier to final cost\n```\n\n### 9. Backend wiring: `lib/crates/fabro-workflows/src/backend/api.rs`\n\n- SessionConfig construction (~line 195): add `speed: node.speed().map(String::from)`\n- Prompt-mode Request construction (~line 285): add `speed: node.speed().map(String::from)`\n- Both StageUsage constructions (~lines 389, 589): add `speed: response.usage.speed.clone()` / `total_usage.speed.clone()`\n\n### 10. Tests\n\n- `anthropic.rs`: `build_api_request_sets_speed`, `build_api_request_injects_fast_mode_beta_header`, `beta_header_includes_both_cache_and_fast_mode`\n- `cost.rs`: `compute_stage_cost_fast_mode_6x_multiplier`\n- `stylesheet.rs`: `apply_speed_property`\n- `session.rs`: `set_speed_mid_session`\n- Update all existing struct literals (`Request`, `Usage`, `SessionConfig`, `StageUsage`, `ApiRequest`, `ApiUsage`) in tests to include `speed: None`\n\n## Implementation order\n\n1. `types.rs` (Request + Usage) — everything depends on this\n2. `graph/types.rs` + `stylesheet.rs` — graph layer\n3. `config.rs` + `session.rs` — agent layer\n4. `anthropic.rs` — provider (ApiRequest, beta header, response parsing)\n5. `outcome.rs` + `cost.rs` — usage tracking\n6. `backend/api.rs` — final wiring\n7. Tests throughout\n\n## Verification\n\n```\ncargo build --workspace\ncargo test --workspace\ncargo fmt --check --all\ncargo clippy --workspace -- -D warnings\n```\n\n\n## Completed stages\n- **toolchain**: success\n - 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`\n - Stdout:\n ```\n cargo 1.94.0 (85eff7c80 2026-01-15)\n ```\n - Stderr: (empty)\n- **preflight_compile**: success\n - Script: `cargo check -q --workspace 2>&1`\n - Stdout: (empty)\n - Stderr: (empty)\n", + "current.preamble": "Goal: # Plan: Support Anthropic fast mode (`speed: fast`) — Issue #120\n\n## Context\n\nAnthropic's API supports [fast mode](https://platform.claude.com/docs/en/build-with-claude/fast-mode) (beta) for Claude Opus 4.6 — up to 2.5x faster output at 6x pricing ($30/$150 per MTok vs $5/$25 standard). The API requires:\n- `\"speed\": \"fast\"` top-level field in request body\n- `anthropic-beta: fast-mode-2026-02-01` header\n- Response includes `usage.speed` confirming actual speed used\n\nThis follows the same pattern as `reasoning_effort`, which is already implemented end-to-end.\n\n## Implementation\n\n### 1. Foundation: `lib/crates/fabro-llm/src/types.rs`\n\n- Add `speed: Option` to `Request` (after `reasoning_effort`, line ~454)\n- Add `speed: Option` to `Usage` (before `raw`, line ~372)\n- In `Usage::Add` impl (line ~389): propagate `speed` from `self` (left-hand side wins, since all requests in a session use the same speed)\n\n### 2. Graph layer: `lib/crates/fabro-graphviz/src/graph/types.rs`\n\nAdd after `reasoning_effort()` (~line 211):\n```rust\n#[must_use]\npub fn speed(&self) -> Option<&str> {\n self.str_attr(\"speed\")\n}\n```\nReturns `Option` (no default) — unlike `reasoning_effort` which defaults to \"high\", speed defaults to None (standard) since fast mode is 6x cost.\n\n### 3. Stylesheet: `lib/crates/fabro-workflows/src/stylesheet.rs`\n\nAdd `\"speed\"` to `STYLESHEET_PROPERTIES` array (line 5):\n```rust\nconst STYLESHEET_PROPERTIES: &[&str] = &[\"model\", \"provider\", \"reasoning_effort\", \"speed\", \"backend\"];\n```\n\n### 4. Agent config: `lib/crates/fabro-agent/src/config.rs`\n\n- Add `pub speed: Option` field to `SessionConfig` (line ~66)\n- Add `speed: None` to `Default` impl (line ~135)\n\n### 5. Session wiring: `lib/crates/fabro-agent/src/session.rs`\n\n- Add `speed: self.config.speed.clone()` to Request construction (~line 908)\n- Add `set_speed()` method analogous to `set_reasoning_effort()` (~line 458)\n\n### 6. Anthropic provider: `lib/crates/fabro-llm/src/providers/anthropic.rs`\n\n**6a. `ApiRequest` struct** (~line 87): Add `speed: Option` field\n\n**6b. Beta header constant** (~line 510):\n```rust\nconst FAST_MODE_BETA_HEADER: &str = \"fast-mode-2026-02-01\";\n```\n\n**6c. `build_beta_header()`** (~line 582): Add `include_fast_mode_header: bool` parameter. Inject fast-mode header (same pattern as cache header).\n\n**6d. `build_api_request()`** (~line 1082):\n- Set `speed: request.speed.clone()` on `ApiRequest`\n- Compute `let is_fast = request.speed.as_deref() == Some(\"fast\")`\n- Pass `is_fast` to `build_beta_header()`\n\n**6e. `ApiUsage`** (~line 160): Add `speed: Option` field\n\n**6f. Response parsing**: Map `api_resp.usage.speed` → `Usage.speed` in both:\n- Non-streaming path (~line 1192)\n- Streaming `message_start` handler (~line 690)\n\n### 7. Usage tracking: `lib/crates/fabro-workflows/src/outcome.rs`\n\n- Add `speed: Option` to `StageUsage` (with `serde(default, skip_serializing_if)`)\n- Update `From<&StageUsage> for Usage` to include `speed`\n\n### 8. Cost multiplier: `lib/crates/fabro-workflows/src/cost.rs`\n\nApply 6x multiplier when `speed == \"fast\"`:\n```rust\nlet multiplier = if usage.speed.as_deref() == Some(\"fast\") { 6.0 } else { 1.0 };\n// Apply multiplier to final cost\n```\n\n### 9. Backend wiring: `lib/crates/fabro-workflows/src/backend/api.rs`\n\n- SessionConfig construction (~line 195): add `speed: node.speed().map(String::from)`\n- Prompt-mode Request construction (~line 285): add `speed: node.speed().map(String::from)`\n- Both StageUsage constructions (~lines 389, 589): add `speed: response.usage.speed.clone()` / `total_usage.speed.clone()`\n\n### 10. Tests\n\n- `anthropic.rs`: `build_api_request_sets_speed`, `build_api_request_injects_fast_mode_beta_header`, `beta_header_includes_both_cache_and_fast_mode`\n- `cost.rs`: `compute_stage_cost_fast_mode_6x_multiplier`\n- `stylesheet.rs`: `apply_speed_property`\n- `session.rs`: `set_speed_mid_session`\n- Update all existing struct literals (`Request`, `Usage`, `SessionConfig`, `StageUsage`, `ApiRequest`, `ApiUsage`) in tests to include `speed: None`\n\n## Implementation order\n\n1. `types.rs` (Request + Usage) — everything depends on this\n2. `graph/types.rs` + `stylesheet.rs` — graph layer\n3. `config.rs` + `session.rs` — agent layer\n4. `anthropic.rs` — provider (ApiRequest, beta header, response parsing)\n5. `outcome.rs` + `cost.rs` — usage tracking\n6. `backend/api.rs` — final wiring\n7. Tests throughout\n\n## Verification\n\n```\ncargo build --workspace\ncargo test --workspace\ncargo fmt --check --all\ncargo clippy --workspace -- -D warnings\n```\n\n\n## Completed stages\n- **toolchain**: success\n - 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`\n - Stdout:\n ```\n cargo 1.94.0 (85eff7c80 2026-01-15)\n ```\n - Stderr: (empty)\n- **preflight_compile**: success\n - Script: `cargo check -q --workspace 2>&1`\n - Stdout: (empty)\n - Stderr: (empty)\n- **preflight_lint**: success\n - Script: `cargo clippy -q --workspace -- -D warnings 2>&1`\n - Stdout: (empty)\n - Stderr: (empty)\n", "thread.toolchain.current_node": "preflight_compile", "failure_class": "", "outcome": "success", "internal.retry_count.toolchain": 1, + "thread.preflight_lint.current_node": "implement", + "last_stage": "implement", "graph.rankdir": "LR", "internal.run_id": "01KM6JGQQDB57HD9DCDRGN4FSW", "internal.fidelity": "compact" @@ -51,6 +58,53 @@ "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", "duration_ms": 218 }, + "implement": { + "status": "success", + "context_updates": { + "last_stage": "implement", + "last_response": "All verification passes. Let me summarize all the changes made:\n\n## Summary of Changes\n\n### 1. Foundation: `lib/crates/fabro-llm/src/types.rs`\n- Added `speed: Option` to `Request` struct (afte", + "response.implement": "All verification passes. Let me summarize all the changes made:\n\n## Summary of Changes\n\n### 1. Foundation: `lib/crates/fabro-llm/src/types.rs`\n- Added `speed: Option` to `Request` struct (after `reasoning_effort`)\n- Added `speed: Option` to `Usage` struct (with `serde(default, skip_serializing_if)`, before `raw`)\n- Added `speed: self.speed` to `Usage::Add` impl (left-hand side wins)\n- Updated all test struct literals to include `speed: None`\n\n### 2. Graph layer: `lib/crates/fabro-graphviz/src/graph/types.rs`\n- Added `speed()` method to `Node` returning `Option<&str>` (no default, unlike `reasoning_effort` which defaults to \"high\")\n- Added `assert_eq!(node.speed(), None)` to `node_defaults` test\n\n### 3. Stylesheet: `lib/crates/fabro-workflows/src/stylesheet.rs`\n- Added `\"speed\"` to `STYLESHEET_PROPERTIES` array\n- Added `apply_speed_property` test\n\n### 4. Agent config: `lib/crates/fabro-agent/src/config.rs`\n- Added `pub speed: Option` to `SessionConfig`\n- Added `speed: None` to `Default` impl\n\n### 5. Session wiring: `lib/crates/fabro-agent/src/session.rs`\n- Added `speed: self.config.speed.clone()` to Request construction\n- Added `set_speed()` method\n\n### 6. Anthropic provider: `lib/crates/fabro-llm/src/providers/anthropic.rs`\n- **6a.** Added `speed: Option` to `ApiRequest` struct\n- **6b.** Added `FAST_MODE_BETA_HEADER` constant (`\"fast-mode-2026-02-01\"`)\n- **6c.** Extended `build_beta_header()` with `include_fast_mode_header: bool` parameter\n- **6d.** In `build_api_request()`: set `speed` on ApiRequest, compute `is_fast`, pass to `build_beta_header()`\n- **6e.** Added `speed: Option` to `ApiUsage` struct\n- **6f.** Map `api_resp.usage.speed` → `Usage.speed` in both non-streaming and streaming paths\n- Added 3 new tests: `build_api_request_sets_speed`, `build_api_request_injects_fast_mode_beta_header`, `beta_header_includes_both_cache_and_fast_mode`\n\n### 7. Usage tracking: `lib/crates/fabro-workflows/src/outcome.rs`\n- Added `speed: Option` to `StageUsage` (with `serde(default, skip_serializing_if)`)\n- Updated `From<&StageUsage> for Usage` to include `speed`\n\n### 8. Cost multiplier: `lib/crates/fabro-workflows/src/cost.rs`\n- Applied 6x multiplier when `speed == \"fast\"`\n- Added `compute_stage_cost_fast_mode_6x_multiplier` test\n\n### 9. Backend wiring: `lib/crates/fabro-workflows/src/backend/api.rs`\n- SessionConfig construction: added `speed: node.speed().map(String::from)`\n- Prompt-mode Request construction: added `speed: node.speed().map(String::from)`\n- Both StageUsage constructions: added `speed: response.usage.speed.clone()` / `total_usage.speed.clone()`\n\n### 10. Other files updated with `speed: None` in struct literals:\n- `fabro-agent/src/compaction.rs`, `fabro-agent/src/tools.rs`, `fabro-agent/src/types.rs`\n- `fabro-hooks/src/executor.rs`\n- `fabro-api/src/server.rs`\n- `fabro-cli/src/doctor.rs`\n- `fabro-llm/src/generate.rs` (also added `speed` to `GenerateParams`)\n- `fabro-llm/src/client.rs`, `fabro-llm/src/providers/fabro_server.rs`, `fabro-llm/src/providers/gemini.rs`, `fabro-llm/src/providers/openai.rs`, `fabro-llm/src/providers/openai_compatible.rs`\n- `fabro-workflows/src/backend/cli.rs`, `fabro-workflows/src/event.rs`, `fabro-workflows/src/preamble.rs`\n- `fabro-llm/tests/integration.rs`, `fabro-workflows/tests/integration.rs`" + }, + "notes": "Stage completed: implement", + "usage": { + "model": "claude-opus-4-6", + "input_tokens": 169912, + "output_tokens": 37515, + "cache_read_tokens": 24879463, + "cache_write_tokens": 174258, + "reasoning_tokens": 38, + "cost": 5.362305 + }, + "files_touched": [ + "/home/daytona/workspace/lib/crates/fabro-agent/src/compaction.rs", + "/home/daytona/workspace/lib/crates/fabro-agent/src/config.rs", + "/home/daytona/workspace/lib/crates/fabro-agent/src/session.rs", + "/home/daytona/workspace/lib/crates/fabro-agent/src/tools.rs", + "/home/daytona/workspace/lib/crates/fabro-agent/src/types.rs", + "/home/daytona/workspace/lib/crates/fabro-api/src/server.rs", + "/home/daytona/workspace/lib/crates/fabro-cli/src/doctor.rs", + "/home/daytona/workspace/lib/crates/fabro-graphviz/src/graph/types.rs", + "/home/daytona/workspace/lib/crates/fabro-hooks/src/executor.rs", + "/home/daytona/workspace/lib/crates/fabro-llm/src/client.rs", + "/home/daytona/workspace/lib/crates/fabro-llm/src/generate.rs", + "/home/daytona/workspace/lib/crates/fabro-llm/src/providers/anthropic.rs", + "/home/daytona/workspace/lib/crates/fabro-llm/src/providers/fabro_server.rs", + "/home/daytona/workspace/lib/crates/fabro-llm/src/providers/gemini.rs", + "/home/daytona/workspace/lib/crates/fabro-llm/src/providers/openai.rs", + "/home/daytona/workspace/lib/crates/fabro-llm/src/providers/openai_compatible.rs", + "/home/daytona/workspace/lib/crates/fabro-llm/src/types.rs", + "/home/daytona/workspace/lib/crates/fabro-llm/tests/integration.rs", + "/home/daytona/workspace/lib/crates/fabro-workflows/src/backend/api.rs", + "/home/daytona/workspace/lib/crates/fabro-workflows/src/backend/cli.rs", + "/home/daytona/workspace/lib/crates/fabro-workflows/src/cost.rs", + "/home/daytona/workspace/lib/crates/fabro-workflows/src/event.rs", + "/home/daytona/workspace/lib/crates/fabro-workflows/src/outcome.rs", + "/home/daytona/workspace/lib/crates/fabro-workflows/src/preamble.rs", + "/home/daytona/workspace/lib/crates/fabro-workflows/src/stylesheet.rs", + "/home/daytona/workspace/lib/crates/fabro-workflows/tests/integration.rs" + ], + "duration_ms": 2851370 + }, "preflight_compile": { "status": "success", "context_updates": { @@ -70,9 +124,10 @@ "duration_ms": 14617 } }, - "next_node_id": "implement", + "next_node_id": "simplify_opus", "node_visits": { "preflight_compile": 1, + "implement": 1, "toolchain": 1, "start": 1, "preflight_lint": 1 diff --git a/nodes/implement/prompt.md b/nodes/implement/prompt.md new file mode 100644 index 000000000..b88953c36 --- /dev/null +++ b/nodes/implement/prompt.md @@ -0,0 +1,135 @@ +Goal: # Plan: Support Anthropic fast mode (`speed: fast`) — Issue #120 + +## Context + +Anthropic's API supports [fast mode](https://platform.claude.com/docs/en/build-with-claude/fast-mode) (beta) for Claude Opus 4.6 — up to 2.5x faster output at 6x pricing ($30/$150 per MTok vs $5/$25 standard). The API requires: +- `"speed": "fast"` top-level field in request body +- `anthropic-beta: fast-mode-2026-02-01` header +- Response includes `usage.speed` confirming actual speed used + +This follows the same pattern as `reasoning_effort`, which is already implemented end-to-end. + +## Implementation + +### 1. Foundation: `lib/crates/fabro-llm/src/types.rs` + +- Add `speed: Option` to `Request` (after `reasoning_effort`, line ~454) +- Add `speed: Option` to `Usage` (before `raw`, line ~372) +- In `Usage::Add` impl (line ~389): propagate `speed` from `self` (left-hand side wins, since all requests in a session use the same speed) + +### 2. Graph layer: `lib/crates/fabro-graphviz/src/graph/types.rs` + +Add after `reasoning_effort()` (~line 211): +```rust +#[must_use] +pub fn speed(&self) -> Option<&str> { + self.str_attr("speed") +} +``` +Returns `Option` (no default) — unlike `reasoning_effort` which defaults to "high", speed defaults to None (standard) since fast mode is 6x cost. + +### 3. Stylesheet: `lib/crates/fabro-workflows/src/stylesheet.rs` + +Add `"speed"` to `STYLESHEET_PROPERTIES` array (line 5): +```rust +const STYLESHEET_PROPERTIES: &[&str] = &["model", "provider", "reasoning_effort", "speed", "backend"]; +``` + +### 4. Agent config: `lib/crates/fabro-agent/src/config.rs` + +- Add `pub speed: Option` field to `SessionConfig` (line ~66) +- Add `speed: None` to `Default` impl (line ~135) + +### 5. Session wiring: `lib/crates/fabro-agent/src/session.rs` + +- Add `speed: self.config.speed.clone()` to Request construction (~line 908) +- Add `set_speed()` method analogous to `set_reasoning_effort()` (~line 458) + +### 6. Anthropic provider: `lib/crates/fabro-llm/src/providers/anthropic.rs` + +**6a. `ApiRequest` struct** (~line 87): Add `speed: Option` field + +**6b. Beta header constant** (~line 510): +```rust +const FAST_MODE_BETA_HEADER: &str = "fast-mode-2026-02-01"; +``` + +**6c. `build_beta_header()`** (~line 582): Add `include_fast_mode_header: bool` parameter. Inject fast-mode header (same pattern as cache header). + +**6d. `build_api_request()`** (~line 1082): +- Set `speed: request.speed.clone()` on `ApiRequest` +- Compute `let is_fast = request.speed.as_deref() == Some("fast")` +- Pass `is_fast` to `build_beta_header()` + +**6e. `ApiUsage`** (~line 160): Add `speed: Option` field + +**6f. Response parsing**: Map `api_resp.usage.speed` → `Usage.speed` in both: +- Non-streaming path (~line 1192) +- Streaming `message_start` handler (~line 690) + +### 7. Usage tracking: `lib/crates/fabro-workflows/src/outcome.rs` + +- Add `speed: Option` to `StageUsage` (with `serde(default, skip_serializing_if)`) +- Update `From<&StageUsage> for Usage` to include `speed` + +### 8. Cost multiplier: `lib/crates/fabro-workflows/src/cost.rs` + +Apply 6x multiplier when `speed == "fast"`: +```rust +let multiplier = if usage.speed.as_deref() == Some("fast") { 6.0 } else { 1.0 }; +// Apply multiplier to final cost +``` + +### 9. Backend wiring: `lib/crates/fabro-workflows/src/backend/api.rs` + +- SessionConfig construction (~line 195): add `speed: node.speed().map(String::from)` +- Prompt-mode Request construction (~line 285): add `speed: node.speed().map(String::from)` +- Both StageUsage constructions (~lines 389, 589): add `speed: response.usage.speed.clone()` / `total_usage.speed.clone()` + +### 10. Tests + +- `anthropic.rs`: `build_api_request_sets_speed`, `build_api_request_injects_fast_mode_beta_header`, `beta_header_includes_both_cache_and_fast_mode` +- `cost.rs`: `compute_stage_cost_fast_mode_6x_multiplier` +- `stylesheet.rs`: `apply_speed_property` +- `session.rs`: `set_speed_mid_session` +- Update all existing struct literals (`Request`, `Usage`, `SessionConfig`, `StageUsage`, `ApiRequest`, `ApiUsage`) in tests to include `speed: None` + +## Implementation order + +1. `types.rs` (Request + Usage) — everything depends on this +2. `graph/types.rs` + `stylesheet.rs` — graph layer +3. `config.rs` + `session.rs` — agent layer +4. `anthropic.rs` — provider (ApiRequest, beta header, response parsing) +5. `outcome.rs` + `cost.rs` — usage tracking +6. `backend/api.rs` — final wiring +7. Tests throughout + +## Verification + +``` +cargo build --workspace +cargo test --workspace +cargo fmt --check --all +cargo clippy --workspace -- -D warnings +``` + + +## Completed stages +- **toolchain**: success + - 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` + - Stdout: + ``` + cargo 1.94.0 (85eff7c80 2026-01-15) + ``` + - Stderr: (empty) +- **preflight_compile**: success + - Script: `cargo check -q --workspace 2>&1` + - Stdout: (empty) + - Stderr: (empty) +- **preflight_lint**: success + - Script: `cargo clippy -q --workspace -- -D warnings 2>&1` + - Stdout: (empty) + - Stderr: (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/nodes/implement/provider_used.json b/nodes/implement/provider_used.json new file mode 100644 index 000000000..c1905ff10 --- /dev/null +++ b/nodes/implement/provider_used.json @@ -0,0 +1,5 @@ +{ + "mode": "agent", + "provider": "anthropic", + "model": "claude-opus-4-6" +} \ No newline at end of file diff --git a/nodes/implement/response.md b/nodes/implement/response.md new file mode 100644 index 000000000..3bc91db22 --- /dev/null +++ b/nodes/implement/response.md @@ -0,0 +1,57 @@ +All verification passes. Let me summarize all the changes made: + +## Summary of Changes + +### 1. Foundation: `lib/crates/fabro-llm/src/types.rs` +- Added `speed: Option` to `Request` struct (after `reasoning_effort`) +- Added `speed: Option` to `Usage` struct (with `serde(default, skip_serializing_if)`, before `raw`) +- Added `speed: self.speed` to `Usage::Add` impl (left-hand side wins) +- Updated all test struct literals to include `speed: None` + +### 2. Graph layer: `lib/crates/fabro-graphviz/src/graph/types.rs` +- Added `speed()` method to `Node` returning `Option<&str>` (no default, unlike `reasoning_effort` which defaults to "high") +- Added `assert_eq!(node.speed(), None)` to `node_defaults` test + +### 3. Stylesheet: `lib/crates/fabro-workflows/src/stylesheet.rs` +- Added `"speed"` to `STYLESHEET_PROPERTIES` array +- Added `apply_speed_property` test + +### 4. Agent config: `lib/crates/fabro-agent/src/config.rs` +- Added `pub speed: Option` to `SessionConfig` +- Added `speed: None` to `Default` impl + +### 5. Session wiring: `lib/crates/fabro-agent/src/session.rs` +- Added `speed: self.config.speed.clone()` to Request construction +- Added `set_speed()` method + +### 6. Anthropic provider: `lib/crates/fabro-llm/src/providers/anthropic.rs` +- **6a.** Added `speed: Option` to `ApiRequest` struct +- **6b.** Added `FAST_MODE_BETA_HEADER` constant (`"fast-mode-2026-02-01"`) +- **6c.** Extended `build_beta_header()` with `include_fast_mode_header: bool` parameter +- **6d.** In `build_api_request()`: set `speed` on ApiRequest, compute `is_fast`, pass to `build_beta_header()` +- **6e.** Added `speed: Option` to `ApiUsage` struct +- **6f.** Map `api_resp.usage.speed` → `Usage.speed` in both non-streaming and streaming paths +- Added 3 new tests: `build_api_request_sets_speed`, `build_api_request_injects_fast_mode_beta_header`, `beta_header_includes_both_cache_and_fast_mode` + +### 7. Usage tracking: `lib/crates/fabro-workflows/src/outcome.rs` +- Added `speed: Option` to `StageUsage` (with `serde(default, skip_serializing_if)`) +- Updated `From<&StageUsage> for Usage` to include `speed` + +### 8. Cost multiplier: `lib/crates/fabro-workflows/src/cost.rs` +- Applied 6x multiplier when `speed == "fast"` +- Added `compute_stage_cost_fast_mode_6x_multiplier` test + +### 9. Backend wiring: `lib/crates/fabro-workflows/src/backend/api.rs` +- SessionConfig construction: added `speed: node.speed().map(String::from)` +- Prompt-mode Request construction: added `speed: node.speed().map(String::from)` +- Both StageUsage constructions: added `speed: response.usage.speed.clone()` / `total_usage.speed.clone()` + +### 10. Other files updated with `speed: None` in struct literals: +- `fabro-agent/src/compaction.rs`, `fabro-agent/src/tools.rs`, `fabro-agent/src/types.rs` +- `fabro-hooks/src/executor.rs` +- `fabro-api/src/server.rs` +- `fabro-cli/src/doctor.rs` +- `fabro-llm/src/generate.rs` (also added `speed` to `GenerateParams`) +- `fabro-llm/src/client.rs`, `fabro-llm/src/providers/fabro_server.rs`, `fabro-llm/src/providers/gemini.rs`, `fabro-llm/src/providers/openai.rs`, `fabro-llm/src/providers/openai_compatible.rs` +- `fabro-workflows/src/backend/cli.rs`, `fabro-workflows/src/event.rs`, `fabro-workflows/src/preamble.rs` +- `fabro-llm/tests/integration.rs`, `fabro-workflows/tests/integration.rs` \ No newline at end of file diff --git a/nodes/implement/status.json b/nodes/implement/status.json new file mode 100644 index 000000000..cedd85c8f --- /dev/null +++ b/nodes/implement/status.json @@ -0,0 +1,6 @@ +{ + "status": "success", + "notes": "Stage completed: implement", + "failure_reason": null, + "timestamp": "2026-03-20T22:20:17.081529+00:00" +} \ No newline at end of file