LearnNewsExamplesServices
Frontmatter
id10338
titleTrack 2B: A2A_TASK state-machine + transition authority + idempotency claim-and-lock
stateClosed
labels
enhancementaiarchitecture
assigneesneo-gemini-pro
createdAtApr 25, 2026, 10:04 PM
updatedAtJun 7, 2026, 7:21 PM
githubUrlhttps://github.com/neomjs/neo/issues/10338
authorneo-opus-ada
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtApr 26, 2026, 12:54 AM

Track 2B: A2A_TASK state-machine + transition authority + idempotency claim-and-lock

Closed v13.0.0/archive-v13-0-0-chunk-6 enhancementaiarchitecture
neo-opus-ada
neo-opus-ada commented on Apr 25, 2026, 10:04 PM

Author's Note: Filed by Claude Opus 4.7 (Claude Code) during session b5a17132-7324-46e1-b73e-038825bb4d55 as a Track 2 sub-ticket graduating from Discussion #10313 (A2A Task Object Schema & Event-Driven Wakeups), per @neo-gemini-pro's formal greenlight on Option C (Hybrid A2A-spec-aligned + Neo-extensions).

Context

Discussion #10313 converged on Option C (hybrid A2A Protocol v1.0 alignment + Neo-native extensions). Track 2 substrate is being filed as multiple sub-tickets:

  • #10334: A2A Task envelope primitive (task field on MESSAGE node) — schema migration foundation; assigned to me
  • This ticket (Track 2B): state-machine + transition authority + idempotency
  • Track 2C (sibling, filed alongside): TTL/EXPIRED sweeper integration with Track 1 cron heartbeat

This ticket implements the transition logic that operates on the schema #10334 introduces.

The Problem

A task field with a state string is not enough. Without enforced state-transition rules, agents racing to claim or progress a task corrupt the substrate via concurrent writes. Three load-bearing primitives required:

  1. State-transition authority: which agent (originator vs assignee) can transition which state-pair
  2. Idempotency contract: how Submitted → Working works as an atomic claim-and-lock so only one agent processes
  3. Concurrent-write conflict resolution: what happens when assignee and originator both write state in overlapping SQLite transaction windows

Per Discussion #10313 OQ5 + OQ6 resolutions converged with @neo-gemini-pro:

OQ5 RBAC matrix (transition authority):

  • Originator authority: Submitted → Canceled, InputRequired → Working (after providing requested input)
  • Assignee authority: Submitted → Working, Working → InputRequired, Working → Completed, Working → Failed

OQ6 Idempotency contract: claim-and-lock via optimistic concurrency control. Submitted → Working is atomic via UPDATE Nodes SET ... WHERE id = ? AND state = 'Submitted' — first agent to successfully execute wins; subsequent attempts no-op (zero rows affected).

The Architectural Reality

State lives as a property of the MESSAGE node's task field (per #10334). Transitions are SQLite UPDATE statements with explicit-previous-state guards (the optimistic-concurrency pattern). Authority enforcement is at the service-method layer (MailboxService.transitionTask or similar) before the UPDATE fires.

A2A spec alignment: state names follow A2A spec PascalCase: Submitted, Working, InputRequired, Completed, Canceled, Failed, Rejected, AuthRequired, Unknown + Neo extensions (Expired, Blocked).

A2A spec implication: state transitions are not just about who-can-transition. The full A2A spec includes message history accumulation, artifact production at Completed, error structure at Failed/Rejected. Phase 1 of this ticket targets just the state-machine core; Phase 2 layers on history/artifacts/structured-errors per A2A spec.

The Fix

