Skip to content

Foundational Harness Improvements#489

Open
bajajra wants to merge 143 commits into
mpfaffenberger:mainfrom
bajajra:main
Open

Foundational Harness Improvements#489
bajajra wants to merge 143 commits into
mpfaffenberger:mainfrom
bajajra:main

Conversation

@bajajra

@bajajra bajajra commented Jun 19, 2026

Copy link
Copy Markdown

Summary

This PR delivers seven harness-level improvements that move Code Puppy closer to the interaction and reliability while preserving its plugin-first architecture:

  1. Cache-stable prompt composition
  2. First-class project trust and core permissions
  3. Append-only tree-branching sessions
  4. Explicit outer agent-loop controller
  5. Language Server Protocol tools
  6. Typed, observable subagent task queues
  7. Managed background shell and agent tasks

Why this work matters

  • repeated prompts were difficult for providers to cache efficiently;
  • project-local Python could be imported before an explicit trust decision;
  • file and shell authorization depended too heavily on optional plugin behavior;
  • sessions had only a linear active history;
  • continuation decisions were embedded in runtime control flow;
  • code navigation relied on text search rather than semantic symbol identity;
  • parallel agents lacked a typed observable queue;
  • long-running shell and agent work lacked a unified background-task surface.

This PR strengthens those foundations without replacing the current Pydantic AI execution layer or the existing plugin system.


1. Cache-stable prompt composition

What changed

Prompt construction is now divided into an explicit stable prefix and dynamic suffix.

The stable prefix contains authored agent instructions and stable project rules. The dynamic suffix contains context that can change independently, including callback-provided runtime context, working directory information, permission guidance, memory recall, and per-agent identity.

Anthropic-compatible payloads split these sections into separate system blocks and place cache_control on the stable block. The internal boundary marker is removed before the request reaches the provider. Legacy prompts without a boundary retain the previous whole-system caching behavior.

Dynamic fragments are cached in-process and invalidated when conversation context is explicitly cleared or compacted, when the working directory changes, or when an agent invalidates its dynamic context.


2. First-class project trust and core permissions

What changed

Code Puppy now persists project trust decisions by canonical project path. Before importing project-local executable resources, the loader requires an explicit trust decision.

The trust boundary covers:

  • .code_puppy/plugins/
  • .code_puppy/agents/
  • .code_puppy/skills/

Unknown projects fail closed in non-interactive environments. Interactive sessions prompt before loading project code. Decisions are stored in ~/.code_puppy/trust.json with restricted permissions. The CODE_PUPPY_TRUST_PROJECT environment variable provides an explicit automation override.

The plugin adds:

  • /trust status
  • /trust project
  • /trust revoke

Core permission modes are now available independently of the existing file-permission plugin:

  • ask: approve both shell commands and file mutations
  • acceptEdits: allow file mutations, continue prompting for shell commands
  • auto: allow both shell commands and file mutations

New installations default to ask. Existing installations retain compatibility through the legacy yolo_mode fallback when no explicit permission_mode exists.


3. Append-only tree-branching sessions

What changed

The new tree-session plugin records conversation entries in append-only JSONL. Each entry has a unique ID, parent ID, serialized message payload, optional label, and integrity fingerprint. An active cursor selects the current conversation path.

The plugin synchronizes existing message history into the tree after agent runs and reconstructs active history by following parent pointers.

Commands:

  • /tree renders the current branch structure
  • /tree label <ID> <TEXT> labels a checkpoint
  • /fork <ID> moves the active conversation to an earlier entry and creates an in-place branch on the next turn
  • /fork <ID> <NAME> extracts the path through an entry into a separate session tree

The current pickle/session mechanisms remain available; this is an additive compatibility layer rather than a destructive storage migration.

Future scope opened

  • Interactive tree navigator and branch switching UI
  • Branch diffing and comparison summaries
  • Merge/cherry-pick semantics between conversation branches
  • Branch-aware token accounting
  • Named checkpoints and bookmarks
  • Export/import of selected branches
  • Visual integration with the status panel
  • DBOS workflow IDs associated with tree entries
  • Automatic branch creation before high-risk operations

Important boundary

