Foundational Harness Improvements#489
Open
bajajra wants to merge 143 commits into
Open
Conversation
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>
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR delivers seven harness-level improvements that move Code Puppy closer to the interaction and reliability while preserving its plugin-first architecture:
Why this work matters
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_controlon 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.jsonwith restricted permissions. TheCODE_PUPPY_TRUST_PROJECTenvironment variable provides an explicit automation override.The plugin adds:
/trust status/trust project/trust revokeCore permission modes are now available independently of the existing file-permission plugin:
ask: approve both shell commands and file mutationsacceptEdits: allow file mutations, continue prompting for shell commandsauto: allow both shell commands and file mutationsNew installations default to
ask. Existing installations retain compatibility through the legacyyolo_modefallback when no explicitpermission_modeexists.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:
/treerenders 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 treeThe current pickle/session mechanisms remain available; this is an additive compatibility layer rather than a destructive storage migration.
Future scope opened
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:
CREATEDRUNNINGFOLLOW_UPCOMPLETEDCANCELLEDFAILEDActions are explicit:
STEERHOOK_RETRYSTOPThe 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, selectnoworqueue, 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 statuswith model calls, tool calls, retries, and stateImportant 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_definitionlsp_referenceslsp_hoverlsp_diagnosticslsp_workspace_symbols/lsp statusreports 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
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_implexecution backend with typed Pydantic contracts and an observable queue.A request includes:
Each task receives a UUID and transitions through:
queuedrunningsucceededfailedcancelledTools:
submit_agent_taskslist_agent_taskswait_agent_taskscancel_agent_tasksBatches use a bounded semaphore.
max_paralleldefaults 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
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 timeoutagent: agent name, prompt, optional session/model overridesStates:
queuedrunningsucceededfailedcancelledinterruptedTools:
start_background_tasklist_background_taskswait_background_taskread_background_task_outputcancel_background_taskThe 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
interruptedbecause 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
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
permission_moderetain the legacyyolo_modefallback.permission_mode=ask.invoke_agentandinvoke_agent_with_modeltools remain available.Validation
Focused feature validation: