WIP: anima.work exchange, lane queues, and the runner AMQP-to-ACP translator #481

Closed
toasterson wants to merge 7 commits from claude/wi-019fa59c-anima-work-exchange-lane-queues-and-the into main
Owner

Anima work item 019fa59c-21b8-7080-adb1-7f7814dd00e7.

What to build

The unaddressed half of the Execution fabric, end to end: the anima.work direct exchange, one quorum lane queue per Lane, the server publishing a dispatch there, and the runner consuming it and translating AMQP into an ACP session/new + first session/prompt. Demoable by publishing one dispatch and watching a real agent session start.

Consumer prefetch = MAX_SLOTS, and the ack releases the slot.

Pushed context

  • ADR 0026 (docs/adr/0026-execution-fabric-routing-topology.md).
  • Routing key is the Lane: lane.akh.tecton, lane.exec.claude-code — one lane per akh, one per executor (CONTEXT.md, "Lane").
  • The runner is a dumb transport translator. It does not interpret Work Items, does not assemble prompts, and has no Postgres reach. The payload is already an assembled ACP message.
  • Prefetch is a sliding window of unacknowledged messages, not a batch — the broker delivers the next message as soon as the outstanding count drops below the limit, which is what makes prefetch == MAX_SLOTS an exact model of slot occupancy. It must equal, never exceed, real concurrency, or a runner hoards work it cannot start while another sits idle.
  • Lane queues are quorum queues with x-consumer-timeout set above the 5-hour sanctioned turn. The default is 30 minutes and blowing it closes the channel with PRECONDITION_FAILED, requeueing every delivery on that channel from all consumers.
  • Existing dispatch path to replace: crates/anima-server/src/dispatch.rs and crates/anima-server/src/scheduler.rs.

Acceptance criteria

  • A published dispatch starts exactly one agent session on a free consumer in the target Lane
  • Prefetch equals the runner's MAX_SLOTS; a saturated runner receives nothing further until it acks
  • A turn lasting over 30 minutes does not trip the ack timeout or requeue neighbours
  • The runner performs no database access on this path

Blocked by

  • 019fa59a-c5ae-7c90-be1b-b66647705e22 (anima-mq crate)
  • 019fa59a-ef3d-7b11-807a-a8330d20f616 (dispatch payload + idempotency key)
Anima work item `019fa59c-21b8-7080-adb1-7f7814dd00e7`. ## What to build The unaddressed half of the Execution fabric, end to end: the `anima.work` direct exchange, one quorum lane queue per Lane, the server publishing a dispatch there, and the runner consuming it and translating AMQP into an ACP `session/new` + first `session/prompt`. Demoable by publishing one dispatch and watching a real agent session start. Consumer prefetch = `MAX_SLOTS`, and **the ack releases the slot**. ## Pushed context - ADR 0026 (`docs/adr/0026-execution-fabric-routing-topology.md`). - Routing key is the **Lane**: `lane.akh.tecton`, `lane.exec.claude-code` — one lane per akh, one per executor (`CONTEXT.md`, "Lane"). - **The runner is a dumb transport translator.** It does not interpret Work Items, does not assemble prompts, and has **no Postgres reach**. The payload is already an assembled ACP message. - Prefetch is a **sliding window of unacknowledged messages, not a batch** — the broker delivers the next message as soon as the outstanding count drops below the limit, which is what makes prefetch == `MAX_SLOTS` an exact model of slot occupancy. It must **equal**, never exceed, real concurrency, or a runner hoards work it cannot start while another sits idle. - Lane queues are **quorum** queues with `x-consumer-timeout` set above the 5-hour sanctioned turn. The default is 30 minutes and blowing it closes the channel with `PRECONDITION_FAILED`, requeueing *every* delivery on that channel from *all* consumers. - Existing dispatch path to replace: `crates/anima-server/src/dispatch.rs` and `crates/anima-server/src/scheduler.rs`. ## Acceptance criteria - [ ] A published dispatch starts exactly one agent session on a free consumer in the target Lane - [ ] Prefetch equals the runner's `MAX_SLOTS`; a saturated runner receives nothing further until it acks - [ ] A turn lasting over 30 minutes does not trip the ack timeout or requeue neighbours - [ ] The runner performs no database access on this path ## Blocked by - 019fa59a-c5ae-7c90-be1b-b66647705e22 (anima-mq crate) - 019fa59a-ef3d-7b11-807a-a8330d20f616 (dispatch payload + idempotency key)
toasterson force-pushed claude/wi-019fa59c-anima-work-exchange-lane-queues-and-the from 7f0d260109 to 8ff02bdbcb 2026-08-02 20:17:38 +00:00 Compare
toasterson force-pushed claude/wi-019fa59c-anima-work-exchange-lane-queues-and-the from 8ff02bdbcb to 7d47d22c6b
All checks were successful
CI / conflict-check (push) Successful in 6m46s
CI / docker (push) Successful in 23m28s
2026-08-02 23:14:52 +00:00
Compare
toasterson force-pushed claude/wi-019fa59c-anima-work-exchange-lane-queues-and-the from 7d47d22c6b
All checks were successful
CI / conflict-check (push) Successful in 6m46s
CI / docker (push) Successful in 23m28s
to 8f449ad81f 2026-08-03 00:40:40 +00:00
Compare
Fix CI build: Add prost dependency and fix AMQP dispatch handling
All checks were successful
CI / conflict-check (push) Successful in 44s
CI / docker (push) Successful in 13m38s
b9d99388e4
Fixed three compilation errors:
1. Added missing `prost` dependency to anima-runner/Cargo.toml for protobuf decoding
2. Fixed AMQP dispatch architecture - created dedicated channel for AMQP Assign messages
3. Updated connect_once to multiplex gRPC and AMQP assigns via tokio::select!

The original code incorrectly tried to send ServerToRunner messages through a
RunnerToServer channel. The fix creates a separate channel for decoded AMQP Assign
messages and handles them through the same registration and slot management logic
as gRPC Assigns.

Also updated all tests and examples to include the assign_proto parameter in
ExecutionPayload::new() calls.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fix clippy warning: Allow too_many_arguments in publish_dispatch
All checks were successful
CI / conflict-check (push) Successful in 2m1s
CI / docker (push) Successful in 19m59s
72c5028d67
The CI failure was caused by a clippy warning about publish_dispatch having
8 parameters when the limit is 7. Added #[allow(clippy::too_many_arguments)]
to suppress this warning.

The function signature is well-documented with clear parameter descriptions,
and the parameters represent logically distinct dispatch metadata that would
be awkward to group into a struct given the varied call sites and optional
AMQP channel.

This fix allows the CI build to complete successfully.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
toasterson force-pushed claude/wi-019fa59c-anima-work-exchange-lane-queues-and-the from 72c5028d67
All checks were successful
CI / conflict-check (push) Successful in 2m1s
CI / docker (push) Successful in 19m59s
to 42feb6823e 2026-08-05 02:43:13 +00:00
Compare
toasterson closed this pull request 2026-08-06 17:08:02 +00:00
Some checks failed
CI / conflict-check (push) Successful in 26m29s
CI / docker (push) Failing after 46m56s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
toasterson/Anima!481
No description provided.