Skip to content

Upgrading to v0.5

Joey King edited this page Oct 8, 2026 · 1 revision

Upgrading to v0.5

v0.5.0 of the core module, published 2026-10-08, records what a decision needs to be decided again, chains the events, and replays a finished run through a policy. See Re-checking a Run. An upgrade needs more than re-pinning: it adds a schema migration, so every older process and operator binary must be stopped before the first open. It also carries one behaviour change, in the form a policy is handed its arguments.

Both are below, after the versions. They are the reason for the minor bump, as Upgrading to v0.3 was for the lease.

Before the first open

Migration 6 runs on the first OpenStore of the database by v0.5.0. OpenExisting never migrates. It adds tool_specs, the columns steps.spec_hash, steps.policy_id, and events.hash, and it computes the hash of every event already stored, in seq order, in the migration's transaction.

Before that first open, stop every process and operator binary of v0.4.0 or earlier that has the database open, on every host that shares it. The reasons:

  • An older process that keeps writing breaks the chain. It does not know the hash column. Every event it appends afterwards has no hash, and the first such event breaks the chain for agentrt verify. Its steps carry no spec hash either. Nothing in the file can stop it.
  • A v0.4.0 binary refuses a migrated database with ErrSchemaVersion, and OpenExisting of v0.5.0 refuses an unmigrated one, as for any other version. That includes an agentrt operator binary left over from v0.4.0: rebuild it from the same release as the consumer.
  • The migration hashes every stored event once. It takes time in proportion to the log, in one transaction that holds the write lock. A very large database is migrated while nothing else needs it.

Restart the consumer and the operator command on the v0.5.0 build once the migration has run. A database written by v0.4.0 migrates with its runs intact and its events chained (TestStore_V040DatabaseMigratesWithChainedEvents), and the same events give the same hashes on every migration. Pending approvals survive, and a waiting run resumes onto the migrated database, recording the spec it ran with. The migration is the same procedure as v0.3.0's and Releasing says why.

What the migration cannot do is say anything about the past. The hashes of events stored before it say what those events held when it ran, not that nobody changed them earlier. The steps already stored have an empty spec hash and policy identity. See what the chain proves.

The versions

Two nested modules carry new tags, and every module that pins the core now pins v0.5.0 on main.

Module Tag Notes
core, github.com/joeylking/agent-runtime v0.5.0 recorded specs and policy identity, the event chain, Recheck, PolicyJSON, migration 6
proxy proxy/v0.2.0 adds agentrt-proxy recheck and Config.PolicyID; requires core v0.5.0 and mcp/v0.2.1
export/otel export/otel/v0.1.2 carries agentrt.policy.id and agentrt.policy.spec_hash; requires core v0.5.0
mcp unchanged, mcp/v0.2.1 re-pinned to core v0.5.0 on main without a new tag
providers/ollama, providers/anthropic, providers/openai unchanged, v0.2.0 likewise
bench unchanged, bench/v0.1.1 has no requirements at all

Tags are created only where code changed. The tagged mcp and provider modules still pin an older core, and nothing in them depends on what changed, so a consumer that imports one and raises the core to v0.5.0 gets the new core with the old adapter.

go get github.com/joeylking/agent-runtime@v0.5.0
go get github.com/joeylking/agent-runtime/export/otel@v0.1.2
go mod tidy

Only the lines for modules you import are needed; export/otel requires core v0.5.0, so raising it raises the core with it. The proxy is a program, built from a clone of the repository:

(cd proxy && go build -o ~/bin/agentrt-proxy ./cmd/agentrt-proxy)

Build the operator command from the same release, as before, and stop the old one first:

go install github.com/joeylking/agent-runtime/cmd/agentrt@v0.5.0

The proxy's own migrations are separate (proxy_schema_migrations), and a database a proxy uses is migrated by the core's migration 6 like any other. Stop an older agentrt-proxy before the first open too.

The behaviour you could notice: the policy form

A policy is now handed the request's arguments and its tool's input schema in one form, PolicyJSON: object keys sorted, no insignificant whitespace, every number as its literal was written, and strings escaped only as JSON requires. In particular &, <, >, U+2028, and U+2029 stay as they are. This holds on every path: the loop, a gate's Propose and Attach, Resume, and a re-check.

Before, one request reached a policy as different bytes. The loop handed the agent's own bytes and the schema as registered. A resume handed the stored request, compacted and HTML-escaped (& as &). So a policy that matched bytes, denying && in an argument say, could decide one way live and another on resume, and a re-check could not have held it to its record.

  • A policy that unmarshals what it is handed is unaffected. Most are in this group.
  • A policy that matched bytes would notice: one that compared whitespace, key order, or HTML escapes in the arguments or the schema. It now sees one form wherever it is asked (TestPolicy_LoopAndResumeHandedTheSameBytes, TestPolicy_GateProposeAndAttachHandedTheSameBytes). Read the bytes you match against once, in PolicyJSON form.
  • A policy that copies the arguments into its approval's capability or presentation now copies them in that form, as DefaultPolicy does. The stored capability and presentation, and the approval prompt an operator reads, list the arguments' keys sorted. The approval's hash is over the canonical form and is unchanged (TestApprovalHash_UnchangedByThePolicyForm), so an approval pending across the upgrade is still the same approval.

What the agent proposed, the decision as stored, the arguments a tool is called with, and an Allowed verdict carries, approval and content hashes, loop detection, and clock readings are unchanged. Every payload and event type is the same except that run.created and step.policy gain fields. Event has no new field, so traces and the JSON Lines record are byte for byte as before (TestJSONL_RecordIsByteStable). One small change goes with it: the view a policy is handed on resuming a run's first step has Steps nil, as the loop's view has when there are no steps before, instead of an empty slice (TestRecheck_ResumeAtTheFirstStepHasNoSteps).

agentrt.PolicyJSON(raw) returns the form for a policy that wants to compare against it.

Runs recorded before v0.5.0

A run whose decisions were made before v0.5.0 recorded neither the spec the policy was handed nor what it saw of the run. Recheck reports each such evaluation as "not re-checkable", and says so for the run (TestRecheck_PreMigrationRunIsNotRecheckable). That is a statement about the record and not a failure. An evaluation made after the migration, such as the resume of a run that was waiting during the upgrade, is re-checked. The testkit helpers fail on a run with nothing re-checked, so use RecheckPartial for one. See Re-checking a Run.

What does not change

  • The API. Everything added is additive: IdentifiedPolicy, PolicyJSON, Recheck and its report, Store.VerifyEvents, ChainHead, EventHash, ToolSpec, Step.SpecHash, Step.PolicyID, trace.WriteRecheck, the testkit helpers, and export.Follower.Head. A policy without PolicyID records an empty identity.
  • Approvals and recordings. Approval hashes, content hashes, and the replay key are the same, so pending approvals and recorded model replays survive.
  • The loop. The rows, events, order, transaction boundaries, and clock readings are the same (TestDriver_ClockReadingsAreStable).

What you can adopt

None of this is required. A consumer can name its policy by implementing PolicyID() string, so that each decision records which policy made it. It can keep a head from export.Follower.Head, run agentrt verify -head (The Operator Command), and add Scenario.Recheck to its tests (Testing a Consumer).

Next

Re-checking a Run says what is recorded and what a re-check proves. Status and Roadmap lists what v0.5.0 contains with the tests that verify it. Upgrading to v0.4 is the one to read if you are coming from v0.3.

Clone this wiki locally