You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A multi-line edit no longer deadlocks the run (#127 by @brlucasdx). pi already recovers an edits argument sent as a JSON string instead of an array, and that recovery is a bare JSON.parse in a try. It fails on the single most common way a small local model writes one: raw newlines inside oldText/newText, which is what multi-line code is. JSON.parse throws Bad control character in string literal, the empty catch swallows it, edits stays a string, and schema validation refuses the call with edits.0: must be object. brlucasdx counted six in one session. The consequence is worse than a failed call: write is refused for a file that already exists, so a model that had diagnosed the bug correctly had no way left to deliver the patch at all. This cannot be fixed from an extension (pi's loop runs prepareArguments then validateToolArguments then beforeToolCall, so validation has already rejected the call before any tool_call hook sees it), so it is a patch-pi.mjs patch, applied to both copies of the edit tool pi ships. Control characters inside string literals are escaped and the parse retried; structural whitespace between tokens is left alone, and an escaped \" does not flip the scanner out of the string, which is exactly the case in code being edited.
Behind a router, each model keeps its own context window (#121 by @araujoigor). Two bugs, one line apart. The probe asked /v1/models about entry.models[0].id (array position, not the model the user declared as default), and then withContextWindow stamped whatever came back onto every model. araujoigor's llama-swap setup has 64k/128k/256k presets of one model with the 64k listed first and the 128k declared default: all three registered as 64k, and the default compacted at half its real window. Nothing was logged, because the probe "succeeded". The window is not a readout: it drives read-guard truncation and the whole context budget. The declared default is now threaded through models.json (splitting on the first / only, since llama-swap preset ids contain slashes), and a router listing is stamped per model from its own meta.n_ctx / --ctx-size, with no blanket fallback: a model the router cannot describe keeps the number the user chose rather than another model's measurement. A direct llama.cpp server is unchanged: it serves one model, so its /propsn_ctx really is every model's window. The swap-time re-probe (#54) now re-stamps only the model being selected, and a selection of a model we never registered is ignored instead of emitting a "context window updated" notice for a change that did not happen. One /v1/models fetch now answers all three questions the startup path used to ask separately.
A tool call in prose is no longer "recovered" (#96 by @cal101). Working on JS, cal101 got harness intervention: the model wrote 1 tool call(s) as text [myCustomAction], followed, delightfully, by the model replying that it had not done anything. It had not. #117 narrowed the nested shape by also requiring an argument key, but a flat object still matched on "name" alone, which is the shape of a config blob or an array of records. The reliable discriminator is not the object's shape, it is whether the tool exists: the nudge asks the model to re-issue the call natively, and a tool that is not registered cannot be issued natively by anyone, so for an unknown name the intervention was not merely a false positive but an instruction the model could not carry out. Names are now checked against ctx.getAllTools() (the full configured set, not the currently active one, since a real tool parked by tool-gating is still a slip worth catching), case-insensitively. A ctx without the accessor filters nothing, since guessing would silently drop real recoveries. LFM2/Liquid calls are exempt: their <|tool_call_start|> tokens appear in no prose by accident, and their --jinja diagnostic is about the channel rather than the tool.
Compaction no longer arrives late on Ollama (#128 by @brlucasdx). Measured, not inferred: ~50k tokens of Python sent to Ollama at num_ctx 32768 came back with prompt_eval_count: 16386: half the window, plus two, no error, and a constant placed at 50% depth answered "not found". Ollama reports its prompt size after truncating it. The existing silent-overflow check (usage.input > contextWindow) can therefore never fire there, the reading climbs to its ceiling and stops, and by the time the threshold is reached the runtime has been dropping the older half of the conversation on every request. brlucasdx watched the status bar read 110.0% / 33k, with the agent re-deriving the same diagnosis and re-reading files it had already read. For Ollama only, the watchdog now measures what is actually in context (sessionManager.buildContextEntries() plus the system prompt, at 4 chars/token) and compacts on the larger of the two figures. max rather than "prefer the estimate": the estimate omits tool schemas and per-message framing, so it is a lower bound, and taking the larger can only move compaction earlier, never later. Every other provider reports honestly and is untouched; LITTLE_CODER_LOCAL_CONTEXT_ESTIMATE=1/0 forces it either way. The first time the gap looks like truncation you get one warning naming both numbers, because the real fix is your num_ctx.
Skill cards actually load in the pi package (found while reviewing #62). skill-inject and knowledge-inject resolved skills/ by counting three directories up from the extension, which is right in this checkout (.pi/extensions/<name>/) and wrong in the built pi package (extensions/<name>/). Both loaders end in if (!existsSync(dir)) return;, so the package would have shipped the extensions with none of their content and no way to tell. They search upward for the directory now, which is correct in both layouts.
Added
Your project's AGENTS.md is read again (#104 by @highlyunavailable, confirmed by @dcazrael). --no-context-files is deliberate (little-coder's AGENTS.md should be the system prompt, not whatever sits in the cwd) but it also threw away the project's own file, which serves a different purpose. highlyunavailable stated the cost plainly: with nothing telling it what the repository is, the model globs the whole tree at the start of every run. dcazrael's is sharper: his AGENTS.md exists to say "RULES.md is mandatory, LLM.txt is the map", i.e. to control discovery, so a file that arrives only if the model happens to find it defeats the point. The new project-context extension walks up from the launch directory for the nearest AGENTS.md (then CLAUDE.md) and injects it. It adds rather than replaces, and says so in the block, so a project file cannot quietly re-specify the harness. It is capped at 4000 characters with truncation reported to both you and the model, because the lean cold start is the product. It is injected once, as a hidden tail message rather than a system-prompt append, so the cached prefix survives (#73); the repo's own guard test caught the first draft doing it the expensive way. little-coder's own AGENTS.md is never loaded this way, so running it inside its own checkout does not inject a second copy of the prompt it is running on. /project-context shows what loaded; LITTLE_CODER_PROJECT_CONTEXT=0 turns it off.
little-coder -p --plan-mode works (#95 by @cal101). A local model is a slow shared resource, so cal101 queues jobs, runs them sequentially in the background, and checks the output later. Plan Mode could not take part: its middle is interactive, and under -p there is nobody to ask. Of the three policies that make sense (skip the questions, pre-supply the answers, or stop after generating them) this ships the first, the only one that survives an unattended queue. The questions are still generated and are handed to synthesis to be answered from the research, with the plan opening on an Assumptions section stating what was decided for each and why. That costs no extra model call, which matters at 4-7 tok/s, and it puts the assumptions where a batch user can review them rather than in a dialog nobody saw. The plan is printed and written to .pi/approved-plan.md, so the next job can /implement it. The interactive shape could not simply be reused: print mode awaits one session.prompt() and reads the run's verdict off the last message, so swallowing the input and orchestrating detached tears the process down mid-plan (the #115 hazard). A batch run awaits the research inside the input handler and then lets the original input through, so the ordinary agent turn pi was about to run is the synthesis turn, inside the await print mode is already holding.
Write-capable sub-coders (#93 by @aole). LITTLE_CODER_SUBCODER_ACCESS=write adds edit/write to a child's tools. Off by default and staying that way: read-only children are what make fanning them out safe, since their answers come back as text and two of them cannot race on the same file, and a child that misunderstands its task wastes tokens rather than the working tree. dispatch is withheld at both levels regardless: a child that can spawn children is a fan-out bomb, and that does not change when it can also write. The dispatch tool description reports the level in force, since a model told children "CANNOT edit or write files" will not delegate work that needs an edit.
pi-little-coder, a pi package (#62 by @dbmrq). The extension and skill layer, published alongside each release for vanilla pi users: npm install pi-little-coder. Extensions are discovered from .pi/extensions/*/index.ts at build time rather than enumerated, so a new one cannot be forgotten: this release's project-context was picked up automatically, and then deliberately excluded, since pi discovers context files itself and shipping it would inject a second copy. It is a curated subset of the extension layer, not little-coder-in-a-box, and the generated README says so: scripts/patch-pi.mjs, the explicit --no-extensions load order, the global-settings merge, the llama.cpp context re-probe and the update flow all live in the launcher and do not come along.
Changed
manual mode asks about edit and write too (#122 by @marek2901). It gated shell commands only, so the tools that actually change your files went through unprompted, which is not what the name promises. They now offer Apply, Deny, or Apply all (this session), and a session with no UI denies, which is the right default for a mode built around a human in the loop and matches the precedent #90 set. write-guard still runs on top of an Apply. Sub-coders are unaffected: they are pinned to auto.
2>/dev/null || true is no longer refused.true and false are on the whitelist. Neither builtin can do anything, and refusing them was actively misleading: @guppy42 reported the model concluding from the refusal that ls was the problem and switching to glob.