Durable Instruction Activation
Status: Accepted for implementation, pending integrated review and CI Date: 2026-08-25 Scope: persistent Meerkat sessions and model-visible textual instructionsNeed
Long-lived agents need to adopt reviewed instruction revisions without silently changing the meaning of an earlier transcript. The durable change must be reproducible, auditable, exactly-once under retry, safe while a turn is active, and distinguishable from a controller’s desired assignment. The application remains responsible for authoring instruction content, evaluating it, approving it, and choosing a rollout. This primitive does not hot-swap executable skill resources, tool implementations, hooks, provider configuration, or build composition. Those remain with their existing typed factory and runtime owners and may require respawn or a new lineage.Existing authority is the baseline
Meerkat does not need another chronological delivery mechanism:Sessionowns the ordered transcript.Message::Systemis repeatable and position-sensitive.- provider lowering consumes the transcript order when the resolved model can represent mid-conversation System rows.
- the persistent session service already serializes control work with turn finalization and recovery gates.
- an append admitted while a content turn is active cannot alter that in-flight request.
session/update_system_prompt owns a different semantic. It versions a
replaceable prompt slot through transcript rewrite and projects only the latest
version. Reusing it would silently reinterpret older conversation under a new
prompt, so it is not the instruction-activation authority.
Rejected alternatives
A parallel activation ledger or pending table
Rejected. It would create a second answer to what entered model context and a cross-store commit problem. Desired rollout is application/MobKit state; effective durable context is Meerkat transcript state.Generic append metadata or string conventions
Rejected.source, idempotency strings, or rendered prefixes cannot prove
artifact identity, content digest, supersession, rollback, or stale-writer
rejection. Typed truth must live on the transcript row.
Keyed prompt replacement
Rejected. Latest-only projection erases the chronological transition from model context and changes the interpretation of prior conversation.Cumulative raw instruction bodies
Rejected. Two equal-priority bodies that happen to occur in order do not define supersession. Contradictory revisions would both remain instructions.Fork on every revision
Rejected as the default. A new lineage is valid for destructive reset or a provider that cannot represent the transition, but ordinary compatible agents should retain continuity.Domain fact and model projection
An activation is an ordinary orderedMessage::System row carrying a sealed
typed instruction-activation identity. It is not a new queue, message role, or
session metadata ledger. The canonical row stores one exact, versioned render
envelope. The v1 envelope contains JSON-quoted single-line identity headers, a
blank-line delimiter, then the exact approved UTF-8 body without normalization.
The typed identity stores render_version = 1 and the explicit predecessor in
supersedes. Deserialization re-renders from the typed fields and body and
requires byte equality with SystemMessage.content.
In the origin session, model materialization preserves the row’s role, position,
and already-stored envelope bytes. The origin envelope states:
- the named revision becomes effective from this transcript boundary
- it supersedes the earlier activation for the same namespace and key
- the exact approved instruction body follows
SessionId; it quotes the original envelope as historical
context and states that it is not an activation in the child. This explicit
versioned rendering, rather than ordering folklore, gives the
later body its same-key supersession meaning and makes replay independent of a
future renderer upgrade. Earlier rows remain durable evidence for conversation
that occurred under them. New render versions are additive and permanently
ratcheted; an existing row is never re-rendered under a newer version.
The exact v1 origin envelope uses LF separators, headers in the order below,
canonical JSON string escaping for string values, lowercase digest hex, decimal
body byte length, one empty line, then the body bytes with no added suffix:
[meerkat-inherited-instruction-v1], inserts
child_session_id=<json-string> after origin_session_id, and inserts
status=historical_only_not_active_in_child before body_bytes. These exact
algorithms remain available for every persisted render_version = 1 row.
The sealed identity contains:
InstructionNamespace, an application or issuer namespaceInstructionKeyInstructionRevisionIdInstructionContentDigestInstructionActivationIdsupersedes: Option<InstructionActivationId>origin_session_id, the MeerkatSessionIdthat minted the rowrender_version
sha256:<64 hex digits> over the exact UTF-8
bytes of the approved body. Requests enforce configured body and identifier
bounds. Meerkat verifies the digest before mutation. Within one retained
session lineage, one (namespace, key, revision_id) can bind only one digest
and body. Global cross-session artifact immutability remains application-owned;
Meerkat is not an artifact registry.
The System row’s semantic carriers must be mutually exclusive. An ordinary
append identity, replaceable prompt-version identity, and instruction
activation identity cannot coexist. All constructors, deserialization paths,
head materialization, rewrites, compaction, and activation validate this
closed invariant.
origin_session_id is the lineage fact. Meerkat does not infer a MobKit
generation or invent a second lineage identifier. In the origin session, the
latest activation for a key is effective. In a fork whose SessionId differs,
copied rows are inherited historical context until that child appends an
origin-local activation.
Compatibility, machine admission, and effective truth
An activation receipt means the row is committed to the authoritative persistent session.Applied additionally means the already-materialized
session projection was converged to that committed transcript before the
command completed. No disposition claims that a provider request has already
occurred.
Repository evidence rules out a separate actor-retirement mechanism for the
compatible text path. Every turn rebuilds its model request from
Session::messages_for_model_boundary; a compatible provider does not retain a
second instruction context inside the actor. The existing runtime-turn
finalization boundary already serializes a transcript mutation after any active
turn and before a new one. The activation facade therefore holds that boundary,
asks the generated runtime owner for the current transcript-edit admission
verdict, checks the machine-owned resolved capability surface for the current
materialization, rejects an open realtime channel, and only then calls the
persistent typed mutation seam. Model reconfiguration threads its already
resolved target capability through live apply and rollback; it never re-reads a
mutable registry between generated admission and installation.
This is intentionally not a new MeerkatMachine transition. The machine already
owns the active-work admission fact used here. Adding another generated state
plane would duplicate transcript authority without adding a runtime fact. The
persistent service remains unable to expose activation directly through the
generic control extension; surfaces must use the facade that holds runtime
admission and compatibility.
Admission outcomes are:
- a model without mid-conversation System support returns
UnsupportedCurrentLoweringand appends nothing - a detached, dormant, staged, unknown, or otherwise unmaterialized target
returns
TargetNotMaterializedand appends nothing in v1 - an open live channel returns
LiveChannelOpenand appends nothing; v1 does not claim a close/reopen handoff it has not completed - a staged session returns
TargetNotMaterialized - an ephemeral service returns
DurabilityUnavailable
CAS, idempotency, and rollback
InstructionActivationExpectation is a closed enum:
AbsentEffective(InstructionActivationId)
- An exact retry while that activation is still effective returns
Duplicate. When the caller supplied an external write fence, the retry still enters the target-locked RuntimeStore boundary and revalidates that fence before the receipt is returned; it appends no second activation row. - Reusing an activation ID with another ref or body returns
ActivationIdentityConflict. - Replaying an exact old activation ID after a later activation returns
RecordedButNotEffective; it never proves adoption and never reactivates it. - A stale predecessor returns
EffectiveActivationConflictand appends nothing. - A repeated revision ID with another body or digest returns
ImmutableRevisionConflict.
Persistent and cancellation-safe mutation
OnlyPersistentSessionService implements durable activation. The operation
uses the existing turn-finalization and recovery gates and the existing actor
command path. The session actor derives and appends the exact activation. The
persistent owner then commits that actor state through the configured
WholeBlob or HeadCanonical boundary. There is no detached candidate document,
post-commit actor convergence step, or policy-specific recovery state machine.
The command runs in a detached owner task that retains the turn boundary until
the actor-owned durable commit completes. Transport or caller cancellation can
drop only its waiter, not the commit owner. A persistence failure returns no
receipt and discards the mutated actor through the existing cleanup path, so
that actor cannot admit a later turn or overwrite canonical state. A lost
successful response is recovered by querying canonical activation records
before retry; an exact retry returns Duplicate.
When live support is enabled, activation first acquires the existing
machine-owned live/open lifecycle lease, then the turn-finalization boundary,
and rechecks live-channel absence and generated admission while both are held.
This prevents a new live/open from crossing the actor/store commit and makes an
already-started live/open finish before activation decides admission.
An external composition may supply a generic RuntimeStoreWriteFence through
the explicit fenced activation verb. Meerkat first owns its existing live-open
lease and turn-finalization boundary. The selected RuntimeStore then invokes
the fence synchronously from inside its own target write lock or SQLite
transaction, with the physical target publication itself as the fenced
operation. Applied means that operation ran exactly once. Conflict and
Backoff mean it did not run, so neither durable transcript nor live session
projection advances.
The fence must acquire external lifecycle authority without waiting, reread
its canonical facts, and retain its serialization guard while invoking the
target publication. This avoids lock inversion with an external retirement
path that already owns lifecycle authority and is waiting for Meerkat’s safe
boundary. MobKit uses the seam for stable identity, generation, session, and
fencing-token continuity. Meerkat does not persist or interpret a transient
runtime alias, and the fence remains a precondition on the existing
RuntimeStore commit rather than another activation authority. RuntimeStore
decorators must opt in to the fenced verb explicitly; the default is a typed
unsupported refusal so a decorator cannot silently bypass its projection or
recovery behavior.
Physical store revisions and fencing tokens remain server-internal. A public
command receipt binds session ID, lineage, full activation identity,
predecessor, a stable activation ordinal, the current transcript projection
witness, and the event’s typed disposition. Persistence introspection derives
that record from the RuntimeStore-backed transcript, never from a receipt
cache. It does not reconstruct the historical live-projection disposition.
Current rollout observation may join that durable record with runtime
availability, but it may never replace the record with a cache.
Rewrite, compaction, restore, and fork
Activation rows are immutable transcript-boundary anchors.- Ordinary append and prompt-update APIs cannot mint or adopt them.
- Generic same-session rewrite and prompt update require exact equality of every activation row at its transcript index; they cannot mint, alter, move, erase, or restore an activation across conversational content.
- Same-session restore requires every activation row to remain byte-for-byte equal at its exact transcript index. It cannot move an activation across conversational content or restore an erased activation boundary.
- Deserialization validates current rows and every retained revision body.
- Fork-at may copy only exact activation rows from the selected source prefix. Generic fork replacement rejects every activation-bearing replacement, so it cannot forge inherited or effective authority.
- In a child session, copied rows render with a permanently versioned
historical-only envelope. A child with inherited rows cannot start a model
turn until it has appended an explicit origin-local activation for every
inherited effective key. The child activation may name the inherited row in
supersedeswhile its CAS expectation remainsAbsentfor the child origin.
Typed public contract
All identifiers are non-empty trimmed UTF-8, at most 128 bytes, and reject control characters. Instruction bodies are at most 256 KiB. Namespace naming does not introduce an authorization system: the application remains responsible for exposing the activation command only through its governed approval workflow, while Meerkat authenticates and authorizes the containing surface according to that surface’s existing rules.InstructionActivationRequest contains:
- namespace, key, revision ID, declared digest, and exact body
- activation ID
- closed CAS expectation
Absent | Effective(id) - explicit
supersedes, which must equal the observed previous same-key activation when present but is not the CAS carrier
InstructionActivationReceipt contains session ID, origin session ID, full
activation identity, predecessor activation ID, the stable zero-based ordinal
among activation rows, the current projection witness, and Applied | Duplicate. Identity and ordinal are stable across compaction. The projection
witness contains the current zero-based message index and current prefix
revision, so it can change when compaction summarizes earlier conversation.
Applied proves the actor-owned materialized session state was committed by
the lifecycle/persistence owner.
InstructionActivationRecord is persistence-only and contains the durable
identity, origin, predecessor, stable activation ordinal, and current
projection witness.
InstructionActivationReadQuery accepts optional namespace/key filters and a
bounded offset with limit from 1 through 200.
InstructionActivationReadPage returns durable records in transcript order
and an optional next offset. When the query names one exact namespace and key,
it also returns core-derived InstructionActivationKeyState: the effective
origin-local record, chronological head, child explicit-activation
requirement, and exact expectation/supersedes values for the next request.
MobKit consumes this state instead of reimplementing transcript or fork
semantics.
Stable InstructionActivationErrorCode variants are:
invalid_requestdigest_mismatcheffective_activation_conflictactivation_identity_conflictimmutable_revision_conflictrecorded_but_not_effectiveinherited_activation_requires_explicit_activationmalformed_activation_history
InstructionActivationAdmissionErrorCode variants are:
target_not_materializedunsupported_current_loweringlive_channel_opendurability_unavailablesession_busyexternal_write_fence_conflictexternal_write_fence_backoff
InstructionNamespace,InstructionKey,InstructionRevisionIdInstructionContentDigest,InstructionRevisionRefInstructionActivationId,InstructionActivationIdentityInstructionActivationExpectation,InstructionActivationRequestInstructionActivationDisposition,InstructionActivationReceiptInstructionActivationRecordInstructionActivationErrorwith stable error codes for every refusal above
MeerkatSessionRuntime::activate_instructionMeerkatSessionRuntime::activate_instruction_with_write_fenceMeerkatSessionRuntime::read_instruction_activationsSessionServiceHistoryExt::read_instruction_activation_recordsas the persistence-only internal/read-model seam
- JSON-RPC
session/activate_instructionandsession/instruction_activations - generated Rust wire and JSON Schema contracts
- model catalog capability
supports_mid_conversation_system_messages
Ownership and failure table
Release binding and delivery
The reviewed durable activation authority is integrated into the release authority’s exact qualified realtime Meerkat head for the 0.8.30 candidate. Its manifest and lockfile both resolve the publicoai-rt-rs 0.4.1 release,
tagged from exact commit c051ee75479aaf71a8f1fd5cef2ea2bbe90f15e1 and
independently verified from crates.io with SHA-256
fe8d87027059933028b385e8ea9059d452cad805c632531ed3c20314492b6aa8.
Until the candidate is merged, tagged, and published, this exact integration
commit remains development Git truth rather than a released package.
MobKit binds its Meerkat dependency to the exact origin-reachable integration
commit, not to a package release or movable branch, and preserves the same
locked Realtime release. Cross-repository CI consequently tests the development
graph it claims to review.
After the integrated gate, the release authority owns BuildBuddy Turbo S
e2e-smoke diagnosis, repair, rerun, and green evidence on that exact Meerkat
commit. The exact Meerkat/MobKit pair must then pass isolated no-deploy OB3 and
HomeCore qualification before the Meerkat lead may publish 0.8.30. This feature
lane does not independently merge, tag, or publish either repository.
Dogma check
- The ordered transcript remains the sole model-context authority.
- No new activation machine, queue, or ledger is introduced.
- RuntimeStore alone proves physical currentness.
- Provider capability and live state fail closed before mutation.
- Typed core code owns semantics; surfaces stay thin.
- Global policy governance stays outside Meerkat.
- Durable receipts are unavailable where durability cannot be proved.
