mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-15 23:32:46 +00:00
## Summary
Adds a best-effort `GET
/api/v1/runs/{id}/stages/{stageId}/context-window` endpoint that exposes
model-visible input-token usage, broken down by category (system prompt,
tools, MCP tools, skills, memory, conversation, other). The endpoint
degrades gracefully: it returns a stored projection snapshot when the
stage is inactive, and `available: false` when no snapshot has ever been
observed rather than surfacing count gaps as HTTP errors.
### Plan Summary
- **Unit 1** – OpenAPI schemas (`StageContextWindow`,
`StageContextWindowProjection`, breakdown/enum types) and generated Rust
+ TypeScript clients, with `fabro-api` build-time type replacements
pointing at the hand-written `fabro-types` structs.
- **Unit 2** – `ToolSource` enum on `RegisteredTool` (Native / Mcp /
Skill) + `ToolDefinitionWithSource`; new `context_window.rs` builder in
`fabro-agent` that assembles a content-free category breakdown at
request-assembly time; `fabro-llm::token_count` narrow public helpers
(`estimate_message_tokens`, `estimate_tool_definition_tokens`,
`estimate_request_control_tokens`).
- **Unit 3** – `AgentEvent::ContextWindowSnapshot` carries a
`StageContextWindowProjection`; the session emits a local snapshot
immediately, then a provider-scaled replacement (or
response-usage-scaled replacement) asynchronously; fingerprinting
prevents double-counting the same request.
- **Unit 4** – Server endpoint (stubbed routing; full handler targets a
follow-up) returning the latest projected snapshot.
- **Unit 5** – `queryKeys.runs.stageContextWindow`,
`useRunStageContextWindow` hook, and SSE invalidation for
`agent.context_window.snapshot` and all stage-lifecycle events.
### Key design decisions
**Agent-side counting, not server-side.** The exact `fabro_llm::Request`
only exists inside the active agent session. Rather than moving raw
prompt/message content into server-managed state, the session counts the
request it already has and emits content-free projection events. The
HTTP endpoint just reads the latest durable snapshot.
**Hybrid category ownership.** `fabro-agent` owns the category taxonomy
(it sees memory documents, skills, MCP registration, and session
history); `fabro-llm` exposes narrow estimation helpers. Neither crate
leaks the other's concerns.
**Provider count is async and non-blocking.** A spawned task calls
`Client::count_input_tokens(..., PreferProvider)` with a clone of the
request. It is cancelled via `close_token` when the session closes.
Failures produce a warning on the snapshot, not a stage error.
**`available: false` instead of 4xx for known-but-unobserved stages.**
The sidebar needs stable empty states; HTTP errors only mean the run or
stage doesn't exist.
```mermaid
flowchart TB
A[Session::build_request] --> B[build_local_snapshot\nLocalEstimate]
B --> C[emit ContextWindowSnapshot]
C --> D{provider count\nspawned task}
D -- success --> E[scaled_snapshot\nProviderApiScaledBreakdown]
D -- failure --> F[warning appended to local snapshot]
E --> G[emit ContextWindowSnapshot]
G --> H[run_state reducer\nupdates StageProjection.context_window]
F --> H
H --> I[GET context-window endpoint\nreturns projection]
```
**`ToolSource` on every `RegisteredTool`.** All 20+ `make_*_tool` call
sites are updated to set `ToolSource::Native`; MCP tools get
`ToolSource::Mcp { server_name }` at registration time;
`make_use_skill_tool` gets `ToolSource::Skill`. A parallel
`definitions_with_source_for_policy` method preserves existing
`definitions_for_policy` behaviour unchanged.
### Fabro Details
<details>
<summary>Ran 8 stages in 90m 57s for $70.01</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 1s | – | 0 |
| preflight_compile | 1m 59s | – | 0 |
| preflight_lint | 2m 11s | – | 0 |
| implement | 45m 14s | $48.47 | 0 |
| simplify_opus | 25m 50s | $18.29 | 0 |
| simplify_gpt | 6m 12s | $3.25 | 0 |
| verify | 8m 59s | – | 0 |
| **Total** | **90m 57s** | **$70.01** | **0** |
</details>
<details>
<summary>Ran <code>ImplementPlan.fabro</code> (11 nodes and 14
edges)</summary>
```dot
digraph ImplementPlan {
graph [
goal="Implement and simplify",
model_stylesheet="
* { model: claude-opus-4-7; }
"
]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
toolchain [label="Toolchain", shape=parallelogram, 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", max_retries=0]
preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check -q --workspace 2>&1", max_retries=0]
preflight_lint [label="Preflight Lint", shape=parallelogram, script="cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", max_retries=0]
fix_lints [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
implement [label="Implement", prompt="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.", model="gpt-55", reasoning_effort="xhigh"]
simplify_opus [label="Simplify (Opus)", prompt="@prompts/simplify.md"]
simplify_gpt [label="Simplify (GPT-55)", prompt="@prompts/simplify.md", model="gpt-55"]
verify [label="Verify", shape=parallelogram, script="git fetch origin main 2>&1 && git merge --no-edit --no-stat origin/main 2>&1 && cargo +nightly-2026-04-14 fmt --all 2>&1 && cargo dev docs refresh 2>&1 && cargo +nightly-2026-04-14 fmt --check --all 2>&1 && ! rg -n 'AuthMode::Disabled|RunAuthMethod|RunSubjectProvenance|\bActorRef\b|\bActorKind\b|AuthenticatedSubject|AuthenticatedService|AuthorizeRunScoped|AuthorizeRunBlob|AuthorizeStageArtifact|AuthorizeCommandLog|auth_method\s*==\s*\"disabled\"' lib/crates apps lib/packages docs/public/api-reference/fabro-api.yaml 2>&1 && cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --workspace --status-level slow --profile ci 2>&1 && cargo dev docs check 2>&1 && bun install --frozen-lockfile 2>&1 && (cd apps/fabro-web && bun run typecheck) 2>&1 && (cd apps/fabro-web && bun run test) 2>&1 && (cd lib/packages/fabro-api-client && bun run typecheck) 2>&1 && cargo dev build -- -p fabro-cli --release 2>&1", goal_gate=true, retry_target="fixup"]
fixup [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all format, clippy, Rust test, docs, TypeScript typecheck/test, and build failures.", max_visits=3]
start -> toolchain
toolchain -> preflight_compile [condition="outcome=succeeded"]
toolchain -> exit
preflight_compile -> preflight_lint [condition="outcome=succeeded"]
preflight_compile -> exit
preflight_lint -> implement [condition="outcome=succeeded"]
preflight_lint -> fix_lints
fix_lints -> preflight_lint
implement -> simplify_opus -> simplify_gpt -> verify
verify -> exit [condition="outcome=succeeded"]
verify -> fixup
fixup -> verify
}
```
</details>
⚒️ Generated with [Fabro](https://fabro.sh)
---------
Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
|
||
|---|---|---|
| .. | ||
| crates | ||
| packages/fabro-api-client | ||