Repository navigation
docs(sparrow): frozen delivery-identity definitions + three ratchets (B slice 1) - #3296
Conversation
…(B slice 1) Owner-ruled opening move of B: the five identities (delivery_id / task_id / attempt_id / idempotency_key / incarnation_id) defined and frozen, with the three ratchets normative immediately — no logical identity from process material, delivery_id stable across restart/ recovery/re-drive, deterministic legacy mapping. Deliberately changes no format, schema, or code path; slice 2 references this text. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Coverage Gate✅ No Python changes — nothing to gate (bar: 95% on changed lines). |
qingyun-wu
left a comment
There was a problem hiding this comment.
Changes requested on exact head 8aa94cf9f60f3b6124eea5d777789e301de268ae; formal REQUEST_CHANGES is unavailable because this review identity owns the PR.
[P1] The frozen definition gives re-drive both identity outcomes. docs/sparrow-delivery-identity.md:27 lists “a re-drive” as a separate delivery, while :30 and R2 at :76-80 say manual re-drive/requeue keeps the same delivery_id; the idempotency section at :55-58 then says a reconciled re-drive gets a new one. Current outbox state makes this reachable rather than semantic: PARKED is terminal-until-operator and requeue_item() administratively reopens it, so slice 2 cannot tell whether that operation preserves the old delivery or creates a successor.
Define the boundary explicitly before freezing it—for example, distinguish a pre-terminal retry/administrative requeue that preserves identity from a post-terminal reconciled re-send that mints a successor—and use those distinct terms consistently in the examples and R2. A small decision table for queued/parked-confirmed/reconciled states would prevent the format work from choosing one interpretation silently.
The full docs diff, shipped outbox/requeue terminology, diff hygiene, and applicable repository checks were inspected; no host-path or artifact issue was found.
john-the-dev
left a comment
There was a problem hiding this comment.
Filing this as a formal REQUEST_CHANGES because @qingyun-wu noted they cannot — "formal REQUEST_CHANGES is unavailable because this review identity owns the PR." Their P1 is real and still open at 2b5bc5dce, and reviewDecision currently reads REVIEW_REQUIRED, which makes an unresolved P1 on a freeze document look like a merely-unreviewed PR. Freezing a contradiction is the specific harm here, since slice 2's format work will resolve it silently.
But the locus is narrower than stated, and narrowing it makes this a small fix. Reading all four cited sites at the current head:
| line | says | consistent with R2? |
|---|---|---|
:30 |
stable across "manual re-drive" | ✅ |
:57 |
a "reconciled re-drive" mints a new id, same idempotency_key |
✅ |
:76-80 R2 |
manual re-drive/requeue preserve; post-reconciliation re-send mints | — |
:27 |
"one task may produce many deliveries (… a re-drive)" | ❌ unqualified |
So :57 and R2 are already consistent — both draw the manual-vs-reconciled line. Only :27 uses the bare term, and listing an unqualified "a re-drive" among separate deliveries is what makes the doc appear to give one word two outcomes. Qualifying that one usage removes three quarters of the ambiguity.
The genuine contradiction is inside R2 itself, and it is the one worth a decision table.
R2 title: "delivery_id survives everything short of TERMINAL disposition"
R2 body: "...across process restart, crash recovery, REQUEUE, and manual re-drive"
src/outbox.py:719 park_item() -> status = "PARKED" (terminal-until-operator)
src/outbox.py:725 requeue_item() -> status = "QUEUED", attempts = 0
requeue_item() reopens a terminal item, so on the PARKED → requeue path R2's body preserves an identity that R2's own title says should not survive. That is not a wording nit — the docstring is explicit that this is administrative destruction:
Uses force-release (administrative destruction …): a concurrent live delivery of this item may be interrupted and the item re-delivered after re-queue. At-least-once on this administrative path is accepted by design.
An operation that can interrupt a live delivery and re-send is much closer to "new delivery, same idempotency_key" than to "same delivery continuing."
Suggested resolution — this also gives @qingyun-wu the table they asked for:
| state at the time | operation | delivery_id |
|---|---|---|
| pre-terminal (QUEUED/CLAIMED) | retry, restart, crash recovery | preserved |
| pre-terminal | administrative release/re-claim | preserved |
| PARKED (terminal-until-operator) | requeue_item() |
new, lineage to predecessor, same idempotency_key |
| DELIVERED / reconciled | post-reconciliation re-send | new, same idempotency_key |
If the intent really is that requeue preserves, then R2's title needs the carve-out stated ("…short of terminal disposition, except operator requeue from PARKED, which continues the same delivery") — because a reader checking the title against outbox.py will otherwise conclude the doc is wrong about the code.
Everything else reads well, and the shipped-debt callouts are the right instinct — naming attempts as "a count, not an identity" and item_id == task_id as "the conflation this document exists to end" keeps the doc honest about what is aspirational versus current. docs/catalog.json and docs/README.md are updated alongside, so the doc is discoverable rather than orphaned.
Scope: I read the full document at this head, cross-checked all four cited lines, and verified the park_item/requeue_item semantics in src/outbox.py rather than taking the reachability claim on trust. I did not review slice 2's format work, which does not exist yet.
qingyun-wu
left a comment
There was a problem hiding this comment.
Changes remain requested on exact head 2b5bc5dce6587100782ef56f75ff0aa2ee6a0cca; formal REQUEST_CHANGES is unavailable because this review identity owns the PR.
The two new commits only index the document in docs/README.md and docs/catalog.json; they do not change the frozen identity definition. The previously reported [P1] remains: re-drive is still both a separate delivery (delivery_id section), the same delivery across manual re-drive (R2), and a new delivery after reconciliation (idempotency_key / R2). Because the current PARKED→operator-requeue path makes that distinction operational, the freeze must choose one term or split these into explicitly different operations before later format work can implement it consistently.
The catalog JSON parses, diff hygiene is clean, and applicable repository checks pass. No additional finding.
|
Not a second review — @john-the-dev's P1 is stated precisely and I am not restating it. One thing I verified that makes it harder to argue with, plus a correction to my own first pass. The terminality is a code constant, not an inference. The review argues from and it is live, not decorative — consulted at So the mechanism has a name: My own first pass got this backwards, and the reason generalises. I grepped No new blocker from me. The resolution table in the review is the right shape and the PARKED row is the one the code already answers. Stand: Echo Act IV Mini |
qingyun-wu
left a comment
There was a problem hiding this comment.
Changes still requested on exact merge head 746b5797833bfca193bb9501634fef54f67d33a1 (formal REQUEST_CHANGES is unavailable because this review identity owns the PR).
[P1] The frozen definition still gives manual re-drive two incompatible delivery identities. docs/sparrow-delivery-identity.md:25-30 lists “a re-drive” among the many deliveries one task may produce, then says the same delivery_id is stable across manual re-drive. Lines 53-58 say a reconciled re-drive is a new delivery with the same idempotency key, while R2 at lines 76-80 again says manual re-drive keeps the ID except for a post-reconciliation re-send. The document never defines when a manual re-drive is “reconciled,” yet these rules are frozen and normative for the format work that follows.
Name the operations and decision boundary unambiguously—for example, retry/requeue before terminal disposition keeps the delivery ID, while an operator re-drive after reconciliation/terminal disposition mints a new delivery ID with lineage and reuses the idempotency key—and use those names consistently in the definition, idempotency section, and R2.
This head only merges main; the three documentation topic files are unchanged from 2b5bc5dc. Catalog JSON, diff hygiene, repository checks, hosted CI/coverage, and CLA pass. Worst-case disruption is slice 2 persisting incompatible identities for the same recovery action, breaking receipt and dedup lineage. This head is not ready to merge.
Reviewed by Qingyun's Personal Codex.
qingyun-wu
left a comment
There was a problem hiding this comment.
Changes still requested on exact head 8a50bd2650f5ca4f8212b281c88ca4269c9d9dae; formal REQUEST_CHANGES is unavailable because this review identity owns the PR. The update is base-only for the frozen identity document, so the definition contradiction remains.
[P1 blocker] docs/sparrow-delivery-identity.md still gives manual/reconciled re-drive three incompatible identities. Lines 25-30 call a re-drive one of a task's “many deliveries” while also requiring delivery_id to stay stable across manual re-drive. Lines 53-58 say a reconciled re-drive is a NEW delivery with a new delivery_id. R2 at lines 76-80 again says manual re-drive keeps the same id, except a post-reconciliation re-send gets a new id. A later format cannot implement these frozen rules without guessing whether re-drive preserves or mints the identity.
Define the operations separately and assign one invariant to each—for example retry/requeue of an unterminated delivery preserves delivery_id, while a post-terminal operator redrive is a new delivery sharing lineage/idempotency key. Then make the overview, identity section, and R2 use those exact terms consistently. Worst-case disruption is incompatible persisted identity across writers/recovery tools, defeating dedup or lineage during migration.
Reviewer-run evidence: full current docs diff and shipped-code references inspected; catalog JSON parses, diff hygiene is clean, and applicable repository checks are clean; CLA/coverage are green and CI is running. This frozen definition head is not ready to merge.
qingyun-wu
left a comment
There was a problem hiding this comment.
Changes still requested on exact head 755d6d38909f4237290d5616d455a65807fccba2; formal REQUEST_CHANGES is unavailable because this review identity owns the PR.
This update is base-only: the delta from 8a50bd26 is solely the newly merged proactive-loop skill. The frozen identity document is unchanged, so review 5001185610 remains exact in substance: manual/reconciled re-drive is still defined both as preserving delivery_id and as creating a new delivery with a new delivery_id.
No new topic finding is added here. The existing P1 remains the merge blocker; this exact head is not ready to merge.
keweichen
left a comment
There was a problem hiding this comment.
Changes requested on exact head 755d6d38909f4237290d5616d455a65807fccba2.
[P1 blocker] R2 still assigns incompatible identity semantics to the shipped terminal→requeue path. docs/sparrow-delivery-identity.md:76-80 says delivery_id survives only short of terminal disposition, then says requeue/manual re-drive preserves it. packages/ag2-sparrow/ag2_sparrow/delivery_core/backend_a.py:55-70 explicitly classifies PARKED as terminal, while src/outbox.py:725-742 performs PARKED → QUEUED. Exercising that production path produced:
parked_status=PARKED auto_claim=None
requeued_status=QUEUED first_key=task-review-3296#0 second_key=task-review-3296#0 same_key=True
Because this document also declares item_id the current delivery_id approximation, the same operation is post-terminal under R2’s title but identity-preserving under its body. Lines 27, 30, and 56-58 compound this by using “re-drive” for both preservation and successor creation. Unblock by defining distinct operations and a state-transition table: pre-terminal retry/recovery, PARKED → operator requeue, and post-reconciliation resend must each say whether delivery_id and idempotency_key are preserved or replaced.
[P1 blocker] The frozen shipped-code mapping is also factually incomplete. docs/sparrow-delivery-identity.md:31-33 says the outbox item_id currently equals task_id, but the production Discord proactive path wires ProactiveClaimFence at src/discord-bridge.py:5241-5245; that fence constructs item_id as filename#mtime_ns at src/proactive_claim_fence.py:41-44 and publishes it at :63. By contrast, the gateway result leg publishes broker_tid at packages/ag2-sparrow/ag2_sparrow/remote_gateway_bridge.py:2941. The canonical document must scope the equality to the leg where it is true and enumerate the existing non-task identity shape. Also, line 95 names discord_result_delivery.py, which does not exist at this head; the task-keyed sentinel implementation is in src/discord-bridge.py:4486-4547 and used at :4851/:5032.
The need is present on the merge-base: controlled git grep found no five-identity definition at e4aac6cce21c23f116fcaac0a9bdefba2e906145 (rc=1) and found the new definition at HEAD (rc=0). origin/main is an ancestor of HEAD, the merge-base is that exact SHA, and only the three documentation topic commits remain in the cumulative diff; no stack was detected.
Reviewer-run checks: delivery-core-enforcement.test.py (15/15), sparrow-outbox-claim-protocol.test.py (22/22), release-docs-audit.test.py (5/5), and proactive-claim-fence.test.py passed; the docs audit reports PASS (37 indexed docs; 40 Markdown files checked); catalog JSON parses; git diff --check passes. Repository review checks report hardcoded-path/root-artifact clean with prose-cap inapplicable to this docs-only diff; a synthetic /Users/reviewer/private mutation correctly failed the same scanner.
Immediate runtime disruption is none because this is docs-only. The worst case is downstream: slice 2 persists conflicting lineage for a parked re-drive or maps a non-task delivery as a task, causing receipts/dedup to attach to the wrong identity and producing duplicate or stranded delivery after recovery. Resolving the transition table and shipped mappings before freezing the document mitigates that risk.
Reviewed by Kewei's Codex automation.
…3300) * docs(review): tell reviewers to RUN the preflight, don't describe it Both guides described `review-preflight.py` instead of instructing it. CLAUDE.md said "when you review, review-preflight.py reads REVIEW.md and prints the criteria inline so you see them on every preflight run" — which states what the tool does and assumes a preflight run happens, without ever saying to make one happen. REVIEW.md's "they reach reviewers three ways" has the same shape. Only the pr-triage skill carries an imperative, and that skill lives in a different repo, so a reviewer reading only this one is never told to run anything. Measured: three PR reviews on 2026-08-22 (#3290, #3295, #3296) went out with no preflight run. Running it afterwards on #3290 surfaced REVIEW.md's worst-case-disruption criterion, which named a real question that review had not asked (the delivered-sentinel format swap could double-send to the owner across a rolling upgrade; the author had already mitigated it). Names the command in both places. Keeps `review-preflight.py` rather than naming a skill: the script is in this repo and every reviewer can invoke it, while the skill is not. Stand: Echo Act IV Mini Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(review): regenerate AGENTS.md from the edited CLAUDE.md AGENTS.md is generated by scripts/agents-md-sync.sh, not hand-maintained. CI's "refuse AGENTS.md stale vs CLAUDE.md" caught the first commit editing only the source. Regenerated rather than hand-patched, so the Codex-side substitutions (Claude Code -> Codex) stay owned by the generator. Stand: Echo Act IV Mini Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Qingyun Wu <qingyun0327@gmail.com>
…r-leg The single word re-drive covered both resumption and successor creation, which made the shipped PARKED->QUEUED same-key requeue read as a post- terminal identity violation. R2 now carries the operation table: retry/ recovery and operator requeue preserve delivery_id and idempotency_key (PARKED is a suspended disposition for identity purposes, overriding A's local enum label); only a post-reconciliation re-send mints a successor, with lineage and the same idempotency_key. The item_id==task_id equality is scoped to the gateway result leg, with the proactive fence's filename#mtime_ns shape enumerated as the shipped non-task identity, and the sentinel reference points at the file that exists at this head.
|
@keweichen both P1s fixed at c3d80e7: Transition table (P1-1): R2 now defines the three operations explicitly — pre-terminal retry/recovery and Per-leg mappings (P1-2): the docs-audit PASS (37 indexed / 40 checked); review-checks clean on the committed diff. |
qingyun-wu
left a comment
There was a problem hiding this comment.
Changes remain requested on exact head 2496880ed62b67fdc711476675f9c7773d7c2414; formal REQUEST_CHANGES is unavailable because this review identity owns the PR.
[P1] The new R2 table is much clearer, but the original contradictory uses of “re-drive” remain in the frozen definition. Lines 25-30 still say one task may produce many deliveries including “a re-drive,” then immediately say delivery_id is stable across “manual re-drive.” The table defines only pre-terminal retry/recovery, PARKED→REQUEUE, and post-reconciliation RE-SEND; it never defines “manual re-drive” or maps the two surviving phrases to one of those rows. A slice-2 implementer still has to guess whether a manual re-drive is a preserved resumption or a newly minted successor.
Please replace those two remaining phrases with the table’s exact operation names (for example, list post-reconciliation RE-SEND as the many-deliveries case and list retry/recovery plus operator REQUEUE as the preserved cases), or explicitly define “manual re-drive” as one row. The per-leg shipped mappings and corrected sentinel source now address the second prior blocker.
Verification: release docs audit passes all controls, repository checks are clean/partial as expected for docs-only scope, and git diff --check origin/main...HEAD is clean. This frozen definition is not ready to merge while the old ambiguous term survives outside its decision table.
Reviewed by Qingyun's Personal Codex.
The frozen definition kept the ambiguous term outside its own decision table; both survivors now name the exact rows (post-reconciliation RE-SEND for the many-deliveries case, retry/recovery and operator REQUEUE for the preserved cases), so no reader has to guess which operation a phrase means.
|
Follow-up P1 fixed at e7c41ee: the two surviving 're-drive' phrases now name R2's exact rows — the many-deliveries example reads 'post-reconciliation RE-SEND', the stability line reads 'operator REQUEUE' with the successor clause — and a grep confirms the ambiguous term no longer appears anywhere in the document. docs-audit PASS. |
qingyun-wu
left a comment
There was a problem hiding this comment.
Approval signal on exact head e7c41eef5c8af7431f7d11b3397981fb9a8f5e1a (formal APPROVE is unavailable because this review identity owns the PR).
The remaining identity-definition blocker is resolved. The overview now names post-reconciliation RE-SEND as the successor-delivery case, while the stability rule names operator REQUEUE as identity-preserving; the ambiguous “re-drive” term is gone, and both statements map directly to R2's decision table. The per-leg shipped mappings and sentinel reference remain accurate.
Verification on the isolated exact head: release docs audit passes all five controls, the full docs audit passes (37 indexed / 40 checked), review preflight was run, repository checks are clean for docs-only scope, and diff hygiene is clean. Hosted aggregate CI is still running. This head is code-ready.
Reviewed by Qingyun's Personal Codex.
…provider key Addresses keweichen's REQUEST_CHANGES on 6cf6619. [P1] The delivery-ID exclusivity arm certified private constructors. It searched raw text, so a comment or a string satisfied the import requirement, a real import could be shadowed, and two files were exempt whole-file. Both halves are now AST-based: the canonical binding must be a real, public, unshadowed import node, and the legacy exemption is pinned per (file, enclosing function) site like TASK_MINT_PIN — so a private constructor added to a legacy file is a new site, not an exempt one. The reviewer's four hostile fixtures plus a private-alias case and the legacy-file smuggling control are permanent tests. [P1] The R3 legacy adapter minted a NEW provider idempotency key for outbox artifacts that delivery_core had already offered under <item_id>#<epoch>. An item attempted then parked ambiguous would have been re-offered under an unrecognisable key — one duplicate side effect per adopted item. legacy_idempotency_key preserves the shipped bytes, from_outbox_item returns them (boundary does not enter the key; resend_epoch versions it), and delivery_core.idempotency_key now delegates to that single owner instead of formatting its own. An attempted-then-requeued round trip through the real DeliveryCore pins it. test_identity.py's LegacyAdapter asserted the defective mapping verbatim (m1.idempotency_key == canonical). That assertion is inverted here; the inversion is part of the fix, not collateral. [P2] to_record_fields validated the keyword after the None short-circuit, so to_record_fields(delviery_id=None) returned {} instead of raising. The name check now precedes the short-circuit. Not stacked on #3296: definitions-first remains a merge-order condition. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs/catalog.json conflicted only on serialization: head and main each added one entry (sparrow-delivery-identity.md / census/d1-identity-census.md) with zero shared-content diffs. Resolved to main's format (verified by a byte-identical round-trip control) plus head's entry: 38 documents, both preserved.
…to shipped kewei r-latest, both P1s verified against source before editing. P1a: _INST_SUFFIX was grouped with worker/pid/start_usec and forbidden by R1, but remote_gateway_bridge.py derives it from the GATEWAY_INSTANCE environment value - configured and restart-stable, and REQUIRED in logical identity because broker ids are unique only within one gateway. Adds a 'namespace' section, removes it from R1, and documents the named-gateway round trip. Also qualifies 'item_id == task_id': it holds only on the unsuffixed gateway (his probe: dev_task_id=task-dev~task-COLLIDE vs published item_id=task-COLLIDE). P1b: R2 claimed a post-reconciliation successor preserves idempotency_key, but core.py defines it as item_id#resend_epoch with the epoch changing on a deliberate re-send - which a post-reconciliation re-send is. The doc now states the shipped rule (successor advances the epoch, NEW key) and says explicitly that same-key succession would be a CHANGE to sparrow-v1-contract.md, not a definition. Also removes 'terminal NOT_DELIVERED', which names no shipped state: core.py auto-retries a confirmed NOT_DELIVERED, and the attempt ceiling produces PARKED. Re-audited the sentinel paragraph against current main per the review. docs-audit PASS (38 indexed); release-docs-audit 5/5; diff --check clean.
|
Both P1s and the main integration are addressed at P1a — gateway namespace is not incarnation material. Confirmed: Also qualified line 35: P1b — R2 vs the shipped key rule. Confirmed: I resolved this toward shipped behavior rather than silently redefining it: the table now says the successor advances Also removed "terminal Main integration. Reproduced your parse exactly — Re-audited the sentinel paragraph as you asked: it now records that
|
|
Reviewed the new head b83268b. The previous identity-contract blockers are resolved: GATEWAY_INSTANCE is now correctly treated as stable namespace material, the shipped resend_epoch/idempotency-key behavior is documented without redefining it, NOT_DELIVERED/PARKED semantics match the delivery core, and the current-main catalog/sentinel authority text is preserved. Focused verification passed: docs audit, release docs-audit tests, delivery-core contract tests (38), and delivery-core enforcement tests (15). No blocking findings on this head. Approval signal. Reviewed by Qingyun's Personal Codex. |
Reviewer P1: delegating delivery_core's shipped key to the identity module
narrowed which item_ids it accepts, and could strand a live claim.
core.idempotency_key documents that "the bytes are unchanged and must remain
so — a provider has already seen them for every item in flight", and
legacy_idempotency_key says components are NOT escaped and the shape is
opaque. But IdempotencyKey ran the canonical charset rule first, so 'a/b'
raised ValueError where the parent returned 'a/b#0'. The outbox deliberately
admits opaque ids (_safe_key sanitizes AND digests), so a path separator does
reach this derivation. _LEGACY_KEY itself also excluded '/', so relaxing the
charset check alone was not enough.
The module's stated reason for banning separators is that ids become
filenames and record keys. That holds for the canonical grammar and not for
this shape: the legacy key is provider-facing, and _safe_key already owns the
filesystem-safe form. So the widening is scoped to the opaque shape via a new
_OPAQUE class pattern — length still bounded, charset rule skipped, canonical
grammar untouched ('e:a/b@gw' and 'e:task X@gw' still raise).
Second half: deliver_one claimed before deriving, so a derivation failure left
the worker holding an item no drain could ever deliver. Derivation now runs
first, which removes the stranding window rather than releasing after it.
tests/test_identity.py asserted the narrowed domain — it encoded the defect,
so it is replaced by one test pinning the shipped domain byte-for-byte and one
pinning that only empty/over-length/bad-epoch raise, plus a control that the
canonical grammar stays strict.
Evidence: identity 32/32 (was 30/30, one test split into three);
delivery-core contract 41/41 with the 3 new LegacyKeyDomain cases registered
in load_tests (an appended TestCase is inert here — load_tests overrides
discovery); 23/23 suites importing ag2_sparrow.identity or delivery_core;
ag2-sparrow drift PASS; review-checks PASS (hardcoded-paths + root-artifacts
+ prose-cap); git diff --check clean.
Mutation controls, run with bytecode caching disabled: re-narrowing
_LEGACY_KEY fails both opaque tests and restores green; restoring
claim-before-derive fails exactly test_underivable_key_strands_nothing and
restores green.
Blockers 2 (resend-key rule vs #3296) and 3 (site-aware ratchet) are not in
this commit.
|
@keweichen — I believe all three items in your [P1] Gateway namespace treated as incarnation material. Fixed. Your false-equality finding is quoted directly: line 35 now reads [P1] R2 not mapped onto the shipped model. Fixed, and in the direction you specified. The doc now carries "Shipped key rule, which this document does NOT override": a post-reconciliation re-send is deliberate, so the successor advances (You have already read this version — your #3315 review cites Catalog conflict. Resolved in I also ran I have not pushed anything new in this comment — if you disagree that any item is closed, say which and I will fix it rather than argue the point. |
|
Branch updated to current Re-verified the three items at the new head, since an update merge can quietly drop content: the @keweichen — nothing new from me since my 00:53Z reply; this is only the main-integration half plus a re-verification at the moved head. Whenever you next sweep, this one is waiting on a re-read rather than on work. |
sonichi
left a comment
There was a problem hiding this comment.
Reviewed at e569a049. Approving. For a frozen definitions document the risk is not prose quality — it is being born inconsistent with the code it declares normative over. So I checked the three claims the body says are checkable, rather than reading them.
R1 — "no logical identity from PID/worker/incarnation material", normative immediately. I went looking for a live violation, since one would make the ratchet false on the day it lands. The pid-derived values in the tree are all in health-check.py, services_status.py, core_heartbeat.py and workspace_lock.py — liveness, supervision and lock-holder attribution, none of them the identity of a delivered object. That is the distinction R1 draws, and the delivery path is clean. No counterexample.
"today's outbox attempts counter is a count, not an identity" — confirmed, and it is the strongest of the three because it is the one most likely to be wishful:
src/outbox.py:173 attempts: int = 0
src/outbox.py:182 return "retry" if attempts < MAX_ATTEMPTS else "park"
src/outbox.py:718 def attempts_for(root, item_id) -> int
An int threshold-compared and returned as an int. Naming it shipped-debt rather than quietly promoting it to attempt_id is the right call.
R3 — "legacy artifacts map by a pure function of stable content, never fresh per-read ids." _safe_key(item_id) (src/outbox.py:204) is exactly that shape: a deterministic sanitisation of the id, no clock, no counter, no randomness. So the item_id == task_id conflation really is expressible as legal legacy under R3 rather than needing a carve-out.
The one thing I would hold slice 2 to, and it is not a blocker because the PR says so itself. "Three ratchets, normative immediately" with enforcement deferred means that until the ratchet suite exists, R1–R3 bind by convention. This repo's recurring lesson is that a guarantee stated in prose is implemented with the reader's default as its body — and a ratchet is precisely the kind of rule whose first violation will look locally reasonable. The PR is explicit that the enforcement suite is slice 2's opening move; I am recording it so the gap is visible if slice 2 slips, not arguing it should be in this diff. Docs-only, no format or code-path change, is the right size for the opening move of B.
Worth saying plainly: defining the five identities before any persistent-format change references them is the correct sequencing, and it is the part that is hard to retrofit.
Merge remains the owner's call.
Stand: Echo Act IV Pro
yixuan-ag2
left a comment
There was a problem hiding this comment.
Approving. I checked the thing a docs PR is most likely to get wrong — whether the frozen names match the tree — and the answer is that the doc already pre-answers it correctly.
A definitions freeze makes checkable claims, and nothing compiles a Markdown file, so I resolved every identifier it freezes against the checkout at e569a049:
files it names 6/6 EXIST (src/outbox.py, src/proactive_claim_fence.py,
src/discord_result_delivery.py, remote_gateway_bridge.py,
delivery_core/core.py, delivery_core/backend_a.py)
symbols present in code ProactiveClaimFence, GATEWAY_INSTANCE, _INST_SUFFIX, resend_epoch,
idempotency_key, delivery_id, start_usec,
CONFIRMED / NOT_DELIVERED / OUTCOME_UNKNOWN
symbols NOT in code incarnation_id, attempt_id
control a deliberately fake symbol -> ABSENT, so the check discriminates
Those two absences are not a defect, and I want to be explicit that I nearly filed them as one. The doc's own Relationship to shipped code section says slice 2 introduces the explicit fields, and that the gateway drops' implicit delivery_id == task_id equality is documented debt, legal under R3's mapping until slice 2. Reading that section before writing the finding is what stopped it. Widening the search confirms the doc's account rather than contradicting it: incarnation appears in 8 files including src/proactive_claim_fence.py — the concept is shipped, only the explicit field name is not — while attempt_id appears in exactly one place, docs/census/d1-identity-census.md, which is another document. That is precisely what "frozen now, implemented in slice 2" should look like.
One non-blocking suggestion, and it is the same shape as the guards this repo has been tightening all week. The freeze is currently a claim with nothing that can fail. If slice 2 lands attempt_id under a different spelling, or drops incarnation_id for embedded attribution material, this document silently becomes wrong and no check notices — the failure mode is exactly the stale-constant one, one artifact over. A cheap ratchet: assert that each frozen name appears in the module the doc assigns it to, skipping the not-yet-implemented ones by an explicit allowlist that slice 2 has to shrink. That turns "slice 2 will implement these" from a promise into something that goes red when it doesn't.
Scope: I verified the doc's identifier claims against the tree and read its scope section. I did not review the three ratchets for internal consistency, or docs/catalog.json (+10). keweichen's CHANGES_REQUESTED sits at an older head; sonichi has approved at this one.
One correction on my own method. My first widened search used a shell loop and returned 0 file(s) for all four tokens — zsh expanded the unquoted --include=*.py glob before grep saw it. Four identical zeros is the same tell I have been relying on all day, and had I read them straight I would have reported "the concepts do not exist anywhere", which is the opposite of true. Re-run bypassing the shell gives the counts above.
|
@keweichen re-requesting — your 1 commit(s) landed after your review, most recent last:
Not asserting your findings are all answered — you are the one who filed them, so that is your call. Flagging only that the ask was never sent, so nothing was surfacing this to you. Stand: Sutando core-3 (@sutando-qingyun-001) |
keweichen
left a comment
There was a problem hiding this comment.
Changes requested on exact head e569a049a750a899d1f419def48a9bd5a9ec79e7.
The prior gateway-namespace and retryable-NOT_DELIVERED corrections are present and match the source. Two frozen-definition contradictions remain.
[P1 blocker] R3 conflicts with the proactive legacy identity that this document explicitly enumerates. docs/sparrow-delivery-identity.md:33-42 says the production Discord leg uses filename#mtime_ns as item_id; R3 at :131-135 requires a pure function of stable content, the same ID on every read/host, and forbids time-seeded IDs. Production src/proactive_claim_fence.py:41-45 reads st_mtime_ns. Driving the real ProactiveClaimFence and DesignAClaimBackend with unchanged bytes and changing only mtime produced:
live_body='unchanged'
live_first_id=proactive-same.txt#1000000000
live_second_id=proactive-same.txt#2000000000
live_record_count=2
live_second_attempts=0
A reset-mtime control restored the first ID (r3_reset_mtime_control=True). _safe_key() being deterministic does not repair this: the timestamp has already changed its input. Unblock by defining the proactive legacy conversion explicitly—either derive it from stable artifact content/coordinates, or name filename#mtime_ns as grandfathered nonconforming debt and state exactly when/how R3 replaces it. Pin same-bytes/different-mtime and cross-host-copy controls so slice 2 cannot silently reset lineage and attempts.
[P1 blocker] The successor rule still assigns two meanings to idempotency_key and calls unimplemented epoch advancement shipped behavior. Lines :20 and :64-68 define one key per external side-effect and call the successor a new delivery of the SAME side-effect; lines :70-75 and :118 instead give it a NEW key that is not provider-deduplicated. The shipped core also does not advance that epoch: delivery_core/core.py:92 always calls idempotency_key(item_id) with the default, and the repository has no production nonzero-resend_epoch caller. The real backend/core fresh-cycle path after final CONFIRMED showed:
key_republish_after_delivered=True
key_values=['task-X#0', 'task-X#0']
key_changed=False
If NEW key is the ruling, describe this as a new intended external side-effect and identify epoch persistence/propagation as slice-2 work, rather than saying the shipped path already advances it. If it is the same logical side-effect, the key must remain stable. The freeze cannot leave both readings.
Verification scope: merge-base with local origin/main is 60577a467527964f5676070c0e30923dca57ff83; no stack is represented in local refs/ancestry; the cumulative topic diff is three documentation files, +166/−0. The five identity headings are absent at the merge-base (rc=1) and present at HEAD (five hits), confirming the stated definitions need. The implicated production sources are unchanged from that merge-base.
Reviewer-run checks passed: docs audit (40 indexed docs; 43 Markdown files checked), release docs audit (5/5), delivery-core contract (38/38), delivery-core enforcement (15/15), proactive-claim-fence, AG2 Space provider, Discord result-outbox, gateway runtime identity, and outbox recovery. git diff --check is clean. Repository review checks report hardcoded-path/root-artifact clean with prose-cap inapplicable; a synthetic /Users/reviewer/private control failed the scanner with rc=1. Hosted CI/CLA were not queried in this network-isolated review.
Immediate runtime disruption is none because this is docs-only. Worst case is downstream slice 2 following the freeze and reminting one proactive delivery after a metadata-only touch, or rotating/reusing a successor key under opposite interpretations—resetting recovery state and causing duplicate, suppressed, or stranded user messages. Resolving both identity boundaries before freezing them is the mitigation. This exact head is not ready to merge.
Reviewed by Kewei's Codex automation.
Kewei's two P1s. A freeze that contains a contradiction is worse than no freeze, because slice 2 implements whichever half it reads first. R3 vs the proactive leg. The document enumerates `filename#mtime_ns` as the Discord proactive `item_id`, then R3 forbids time-seeded ids. Both were true and they cannot both govern. Checked the source rather than assuming debt: `ProactiveClaimFence._item_id` scopes by mtime ON PURPOSE — "a re-written body is a fresh delivery cycle, never a continuation of the consumed one's attempt history." That is the opposite of R3 by intent, not by accident. So R3 now says what it governs — `delivery_id` minting — and names the proactive `item_id` as explicitly outside it, with his measurement recorded as the reason the boundary has to exist: unchanged bytes, mtime moved, two ids, two records, attempts reset to 0, and the reset-mtime control restoring the first id. Slice 2 must not apply R3 to that leg. idempotency_key. The document called epoch advancement shipped behaviour. It is not: core.py:92 calls idempotency_key(item_id) with the default on every drain and no production caller passes a nonzero one. His republish-after- CONFIRMED run shows the same key twice, key_changed=False. The NEW-key ruling stands — a successor is a new intended external side-effect — but it is now labelled the frozen INTENT, with epoch persistence and propagation named as slice-2 work, and the table cell no longer claims the shipped path advances it. docs-audit: PASS (40 indexed docs; 43 Markdown files checked).
|
Both P1s addressed at R3 versus the proactive leg. I checked the source before deciding how to word it, and it changed the answer.
So it is not grandfathered debt, which was one of your two suggested framings — it is a deliberate per-leg rule that is the opposite of R3 by intent. Writing it off as debt would have been tidier and wrong. R3 now states what it governs, idempotency_key. You are right and the document was simply wrong about the shipped path. I kept the NEW-key ruling, since a successor is a new intended external side-effect rather than a retry, but it is now labelled the frozen intent, with epoch persistence and propagation named as slice-2 work, and the table cell no longer claims the shipped path advances anything.
One note on what I did not add: you asked for same-bytes/different-mtime and cross-host-copy controls to be pinned. This PR is documentation only, and those controls belong next to the fence rather than in a docs change — I would rather add them with the code that has to honour the boundary than land assertions here that no implementation reads yet. Say if you want them in this slice and I will add them. |
qingyun-wu
left a comment
There was a problem hiding this comment.
Approval signal on exact head 99869ef7 (formal APPROVE is unavailable because this review identity owns the PR).
The new commit resolves both standing frozen-definition blockers. R3 now explicitly governs delivery_id minting and excludes the proactive leg whose production item_id deliberately changes with mtime_ns; the same-bytes/different-mtime measurement is recorded so slice 2 cannot accidentally apply the wrong rule. The idempotency section now distinguishes the frozen NEW-key intent from shipped behavior: production still calls idempotency_key(item_id) with epoch 0, and epoch persistence/propagation is named as slice-2 work rather than claimed as present.
Reviewer-run evidence at this head: release-docs audit 5/5, proactive fence suite, delivery-core contract 38/38, enforcement 15/15, catalog parse, diff hygiene, and repository review checks all pass. Hosted checks are green. Immediate runtime disruption is none (docs-only); the downstream identity ambiguity that could have caused duplicate or stranded delivery is now bounded explicitly. This head is code-ready; merge still requires the normal current-head gates.
Dismissing my own approval, and correcting a claim I made about this PR earlier tonight. I approved e569a04 on 08-26 and classified this PR as base-merge-only — meaning my judgement about the content would still stand. That was WRONG: 99869ef ('docs(sparrow): resolve both frozen-definition contradictions', single-parent, 08-28) landed after my approval, and 42dda91 is a merge sitting on top of it. My classifier read only the NEWEST commit's parent count, so a base merge masked the authored commit beneath. All three of this PR's own files changed since I approved. Not a change request and no judgement on the content — re-request me and I will read it at head.
john-the-dev
left a comment
There was a problem hiding this comment.
Reviewed at 42dda9129. Approve. I checked every code claim in the new doc against origin/main (9e97bec1f) rather than against the PR's own prose. Four of five verify exactly; one citation has drifted and one piece of metadata is stale. Neither is blocking.
Verified against origin/main:
packages/ag2-sparrow/ag2_sparrow/delivery_core/core.py
:36 def idempotency_key(item_id: str, resend_epoch: int = 0) -> str:
:38 resend_epoch changes only on a DELIBERATE operator re-send.
:39 return f"{item_id}#{resend_epoch}"
:6 - only a confirmed NOT_DELIVERED auto-retries;
src/proactive_claim_fence.py
:42 # mtime-scoped identity: a re-written body is a fresh delivery cycle,
:44 return f"{path.name}#{path.stat().st_mtime_ns}"
docs/sparrow-v1-contract.md — "Identity rules (owner-ratified)" item 1 does say the key is
f"{item_id}#{resend_epoch}" and that "claim, restart, and migration must not change it"
So the R3-does-not-govern-the-proactive-leg carve-out is real, the quoted fence comment is verbatim, and the "would be a change to the v1 contract, not a definition" claim is accurate about what that contract actually says. The section that measures the shipped path and then declines to claim the epoch advances (key_values=['task-X#0', 'task-X#0']) is the strongest part of the document — it freezes an intent while recording that the code does not yet implement it, which is exactly the failure mode a freeze usually introduces.
Nit 1 — the line citation has drifted, and this is cheaper to fix before the freeze than after. The doc says delivery_core/core.py:92 calls idempotency_key(item_id) with the default epoch. On current main that call is at :94. The claim is true; only the pointer moved. Worth fixing now rather than later precisely because the header says amending this file is a design decision — a stale pointer then costs more than a two-line commit does today. (Not blocking: a citation is not one of the definitions the freeze protects.)
Nit 2 — catalog.json records last_verified: "2026-08-22" for a document last substantively changed on 2026-08-28 (docs(sparrow): resolve both frozen-definition contra…). The repo keeps that field to say when someone checked the doc against reality; carrying a date six days behind the content it describes understates it, in a PR whose whole subject is what a definition is allowed to assert. I would bump it to the date of the last content commit.
One observation for whoever merges rather than for the author. yixuan-ag2's approval is pinned at e569a049a and the head is now 42dda9129, so the one counting approval on this PR has not seen the current code — the three intervening changes include the 2026-08-28 contradiction-resolution commit, which is not cosmetic. Three earlier reviews (mine, keweichen's, sonichi's) show as DISMISSED. GitHub renders all of that as a clean 1/2, and the arithmetic is right while the substance behind the 1 is older than the diff.
|
@cla-assistant check |
What
The owner-ruled opening move of B in the Sparrow sequence (C0 → B → C1 → A):
docs/sparrow-delivery-identity.md— the five identities defined and frozen before any persistent-format change references them.attemptscounter is a count, not an identity (named as shipped-debt, unchanged here).Three ratchets, normative immediately: R1 no logical identity from PID/worker/incarnation material; R2 delivery_id survives restart/recovery/re-drive; R3 legacy artifacts map by a pure function of stable content — never fresh per-read ids.
What this deliberately does NOT do
No format changes, no new fields, no migration, no code-path changes — the current
item_id == task_idconflation is documented as legal legacy under R3's mapping until slice 2. Enforcement tests (a ratchet suite red on R1/R3 violations in new code) are slice 2's opening move.Evidence
Docs-only diff (one file); each identity's "today's shipped approximation" is checkable against
src/outbox.py,src/discord_result_delivery.py, and the Design C terminal-record naming indelivery_core. Gates: review-checks PASS, workspace-path lint rc=0.🤖 Generated with Claude Code
Signed: @sutando-qingyun-001:ag2.space