Skip to content

Explainability (/lens), training export, prompt caching#7

Merged
bajajra merged 8 commits into
mainfrom
bun-migration
Jul 19, 2026
Merged

Explainability (/lens), training export, prompt caching#7
bajajra merged 8 commits into
mainfrom
bun-migration

Conversation

@bajajra

@bajajra bajajra commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

Three features that make Mist observable, reusable, and cheaper — plus two legacy-test repairs.

/lens — explainability & interpretability module (f9cf8a3)

Every turn records a structured ledger: model requests (real input/output tokens, thinking share, stop reason), tool calls (duration, output size, previews, errors, hook blocks), subagents with their own token attribution, auto-continues, cap hits, and compactions.

  • /lens — in-TUI summary: where the tokens went for the last turn
  • /lens html — self-contained interactive report (token bars, expandable tool outputs, subagent cards)
  • /lens json — raw ledger for scripting

--export-training — sessions as SFT-ready trajectories (5e4ba16)

mist --export-training out.jsonl converts recorded sessions into one-trajectory-per-line JSONL in Anthropic or OpenAI function-calling format (--format), with secret redaction (API keys, tokens, bearer headers) on by default.

Prompt caching (402c4e7)

Mist never sent cache_control before — every request repaid the full history at uncached input price. Now:

  • system prompt + last-message breakpoints (ephemeral), moving forward each request so the history prefix stays cached; MIST_CACHE=0 opts out
  • true prompt size = input + cache_read + cache_write — fixes context-size and compaction-trigger accounting, which previously saw only the uncached remainder
  • cache read/write tracked per request through the lens; /lens shows a cache line with % of input served from cache, or a hint when the endpoint ignores caching
  • wire-format test asserts breakpoints are actually sent and that opt-out reverts to plain string system

Legacy Python test repairs (358e7ad)

Two stale mocks in the deprecated mist-py suite: a credential-prompt input mock with no return value, and get_current_agent patched at the package but not at the start/stop command import sites.

Test plan

  • cd ts && bun test — 106 tests, 0 fail (includes new cache accounting, context-size, and wire-format tests)
  • bun run build — binary compiles
  • /lens verified live against mock model in tmux

🤖 Generated with Claude Code

bajajra and others added 8 commits July 18, 2026 10:05
Answers 'why did this small command burn 80k tokens?' with a structured
per-turn ledger recorded by the engine:

- Per model request: REAL input/output tokens, thinking share
  (estimated from thinking-delta chars at chars/3.5, or the provider's
  real reasoning_tokens on OpenAI o-series), duration, stop reason
- Per tool call: name, label, duration, output size, 1500-char output
  preview (harness debugging), is_error, hook blocks; ask_user records
  human-wait time; MCP calls timed + failures captured
- Per subagent: OWN token attribution (child usage credited to the
  subagent entry, still counted in parent totals), steps, duration,
  report size, failures
- Engine events: auto-continues, request-cap exits, compactions
- Key insight surfaced explicitly: billed input = Σ per-request input
  (whole history resent every request) vs. live context size

/lens           → text summary: totals, hottest request, biggest tool
                  outputs, subagent table, engine events
/lens html      → self-contained interactive report (token bars per
                  request, click-to-expand tool outputs + subagents)
/lens json      → full ledger for external tooling

Thinking tokens: output_tokens from the API already INCLUDES thinking
(providers bill reasoning as output) — lens now separates the share.

Ledger capped at 50 turns; tests pin request/tool/total accounting and
subagent attribution against the scripted mock. 102 tests, 0 fail.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stored sessions are full multi-turn agentic trajectories (real user
prompts, tool calls, tool results, steers) — exactly what open-model
SFT needs. New exporter turns them into training JSONL:

  mist --export-training [out.jsonl] [--format=openai] [--min-turns=N]
       [--no-redact]

- One line per session: system prompt, the 9 tool specs the model saw,
  full message history, session metadata (id/title/created_at/source)
- --format=openai emits the function-calling shape (system message,
  assistant.tool_calls, role:'tool' results) consumed directly by
  LLaMA-Factory / Axolotl / torchtune; default is the native
  Anthropic content-block shape
- Secret redaction ON by default: Anthropic/OpenAI keys, GitHub PATs,
  Slack tokens, AWS keys, Bearer tokens, quoted api_key/token/password
  JSON values → [REDACTED_*]; --no-redact for raw (labeled loudly)
- --min-turns filters empty/trivial sessions (genuine prompts only —
  auto-continue nudges and compaction summaries don't count)
- Shared anthropic→openai translation extracted from OpenAIClient
  (toOpenAIMessages) — one mapping, used by both wire client and export

Verified on 11 real stored sessions; tests pin both shapes, redaction
patterns, and filtering. 105 tests, 0 fail.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mist never sent cache_control before, so Anthropic-protocol endpoints
repaid the full history price on every request. Now:

- anthropic.ts: system sent as a text block with cache_control ephemeral,
  plus a breakpoint on the last message's final content block (moves
  forward each request so the whole history prefix stays cached).
  MIST_CACHE=0 opts out. Parses cache_read/creation_input_tokens.
- TurnResult carries cacheReadTokens/cacheWriteTokens; agent.ts sums
  input + read + write for the true prompt size — fixes context/compaction
  accounting, which previously saw only the uncached remainder.
- Lens: per-request + total cache fields; /lens shows a cache line
  (read/written/% served from cache, or a hint when the endpoint
  doesn't support caching); HTML report shows '(N cached)'.
- mock_model: normalize block-array system for branch sniffing; ANSWER
  usage now includes cache fields; wire-format test asserts breakpoints
  are sent and MIST_CACHE=0 disables them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- test_run_pending_credentials_success: input mock had no return value,
  so the credential prompt received a Mock object instead of a string
- mcp conftest: get_current_agent is imported directly by start/stop
  command modules — patch those import sites too, not just the package

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The user row used a color nearly identical to body text in three of four
themes (cinnamon was literally the same hex), so your own inputs vanished
while scrolling a long conversation. Now each theme defines a distinct
user color plus a subtle userBg band behind the whole ' ❯ prompt ' line:

- mist:     soft mint on deep misty teal
- cinnamon: butterscotch on toasted brown
- hinokami: gold on embered dark-gold
- moon:     gold irises on kimono purple

Verified live in tmux against the mock model.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Brings GPT/Gemini to parity with the Anthropic cache work. Both
providers cache automatically (no breakpoints needed) but report the
cached amount as a SUBSET of their prompt-token count — the opposite of
Anthropic's uncached-remainder semantics. The clients now split:
inputTokens = uncached remainder, cacheReadTokens = cached subset, so
agent-side summing and /lens stay provider-agnostic.

- openai.ts: parse prompt_tokens_details.cached_tokens
- gemini.ts: parse cachedContentTokenCount + thoughtsTokenCount (real
  reasoning usage — /lens thinking share is now exact on Gemini too)
- provider mocks report cached tokens; tests assert the split

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A 429/529/5xx or network drop used to kill the whole turn. Now every
leaf model client is wrapped in RetryingClient: exponential backoff
with jitter (1s base, doubling, 30s cap), up to MIST_RETRIES attempts
(default 5, 0 disables; MIST_RETRY_BASE_MS tunes the base). The wait is
visible in the TUI — '⚠ API error (…) — retrying in Ns (attempt x/y)' —
and recorded as a model.retry protocol event.

Safety rule: never retry once visible output has streamed — a re-send
would duplicate text already on screen; those errors surface and the
turn stays resumable. Headless calls (title gen, summarizer) may retry
even mid-stream since nothing was displayed.

Retryable: 408/409/425/429/500/502/503/504/529, provider 'overloaded'
stream errors, fetch-level network failures. 4xx auth/validation errors
surface immediately. round_robin candidates are wrapped individually —
the outer node is not, so attempts never multiply.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bajajra
bajajra merged commit fd6ac4a into main Jul 19, 2026
3 checks passed
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