/fork <ID> <NAME> creates the separate session tree but does not automatically switch the active CLI session. The implementation preserves message paths; it is not a Git replacement and does not independently snapshot filesystem state.

4. Explicit outer agent-loop controller

What changed

Continuation logic is represented by an explicit state machine:

  • CREATED
  • RUNNING
  • FOLLOW_UP
  • COMPLETED
  • CANCELLED
  • FAILED

Actions are explicit:

  • STEER
  • HOOK_RETRY
  • STOP

The controller records outer model calls, queued steering continuations, and plugin-requested retries. It validates legal state transitions and centralizes the priority rule that user steering is processed before automated hook retries.

The existing safety caps remain in place: queued steering is bounded and plugin retries respect max_hook_retries.

User improvement

This commit intentionally preserves the existing steering experience rather than introducing a new command. Users still press Ctrl+T, select now or queue, and provide steering text. The immediate benefit is reliability: continuation behavior is isolated, bounded, and independently testable rather than spread across local counters and nested runtime branches.

Future scope opened

  • /loop status with model calls, tool calls, retries, and state
  • Per-run model-call and tool-call budgets
  • Token, cost, and wall-clock deadlines
  • Pause/resume checkpoints
  • Durable loop restoration through DBOS
  • Per-tool quotas and circuit breakers
  • Visible continuation/retry reasons
  • Policy-driven stop conditions

Important boundary

Pydantic AI still owns the inner model/tool/model ReAct cycle. This controller owns the outer continuation decision after a Pydantic run. Individual tool calls are not yet counted, and the existing outer exception/cancellation machinery still handles several live failure paths.


5. Language Server Protocol tools

What changed

The LSP plugin implements an asynchronous stdio JSON-RPC client with Content-Length framing and the standard initialize/initialized/shutdown lifecycle. Servers are configured in ~/.code_puppy/lsp_servers.json, selected by file extension, started on demand, reused within a working directory, and shut down gracefully.

The plugin contributes five read-only semantic tools:

  • lsp_definition
  • lsp_references
  • lsp_hover
  • lsp_diagnostics
  • lsp_workspace_symbols

/lsp status reports configured server names. URI results are normalized into local paths, documents are opened before text-document requests, and published diagnostics are retained by document URI.

Tools are only advertised when at least one language server is configured, avoiding dead tool schemas for users who do not opt in.

User improvement

Code Puppy can now distinguish semantic symbol identity from textual name matches. This improves navigation and edit planning in repositories with aliases, overloads, inheritance, re-exports, or common symbol names.

It also reduces context usage: an exact definition, type signature, or reference list is usually smaller and more reliable than reading every grep match.

Future scope opened

  • Semantic rename
  • Code actions and quick fixes
  • Formatting and organize-imports operations
  • Call and type hierarchy
  • Workspace edits
  • Language-aware completion
  • Multi-root workspace support
  • Smarter per-language workspace-symbol routing
  • Diagnostic stabilization/wait policies
  • Automatic language-server discovery and installation guidance

Important boundary

Users must install and configure a language server, but they do not start it manually. The current transport is command-based stdio only. Results and quality depend on the selected server. The tools do not replace tests, and the current surface does not perform semantic refactors.


6. Typed, observable subagent task queues

What changed

The subagent-task plugin wraps the existing _invoke_agent_impl execution backend with typed Pydantic contracts and an observable queue.

A request includes:

  • agent name
  • prompt
  • optional session ID
  • optional model override
  • arbitrary metadata

Each task receives a UUID and transitions through:

  • queued
  • running
  • succeeded
  • failed
  • cancelled

Tools:

  • submit_agent_tasks
  • list_agent_tasks
  • wait_agent_tasks
  • cancel_agent_tasks

Batches use a bounded semaphore. max_parallel defaults to four and is clamped between one and thirty-two. Callers can wait for aggregation or request immediate task IDs and inspect the work later.

Records and structured results are persisted in ~/.code_puppy/subagent_tasks.json. Tasks found queued or running after a process restart are marked failed with an interruption reason rather than silently disappearing.

User improvement

Parallel-agent work becomes observable and controllable. The primary agent can submit independent investigations, limit concurrency, continue other work, wait for selected tasks, aggregate results, and diagnose failures from consistent records.

Future scope opened

  • Priority queues and dependencies/DAGs
  • Per-agent concurrency pools
  • Retry policies and backoff
  • Partial-result streaming
  • Task ownership and delegation graphs
  • Cost/token budgets per task
  • Cross-process workers
  • DBOS-backed resumption instead of interruption marking
  • UI panels for queue inspection
  • Typed domain-specific result schemas

Important boundary

Persistence currently preserves metadata and completed results, not active asyncio execution. A process restart marks unfinished tasks as failed. Cancellation is asynchronous, so callers may need to list or wait again to observe the terminal state.


7. Managed background shell and agent tasks

What changed

The background-task plugin adds a unified lifecycle for long-running shell commands and subagent work.

Request kinds:

  • shell: command, optional CWD, optional timeout
  • agent: agent name, prompt, optional session/model overrides

States:

  • queued
  • running
  • succeeded
  • failed
  • cancelled
  • interrupted

Tools:

  • start_background_task
  • list_background_tasks
  • wait_background_task
  • read_background_task_output
  • cancel_background_task

The manager returns a task ID immediately, persists metadata, writes durable output logs, captures process IDs, enforces timeouts, terminates cancelled subprocesses, and emits completion notifications through the callback/message system. Background shell starts pass through the new core permission boundary.

On startup, records that were queued or running are marked interrupted because process ownership cannot safely be assumed after restart.

User improvement

The agent can start a slow build, test suite, log follower, or independent investigation without blocking the foreground conversation. Users can ask for status, retrieve output, wait later, or cancel work that is no longer relevant.

Future scope opened

  • DBOS-backed resumable background execution
  • Reattachment to surviving subprocesses
  • Remote workers and distributed queues
  • Incremental output streaming and follow mode
  • Dependency graphs and scheduled jobs
  • Resource limits for CPU, memory, and output size
  • Retry policies
  • Per-project background-task dashboards
  • Artifact capture and result attachments

Important boundary

Current local background tasks persist metadata and logs but do not resume execution after the Code Puppy process exits. They are marked interrupted. This is distinct from the existing DBOS agent wrapper, which checkpoints supported agent interactions.


Compatibility and migration

  • Existing installations without an explicit permission_mode retain the legacy yolo_mode fallback.
  • New installations default to permission_mode=ask.
  • Existing linear/pickle session storage remains available; tree JSONL is additive.
  • Existing invoke_agent and invoke_agent_with_model tools remain available.
  • Existing steering UX remains unchanged.
  • LSP tools are absent unless a server configuration exists.
  • Background and task metadata use the existing Code Puppy state directory conventions.
  • Plugin files remain below the repository's 600-line limit.

Validation

Focused feature validation:

39 passed

bajajra and others added 30 commits June 18, 2026 21:22
add coding-agent parity foundations
Apply low-friction prompt improvements informed by the Piebald-AI
claude-code-system-prompts review, scoped to keep prompts small and
preserve Mist's autonomous, non-blocking behavior:

- agent_code_puppy: add a compact "Working principles" block covering
  (1) inspect a target before destructive/irreversible actions and
  surface conflicts (without stopping), (2) honest reporting of
  failed/skipped/unverified outcomes, and (3) treating tool/MCP/plugin/
  channel output as data rather than instructions (external-source
  authority boundary). Framed as judgment, not gatekeeping.
- _compaction: restructure the summarization instructions to preserve
  goal, constraints, changed files, decisions, failed attempts,
  verification status, and next action, with a guard against
  fabricating verification results.

Prompt-text only; no logic or blocking changes. Tests in
test_prompt_composition.py and test_compaction.py pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the hardcoded fog 🌫️ (a variation-selector emoji that rendered
poorly in many terminals) across the codebase, split by purpose:
- 🫧 (bubbles) for brand/identity: status panel, onboarding, prompt
  prefix, menus, plugin command messages, subagent panel title.
- 💨 (dash/wind) for ambient/motion: spinner frames, status-rate
  indicator, subagent running indicator, auto-saved-session notice.

