Skip to content

feat(advisor): TUI status bar + legacy REPL show advisor token spend separately - #191

Merged
agentforce314 merged 2 commits into
mainfrom
advisor/tui-tokens-and-iterations
May 21, 2026
Merged

feat(advisor): TUI status bar + legacy REPL show advisor token spend separately#191
agentforce314 merged 2 commits into
mainfrom
advisor/tui-tokens-and-iterations

Conversation

@agentforce314

Copy link
Copy Markdown
Owner

Summary

Bundles two followups from PR #189 into one PR per user request:

  1. Legacy REPL bottom toolbar shows worker vs advisor tokens separately (was PR feat(advisor): surface separate worker + advisor token counts in status bar #190; cherry-picked here).
  2. TUI StatusLine widget mirrors the same counts (new in this PR).

Both surfaces now display advisor token spend whenever the client-side advisor has been consulted this session.

Display

Legacy REPL bottom toolbar (from PR #190's cherry-picked content):
```
openai · haiku-4-5 · ~/workspace/clawcodex · advisor: opus-4-7 (client) · turns: 2 · tokens: 5400 in / 320 out (advisor: 1326 in / 282 out)
```

TUI status line (new):
```
openai · haiku-4-5 · advisor: opus-4-7 (client) Synthesizing 4s ~/workspace turn 2 · tokens 5720 · advisor 1326/282
```

Both segments hidden when zero.

Data flow

`AdvisorTool._advisor_call` (PR #190 content) accumulates per-consultation token counts onto `tool_context.advisor_input_tokens` / `advisor_output_tokens`. Two surfaces consume them:

  • REPL (`src/repl/core.py:_bottom_toolbar`) reads ctx directly + renders parenthetical
  • TUI (`src/tui/agent_bridge.py:run_agent_in_thread`) mirrors ctx → `state.usage["advisor*"]` after each run; `src/tui/widgets/status_line.py:_compose_text` reads and renders `advisor N/N`

What I attempted and dropped

I initially also implemented server-side advisor token attribution — parsing `usage.iterations[]` for entries with `type == "advisor_message"`, per the user-shared `security_audit_advisor.py` reference + the Anthropic API spec. Critic verified the SDK shape and found:

  • `BetaIterationsUsageItem` (in `anthropic 0.88.0`) is a Union of `BetaMessageIterationUsage` and `BetaCompactionIterationUsage` only.
  • Both variants have `type: Literal["message" | "compaction"]`.
  • There is no `"advisor_message"` discriminator anywhere in the SDK.
  • `BetaServerToolUsage` only has `web_fetch_requests` / `web_search_requests` — no advisor field.

The reference example was either documentation for a future SDK release or a different parser layer. With the SDK as shipped, my parser would never match a real response. Dropped the speculative iteration parser + the test for it; left a NOTE in `agent_bridge.py` documenting what's needed when the SDK gains the discriminator.

Tests

  • 665 passed across full advisor + adapter + TUI + helpers + smoke suites.

Closes

🤖 Generated with Claude Code

agentforce314 and others added 2 commits May 20, 2026 20:27
…us bar

The bottom toolbar already shows `tokens: N in / N out` for the
worker (haiku-4-5 in the user's setup). Now it also shows
`(advisor: N in / N out)` next to it whenever the advisor has
been consulted, so the user can see how much of the spend went to
the reviewer (opus-4-7) versus the worker.

## Display

```
 openai · claude-haiku-4-5 · ~/workspace/clawcodex · advisor: opus-4-7 (client) · turns: 2 · tokens: 5400 in / 320 out (advisor: 1326 in / 282 out)
```

The `(advisor: N in / N out)` segment is suppressed when zero so the
toolbar stays compact for users who haven't enabled the advisor or
haven't triggered it yet this session.

## Data flow

1. `execute_client_advisor` (in `src/utils/advisor.py`) now returns
   a 3-tuple `(ok, text, usage)` instead of `(ok, text)`. The
   `usage` dict has `input_tokens` / `output_tokens` keys pulled
   off the provider's ChatResponse. Failure paths return zero usage.
2. `AdvisorTool._advisor_call` (in `src/tool_system/tools/advisor.py`)
   reads the 3-tuple and ACCUMULATES the counts onto
   `tool_context.advisor_input_tokens` / `advisor_output_tokens`.
   Multiple consultations in one session add together (not replace).
3. `ToolContext` gains two new int fields with sane defaults; the
   bookkeeping is wrapped in try/except so a status-bar concern can
   never break a tool result.
4. `src/repl/core.py:_bottom_toolbar` reads the ctx fields and
   appends the advisor segment when non-zero.

## What didn't change

* TUI StatusLine (`src/tui/widgets/status_line.py`) — left as a
  follow-up. The TUI reads token counts from AppState.usage which
  doesn't yet have an advisor-specific key. Adding the AppState
  field + watcher is a separate ~3-file edit. Mention it if it's
  needed.
* Server-side advisor path — the worker's response already includes
  `usage.iterations[]` with per-model breakdowns (per the
  Anthropic API spec in the user-shared example). We don't parse
  those yet; if the user moves to a 1P Anthropic main + server-side
  advisor, the counts won't show. Followup.

## Tests

* New: `test_call_accumulates_advisor_tokens_onto_ctx` —
  verifies two sequential AdvisorTool.call invocations sum the
  token counts rather than overwriting.
* Updated 8 destructuring sites + 2 mock returns in
  `test_advisor_client_side.py` for the 3-tuple signature.
* Full advisor (117) + TUI (525) suites pass.

## Live verified

```
>>> ok, text, usage = execute_client_advisor('claude-opus-4-7', fwd, main_provider=provider)
ok=True, usage={'input_tokens': 663, 'output_tokens': 141}
```

Real opus-4-7 call via litellm.singula.ai returned correct usage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR #190's content (cherry-picked from advisor/separate-token-counts —
`AdvisorTool` accumulates client-side advisor tokens onto
`tool_context.advisor_input_tokens/advisor_output_tokens`; the legacy
REPL bottom toolbar reads them) provides the data. This commit adds
TUI parity: the StatusLine widget shows the same counts.

## What this adds on top of PR #190

1. `src/tui/agent_bridge.py` (`_run_agent_in_thread`) — after the run
   completes, mirrors `tool_context.advisor_input_tokens` /
   `advisor_output_tokens` into `state.usage["advisor_input_tokens"]`
   / `["advisor_output_tokens"]` so the StatusLine widget can read
   them. Cumulative across the session (the TUI's tool_context
   persists across runs).

2. `src/tui/widgets/status_line.py` (`_compose_text`) — reads the two
   new keys off `state.usage` and appends `advisor N/N` to the
   right-side bits when non-zero. Worker-token display
   (`tokens TOTAL`) is unchanged to keep the diff minimal.

## Display

TUI status line now shows e.g.:

```
openai · haiku-4-5 · advisor: opus-4-7 (client)    Synthesizing 4s    ~/workspace    turn 2 · tokens 5720 · advisor 1326/282
```

## What's NOT in this PR (intentional)

Server-side advisor token attribution. The user-shared
`security_audit_advisor.py` example claims `usage.iterations[*].type
== "advisor_message"` distinguishes advisor iterations — but the
Python SDK 0.88.0 we depend on only defines `"message"` and
`"compaction"` discriminators on `BetaIterationsUsageItem` (verified
in `.venv/lib/.../beta/beta_iterations_usage.py` +
`beta_message_iteration_usage.py`). And `BetaServerToolUsage` only
has `web_fetch_requests` / `web_search_requests` — no advisor field.

I drafted a parser for the `"advisor_message"` discriminator earlier
in this PR but the critic correctly flagged it as non-functional —
Pydantic's tagged-union discriminator would reject any value outside
the two literals. Removed.

When the SDK gains the advisor discriminator OR when we move to
direct HTTP parsing of the API response (bypassing typed Pydantic
parsing), add a second accumulator alongside the client-side flow.
The TUI display logic is already in place — it just needs a second
data source to feed `state.usage["advisor_*"]`.

## Critic-driven fixes applied

* **Bundled PR #190** into this PR per the user's "both followups, in
  one PR" instruction. Cherry-picked the commit verbatim; this PR
  builds on top.
* **Dropped server-side iteration parsing** after verifying the SDK
  shape doesn't match the speculative `"advisor_message"` literal.
* **Kept the existing `tokens TOTAL` format** in the TUI (didn't
  switch to `tokens N/N`) — avoids a user-visible format change in
  an unrelated part of the status line. The new advisor segment is
  `advisor N/N` for compactness; the asymmetry vs the worker total
  is acceptable as long as it's the only thing changing.

## Tests

* 665 passed across full advisor + adapter + TUI + helpers + smoke
  suites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@agentforce314
agentforce314 merged commit a217130 into main May 21, 2026
singlaamitesh pushed a commit to singlaamitesh/clawcodex that referenced this pull request Jul 7, 2026
…okens-and-iterations

feat(advisor): TUI status bar + legacy REPL show advisor token spend separately
singlaamitesh pushed a commit to singlaamitesh/clawcodex that referenced this pull request Jul 7, 2026
…fficient /advisor)

Two new entries at the top of the README News section:

1. Codebase stats: 890 files / 183,768 lines (up from 894 / 177,428
   on 2026-05-16; net +6.3k lines / -4 files in five days). The file
   delta is the agent_loop.py consolidation; the line additions are
   the /advisor multi-provider rewrite + status-bar cost work.

2. /advisor mode as a token-efficient coding agent — narrates the
   PR agentforce314#181agentforce314#193 arc as one story:
   - Cheap worker (haiku-4-5) + expensive reviewer (opus-4-7) only
     at decision points ≈ 6× cheaper than opus-only on typical sessions
   - Explicit <provider>:<model> syntax (agentforce314#192)
   - Cross-provider routing verified (deepseek worker + opus advisor
     via litellm, agentforce314#182/agentforce314#184/agentforce314#192)
   - Reviewer-quality prompt (agentforce314#188) — Gaps/Risks/Do-next format
   - Live cost + token visibility in status bar (agentforce314#190/agentforce314#191/agentforce314#193)
   - /advisor slash command + dedicated TUI row (agentforce314#181)

Docs-only — no code or test changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant