Skip to content

feat(advisor): surface separate worker + advisor token counts in status bar - #190

Closed
agentforce314 wants to merge 1 commit into
mainfrom
advisor/separate-token-counts
Closed

feat(advisor): surface separate worker + advisor token counts in status bar#190
agentforce314 wants to merge 1 commit into
mainfrom
advisor/separate-token-counts

Conversation

@agentforce314

Copy link
Copy Markdown
Owner

Summary

Show advisor token spend separately from worker token spend in the legacy REPL bottom toolbar. Users can now see at a glance how much of their request went to the reviewer (e.g. opus-4-7) vs the worker (e.g. haiku-4-5).

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) parenthetical is suppressed when zero — the toolbar stays compact for users who haven't enabled the advisor or haven't triggered it yet this session.

Data flow

Layer Change
src/utils/advisor.py execute_client_advisor now returns (ok, text, usage) — 3-tuple. usage is {input_tokens, output_tokens} pulled off the provider's ChatResponse. Failure paths return zero usage.
src/tool_system/tools/advisor.py AdvisorTool._advisor_call reads the 3-tuple and accumulates into ctx.advisor_input_tokens / ctx.advisor_output_tokens. Multiple consultations in one session sum (not replace). Bookkeeping wrapped in try/except so a status-bar concern never breaks a tool result.
src/tool_system/context.py Two new int fields on ToolContext with sane defaults.
src/repl/core.py _bottom_toolbar reads the ctx fields and appends the parenthetical when non-zero.

What's NOT in this PR (followups)

  • TUI StatusLine — left for a separate PR. The TUI reads token counts from AppState.usage which doesn't yet have an advisor-specific key; adding the AppState field + watcher is ~3 files of work, separate concern.
  • Server-side advisor token breakdown — the worker's ChatResponse.usage.iterations[] already includes per-model counts (per the Anthropic API spec), but we don't parse them. If you move to 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 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

Real call against the user's setup (haiku-4-5 main + opus-4-7 advisor via litellm.singula.ai):

```

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

Token counts populated correctly from the provider response.

🤖 Generated with Claude Code

…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>
@agentforce314

Copy link
Copy Markdown
Owner Author

Superseded by #191 — bundled into the TUI parity PR per user's 'both followups in one PR' direction. See critic review for why the server-side iteration parsing was dropped (SDK 0.88.0 doesn't have the discriminator).

singlaamitesh pushed a commit to singlaamitesh/clawcodex that referenced this pull request Jul 7, 2026
PR agentforce314#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 agentforce314#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 agentforce314#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>
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