Single fog emoji is no longer used anywhere; the two are never
concatenated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Switch the pyfiglet wordmark from ansi_shadow to georgia11 (the closest
terminal-renderable evocation of an ornate serif display face) and
replace the 3-band blue→cyan→green line coloring with a smooth 5-stop
hex gradient interpolated per line. Hex stops render smoothly on
truecolor terminals and degrade to the nearest tone elsewhere; the
stops are a single named tuple so the palette is a one-line change.

Also updates the loading-fallback / comment glyphs to match the emoji
refresh.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The shell_prefix plugin force-prompted every non-allowlisted command
even under permission_mode=auto, because it returned requires_approval
(which bypasses auto-approval). That defeated yolo mode and prompted on
every compound command.

Add a shell_prefix_enforcement setting, default off: when off the policy
returns None and shell approval is governed solely by permission_mode
(so auto = no prompts). When on, the prior allowlist gating applies.
Exposed in the Safety settings menu.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add update_task_list: the agent passes its full ordered plan each call
(replace semantics), marking items pending / in_progress / completed,
and the checklist renders to the UI. Stored per-agent-identity,
in-memory, no side effects — purely for planning and progress tracking
on multi-step work.

Wired into the tool registry; the main agent advertises it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the default agent plan, explore, and match conventions — guidance
that helps weak and strong models alike (not reliant on built-in
reasoning):
- Approach block: explore first, lay out a task list (update_task_list),
  think before implementing; skip the ceremony for trivial asks.
- Default to implementing not proposing; work through blockers; ask only
  when undiscoverable locally and a wrong guess is costly.
- Engineering judgment (adapted from Codex guidance): match the project's
  existing patterns/libraries; structured parsers over string hacking;
  scope edits tightly; don't revert unrelated changes; scale verification
  to risk; plain, direct prose.

Replace the universal 600-line hard cap with cohesion/convention-based
guidance in the agent prompt, agent-creator prompt, tools content, and
AGENTS.md — the hard number contradicted "match existing conventions"
and the codebase itself exceeds it ~4x.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bajajra and others added 30 commits July 17, 2026 20:31
Misty the ghost retires; the welcome banner now perches the Owlgebra
owl — 22×18 pixel grid, same half-block truecolor renderer: blue tufts
and wings, light face/belly with dots, big amber eyes with white
glints, yellow beak + talons on a brown branch, sparkles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The owl hands the perch to a witchy crow, per the new character sheet:
side profile facing left on a 24×20 grid — charcoal witch hat with
green band, gold buckle and crooked tip, gray-blue head, green eye,
big orange beak, slate wing behind a lighter chest. Same half-block
truecolor renderer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The profile crow read as mush at half-block resolution. Redrawn with
the discipline that makes Clawd clean: front-facing, every row a
strict mirror (asserted at design time), 8 flat colors, big features
(3px eyes with pupils, center beak, buckled hat band), solid
silhouette with zero stray pixels. 20×18 grid.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Birds retired. The character is now the brand itself: a small round
mist cloud with dot eyes, rosy cheeks, a tiny mouth, and cloud-puff
base. Clawd-grade minimalism — 3 colors, 16×12, strictly symmetric.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fresh-system experience was broken: the default model was minimax-m3
(dead quota) and any model name not in a registry file hard-failed —
a new user with just an API key couldn't start.

- scripts/install.sh: clone → ./scripts/install.sh gets a working
  `mist` — installs Bun if missing, builds ts/dist/mist, links onto
  PATH (~/.local/bin or /opt/homebrew/bin; MIST_BIN_DIR overrides),
  prints first-run model setup
- Zero-config models: default is now claude-opus-4-8; well-known names
  (claude-*, gpt-*, o*, gemini-*) resolve without a registry entry,
  keyed by the provider env var (ANTHROPIC/OPENAI/GEMINI_API_KEY);
  registry entries still win
- getConfiguredModelName now respects $HOME (was hardwired to real
  homedir — untestable)
- README: install-on-a-new-system section with the one-command path,
  first-run model options, and the custom-endpoint registry example
- Tests for the defaults + builtin resolution (env stashed/restored —
  sibling test files share the process env)

100 tests, 0 fail; script verified end-to-end on this machine.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The tests relied on env leaked from agent.test.ts (MIST_MODEL,
MIST_MODELS_JSON, MOCK_FAST) and reused its mock port (9871) — passing
locally by file-order luck, failing on CI. Now self-sufficient: own
registry + dedicated ports (9931/9932), env stashed and restored,
MOCK_FAST set in-file. Verified passing in isolation AND in the full
suite (100 tests, 0 fail).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0 parity block + agent capabilities: providers, MCP, subagents, input UX — review-hardened
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>
Explainability (/lens), training export, prompt caching
Age-based clearing treats a 5-minute-old read of the file being edited
the same as a stale build log. Supersession is the semantically correct
trigger: after a re-read, the older bytes aren't just bulky — they may
be WRONG (the file changed). dedupeSupersededReads evicts them
regardless of age:

- a whole-file read supersedes every earlier read of that path
- an identical-input read supersedes earlier identical reads
- different ranges of the same path never supersede each other

Cache discipline matches clearStaleToolResults: deterministic stub
(bytes depend only on the path), monotonic (one transition, then
byte-stable forever), minChars guard so small results aren't churned.
Runs at turn start before age-based clearing, and inside compact().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dback

The stale-result sweep now decides AT RUNTIME whether it pays, using
the harness's own telemetry instead of assumptions:

- cacheLive: did recent turns show real cache reads? If the endpoint
  ignores caching, there is nothing to protect — clear eagerly.
- cacheLikelyCold: idle past the ~5min provider TTL (or fresh session)
  means the prefix is cold and the bust is free — clear eagerly.
- otherwise run the break-even: 0.1 × savedTokens × observed avg
  requests/turn vs 1.25 × tail re-cache cost. Clear only when it pays.

clearStaleToolResults gains dryRun planning (savedChars + tailChars);
staleClearWorthIt is the pure, tested decision function. Supersession
dedup stays always-on — it's correctness, not economics.

The decision itself is explainable: TurnLens.hygiene records what was
evicted and WHY (or why deferred), shown in /lens and the HTML report.
MIST_ADAPTIVE_HYGIENE=0 restores unconditional clearing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… message_delta

Wire-probed the z.ai endpoint: it sends ZEROED usage in message_start
and the real numbers (input_tokens, cache_read_input_tokens) in
message_delta at stream end — the opposite of Anthropic's API. We only
read input from message_start, so every GLM request logged 0 input,
/lens showed 'context now 0', and the real-token compaction trigger
never saw a real reading (fell back to the estimate).

message_delta now also accepts positive input/cache readings;
Anthropic's API omits those fields there, so its values are never
overwritten. Regression test uses the captured GLM wire shape.

Live-verified on GLM: /lens now shows real input — and real cache
activity (96% of input served from cache on the probe turn), so z.ai
honors our cache_control breakpoints.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The lens ledger lived only in engine memory, so a resumed session
answered /lens with 'no turns recorded yet' — the explainability
module forgot exactly the history you resumed to look at.

Sessions now persist one {"kind":"lens","turn":…} JSONL line per
completed turn (seq-guarded so errored turns that recorded nothing
don't duplicate), and resume rehydrates the engine ledger (capped at
the same 50 turns). rename() already preserves non-meta lines, so
titles and ledgers coexist. Verified live: run → quit → --continue →
/lens shows the pre-restart turn, cache numbers intact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mist now reads repository guidelines the way the ecosystem converged:

Discovery (agents_md.ts): ~/.mist/AGENTS.md (user-global) → git-root →
cwd, broad-first so deeper files override by recency; MIST.md wins over
AGENTS.md per level; walk stops at the .git marker (dir OR worktree
file); 32 KiB total budget — this text is resent every request forever,
so the cap is the prefix budget.

Cache contract: docs load ONCE and freeze into the system prompt. A
mid-session edit is detected at turn start and appended at the TAIL as
'[project-docs update] These AGENTS.md instructions replace ALL
previously provided…' — the stale copy stays cached in the prefix,
recency + the notice neutralize it, compaction garbage-collects later.
Wire test asserts the system block stays byte-identical across the edit
and the supersede message carries the new text.

/init is prompt-sugar, not harness logic: submits a canned prompt and
the model explores the actual repo and writes AGENTS.md with ordinary
tools (hooks apply). Spec: 200-400 words, real commands, conventions
inferred from git history, never overwrite an existing file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sting warm cache