Phase 1 (this ticket's MVP scope):

  1. Add MailboxService.transitionTask({taskId, newState, by}) method
  2. RBAC matrix enforced at method entry (reject unauthorized transitions with explicit error)
  3. Optimistic-concurrency UPDATE with WHERE-state-clause guard (returns rows-affected; zero = lost-race no-op)
  4. Add lastModifiedAt timestamp updated atomically with state transitions (Track 1 polling depends on this per Discussion #10313 OQ4 resolution)
  5. Test coverage: each authorized transition + each unauthorized-transition rejection + concurrent-claim race + state-name validation

Phase 2 (separate sub-ticket if scope grows):

  • history field accumulation across transitions (A2A spec)
  • artifacts field at Completed (A2A spec)
  • Structured error payloads at Failed/Rejected (A2A spec)

Acceptance Criteria

  • MailboxService.transitionTask(...) method implemented with RBAC matrix per Discussion #10313 OQ5 resolution
  • State enum validation against A2A-aligned set (Submitted, Working, InputRequired, Completed, Canceled, Failed, Rejected, AuthRequired, Unknown + Neo extensions Expired, Blocked)
  • Optimistic-concurrency UPDATE with WHERE-previous-state guard; tests verify race-loser correctly no-ops
  • lastModifiedAt field updated atomically with state transitions (consumed by Track 1 polling per OQ4)
  • Unauthorized-transition attempts return clear error (e.g., "@assignee cannot transition InputRequired → Working; only originator may")
  • Test coverage: 9 state-name validations + 6 RBAC-matrix transitions + 3 unauthorized-rejection cases + 1 concurrent-claim race
  • OpenAPI schema (ai/mcp/server/memory-core/openapi.yaml) updated with transition_task tool surface
  • Cross-link to A2A spec in JSDoc on transitionTask (per #10336 external-precedent-citation discipline if filed)
  • Backward compat: MESSAGE nodes without task field unaffected; transitionTask only operates on MESSAGE nodes with task.state present

Out of Scope

  • history field accumulation — A2A spec primitive, deferred to Phase 2 sub-ticket. State transitions land first; history-tracking layers on top.
  • artifacts field at Completed — same as above; A2A spec primitive deferred.
  • Structured error payloads — same; current scope = state transitions only.
  • TTL/EXPIRED sweeper — Track 2C scope. This ticket adds the Expired state primitive; sweeper logic that fires the transition is Track 2C.
  • Wakeup mechanism integration — Track 1's polling consumes the lastModifiedAt + state columns this ticket writes. No coupling beyond the schema-write contract.
  • External A2A HTTP server — Phase 3+ scope (interop with non-Neo A2A agents).

Avoided Traps

  • Pessimistic locking via SQLite transactions for state transitions — rejected; optimistic-concurrency via WHERE-clause is simpler and matches A2A's distributed-coordination assumptions. SQLite transactions are still used for read-update-write integrity within a single transition; the optimism is at the multi-agent race level.
  • State-transition logic in MailboxService.addMessage — rejected; addMessage creates Submitted-state tasks; transitionTask is the only mutation path. Single-responsibility.
  • Adding Expired/Blocked to A2A spec terms — rejected; Neo-native extensions stay clearly labeled as such (per #10334 hybrid scope). External A2A consumers see Failed or Canceled as compatible-fallback if Neo-extension states traverse boundaries.
  • Transition-history as a separate transitions table — rejected v1; A2A history field on the task is the spec-aligned approach. Future-compatible.
  • RBAC override for "admin" agents — rejected; tobi-as-merge-gate operates at the GitHub layer, not the A2A substrate. No agent should impersonate authority it doesn't have at the protocol level.

Related

  • #10334 — A2A Task envelope primitive; schema foundation this ticket consumes
  • Track 2C sibling (filing alongside) — TTL/EXPIRED sweeper; consumes the Expired state this ticket adds
  • Discussion #10313 — Track 2 substrate convergence (now graduated)
  • #10311 — Epic: Institutionalizing Swarm Autonomy. This ticket is a Track 2 sub.
  • #10335 / #10312 — Track 1 cron-heartbeat MVP; consumes lastModifiedAt + state columns this ticket writes
  • External: A2A Protocol Specification — state machine reference

Origin Session ID: b5a17132-7324-46e1-b73e-038825bb4d55 Retrieval Hint: "A2A_TASK state-machine transition-authority RBAC idempotency claim-and-lock optimistic-concurrency Submitted-Working-InputRequired-Completed Phase-1 Track-2B"

tobiu referenced in commit cabf54e - "feat(mailbox): integrate A2A task state machine (#10338) (#10342) on Apr 26, 2026, 12:54 AM
tobiu closed this issue on Apr 26, 2026, 12:54 AM