v0.15.0
A Work Plan the model can actually write
The work_plan tool shipped in v0.14.0 with three of its inputs — plan, task,
changes — declared as bare objects. A JSON Schema object with no properties says
nothing: not which fields are required, not which statuses are accepted, not how a
hierarchy is encoded. The model had to learn the contract from rejections. In a real
session, Mistral Medium 3.5 needed four refused attempts before one landed.
Every operation now publishes its own complete schema. Ten typed branches, one per
action, each declaring exactly the fields that action takes — bounded lengths,
enumerated statuses, and the null that clears an optional value, which was previously
undiscoverable.
And creation no longer asks for storage mechanics:
{ "action": "create", "title": "Port to multi-user",
"tasks": [{ "title": "Authentication", "subtasks": [{ "title": "Sessions" }] }] }A title, and tasks that need nothing but a title of their own. The server flattens the
tree, assigns parent relationships, sets version 1, the timestamp, the default todo
statuses and the empty dependency and resource collections, then returns the complete
normalized plan so the next call needs no lookup. Two levels, because the ceiling has to
be visible in the schema before the model composes something invalid; deeper
decomposition belongs in a description or a later refinement.
Nothing that worked before stopped working. replace still takes a full normalized
version-1 document, add_task still takes a fully specified task, and persisted plans
are unchanged.
The model names its own tasks
The first browser-driven run against a real server failed every single first call, in
two independent sessions, for a reason no offline probe had produced: the model puts an
id on each task, and the schema refused it.
The schema is a ten-branch root anyOf sent with strict: false — captured from the
outgoing provider request, so it does arrive intact, and nothing constrains generation to
one branch. id is required by the neighbouring add_task and replace task shapes,
so the model merges them into create.
Then the refusal cannot guide the repair. A failed union reports must not have additional properties once per branch and never names the property. One session deleted
its description fields — which were valid — then its entire second level, and still
failed. Another gave up on hierarchy and persisted six flat tasks.
So create adopts the identifier the model chose, rejects a collection whose identifiers
collide, and generates one for every task that omits it. Identity is unique either way,
the repair round disappears, and the agent can address its own tasks straight away: in
the verification run it marked audit_securite done without an intervening read.
Guidance with one owner
Whether to keep a Work Plan at all is now stated once, in a product-owned system-prompt
fragment, and only when the tool is actually available to the session. The tool
description keeps mechanical calling guidance and nothing else. Embedded and RPC runtimes
compose that prompt through one shared path, so the two cannot drift apart — and every
operator appendSystemPrompt entry is passed through byte for byte, after the
product-owned blocks.
What this does not buy
The compact create call is 64% smaller than the equivalent replace payload, but its
schema branch costs ~2 KB on every request while the saving lands once. Under eager
tool-schema delivery there is no multi-turn break-even. It is here because it produces
valid first calls, not because it saves context.
Under the hood
The continuous-write file-watcher test failed once on Windows and passed on the next run:
it wrote for a fixed 200 ms and assumed the platform would deliver the first event early
enough for two coalescing windows to close inside that stretch. It now writes until the
second announcement arrives, so first-event latency is no longer part of the assertion.
The window it uses also widened, for a worse reason: substituting a resetting debounce —
the exact regression that test exists to catch — left it passing on macOS, because the
platform's own gaps between delivered events were wider than the 25 ms window, so the
resetting timer fired anyway. At 250 ms the substitution fails and the real
implementation passes in half a second.
Upgrading: nothing to change. Existing Work Plans load unchanged, and replace and
add_task accept exactly what they accepted in v0.14.0. Running pi-outpost from npm
needs Node ≥ 24; the executables attached below require Node ≥ 26 to build.