Skip to content

server: the lost-reply binding: a learn names its execution, base step and engine; a repeat is answered from its receipt, a stale one is fenced - #88

Open
joelteply wants to merge 2 commits into
cormac/shadow-snapshotfrom
cormac/learn-receipts
Open

joelteply wants to merge 2 commits into
cormac/shadow-snapshotfrom
cormac/learn-receipts

Conversation

@joelteply

Copy link
Copy Markdown

An engine step can commit while its reply or the core's task terminal is
lost; a missing completion is not proof the step never ran (Codex on
continuum ggml-org#5695; ONE-RESIDENT-MODEL §9.6). Card 4669d845.

  • Every learn (POST /learn and a completion's learn) names execution
    (the task that owns the step), base_step (the shadow's committed step
    it builds on) and engine (the incarnation it was dispatched to). A
    learn without an execution is refused.
  • The engine mints its incarnation at start and reports it in /props and
    on every learn outcome, with the shadow file the step changed.
  • The fence: a step whose engine is not this one (stale_engine) or whose
    base_step is not the shadow's committed step (stale_base_step, with the
    shadow's step in the reply) is refused. So a delayed duplicate, even
    after its receipt was acknowledged and dropped, or a redispatch racing
    the original under a new execution id, commits at most once per (shadow
    generation, step), with no tombstones kept.
  • Receipts: a committed step's outcome is kept by execution; a learn
    naming it is answered with it (replayed, nothing steps). POST
    /learn/receipt reads one execution's state (committed with its receipt,
    pending behind a busy slot, or unknown to this incarnation); POST
    /learn/receipts/ack releases receipts the core adopted. Unacknowledged
    receipts are bounded (refused as receipts_full at the bound, never
    dropped).
  • /lora-adapters/load takes step: a restored generation (a snapshot)
    resumes its fence at its snapshot's step.
  • A learn outcome reports step whenever the shadow has one: after this
    step, or where it stands when refused stale.

Tests (IntelMac CPU, Qwen2.5-0.5B, fresh servers): learn-every-format.py
--receipts covers replay, a stale base and engine, a missing execution,
committed/unknown receipt reads, acknowledgement, and the delayed
duplicate after acknowledgement fenced (disabling the base_step check
fails it); every existing mode now sends fenced steps (--two-minds,
--snapshot, --shadow-retirement and all ten format x stream cases pass).

Co-Authored-By: Claude Opus 5.5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01FRQWzgSfo79JtnwZywHKrE

…p and engine; a repeat is answered from its receipt, a stale one is fenced

An engine step can commit while its reply or the core's task terminal is
lost; a missing completion is not proof the step never ran (Codex on
continuum ggml-org#5695; ONE-RESIDENT-MODEL §9.6). Card 4669d845.

- Every learn (POST /learn and a completion's learn) names `execution`
  (the task that owns the step), `base_step` (the shadow's committed step
  it builds on) and `engine` (the incarnation it was dispatched to). A
  learn without an execution is refused.
- The engine mints its incarnation at start and reports it in /props and
  on every learn outcome, with the shadow file the step changed.
- The fence: a step whose engine is not this one (stale_engine) or whose
  base_step is not the shadow's committed step (stale_base_step, with the
  shadow's step in the reply) is refused. So a delayed duplicate, even
  after its receipt was acknowledged and dropped, or a redispatch racing
  the original under a new execution id, commits at most once per (shadow
  generation, step), with no tombstones kept.
- Receipts: a committed step's outcome is kept by execution; a learn
  naming it is answered with it (replayed, nothing steps). POST
  /learn/receipt reads one execution's state (committed with its receipt,
  pending behind a busy slot, or unknown to this incarnation); POST
  /learn/receipts/ack releases receipts the core adopted. Unacknowledged
  receipts are bounded (refused as receipts_full at the bound, never
  dropped).
- /lora-adapters/load takes `step`: a restored generation (a snapshot)
  resumes its fence at its snapshot's step.
- A learn outcome reports `step` whenever the shadow has one: after this
  step, or where it stands when refused stale.

Tests (IntelMac CPU, Qwen2.5-0.5B, fresh servers): learn-every-format.py
--receipts covers replay, a stale base and engine, a missing execution,
committed/unknown receipt reads, acknowledgement, and the delayed
duplicate after acknowledgement fenced (disabling the base_step check
fails it); every existing mode now sends fenced steps (--two-minds,
--snapshot, --shadow-retirement and all ten format x stream cases pass).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRQWzgSfo79JtnwZywHKrE
@joelteply
joelteply changed the base branch from master to cormac/shadow-snapshot November 1, 2026 23:32
@github-actions github-actions Bot added the server label Nov 1, 2026
@joelteply

Copy link
Copy Markdown
Author

Source review of 273cc3a, focused on the new receipt boundary:

learn_on_slot returns a cached receipt by execution string BEFORE validating the engine or any request binding. Reusing an execution with a different engine, lora/shadow, input or base step therefore returns an unrelated successful receipt. This is not an idempotent retry. Store the immutable typed execution binding and refuse disagreement before returning replay; the engine-incarnation check must also apply to replay. Tests should replay the exact request successfully, then change each bound field and require refusal with no step.

The implemented binding also has no explicit input revision or shadow/checkpoint generation although ggml-org#5695 requires them. A file path and numeric base_step alone do not identify restored weights. If the never-reused adapter-load ID is intended to be the generation fence, make that contract explicit and validate it on both replay and new dispatch; otherwise add the generation identifier. Prove unload/restore within the same engine cannot adopt an old-generation terminal or execute a delayed old request after receipt ACK. I have not independently run the reported fixtures.

Do not infer never-committed from the receipt endpoint's unknown: ACK deletes entries. Keep the authoritative stale-base/generation disposition separate from lookup absence in the core reconciliation protocol.

…lays only the same step, the shadow generation is explicit, input is bound

- The engine check runs first, so it applies to a replay as well.
- A learn names `input` (the revision it learns, required). A receipt
  stores the step's full binding: engine, generation, base_step, input.
  An execution re-sent with any of them changed is refused
  (execution_rebound) and steps nothing; only the same step replays.
- The shadow generation is its adapter load id, which the server never
  reuses: an unload and restore is a new generation, and a request bound
  to a retired generation finds no loaded adapter and is refused. Every
  outcome reports its generation, base_step and input.
- /learn/receipt's `unknown` is documented as not proof of
  never-committed (acknowledgement deletes receipts); redispatch rests on
  the fence, never on that absence.
- Tests (fresh servers, Qwen2.5-0.5B, IntelMac CPU): --receipts adds the
  same execution re-sent with a changed input, base and generation (each
  refused; disabling the binding check fails it), and unload/restore: the
  old generation's delayed request after acknowledgement is refused, the
  restored generation resumes its fence at its snapshot's step (step 3
  from 2). Every other mode and all ten formats pass with the new field.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRQWzgSfo79JtnwZywHKrE
@joelteply

Copy link
Copy Markdown
Author

Rechecked the binding change at 12a0de7. Engine validation now precedes replay, and a replay checks generation/base/input against the stored receipt. The contract explicitly identifies the non-reused adapter-load ID as the generation; the outcomes carry that identity. This resolves my specific cached-unrelated-success and missing explicit input/generation source findings. It is not a full integration approval or an independently executed test receipt.

For the task adapter I will use the immutable tuple (execution, engine incarnation, adapter-load generation, input revision, base step). Unknown receipt remains non-authoritative; no blind redispatch. Task terminal persistence precedes engine receipt release. Unload/reload creates a different generation even when a file name or base step repeats.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant