Skip to content

Add claim/ack API + PID sentinel helper for atomic delivery#372

Open
u-ichi wants to merge 1 commit into
fujibee:mainfrom
u-ichi:claims-api-both-mode
Open

Add claim/ack API + PID sentinel helper for atomic delivery#372
u-ichi wants to merge 1 commit into
fujibee:mainfrom
u-ichi:claims-api-both-mode

Conversation

@u-ichi

@u-ichi u-ichi commented Jul 12, 2026

Copy link
Copy Markdown

Fixes #373 · related to #338

Motivation

External delivery daemons (e.g. downstream projects that build their own delivery pipe on top of agmsg) plug in as a receiver for grok-build's rule-file self-poll fallback or a future explicit-daemon mode. Today's `inbox.sh --quiet` reads unread rows and marks them read in two separate SQL statements. When such a daemon consumes messages, an inject failure between those two statements causes silent message loss, and a race between two receivers can cause duplicate delivery. See #373 for the full failure model.

This PR adds a small claim / lease / ack API that lets any receiver atomically hold a message before consuming it:

  • inject failure → `release`, next poll retries (no loss)
  • concurrent receivers → only one wins the claim (no duplicate)
  • process crash → lease expires, another receiver reclaims

The API is orthogonal to the existing monitor watcher in grok-build. Upstream's monitor watcher could adopt these helpers to atomicize its own delivery loop as a follow-up, and downstream external daemons can start using them immediately.

The `receiver-live.sh` helper also composes with #338 Gap 2 (spawn readiness) as a shared liveness primitive — flagged in the comment thread there.

Changes

  • `scripts/internal/init-db.sh` — add `claims` table (`message_id` PK, `owner`, `expires_at`) + expiry index. `CREATE TABLE IF NOT EXISTS` so existing DBs upgrade in place.
  • `scripts/lib/claims.sh` (new, 52 lines) — `agmsg_claim_next` / `agmsg_ack_claim` / `agmsg_release_claim` built around a single `BEGIN IMMEDIATE` transaction with `INSERT OR IGNORE` + expiry GC.
  • `scripts/receiver-live.sh` (new, 12 lines) — small helper: read `pid=N` from an agent's ready sentinel and `kill -0` it. Exits 0 if live, non-zero if stale or missing. Rule files or watchers can use it to gate a fallback path.
  • `tests/test_claims.bats` (new, 5 cases) — excludes already-claimed / release + reclaim + ack / wrong-owner ack rejection / expired claim reclaim / newline+tab body round-trip.

Notes

🤖 Prepared with Claude Code

Motivation

External delivery daemons plug in as a receiver for grok-build's
rule-file self-poll fallback or a future explicit-daemon mode. Today's
inbox.sh --quiet reads unread rows and marks them read in two separate
SQL statements. When such a daemon consumes messages, an inject failure
between those two statements causes silent message loss, and a race
between two receivers can cause duplicate delivery.

This change adds a small claim/lease/ack API that lets any receiver
atomically hold a message before consuming it:

- inject failure -> release, next poll retries (no loss)
- concurrent receivers -> only one wins the claim (no duplicate)
- process crash -> lease expires, another receiver retries

The API is orthogonal to the existing monitor watcher in grok-build:
upstream's monitor watcher could adopt these helpers to atomicize its
own delivery loop.

Changes

- scripts/internal/init-db.sh
  Add claims table (message_id PK, owner, expires_at) + expiry index.
  Uses CREATE TABLE IF NOT EXISTS so existing DBs upgrade in place.

- scripts/lib/claims.sh (new, 52 lines)
  agmsg_claim_next / agmsg_ack_claim / agmsg_release_claim built around
  a single BEGIN IMMEDIATE transaction with INSERT OR IGNORE + expiry GC.

- scripts/receiver-live.sh (new, 12 lines)
  Small helper: read pid=N from an agent's ready sentinel and kill -0
  it. Exits 0 if live, non-zero if stale or missing. Rule files or
  watchers can use it to gate a fallback path.

- tests/test_claims.bats (new, 5 cases)
  Excludes already-claimed / release + reclaim + ack / wrong-owner ack
  rejection / expired claim reclaim / newline+tab body round-trip.

Notes

- No existing behavior is changed; nothing calls the new API yet.
- Driver-side integration is planned as a follow-up so it can be
  discussed against the current monitor design.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Atomic message consumption: prevent duplicate delivery and inject-failure loss with claim/ack

1 participant