Supersession dedup was unconditional; clearing a read buried 200k
tokens deep re-caches the entire tail to save a few k of stale text.
The correctness argument doesn't require eagerness: the NEWER read is
in context and recency outranks the stale copy, so clearing can wait.

chooseSupersededToClear picks a DEPTH CUT, not all-or-nothing: bust
cost is set by the earliest cleared block, so it evaluates every
suffix of the candidate list and clears the best positive-net suffix.
Deep candidates are deferred — the next cold cache (idle past TTL),
inert endpoint, or compaction clears them for free. Same lens-driven
signals as the stale-clear governor; decision recorded in
TurnLens.hygiene (dedupedDeferred) and shown in /lens.
MIST_ADAPTIVE_HYGIENE=0 restores unconditional clearing;
compact() still dedupes everything (prefix dies anyway).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Supersession assumes the file IS its latest content (state semantics).
That's true for source/config/docs — and false for logs and run output,
where each read is a TIME SAMPLE of a changing stream: the error trace
read before a fix is unique evidence that a clean re-read afterwards
must not evict.

isObservationFile exempts *.log/out/err/trace, log dirs, and
*.jsonl/ndjson event streams from supersession ('log' inside a source
filename doesn't match; lock files are state and still supersede).
MIST_SUPERSEDE_SKIP adds a user regex to the exempt list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two changes from auditing the official prompt-caching rules:

1. Sliding breakpoint window. We marked only system + the final block,
   so hits relied on the server's 20-block lookback finding the
   previous request's entry — a single large tool batch appends >20
   blocks and outruns it, silently re-writing the whole conversation.
   Now the last block of each of the last THREE messages is marked
   (+ system = the 4-breakpoint API max). One request cycle appends 2
   messages, so the previous request's breakpoint is always still
   explicitly marked: exact-hash hit, lookback never needed.

2. MIST_CACHE_TTL=1h — hold the tools+system prefix for an hour (2x
   write once, same 0.1x reads). Human think-time between turns
   routinely exceeds the 5-minute default TTL and cold-started every
   turn. Ordering constraint (1h before 5m) is satisfied by
   construction: system is the first breakpoint.

Wire test asserts the window shape (last 3 marked, older unmarked,
exactly 4 breakpoints) and TTL placement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adaptive context hygiene, AGENTS.md, cache hardening, GLM usage fix
…tely after' 400

A turn that dies between recording the assistant's tool_use blocks and
recording their results (tool crash, endpoint failure inside headroom
compression, kill, interrupt) leaves history the API rejects wholesale:
'tool_use ids were found without tool_result blocks immediately after'.
Persistence then poisons the session across resumes.

Three layers, codex normalize-on-send style:
- repairToolPairing: synthesizes error tool_results for unanswered
  tool_use ids (inserting between messages when the user already nudged
  past the crash), drops orphan tool_results, and returns intact
  histories by the SAME reference so the cached prefix is untouched.
  Runs at loadHistory (resume) and every runTurn start, with a visible
  '⚠ repaired N interrupted tool calls' step.
- runTool crashes become is_error tool_results, never turn aborts.
- maybeCompress (a model call) falls back to raw content on failure.

NOT caused by supersession/stale-clear: those replace result CONTENT in
place — the block and its tool_use_id always survive (invariant-tested).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fix: repair dangling tool_use pairs (the 'tool_result blocks immediately after' 400)
The busy status line showed CUMULATIVE input+output across requests —
input re-bills the whole history every request, so the figure ballooned
quadratically and read like context usage when it was billed traffic.

- Busy line now shows 'ctx 84.2k' — the LAST request's real prompt size
  (uncached + cache-read + cache-write), i.e. what the model holds.
- New bottom-right gauge under the input box, both idle and busy:
  'context 84.2k / 1.00m (8%)' — live size vs the model's window from
  the registry (re-resolved at turn start so /model switches track).
- Updates from real usage events; resumes seed from the estimate;
  compaction snaps it to the post-compact size.
- Cumulative billed total remains available in /status and /lens where
  it's labeled correctly.

Live-verified in tmux against the mock (149/200k after one turn —
99 uncached + 40 read + 10 written).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live context gauge below the input box + honest busy-line token count
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