integrate: Discord-sourced fixes (#286–#298) - #299
Merged
Conversation
Two-part fix for issue #285 (verbatim chain-of-thought delivered to the user as the agent's reply, and native_tools having no effect on model behaviour). Guardrail (step-executor): the native_tools reasoning-only salvage no longer wraps an unparseable `reasoning_content` body into a `reply { text }` call. A GBNF-shaped batch embedded in the think channel is still recovered as real tool calls, but anything else now returns a parse failure and routes through the existing one-shot repair (parse_retry + buildToolCallRepairPrompt, REPAIR_MAX_TOKENS-capped). A repair that fails too ends the step as a GrammarError — a parse error, not a CoT leak. Root cause (prompt): the stable prefix was transport-blind — under native_tools the request carried an OpenAI `tools` array with `tool_choice: "auto"` while the prompt simultaneously ordered "Emit a JSON ARRAY of tool calls now" and the persona mandated "exactly one JSON array". `buildStablePrefix` now takes `toolTransport` (threaded from `StepDependencies` through `BuildPromptInput`): under native_tools the persona's emission mandate and the `### instructions` block switch to native function-calling guidance. The `### tools` text catalog stays in both modes — the provider fallback chain can hand a native-shaped request to a grammar-only llama-server link, and the catalog carries the tier / `tool.view` semantics. The grammar-path prefix is byte-identical to before (KV-cache safe; verified by sha256 against the previous implementation across profile/turn-framing/win32/persona variants). Known caveat: on a native-to-grammar mid-session fallover the prompt lacks the JSON-array mandate while the grammar link parses GBNF. This is acceptable because llama-server's GBNF grammar constrains decoding to the array shape regardless of the prompt mandate. Fixes #285 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…roviders The trailing `<think>` prefill (and Gemma turn-framing tokens) are llama-server text-completion artifacts: the local template expects the open tag pre-typed at the generation point. On the native-tools chat transport the same prompt ships as a chat message to an OpenAI- compatible endpoint, where the literal tag is at best noise the model echoes back — and at worst corrupted server-side: Ollama Cloud mangles literal `<think>`/`</think>` strings in message content (ollama/ollama#17248), the trigger for #283. The injection only fired in hybrid configs (a local llama-server probing a think-tag model while completions route to a cloud provider), but there it also mis-parsed clean cloud replies: `normalizeContent` re-prepended the open tag and the stream parser started pre-opened, so a reply that never emitted `</think>` was swallowed whole as reasoning. - build-prompt: new `suppressReasoningPrefill` input drops the trailing reasoning prefill, the Gemma turn framing, and the reasoning system token; the step executor sets it for `toolTransport: "native_tools"`. - step-executor: parsing no longer assumes a prefill that was not sent (`promptCarriesReasoningPrefill` / `completionAssumesOpenReasoning`); grammar-parsed completions keep the legacy prepend — the GBNF prelude root structurally starts mid-think — including on cross-transport fallover. The one-shot repair prompt stops re-appending `<think>`. - profile-invariants: `checkProfilePromptAligned` learns the suppressed shape (the prompt must NOT end with a reasoning prelude). - provider-presets: document the upstream Ollama Cloud corruption next to the preset. The issue's proposed blanket rewrite of thinking-tag strings in all outgoing content for ollama.com endpoints is deliberately NOT implemented: silently mutating user text and tool results is the same silent-corruption class relocated client-side, and the server-side half is Ollama's bug to fix. Fixes #283 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eplay drift Adversarial review of the issue #285 fix found three residual problems; this commit closes all three. 1. `### rules` still opened with the text-array mandate ("One tool-call array per step ... a solo action is a length-1 array") in BOTH transports, contradicting the native `### instructions` block in the same prefix. The rules line is now transport-variant like the persona and instructions: the grammar line is byte-identical to before (sha256-verified against v0.4.2 across omitted/explicit, win32, persona-override, reasoning-token and turn-framing variants), the native line mandates the function-calling interface. The same sweep caught one more survivor in the shared persona — "if another tool is next, emit that tool JSON" — which likewise becomes "call that tool" under native_tools only. 2. `buildToolCallRepairPrompt` was transport-blind: on the exact parse_retry path the #285 fix routes reasoning-only completions through, it appended "Emit a corrected JSON array only" / "Use a length-1 array" onto a native prefix that forbids text-JSON — the dual mandate recreated at the one retry a failing model gets. The repair mandate now follows `deps.toolTransport`: native repairs order a corrected native tool call, grammar repairs keep the legacy lines byte-for-byte. A new probe test captures the second llmComplete prompt under native transport and asserts no text-array mandate survives anywhere in it. 3. `atomic-agent trace replay` regressed into 100% false drift for native-transport sessions: the prefix now differs by construction per transport, but `replaySession` always rebuilt the grammar variant and traces do not record which transport served the session. The replay is now transport-aware: `ReplayContext.toolTransport` pins the comparison when the caller knows the transport; when omitted (the trace-command case) both variants are built and a recorded hash is clean when it matches either — old (pre-#285) traces keep matching through the byte-identical grammar variant. Each step reports which variant matched (`matchedTransport`). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-transport fallover Adversarial review of the prefill-suppression fix (#283) found a real streaming regression in the documented default hybrid chain — cloud (native_tools) primary with a grammar local last resort (`appendLocal`) on a think-tag profile: - consumeStream keyed `preOpenedThink` off the PRIMARY transport, but a grammar-served fallover stream starts mid-`<think>` (the GBNF prelude root emits `body "</think>"` with no open tag), so the parser silently swallowed the reasoning text — no live `reasoning_delta`s for every sticky-override turn of an outage, where main surfaced them. The `servedTransport` stamp on the stream's return value arrives only after the last delta, too late to reconfigure a parser. Fixes, in dependency order: - completion-types: `StreamChunk.servedTransport` — the fallback streamer seam now stamps the serving link's transport on EVERY chunk, not just the final result, so live consumers can adapt up front. - step-executor.consumeStream: the stream parser is created lazily off the first chunk's stamp (primary transport when unstamped, i.e. the direct non-fallback path), restoring live reasoning classification for grammar-served fallover streams. - llm-fallback-seam: per-link prompt substitution. The main prompt for a native-tools primary is prefill-suppressed, which handed the grammar fallover link a prompt/template mismatch (GBNF still forces mid-think output). `LlmStreamParams.grammarPrompt` carries a lazy, memoized prefill-carrying variant built only when a grammar link is actually chosen; the one-shot repair retry rebuilds both variants repair-shaped so a fallover retry never sees the stale base prompt. - completionAssumesOpenReasoning now keys purely off the served/parse transport: grammar-served output always continues an open think block; a chat completion never does. This also stops the (documented- unsupported) grammar-primary -> native-link ordering from swallowing a clean chat reply whole as reasoning; the literal prefill still shipping to the chat link in that reverse ordering remains, matching AGENTS.md's "order native-tools links at or above the first grammar-only link". - profile-invariants: the prefill-suppressed branch also flags leaked Gemma turn-framing tails, not just reasoning open tags. Coverage for the pinned invariant 8 gap that let this slip past CI: fallback-e2e now exercises think-tag profiles through the REAL seam factories, unary and streaming (live deltas + per-link prompt shape), llm-fallback-seam.test pins the per-chunk stamp and the grammarPrompt substitution, and step-executor.test pins both fallover directions. All 8 new tests fail without the src changes (verified by stashing). lint clean; agent+prompt+llm 877/877; runtime+tui/providers 336/336; full suite 6447/6448 — the one failure (sidecar/send-message-concurrency) fails identically on pristine origin/main in a clean worktree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…catalog The config key skills.catalogTokenBudget (env ATOMIC_AGENT_SKILLS_CATALOG_BUDGET, default 512) was parsed and typed but never read: every buildSkillCatalog call site invoked it without options, so the '### skills' catalog was always cut at the hardcoded 4096-char cap and the knob silently did nothing. Thread the configured budget through all three call sites (runtime bootstrap initial build, refreshSkills rebuild, trace replay). The key speaks tokens while the catalog builder caps characters; convert at a named SKILL_CATALOG_CHARS_PER_TOKEN = 8 factor, chosen so the shipped default of 512 tokens maps exactly to the historical 4096-char cap — users who never set the key see byte-identical prompts (and keep their KV cache) across the upgrade. An explicit maxChars still wins over tokenBudget for direct callers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nv knob Adversarial review of #288 proved the fixed bug was not regression- guarded where it lived: reverting only the bootstrap/trace-command call sites to main (the exact reported defect) left all relevant tests green. - bootstrap.test.ts: boot with ATOMIC_AGENT_SKILLS_CATALOG_BUDGET=4 and assert runtime.skillCatalog is built with that budget, both at boot and after refreshSkills() — covering both bootstrap call sites. - trace-command.test.ts: run `trace replay` twice (default vs 4-token budget) against seeded global skills and assert the recomputed stable- prefix hash shifts — covering the replay call site. Both new tests verified to FAIL with the two call-site files reverted to origin/main, and pass with the fix. - skill-catalog.test.ts: the default-budget mapping guard now imports ENV_DEFAULTS.SKILLS_CATALOG_BUDGET instead of hardcoding 512, so a future default change trips the 4096-char back-compat check. - load-config.ts: ATOMIC_AGENT_SKILLS_CATALOG_BUDGET is now read via readBoundedPositiveInt (clamped to [1, 100000]) so a zero/negative value cannot drive maxChars to 0 and collapse the catalog; documented in config-schema.ts and covered in load-config.test.ts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Activating a completion row submitted the raw editor buffer through handleEditorSubmit, and resolveSlashCommand is exact-match only — so with "/mod" typed, clicking the "/model" row errored with "unknown command: /mod" instead of running /model. Run the clicked row's own completion instead, through the same runSlashCommand path the keyboard palette-highlight branch uses on Enter: the row the operator clicked is the choice, whatever prefix is in the buffer. Reported on Discord: a click on a slash-command completion submits the typed buffer instead of the clicked command. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Users keep pointing the External llama.cpp URL at Ollama (:11434). The /health probe classifies it openai-compat and refuses the save; since 26102be the verdict is at least visible on the panel's status line, but it was a dead end — acting on it meant retyping the same URL into a wizard four screens away. Verified against a live `ollama serve`: /health answers 404 and /v1/models answers the OpenAI list shape, so the probe's verdict for Ollama is exactly `openai-compat` (status 404). - On an openai-compat refusal the External pane now opens a steer prompt naming the server; `y` deep-links into the provider wizard on the OpenAI-compatible route with the probed URL prefilled, `n`/Esc dismisses. A URL on Ollama's default port lands on the "Ollama (local)" preset — same state as picking that row by hand (entry id, env var, no key screen), but keeping the operator's own host — and any other compat server lands on the manual row's URL screen. - describeLlamaHealthFailure names Ollama outright for :11434 URLs and steers to the Ollama preset row, so the onboarding custom-URL branch (which shares the describer) stops calling an Ollama user's server merely "OpenAI-compatible". Tests: verdict-to-steer wizard mapping (preset vs manual row, host kept), the Ollama-named describer line, looksLikeOllamaUrl, and the prompt's reducer + key flow (y opens the prefilled wizard, n/Esc dismisses, hotkeys swallowed). All fail without the change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The v0.4.2 synchronized-update bracketing (eb690e7) stopped the frame tearing but not the Win10 conhost reports: residual shaking, and a duplicated last row under PowerShell. Both fit one mechanism the bracketing cannot address — the TUI pins its root to `height={rows}`, and the frozen inbox conhost scrolls when a full-height frame writes into its bottom row; DEC 2026 is ignored there, and a scroll is buffer movement, not tearing. Once the viewport slides one line, the repaint cursor math is off by one: the UI shakes and the row that scrolled away leaves the last row painted twice. No Windows machine was available to reproduce, so the change is the conservative guard: when the host looks like a legacy conhost (win32, and neither WT_SESSION nor TERM_PROGRAM set), `useTerminalSize` reports one row fewer, so no frame ever touches the bottom terminal row and there is nothing left to scroll. Every modern host — Windows Terminal, VS Code, anything setting those variables — keeps the full height, and non-TTY streams (tests, pipes, CI) are untouched. A one-time transcript hint on such consoles recommends Windows Terminal and names the escape hatch: ATOMIC_AGENT_CONHOST_GUARD=0 disables the guard, =1 forces it on anywhere — which is also how it was verified: a PTY+pyte run at 80x24 shows a 23-row frame, a never-written bottom row, and the hint; with the variable unset the frame is unchanged from main. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reported on Discord: the TUI stops showing reasoning tokens partway
through long turns. The mechanism is in the grammar stream parser, not
the TUI ring buffer: while inside a think block, the first bare "{" or
"[" in the buffer was treated as the start of the tool-call payload,
which emitted reasoning_close and silenced every later reasoning delta
of that step. Chain-of-thought that mentions JSON, code or array
notation hits this almost immediately, so the live reasoning display
froze until the step's final canonical reasoning event replaced it.
The close-sentinel-less handoff the early exit exists for (pre-opened
think, model goes straight to the payload) is preserved: a brace now
ends the reasoning stream only when it actually begins a
'{"tool": "' payload (optional array opener allowed). A candidate cut
off by a chunk boundary ('{"to') is held back until it resolves, with
a 64-char probe cap so the holdback - and the buffered memory - stays
bounded; anything that provably is not a payload streams on as
reasoning. Stream end treats an unresolved candidate as reasoning too.
Five of the six new parser tests fail without the fix; the sixth pins
the new holdback behaviour at chunk boundaries.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…uild cannot serve Windows machines without a working GPU compute stack — typically iGPU-only boxes like the AMD 5600G — were unrescuable: backend variant selection only knew the Vulkan and CUDA turboquant zips, so when the Vulkan build crashed or hung on model load there was no CPU build to reach and no way to ask for one. - windows-backend-variant: register the llama-turboquant-windows-x64-cpu.zip asset the nightly repo already publishes, plus a configured-variant preference (auto | cpu | vulkan | cuda-12.4 | cuda-13.3) that bypasses the nvidia-smi probe and its process-wide cache. - config v46: localModels.managed.backendVariant, default "auto"; pushed into the local-llm layer from loadConfig the same way setCustomLocalModels is. - daemon-lifecycle: the chat health-wait failure is now a typed DaemonHealthError, distinguishing "this compute backend cannot serve on this machine" from pre-spawn failures a backend swap cannot fix. - cpu-backend-fallback: shouldFallBackToCpuBackend (pure eligibility: win32 + variant auto + GPU asset installed + DaemonHealthError) and fallBackToCpuBackend (flip preference, stop the half-started daemon, re-download). - TUI start and CLI `models start` both fall back automatically with a surfaced message, persist backendVariant "cpu" so the next auto-update's variant-staleness check cannot reinstall the broken GPU build, and retry the start once on the CPU build. Reported on Discord (l.hk, Win10, AMD 5600G iGPU, still broken on v0.4.2). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Engaged users on Discord report the memory subsystem is opaque enough to block commitment to the tool. The existing MEMORY.md is an engineering source-of-truth, not an explainer. Add MEMORY_GUIDE.md, an operator-facing walkthrough verified against src/memory and src/tools/memory: - the five stores (profile facts, notes, links, lessons, procedures) and the two automatic writers (reflection, consolidator) - where the SQLite lives and how to inspect it with sqlite3 - how recall reaches the prompt (### profile/lessons/procedures/ memory-index/recalled, pointer-first with drill-down tools) - three worked example transcripts: a profile fact forming and gating, a note recalled a week later, a lesson distilled from a note cluster - inspecting via the TUI Memory tab and /memory dump - forgetting, per-layer master switches, and the full-wipe recipe - OBSIDIAN_VAULT_PATH and the obsidian starter skill vs agent memory Link it from README (memory section + core docs list) and from the MEMORY.md complements list. Reported on Discord: https://discord.com/channels/1515649306781155428/1515649308161085612/1542988707907248148 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The cloud catalogs run past 300 rows and the only way to shop by price
was typing 'free' into the ranked search, which also subsequence-matches
unrelated ids. Both cloud model list surfaces now carry a price facet
next to their existing search, cycled with p: all → free → paid → all.
- Shared predicate in src/llm/provider/model-pricing-filter.ts: a row is
free only when its catalog pricing renders the "free" tag (both
prices zero; openrouter/auto stays out — its tag reads "routed" and
it bills the routed model). Rows without pricing metadata (aimlapi,
live /v1/models ids) cannot be promised free and stay under paid, so
the two facets partition the catalog and no row vanishes from both.
- LLM pane, Cloud text models: p cycles the facet (visible on a new
price: line under filter:, with the key hint); while the filter row is
focused p stays query text. Facet flips snap the cursor to the top of
the result set, same rule as filter edits.
- Providers wizard, curated chat-model screen: p cycles while the search
box is closed; the active facet rides on the title ("· free only")
and the hint line names the key. advanceWizardPhase resets the facet
with the search, so it never leaks into the embedding screen — and the
pane and wizard keep separate facet state, so neither screen leaks
into the other. Default is all everywhere.
Tests: predicate unit tests; wizard key tests against a live-seeded
mixed catalog (narrow, cycle, cursor snap, Enter-selects-narrowed-row,
no leak into pick_embedding, p-as-query-text with the box open);
CloudRows component tests (price line + hint, free/paid narrowing with
the filtered-of-total counter); pane key + reducer tests. All 10 new
behavioral tests fail with the implementation stashed.
Reported on Discord: https://discord.com/channels/1515649306781155428/1515650562430079048/1536840031329853652
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reported on Discord: ssh from a Mac into the TUI and the mouse layer "spams coordinates" as visible text while the app looks hung. Four layers of hardening: - mouse-stdin: hold a chunk-final lone ESC for 10ms. An ssh hop re-chunks the stream, and a report split right after its ESC used to type `[<64;3;9M` into the composer — the reported spam. If the rest of a report follows it rejoins and decodes; if nothing does, it was the Escape key and flushes (under Ink's own ~20ms lone-Esc deferral). - decoder: consume urxvt/1015 reports instead of dropping them through as text, buffer truncated CSI heads so split sequences reach Ink whole, and document + cover that 1005 already lands in the X10 branch. - leak breaker: report-shaped text about to reach Ink (a burst in one read, or three drips across the session, counted across read boundaries) trips once — strips the shapes, disables tracking for the session only, and posts a warn notice naming /mouse and --no-mouse. - signals: a second SIGINT/SIGTERM/SIGHUP now restores the terminal (mouse reporting off, alt screen left) and exits 130 instead of Node's default kill that skips exit hooks and leaves the shell printing coordinates on every click. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…y corpus Adds an `atomic-agent memory` command with an `export` subcommand that mirrors the cross-session memory corpus (notes / lessons / procedures in <stateDir>/memory.sqlite) into an existing Obsidian vault as markdown files with YAML frontmatter (type, created, updated, tags) and [[wikilinks]] along the schema's own edges: memory_links rows, consolidated_into back-pointers, and lesson/procedure parent ids. v1 design decisions: - one-way and read-only: the database is opened readonly and never migrated; older schemas degrade gracefully (missing tables => empty) - stable id-based filenames (note-<id>.md, ...) under a vault subfolder (--folder, default 'atomic-agent') so records keep their Obsidian identity and backlinks across re-exports - idempotent and overwrite-safe: content is a pure function of the rows, unchanged files are not rewritten, and pruning of stale files is restricted to the machine-owned <kind>-<n>.md name patterns inside the export subfolders — user files are never touched - soft parent pointers to evicted rows are skipped, not rendered as dangling links - --vault falls back to $OBSIDIAN_VAULT_PATH (including via <stateDir>/.env); no watch mode, no sync-back in v1 The new command implements the 0/1/2 exit-code split (usage errors return 2). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add localModels.managed.tensorSplit (config v46, default [] = feature off). Two or more non-negative ratios launch the managed chat daemon with --split-mode layer --tensor-split <ratios> so the model's layers spread across GPUs proportionally. Previously resolveManagedDevice always pinned exactly one device (pickBestDevice), so no multi-GPU launch could ever be expressed: a pinned --device defeats --tensor-split. With a split configured, the auto device preference now leaves every GPU visible instead of pinning the best one; cpu still wins outright, and an explicit device id passes through unchanged so a comma-separated list (Vulkan0,Vulkan1) can restrict which devices join the split. models use-device now accepts that comma-separated form. Default behavior is byte-identical: with tensorSplit empty the launch args and the single-device auto-pick are unchanged, and the embedding daemon always keeps pinning one device. Ratios are validated at parse time (at least two finite non-negative numbers, at least one positive). Reported on Discord (managed llama-server multi-GPU support): https://discord.com/channels/1515649306781155428/1515649308161085612/1536440638902636574 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The steer prompt's key branch sat before the External URL draft's, while LlmPanelModals renders the draft first. Both could be non-null — the steer opens asynchronously after the save's /health probe, and Enter on the External row reopens the draft in that window — leaving the visible editor keyboard-dead while y invisibly opened the wizard. The handler now checks the steer after the draft (matching what is on screen), and the reducer refuses to open the steer under an open draft in the first place. Also: the vacuous swallow test now proves its baseline, and the :11434 refusal text routes a remote Ollama through the manual compat row (the preset row has no base-URL screen, so by hand it could only save localhost). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two claims did not survive review against the code: - Worked example 3 said every prompt carries the lesson pointer. ### lessons / ### procedures are query-gated: the per-turn recall query (user message + recent tool-result summaries) is BM25-matched against activation/principle/tags and only the top recallK hits (2 each) render; the unconditional list is the TUI Memory tab. State this in the prompt-sections chapter and in the example. - The dedup bullet called the 0.85 threshold a BM25 similarity. FTS5 only fetches the candidates; the threshold is compared against a Jaccard token-overlap similarity computed in JS (memory-store.ts jaccardSimilarity). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… and progress The TUI path of the CPU fallback called fallBackToCpuBackend(dataDir) bare: no AbortSignal, no onProgress. download-file.ts has no default timeout, so a stalled-open connection pinned the start on phase 'starting' for the life of the process, with zero feedback during the 27-39MB download — the exact hazard the auto-update path (and this feature's own CLI path) already guards against. The download now runs with AbortSignal.timeout(BACKEND_DOWNLOAD_TIMEOUT_MS) and surfaces as a regular backend pull (started/progress/finished/failed) on the bus. Also covers the fallback WIRING with integration tests — previously only the extracted pure pieces were tested, so deleting either caller's retry block left the suite green: - local-models-orchestrator-cpu-fallback.test.ts: eligible health failure swaps + persists + retries exactly once; recursion cap; failed download reported; non-health and cpu-installed cases inert; the download carries a deadline and live progress. - models-handlers.test.ts: CLI retry lands on device 'cpu' (no stale --device Vulkan0), persists the variant, forwards signal/progress, pre-spawn failures and failed downloads exit non-zero untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adversarial review proved a hole in the tripped path: forwardText ran the remnant replace per-chunk, so a straggler split across reads — the exact ssh re-chunking this PR exists to survive — still leaked into the composer after the breaker tripped (write `[<0;9` then `;9M` and Ink received `[<0;9;9M` verbatim), contradicting the "keeps stripping the in-flight stragglers" contract. Unlike the pre-trip counter, which scans an already-forwarded tail, the stripper has to keep the bytes out of Ink — so it withholds a chunk-final remnant *prefix* until the rest arrives (stragglers trail each other by well under a millisecond) or a 10ms timer rules it ordinary typing, mirroring the ESC-split hold. Four new tests cover the split straggler, a byte-at-a-time straggler, a partial straggler on the tripping chunk itself, and the timer releasing withheld typing; all four fail without the fix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
added 5 commits
August 31, 2026 21:58
…6-08-31 # Conflicts: # src/agent/step-executor.ts # src/config/config-schema.ts
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.
Integration branch combining the 13 open Discord-sourced fix PRs, now rebased-by-merge onto current main (post #271/#272/#273/#274/#276) so it can be merged into main.
Contained PRs (merged in numeric order)
skills.catalogTokenBudgetwhen building the skill catalogmemory export— one-way Obsidian vault export of agent memory #296 feat(cli):memory export— one-way Obsidian vault export of agent memoryConflict resolutions
#286 vs #287 —
src/agent/step-executor.ts(4 hunks)buildPromptinput keeps both fix(agent): never leak raw reasoning as a reply; transport-aware prompt #286'stoolTransport(native function-calling guidance vs text-JSON mandate) and fix(agent): stop sending the reasoning prefill to native-tools chat providers #287'ssuppressReasoningPrefill.grammarPromptfallback variant rebuilds withtoolTransport: "grammar"+suppressReasoningPrefill: false, so a grammar fallback link behind a native-tools primary gets both the text-JSON mandate and the prefill.buildToolCallRepairPromptsignature combines both new params (toolTransport?,promptCarriedPrefill = true); the grammar-variant repair passes"grammar", true.#293 vs #298 —
src/cli/models-handlers.tsstartWithDevice(dev)closure and added feat(local-llm): multi-GPU tensor split for the managed llama-server #298's split as...(multiGpu && dev !== "cpu" ? { tensorSplit } : {}), so the forced-CPU rescue retry never hands multi-GPU split args to the CPU backend (matches thetensorSplitfield doc:device: "cpu"wins and disables splitting).Config version renumbering —
src/config/config-schema.tsweb.search.persistCache(main/feat(web-search): persist the search cache and provider cooldown across restarts #274), v47 =localModels.managed.backendVariant(feat(local-llm): CPU llama.cpp backend fallback for Windows boxes whose GPU build cannot serve #293), v48 =localModels.managed.tensorSplit(feat(local-llm): multi-GPU tensor split for the managed llama-server #298);USER_CONFIG_VERSION = 48,SUPPORTED_INPUT_VERSIONSgains 46 and 47, field docs updated. All three changes are additive; nothing gates on the literal version numbers.#272 vs #286/#287 —
src/agent/step-executor.ts(merge of main)terminalOnly→stepToolDescriptorsfilter feeds both the prompt build andbuildLlmStreamParams, alongside the transport-awarepromptInputand thegrammarPromptspread onllmParams.Verification
npm run lint(tsc): clean.npx vitest run src/agent/ src/llm/ src/prompt/ src/tui/ src/skills/ src/config/ src/local-llm/ src/cli/on Node 25.7: 383 files / 4340 tests, all passed (0 failures vs the ~10-known-flaky full-suite baseline).llama-serverspawn EACCES fromlocal-models-orchestrator-auto-update.test.ts, byte-identical to main; exit code 0).