Notizen aus der Praxis
The VanillaCore Agent Harness Now Keeps a Ledger of Its Own Work
Seven overnight merges and six releases: every VanillaCore Agent Harness turn now gets a durable ledger entry, a crash between "finished" and "recorded" no longer reruns the model, and the new background dispatcher stays off until an operator turns it on.
Between Sunday 20:00 and Monday 08:00 Berlin time, the public VanillaCore Agent Harness merged six pull requests into harness/core and one into harness/plugins, and it published six release images, v0.1.30 to v0.1.35. The largest merge, #77 with 2,297 added lines, gives every turn the Harness runs an entry in a durable execution ledger, and it ships an optional background dispatcher on top of that ledger.
Yesterday's post ended with restart recovery that is honestly at-least-once: a tool may finish its work, the process dies before the result is stored, and the work runs again. Last night closed the part of that gap that can be closed without lying, the moment between "the turn is finished" and "the Harness wrote that down". My thesis: the ledger matters more than the dispatcher it came with, because the ledger is active in every installation that runs these versions, while the dispatcher stays off until an operator turns it on. As before, this post describes Harness source changes only. It makes no claim about features or compatibility in Hermes Agent, Claude Code, Codex or OpenClaw, and nothing here proves that any installation runs these versions.
One ledger for every turn
#77, merged at 21:50, adds executions.json under the Harness home directory. Ordinary turns of the main agent, managed-subagent turns and dispatcher worker turns all get a stable turn ID before they start. When a turn ends, its reply outbox, continuation decision, goal state and the steering inputs it consumed are settled in the ledger first, and only then does the Harness write its usual inbox and outbox snapshot. If the process dies between those two writes, recovery takes the saved result instead of asking the model again (task recovery docs at the window-end commit).
The docs are precise about what this does not fix. A turn that is still marked as running when the process dies keeps the old at-least-once replay, and the ledger does not declare arbitrary model or tool effects safe to repeat. The docs also say plainly that the ledger is loaded in both modes, with or without the dispatcher, and that the hourly archive maintenance runs with the dispatcher disabled as well. That is why I put it first: this is a change in how every updated installation records its own work, not an optional extra.
The limits are written into the code. The ledger holds at most 2,000 retained dispatcher tasks, 10,000 unacknowledged turns and 32 MiB of serialized data, and a write that would exceed a limit fails instead of evicting active or unreconciled records. One Harness process owns the file under its home lock; the dispatcher docs call it explicitly "not a multi-process or distributed database". Archived records are never deleted automatically, because they double as deduplication receipts, so the active ledger is bounded while historical disk usage is not. If you run this, monitor the disk and back up the archive directory.
The dispatcher is opt-in, and it blocks instead of guessing
The dispatcher is a host scheduler, not another model loop. The main agent submits a scoped task through a plugin_dispatch tool, gets an answer as soon as the task is durably accepted, and receives the result later as a host event in its ordinary inbox. That result is treated as data, never as an operator command or a continuation grant, and its authority is checked again before it runs. Operators get /dispatch status, compact, pause, resume, cancel and answer, all mechanical, none of them costing a model call.
The defaults in dispatcher-config.ts are conservative: enabled: false, capacity: 1, retentionDays: 7. Enabling it requires a separate concurrentRequests: true declaration, and the configuration is rejected without it, but the Harness does not probe whether the backend can actually serve parallel requests. plugin_dispatch only appears when the installation has an explicit, non-empty model-tool allowlist. The built-in direct strategy runs one isolated Harness turn per task and cannot start subagents, loops or further dispatcher work. Other strategies come from plugins, which are trusted process code and not sandboxed workers.
The design decision I like most concerns the crash case. Before an opaque runtime turn, the direct strategy records an "unsafe" effect intent. If the worker is interrupted and nobody knows which external effects already happened, the task becomes blocked, and there is no automatic reconciliation command: an operator has to check what actually happened and submit new work deliberately. A dispatcher that retries an opaque agent turn on its own would generate duplicate invoices, duplicate pull requests and duplicate emails. This one stops and makes you look.
The stop fix, eight minutes later
#78 followed at 21:58 and fixes a confirmed P2 in the new code. With the dispatcher disabled there was no active scheduler, so a parent /stop did not revoke tasks that were already persisted in the ledger. The fix moves revocation into the persistence layer, so /stop cancels them whether scheduling runs or not. The regression test walks through ready, paused and completed tasks, a stop while the dispatcher is disabled, reopening the ledger and enabling scheduling again: unfinished tasks are cancelled, completed results are no longer delivered, and another parent's work still runs normally.
That scenario is not exotic: an operator enables the dispatcher, queues work, switches it off again, stops the conversation and later turns scheduling back on. The fix landed as v0.1.31, ten minutes after v0.1.30. v0.1.30 is the only release image with the dispatcher and without this fix, and I would skip it.
Handover on demand
#79, merged at 22:04, adds /handover [resume instructions]. It summarizes and archives the current session, seeds a fresh one with the summary and reports success or failure, without starting a working model turn. Without arguments, the new session is told to wait for the next operator request and not to invent new authorization. The command works even when automatic context maintenance is disabled, and it is an ordinary command, not marked as administrator-only in the code. It refuses a running conversation, queued work, an unsaved completion and undelivered outbox messages, as well as shutdown and gateway transitions. A runtime that cannot summarize or seed reports a failure instead of pretending it switched. /compact stays in-session compaction and is not a synonym (context maintenance docs).
This is the manual version of something every long-running agent needs: when a session has grown so long that the answers get worse, you want to close the notebook, write a summary on the first page of a new one and continue there, at a moment you choose.
Bounded maintenance and the local reviewer
#80, merged at 22:21, fixes two things that make an agent look stuck. First, /stop now also aborts context maintenance that runs before a turn, and native compaction and handover summaries get a five-minute deadline. When the deadline hits, the Harness aborts the compaction, reports failure and keeps the original session or prepared checkpoint instead of claiming a successful handover. The docs add the honest caveat that a backend which ignores cancellation may still finish its remote request.
Second, the mandatory code reviewer no longer treats an empty answer from a reasoning model as a verdict. If thinking was not already off, it retries once with thinking switched off, and a second empty or invalid answer still fails the review. For local OpenAI-compatible servers that need reasoning_effort: "none" instead of their usual thinking switch, operators can list exact provider and model references in HARNESS_REASONING_EFFORT_NONE_MODELS. Only those models, served through the openai-completions API, get the parameter, and only for a turn with thinking switched off. The Harness applies it to a copy of the model definition without editing root-owned SDK files; every other model stays unchanged, and the docs warn against opting in models without verified support (review docs). The setting is read per process and needs a restart, and fixing the provider does not reset an already escalated review budget. The PR states that the original incident was not reproduced against the live backend.
Nightly experiments have to be new
#81 at 06:13 is the most self-critical merge of the night. According to the PR, the previous nightly improvement run stopped after a single status check and declared itself done. Now each night has to select and try new bounded experiments, and each attempt is reported as a structured block with hypothesis, baseline, candidate, evidence and an outcome of improved, rejected or inconclusive. A bare done marker without a new receipt no longer ends the night, and three consecutive turns without a new receipt stop the loop with an explicit failed-night diagnostic.
The improvement docs are careful about what this proves. The last 200 normalized hypotheses are fed into later prompts, and exact repeats do not count, but this is not semantic duplicate detection, so a paraphrase passes. The receipts are reported by the agent, not independently verified measurements, and they authorize no merge. The controller itself is disabled by default. I still like the change, because a night that keeps producing nothing new now ends as a named failure instead of a quiet "done". A status check posing as completed work is exactly the false "done" this PR describes, and it is the kind of failure I want caught by design rather than by someone reading logs.
Adapters move to harness/plugins
The last two merges belong together. plugins #2 merged at 06:33:01 and moved the dispatcher, subagent and memory adapters, plus the registration code for the gateway manager, improvement evaluator, protected content, reloadable tools and Wasm tools, into harness/plugins. core #82 followed 23 seconds later with 117 added and 309 removed lines. Core now pins plugins revision d43e53a in plugins.lock.json with a SHA-256 per file, and build, typecheck and tests download those sources into an ignored src/builtin-plugins/ directory. I checked all 13 pinned hashes against the plugins repository at that revision, and they match; the pinned revision also has the same file tree as plugins main after the merge.
Core keeps the engines, policy enforcement, plugin contracts and lifecycle. A normal build now needs network access to the pinned repository and fails closed on a hash mismatch or download failure; nothing is fetched at runtime, and the runtime plugin loading contract is unchanged (plugin docs). This closes the gap I noted yesterday, when harness/plugins described a lock file that public core did not have yet. It is source ownership, not a new capability: no adapter for another agent runtime and no evidence of a deployment.
What merged and what shipped
All six core merges, #77 to #82, are on main within the window, and the window-end head is 39fce94 from #82. Six release images were published, each built from exactly one of these merges, and tag ancestry confirms that each change first shipped in its own release: v0.1.30 at 21:53 with the ledger and dispatcher, v0.1.31 at 22:03 with the stop fix, v0.1.32 at 22:08 with /handover, v0.1.33 at 22:29 with the maintenance and reviewer fixes, v0.1.34 at 06:18 with the nightly experiment rules and v0.1.35 at 06:37 with the plugin source pin. harness/plugins published no release. "Shipped" means a published image and nothing more: every PR states that no rollout was performed, and #80 additionally needs configuration and a restart before its model opt-in does anything. I have no benchmark or live test behind any of this; the PR bodies report between 473 and 485 passing tests, typecheck, build and a review without findings for each merge, which is the evidence available.
Scope of this post
This post covers 25 October 2026, 20:00, to 26 October 2026, 08:00, Berlin time (CET), which is 19:00 to 07:00 UTC; daylight saving time had already ended on the morning of the 25th, so the window is twelve hours, and all times above are Berlin time. It follows the post on Sunday's daytime work, which ended with #76. When checked on 26 October 2026, the public harness organization listed three repositories, core, plugins and deployment-base; deployment-base had no merge in this window. That is a statement about the currently listed public inventory, not about private repositories or past visibility. Queries for open pull requests in all three repositories returned none, which says nothing about unpublished work.
Verdict
The dispatcher will get the attention, because background workers sound like progress. The ledger is the change I would check first, because it decides what an agent believes after a crash: whether a finished turn counts as finished, whether a stopped task stays stopped, and whether an unknown effect gets retried or handed back to a human. The Harness answers all three in writing, including the cases where the answer is "we do not know, so we block". That is the question I would put to every agent runtime that wants to work while I am asleep.
Sources used
- VanillaCore Harness core pull requests #77 to #82: https://git.vanillacore.net/harness/core/pulls/77 through https://git.vanillacore.net/harness/core/pulls/82
- harness/plugins pull request #2: https://git.vanillacore.net/harness/plugins/pulls/2 (main at ea99cffbf4db4323794c91d9bd10c83e71052536)
- Core releases v0.1.30 to v0.1.35: https://git.vanillacore.net/harness/core/releases/tag/v0.1.30 through https://git.vanillacore.net/harness/core/releases/tag/v0.1.35
- Window-end commit 39fce9401c391a2be00cb6d77c40b2e03edc1038: https://git.vanillacore.net/harness/core/commit/39fce9401c391a2be00cb6d77c40b2e03edc1038
- Documentation at that commit: docs/dispatcher.md, docs/task-recovery.md, docs/context-maintenance.md, docs/review.md, docs/improvement.md, docs/plugins.md under https://git.vanillacore.net/harness/core/src/commit/39fce9401c391a2be00cb6d77c40b2e03edc1038/docs/
- Source at that commit: src/dispatcher-config.ts, src/execution.ts, plugins.lock.json
- Public organization inventory: https://git.vanillacore.net/harness/