Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
f7e14f6
feat(chronicle): RecordJournal — typed records that don't follow bran…
Oct 7, 2026
16a6392
fix(surgery): return once marks are scheduled, not after Discord acce…
Oct 7, 2026
2153e3d
fix(chronicle): a RecordJournal write that fails after reaching the s…
Oct 7, 2026
82a267a
fix(surgery): report unresolved marker bookkeeping as unresolved
Oct 7, 2026
0254342
docs(surgery): describe delivery as started without awaiting, not run…
Oct 7, 2026
287eef2
docs(surgery): say what every receipt outcome guarantees, not 'durabl…
Oct 7, 2026
80c74c8
docs(changelog): state the incident's drain duration as projected, no…
Oct 7, 2026
acd95b6
fix(awareness): new gates wait for running drains, not only pending m…
Oct 7, 2026
8b22d1f
docs(surgery): `none` also covers a disabled awareness outbox
Oct 7, 2026
8b530ee
test(surgery): require both surgeries' reactions before traffic, by e…
Oct 7, 2026
7144970
feat(mcpl): type MCPL request failures by what reached the server
Oct 7, 2026
ca83ee9
Merge commit '7144970' into feat/surgery-marks-opt-in
Oct 7, 2026
2cb501a
feat(awareness): make Discord awareness marks an explicit, one-shot c…
Oct 7, 2026
6bb0c61
docs(awareness): document opt-in, one-shot marks, operator controls a…
Oct 7, 2026
6e9e51b
fix(awareness): bound outside error text before it is journaled, logg…
Oct 7, 2026
f603f3f
Merge commit '2153e3d' (RecordJournal, shared base) into feat/surgery…
Oct 7, 2026
799a16b
refactor(awareness): keep the journal in the store (RecordJournal); f…
Oct 7, 2026
c0d2d90
fix(awareness): a newer opposite intent retires in-flight and unknown…
Oct 7, 2026
d44826b
fix(awareness): authorization order decides opposite requests; import…
Oct 7, 2026
c19d5e6
docs(awareness): authorization order, reserved turn undo, imported ac…
Oct 7, 2026
85481af
fix(awareness): legacy outcomes come from the old writer's lastError,…
Oct 7, 2026
9baaeb1
fix(awareness): a repeated cancel writes nothing and still reports tr…
Oct 7, 2026
511bd52
docs(awareness): legacy evidence names the outcome its error field es…
Oct 7, 2026
7623cf2
fix(awareness): refuse a known target-branch collision before prepari…
Oct 7, 2026
dc2928c
fix(recover): name the CLI's actual --branch flag in the collision error
Oct 7, 2026
5b34745
fix(awareness): a hide or turn undo records its batch after the chang…
Oct 7, 2026
9d17ad6
fix(mcpl): a reconnect holds control traffic as well as data until th…
Oct 7, 2026
25891f0
fix(recover): the CLI records its awareness controls and recoveries i…
Oct 7, 2026
75d7eba
fix(awareness): a turn undo records its marks choice and receipt in t…
Oct 7, 2026
538b136
fix(awareness): stop checkpointing the awareness journal; replay ever…
Oct 7, 2026
a5439e2
fix(mcpl): no other plane opener releases a reconnect's traffic befor…
Oct 7, 2026
485a3c7
fix(recover): refuse --dry-run with an awareness control instead of c…
Oct 7, 2026
4221eb8
feat(framework): runAtSafeBoundary, a store reservation held across a…
Oct 7, 2026
84664d6
feat(framework): a tool call carries who initiated it through every r…
Oct 7, 2026
1d918f0
feat(modules): targeted, receipted, optionally durable ModuleContext.…
Oct 7, 2026
9d25528
feat(modules): Module.getSubconsciousTools offers tools on the subcon…
Oct 7, 2026
b83641d
fix(framework): read the lease and subconscious fields safely in prot…
Oct 7, 2026
b032920
fix(framework): close four gaps in the lease, delivery and origin con…
Oct 7, 2026
e7a1690
fix(framework): atomic creation admission under any store hold; bound…
Oct 7, 2026
a959680
fix(framework): a store reservation also waits for admitted ephemeral…
Oct 7, 2026
204dd88
fix(framework): cleanup() withdraws an unrun ephemeral candidate for …
Oct 7, 2026
955a1b9
feat(framework): operator-change gate and scoped admission for self-c…
Oct 7, 2026
b3410fd
fix+feat(framework): self-change gate review fixes; admitted undo-tur…
Oct 7, 2026
491b0f3
fix(framework): turn-owned apply boundary, retryable undo bookkeeping…
Oct 7, 2026
e824379
feat(framework): one leased application route; planned, journaled uns…
Oct 7, 2026
6c25f83
fix+feat(framework): store-bound resolved changes, five unstick revie…
Oct 7, 2026
679ee66
fix(framework): every queue exit settles its unstick re-run; batch se…
Oct 7, 2026
1ae93fc
feat(awareness): staged publication choices, activated by their chang…
Oct 7, 2026
76d0e9e
feat(framework): operator/changes owns gated body recovery; gated hid…
Oct 7, 2026
987a175
fix(framework): a failed cut is never a commitment; unreadable operat…
Oct 7, 2026
3c333d8
fix+feat(framework): only records or what the process saw decide a cu…
Oct 7, 2026
231e3cd
fix(framework): an operator journal that parses but can't be read as …
Oct 7, 2026
d19a9d3
fix(recover): an offline operator-change resolution is logged like th…
Oct 7, 2026
5d1c5d8
fix(framework,recover): validate operator/changes by what each kind's…
Oct 7, 2026
532146b
docs(framework): recovery comments state the rules the code enforces …
Oct 7, 2026
f167efd
fix(agent): a settings preview names the default pace a paced descent…
Oct 7, 2026
8e89124
fix(framework,presentation): a staged tool-presentation edit records …
Oct 8, 2026
e64e6e6
fix(framework): resume() refuses while the active body is an unresolv…
Oct 8, 2026
f4716ab
fix(framework): the unstick journal reader refuses what its readers c…
Oct 8, 2026
5de55ca
fix(framework): an abandoned destination left active is held until it…
Oct 8, 2026
f03d223
fix(framework): an unstick re-run whose turn ends before its stream s…
Oct 8, 2026
5b9b058
fix(framework): a restoration counts only once the source's context i…
Oct 8, 2026
2206c97
fix(framework): stop() closes admission before its first await and le…
Oct 8, 2026
1f6b78c
fix(framework): a caller-named origin is normalized, so a falsy one i…
Oct 8, 2026
b46db52
fix(framework): a delegation inherits the agent's own origin only for…
Oct 8, 2026
5e9e010
fix(framework): only the framework's own dispatch gives a tool-call e…
Oct 8, 2026
58139d5
fix(framework): trace listeners are notified as the host's, never as …
Oct 8, 2026
e5cf57a
fix(code-execution): only the running exec's own work is served, as w…
Oct 8, 2026
f8a818c
fix(api): while an operator-change gate is configured, the API server…
Oct 8, 2026
97ae95a
feat(operator-changes): undo-turns says what its cut removed, beside …
Oct 8, 2026
551c363
docs(changelog): fragments for #255's own changes, including the scri…
Oct 9, 2026
3934785
docs(operator-changes): say what an unnamed call is, what the gate co…
Oct 11, 2026
01152c7
fix(operator-changes): crash bookkeeping, liveness, admission and rea…
Oct 11, 2026
e1cd85b
test(operator-changes): tests that say what they claim; and an unstic…
Oct 11, 2026
e37235d
fix(operator-changes): a fork parked behind a hold wakes when the hol…
Oct 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 115 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,23 @@ agent-framework-recover \

The specified Discord message is the last message the agent will still see;
everything after it is left on the old branch. The command scans in bounded
windows, never compiles message content, activates
the new Chronicle branch, and writes only Discord `{serverId, channelId,
messageId}` metadata to
`<store>/recovery/discord-awareness-outbox.json`. When the host and that
agent's `discord-mcpl` bot reconnect, each addressable discarded message is
marked with 💤. Delivery state is recorded per message: retryable failures
remain queued, permanent deleted/inaccessible-message failures remain in the
audit ledger without blocking later markers. Portal and other non-Discord
records are ignored.
windows, never compiles message content, and activates the new Chronicle
branch. By default the recovery is local to the resident: nothing is posted to
Discord. To let the people involved see that the agent no longer has their
messages, choose awareness marks explicitly with `--marks addressed` (removed
messages that mentioned or replied to the bot, and DMs) or `--marks all`
(every removed Discord message). The output counts what will be marked and
what stays unmarked, and `--dry-run` shows exactly which addresses each choice
covers. Only `{serverId, channelId, messageId}` metadata is written, to the
awareness journal kept in the store itself, and the marks are delivered when
the host and that agent's `discord-mcpl` bot next connect (see
[Live surgery and Discord awareness marks](#live-surgery-and-discord-awareness-marks)).
If the branch is made but the marks can't be recorded, the output's `markers`
says so and the branch stands. Like a live surgery, the recovery (with its
marks choice and receipt) is recorded in the store's operator log,
`operator-actions.jsonl`, with the OS account that ran it; so is each
`--awareness` cancel, retract and release. A dry run records nothing.
Portal and other non-Discord records are ignored.

When the safe point is an assistant/tool-side record rather than a Discord
message, use its exact ContextManager message ID:
Expand Down Expand Up @@ -202,7 +210,7 @@ agent-framework-recover \
`--suppress-range <first>..<last>` suppresses the inclusive context interval
between two Discord messages, including intervening agent/tool entries. These
removals exist only on the recovery branch; the source branch remains intact.
Suppressed Discord messages are queued for the same 💤 awareness marker.
Suppressed Discord messages follow the same `--marks` choice.
Selections that split a sharded body group or a tool-use/tool-result exchange
are rejected. The suppression plan is journaled before the branch switch; if
the recovery process is interrupted between interval removals, framework
Expand All @@ -212,17 +220,10 @@ For old stores whose messages lack `metadata.serverId`, supply
normal agent host must be stopped while this command has the Chronicle store
open.

The marker sidecar is a retained operation ledger, not a delete-on-success
queue. Switching back to the source branch queues removal of the bot's marker;
returning to the recovery branch queues it again. Initial MCPL events remain
buffered until the ledger has been reconciled. Startup, reconnect, runtime
list-change, and online undo use one framework-global generation: every MCPL
data plane waits while all control planes remain live for registration and
marker service. Awareness marker calls also have a mandatory deadline that is
independent of `requestTimeoutMs` (including when that value is `0`); configure
it with `discordAwarenessDeadlineMs` (default 10000ms, clamped to 50..60000ms).
Configure the online marker with `discordAwarenessEmoji`; the offline CLI
accepts `--emoji`.
With the host stopped, the same command inspects and controls the awareness
journal: `--awareness list`, `--awareness cancel <batch|retract-request>`,
`--awareness retract <batch|all>` and `--awareness release <batch>` behave as
the live controls described below.

### MCPL (MCP Live)

Expand Down Expand Up @@ -324,6 +325,99 @@ servers granted `allowHostCommands`. The HTTP host verbs accept an optional
shared secret (`ApiServerConfig.adminToken`, sent as `x-admin-token`) for
deployments that front the port with a proxy.

### Live surgery and Discord awareness marks

`rollbackToMessage(agent, { messageId })` forks the agent's context at a
message and switches to the fork; `suppressMessages(agent, { messageIds })`
forks at the head and removes the chosen messages on the fork. The source
branch stays intact either way. Removing Discord messages from an agent's
context is local to that agent unless the operator also chooses to mark them:

```typescript
const preview = framework.previewSurgeryMarks('cairn', { rollbackTo: messageId });
// preview.scopes.addressed / .all: count, per-channel counts and refs
const result = await framework.rollbackToMessage('cairn', {
messageId,
marks: { scope: 'addressed', refs: preview.scopes.addressed.refs },
});
// result.markers: { status: 'none' | 'queued' | 'not-scheduled' | 'unresolved', ... }
```

- **`marks`** is `'none'` (the default), or `{ scope: 'addressed' | 'all', refs? }`.
`addressed` covers messages tagged `chat:addressed` (mentions, replies to
the bot, DMs). Passing a preview's `refs` binds the choice to exactly that
set: messages that arrive before the surgery applies are removed locally but
never marked. The choice is recorded in the operator log.
- **The surgery returns once marks are scheduled**, never after Discord has
accepted them. `result.markers` says whether they were scheduled (`queued`
with a count and batch id), not chosen or not in scope (`none`), not to be
delivered because recording the batch failed after the body change and it
was retired or never recorded (`not-scheduled`), or `unresolved` (neither
activation nor retirement could be recorded, or the journal could not be
read back to say whether the batch was; it names a batch that may still be
delivered). Every receipt also counts the removed messages left unmarked.
Marker bookkeeping never fails or undoes an applied rollback or
suppression.
- **Delivery runs in the background** and never holds MCPL traffic or turns.
A route that is not connected keeps its work queued until it connects.
Each reaction call has a mandatory deadline independent of
`requestTimeoutMs` (including `0`): `discordAwarenessDeadlineMs` (default
10000ms, clamped to 50..60000ms). The emoji is `discordAwarenessEmoji`
(offline: `--emoji`).
- **Marks are one-shot.** Switching branches, undo/redo and restarts never add
or remove a mark. The journal is an append-only history of requests and
their attempts, kept as typed records in the Chronicle store: it survives
rollbacks, branch deletion and a killed process, and lives with its store.
Each request is written ahead of its dispatch, and a dispatch is admitted
from the journal as it is at that moment, so a cancel takes effect even
while an earlier request is on the wire. A request written and never
answered is recorded as `unknown`, and a later confirmation of a different
attempt never resolves it. Between opposite requests for one reaction,
the later *authorization* wins, whenever each request was created: a
surgery's adds carry the moment of its marks choice, even when activation
comes later, so a retract made in between prevails; a release is a new
act. A record that certifies a body change (an activation, a completed
suppression, a retirement, or the batch a hide or turn undo records after
its change) is written only after that change is synced.
A batch whose surgery was interrupted before its branch switch was
recorded is held at startup until an operator releases it; an interrupted
suppression's redactions are resumed whenever its branch is active at
startup, whatever its marks' state.
- **Operator controls,** each recorded in the operator log:
- `listDiscordAwareness()` lists batches and retract requests;
- `cancelDiscordAwareness(batch | retractRequest)` stops all further sends
and retries of a batch's marks or a retract's removals, and never removes
or undoes anything. Its receipt counts requests in flight or unknown,
including earlier attempts later answered, any of which may still land;
- `retractDiscordAwareness(batch | 'all')` queues removal of this bot's
reaction, through each address's configured MCPL route, for every message
of the batch (or of every batch not retired by its own surgery, prepared,
held or active, and every message an imported ledger recorded), whatever
history says: removing an absent reaction does nothing. As the latest
authorization for those messages, it stops adds that haven't ended and
any an earlier choice would request later; cancelling it never revives
them. Its receipt discloses earlier requests whose outcome is unknown and
imported history that leaves outcomes unrecorded;
- `releaseDiscordAwareness(batch)` queues a held batch.
- **Over `host/command`** (servers granted `allowHostCommands`): `undo` (by
messages or by turns) and `hide` take `marks: 'none' | 'addressed' | 'all'`
(default `none`; any other value is refused) and return `markers`. Each
holds the store like every surgery, so it is refused while any agent
sharing the store is mid-turn. The `marks` verb takes `action: 'list'
| 'cancel' | 'retract' | 'release'` with a `target` (a batch id; for cancel
also a retract request id; for retract also `all`).

A pre-journal awareness ledger (`discord-awareness-outbox.json` under the
store, or `discordAwarenessOutboxPath`) is imported once on first use and the
file renamed `.migrated-v2`. What it recorded about each message is kept as
evidence (attempt count; the last action and the outcome its error field
establishes; the old writer's delivery status, labelled as the
reconciliation state it was; and how many attempt outcomes it leaves
unrecorded), never as a claim about what is on Discord,
and an imported `active` suppression is not taken as proof that its body
completed: startup verifies and resumes its intervals on its branch. Its
undelivered work is held for an explicit release.

## Observability

Subscribe to trace events for logging, UI updates, or debugging:
Expand Down
10 changes: 10 additions & 0 deletions changelog.d/255-operator-change-gate.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
- **An operator-change gate for imposed body changes** (#255). With `FrameworkConfig.operatorChangeGate`, the framework's own operator mutations are resolved to an absolute change and handed to the host's gate, which stages it and answers with a receipt: accepted and queued, not applied. They are a `host/command` undo (by turns or by messages), hide or unstick, and an operator's or module's mutating `agent_settings` or tool-presentation call. The host applies a staged change only through `applyResolvedOperatorChange`, under a held safe-boundary lease. Without a gate, every operator path behaves as before, except that `resume()` refuses while a cut a gated run left unresolved or abandoned holds the active body (see Breaking).
- Each cut and hide attempt is journaled in the store (`operator/changes`) before it acts, and recovery settles it from that record: a cut whose outcome can't be told waits for an operator's verdict, and a hide, whose effect can be read back, is re-run. An unstick keeps its own journal (`operator/unstick`). `getOperatorChangeRecord` reads a change's record, `resolveOperatorChange` settles an attempt whose outcome can't be told (or `agent-framework-recover`, offline), and `restoreOperatorChangeSource` restores the source of a cut that didn't commit.
- An unstick is a planned, journaled operation: it sheds the newest exchanges a step at a time until a re-run responds.
- An applied undo-turns receipt reports what its cut removed (`messagesRemoved`), and a resolved undo, by turns or by messages, records how many messages followed the cut when it was staged (`messagesAfter`).
- `previewSurgeryMarks` returns the store and branch it previewed (`context: {storeId, branch}`), and `getStoreIdentity()` names the store, so a host can bind a staged surgery to where it was previewed.
- A staged change's Discord awareness marks can be chosen at staging (`prepare({ id, staged: true, … })`), and are activated by the change once its body change is established.
- **`runAtSafeBoundary(opts, fn)`** holds a store reservation across a host callback, with no turn running, for work that must not interleave with one (#255).
- **A tool call carries who initiated it** through every redispatch: the agent itself, the operator (through a puppet call), or the host. So a gate can tell an imposed change from the agent's own (#255).
- **`ModuleContext.addMessage` delivery options** (#255): a targeted, receipted delivery, optionally made durable before its receipt says so.
- **`Module.getSubconsciousTools()`** offers a module's tools on the subconscious's surface (#255).
9 changes: 9 additions & 0 deletions changelog.d/255-operator-change-gate.breaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
- **API server and MCP clients:** while an operator-change gate is configured, the API server refuses `undo`, `redo`, `branch.switch` and `branch.create` with `switchTo` whole, saying why, since it can't stage them (#255). The MCP server's `branch_switch`, and its `branch_create` with `switchTo`, are refused the same way; a `branch_create` without `switchTo` still works. An undo can be staged instead, through the host's gated `host/command` undo; redo and branch switching have no gated route, so under a gate they're refused. Without a gate, nothing changes. Gate or not, `branch.delete` refuses a branch that an operator change not yet settled names as its attempt's source or destination, since its recovery reads that branch.
- **Script authors (`code_execution`):** a script's tool call is served only while the exec that made it is running, as whoever started that exec (#255).
- A task a script leaves running past its exec has its later tool calls refused: its standing ended with its exec. They used to run, unrecorded, as the agent's own. A foreground script's result notes in its stderr how many of the tasks it started were still running when it ended (on Python before 3.12, the tasks created while it ran, which can include ones a leftover task from an earlier exec started).
- A call sent from a thread the script started (as with `run_coroutine_threadsafe`) is refused as untraceable.
- **Module and host authors:** a caller-named origin is normalized (#255). Anything other than absent or `'puppet'` acts for the host, never as the agent's own. Likewise:
- a tool-call event that anything other than the framework's own dispatch queues (a module or the host) acts for the host, unless it names the operator;
- trace listeners are notified as the host's;
- an agent's own call delegating to another agent acts for the host; a puppet's or the host's call keeps its origin through a delegation.
- **Hosts:** `resume()` refuses, force or not, while the active body is the destination of an unresolved or abandoned cut, until it's resolved or its source restored (#255).
1 change: 1 addition & 0 deletions changelog.d/255-stop-admission.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- `stop()` closes admission before its first await: no lease, direct surgery, ephemeral agent or conversation fork enters the store once it has begun. Before tearing anything down it waits for the holder of a store reservation and for agents and conversation forks still being created; live agent streams are cancelled, as before (#255).
14 changes: 14 additions & 0 deletions changelog.d/fix-surgery-marks-receipt.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
- Live `rollbackToMessage` and `suppressMessages` (and message-granular
`host/command` undo, which rides on rollback) return once the body change
has landed, with a marker-scheduling receipt, instead of after every 💤
reaction has been delivered. Previously the store stayed reserved, and the
operator got no answer, for the whole serial drain. At the reported pace
of about one reaction per second, 918 queued marks would take roughly 15
minutes; the household's client timed out after 60 seconds. Delivery now
continues in the background. The result's new `markers` field (`none` |
`queued` | `not-scheduled` | `unresolved`) says whether marks were scheduled, never
that Discord accepted them. If the ledger write after the switch fails, the
rollback that already applied is no longer reported as failed: the batch is
retired (`not-scheduled`), or, if even that write fails, the result says
the batch may still be delivered (`unresolved`). A suppression whose
redactions all committed is no longer undone by that failure.
10 changes: 10 additions & 0 deletions changelog.d/mcpl-reconnect-registration.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
- An MCPL server that reconnects now has its channel registration accepted.
A reconnect resets the server's grant, and its traffic used to reach
admission before the host's new §5.3 policy Request was answered: a
`channels/register` sent right after initialize, as chat bridges do, was
refused (-32002) and never replayed, so the route's incoming messages
stayed unknown channels until the server registered again. A reconnect
now holds control traffic as well as data until the new grant is
established, as initial connect already did, both for a connection that
was open and for one whose first connect had failed, and even when
startup or a quiesce resume opens every connection meanwhile.
10 changes: 10 additions & 0 deletions changelog.d/mcpl-request-outcome.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
- **`McplRequestError`: why an MCPL request produced no result.** A request
to an MCPL server that fails now rejects with `McplRequestError` (exported),
whose `outcome` says which of three things happened: `not-sent` (refused
before anything was written: the connection was already closed),
`error-response` (the server answered with a JSON-RPC error; `code` and the
error's `data` are kept), or `no-response` (the request was handed to the
transport and no answer came back: a timeout, or the connection closed
while awaiting; it may or may not have reached the server). Only
`not-sent` proves the server never saw the request. Messages are unchanged,
and it is still an `Error`, so existing handling keeps working.
34 changes: 34 additions & 0 deletions changelog.d/surgery-marks-controls.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
- Operator controls for awareness marks:
- `previewSurgeryMarks(agent, { rollbackTo } | { suppress })` shows what a
surgery would remove and which messages each scope would mark, per
channel.
- `listDiscordAwareness()` lists the journal's batches and retract
requests.
- `cancelDiscordAwareness(batch | retractRequest)` stops all further sends
and retries of a batch's marks, or of a retract's removals, and never
removes or undoes anything. Its receipt counts requests in flight or
unknown, including earlier attempts later answered, which may still land.
- `retractDiscordAwareness(batch | 'all')` queues removal of this bot's
reaction, through each message's configured MCPL route, for every
selected message, whatever history says. As the latest authorization
for those messages, it stops adds that haven't ended and any an earlier
choice would request later (a prepared surgery's activation), and
cancelling it never revives them. Its receipt discloses requests whose
outcome is unknown and imported history that leaves outcomes
unrecorded.
- `releaseDiscordAwareness(batch)` queues a held batch.

Each control is recorded in the operator log. The same controls are
available as the `host/command` `marks` verb (`action: list | cancel |
retract | release`, `target`) and, with the host stopped, as
`agent-framework-recover --awareness list|cancel|retract|release`.
- A turn-based `host/command` `undo` that undoes a turn records one
`undo-turns` entry in the operator log: the requested turns, the marks
choice (`none` included) and the receipt, beside the per-turn `undo-turn`
entries.
- `agent-framework-recover` records its acts in the store's operator log
(`<store>/operator-actions.jsonl`), done or refused, with the OS account
that ran it: each `--awareness` cancel, retract and release, and each
recovery with its marks choice and receipt (kind `recovery`). A list, and
a recovery's dry run, record nothing. `--dry-run` with `--awareness` is
refused: a control has no preview.
Loading