Area
New API, UI, or Deployment Capabilities
Candidate
Add recipient-scoped inbox status to targeted events, so each human or agent can acknowledge and complete a shared instruction independently without clearing it from every other recipient's inbox.
Why this should be the next strategic priority
Agora already models fan-out: an event has a targets array, all is a wildcard target, and the reporting skill tells every agent to acknowledge actionable inbox items and mark them terminal when handled. But lifecycle state is a single scalar on the event.
That turns every shared instruction into an accidental single-consumer queue. The first recipient to mark it done, resolved, or rejected changes the event globally, so it disappears from all other open/actionable inboxes. There is also no way for a coordinator to distinguish "builder handled this" from "every target handled this."
This fails on the first event sent to two recipients, not only after a deployment reaches a scale threshold. It blocks several of Agora's highest-value growth paths:
- Parallel coding agents cannot independently accept and finish the same review, validation, or rollout instruction.
- A human cannot broadcast a decision with
targets: ["all"] and retain it for agents that have not acknowledged it yet.
- CI, reviewer, and deployer handoffs cannot show which consumers have handled an event.
- Larger teams must create duplicate events per recipient, fragmenting the shared timeline and making fan-out targeting misleading.
Evidence from the repository
internal/agora/event.go:83-97 gives an event multiple Targets but only one Status.
internal/agora/event.go:216-230 sends an event targeted to all to every agent and defines openness solely from that scalar status.
internal/agora/store.go:91-119 records the status-changing actor, then overwrites the one stored event status without considering its targets.
internal/agora/store.go:219-227 discards the actor when replaying a status_change; only the last global value survives in memory.
internal/agora/store.go:251-269 applies inbox status and open filters to event.Status before checking which agent is targeted.
internal/server/server.go:140-162 exposes only the event-wide status mutation endpoint.
internal/agoracli/cli.go:237-267 always calls that global endpoint, including when an agent is handling an inbox item.
internal/server/static/app.js:240-249 and internal/server/static/app.js:299-304 render one set of status actions and post a global update for the selected actor.
skills/agora-reporting/SKILL.md:48-52 requires each agent to acknowledge actionable items and then mark them done, resolved, or rejected, which is unsafe for multi-target and all events under the current model.
TestInboxMatchesTargetsAndMentions covers all delivery, while the status and inbox tests use only a single target; there is no test that one recipient's completion leaves work open for another.
Reproduction on current main
I posted one instruction targeted to two agents:
{
"type": "instruction",
"actor": "human",
"thread": "release",
"title": "Validate release",
"targets": ["agent-a", "agent-b"]
}
Both open inboxes initially contained the event. Agent A then used the existing status API:
POST /api/events/<event-id>/status
Content-Type: application/json
{"actor":"agent-a","status":"done"}
The result was:
agent-a open inbox before: [<event-id>]
agent-b open inbox before: [<event-id>]
agent-a marks done: event.status=done
agent-a open inbox after: []
agent-b open inbox after: []
The JSONL log contains actor: "agent-a" on the status_change, but replay applies that record globally. The same failure occurs for targets: ["all"].
Proposed design
Separate event lifecycle from per-recipient handling
Keep the current endpoint and Event.status semantics unchanged for intentional event-wide transitions:
POST /api/events/<event-id>/status
{"actor":"human","status":"resolved"}
Add an explicit recipient-scoped status operation, for example:
POST /api/agents/agent-a/inbox/<event-id>/status
Content-Type: application/json
{"actor":"agent-a","status":"done"}
- Validate that the event targets the named recipient directly or through
all.
- Reuse the current status vocabulary and normalization rather than adding a second lifecycle.
- Store both
recipient and actor; they are usually equal, but retaining both permits an operator to act on behalf of an agent and preserves an audit label.
- Treat recipient state as coordination metadata, not authentication. Local, shared-token, and Kubernetes deployments keep their current bearer-token behavior.
For an agent inbox, compute an effective status as follows:
- A terminal event-wide status (
done, resolved, or rejected) closes the event for everyone.
- Otherwise, use that agent's latest recipient status when one exists.
- Otherwise, fall back to the event-wide status.
Apply open=true and repeated status filters to this effective status. This makes recipient filtering correct before limit is applied.
Inbox responses should remain event-shaped but add an optional field such as recipient_status; timeline responses can add an optional recipient_statuses map when receipts exist:
{
"id": "evt_...",
"status": "open",
"targets": ["agent-a", "agent-b"],
"recipient_status": "done",
"recipient_statuses": {
"agent-a": "done",
"agent-b": "acknowledged"
}
}
The canonical status remains event-wide. The additional fields let agents reason about their own inbox and let a human see partial completion without changing existing response fields.
Persist receipts additively
Append a new JSONL record kind instead of rewriting event records:
{
"kind": "recipient_status_change",
"recipient_status": {
"event_id": "evt_...",
"recipient": "agent-a",
"actor": "agent-a",
"status": "done",
"created_at": "..."
}
}
Replay it into an in-memory event ID -> recipient -> latest status index. Existing event and status_change records retain their exact meaning, so existing JSONL files load without migration. For an all target, create recipient entries lazily as agents acknowledge or complete the item; Agora does not need an agent registry.
Make first-party consumers safe
- Add a CLI form such as
agora status --recipient agent-a <event-id> done that uses the recipient endpoint; keep an unscoped agora status global for compatibility and operator use.
- Update
skills/agora-reporting to use recipient-scoped status for items obtained from the agent's inbox.
- When the browser's selected actor is a direct or
all target, make its acknowledge/done actions recipient-scoped and render that actor's effective status. Event-wide closure should remain an explicit coordinator action.
- Do not require new environment variables, agent-specific tokens, or Kubernetes resources.
Compatibility
- Existing API clients and the current global status endpoint retain their behavior.
- Existing event JSON fields, status values, target matching, and forward polling remain valid.
- Added response fields are optional; old clients can ignore them.
- Existing JSONL logs replay unchanged; missing recipient state falls back to the global event status.
AGORA_URL, AGORA_AGENT, AGORA_THREAD, and AGORA_TOKEN keep their current meanings.
- A global terminal update remains available when a coordinator intentionally wants to close the event for everyone.
Acceptance criteria
- An open instruction targeted to
agent-a and agent-b appears in both open inboxes.
- After agent A acknowledges and completes it through the recipient-scoped path, it is absent from agent A's open/actionable inbox but remains open in agent B's inbox.
- Agent B can independently acknowledge and complete the same event.
- The same isolation works for an event targeted to
all, with recipient records created lazily.
- A terminal event-wide update still removes the event from every recipient's open inbox.
- Inbox
open and repeated status filters use recipient-effective state before applying limit.
- Recipient changes survive server restart, while a fixture containing only the existing JSONL record kinds still replays identically.
- Attempts to update a recipient not targeted by the event return a focused
400 response.
- The CLI and browser use recipient-scoped updates when handling targeted inbox work.
- The reporting skill's acknowledge/complete loop cannot clear another agent's inbox item.
- Tests cover two explicit recipients, the
all wildcard, global closure, filtering-before-limit, persistence/replay, and existing-client behavior.
make verify passes.
Area
New API, UI, or Deployment Capabilities
Candidate
Add recipient-scoped inbox status to targeted events, so each human or agent can acknowledge and complete a shared instruction independently without clearing it from every other recipient's inbox.
Why this should be the next strategic priority
Agora already models fan-out: an event has a
targetsarray,allis a wildcard target, and the reporting skill tells every agent to acknowledge actionable inbox items and mark them terminal when handled. But lifecycle state is a single scalar on the event.That turns every shared instruction into an accidental single-consumer queue. The first recipient to mark it
done,resolved, orrejectedchanges the event globally, so it disappears from all other open/actionable inboxes. There is also no way for a coordinator to distinguish "builder handled this" from "every target handled this."This fails on the first event sent to two recipients, not only after a deployment reaches a scale threshold. It blocks several of Agora's highest-value growth paths:
targets: ["all"]and retain it for agents that have not acknowledged it yet.Evidence from the repository
internal/agora/event.go:83-97gives an event multipleTargetsbut only oneStatus.internal/agora/event.go:216-230sends an event targeted toallto every agent and defines openness solely from that scalar status.internal/agora/store.go:91-119records the status-changing actor, then overwrites the one stored event status without considering its targets.internal/agora/store.go:219-227discards the actor when replaying astatus_change; only the last global value survives in memory.internal/agora/store.go:251-269applies inbox status andopenfilters toevent.Statusbefore checking which agent is targeted.internal/server/server.go:140-162exposes only the event-wide status mutation endpoint.internal/agoracli/cli.go:237-267always calls that global endpoint, including when an agent is handling an inbox item.internal/server/static/app.js:240-249andinternal/server/static/app.js:299-304render one set of status actions and post a global update for the selected actor.skills/agora-reporting/SKILL.md:48-52requires each agent to acknowledge actionable items and then mark themdone,resolved, orrejected, which is unsafe for multi-target andallevents under the current model.TestInboxMatchesTargetsAndMentionscoversalldelivery, while the status and inbox tests use only a single target; there is no test that one recipient's completion leaves work open for another.Reproduction on current
mainI posted one instruction targeted to two agents:
{ "type": "instruction", "actor": "human", "thread": "release", "title": "Validate release", "targets": ["agent-a", "agent-b"] }Both open inboxes initially contained the event. Agent A then used the existing status API:
The result was:
The JSONL log contains
actor: "agent-a"on thestatus_change, but replay applies that record globally. The same failure occurs fortargets: ["all"].Proposed design
Separate event lifecycle from per-recipient handling
Keep the current endpoint and
Event.statussemantics unchanged for intentional event-wide transitions:Add an explicit recipient-scoped status operation, for example:
all.recipientandactor; they are usually equal, but retaining both permits an operator to act on behalf of an agent and preserves an audit label.For an agent inbox, compute an effective status as follows:
done,resolved, orrejected) closes the event for everyone.Apply
open=trueand repeatedstatusfilters to this effective status. This makes recipient filtering correct beforelimitis applied.Inbox responses should remain event-shaped but add an optional field such as
recipient_status; timeline responses can add an optionalrecipient_statusesmap when receipts exist:{ "id": "evt_...", "status": "open", "targets": ["agent-a", "agent-b"], "recipient_status": "done", "recipient_statuses": { "agent-a": "done", "agent-b": "acknowledged" } }The canonical
statusremains event-wide. The additional fields let agents reason about their own inbox and let a human see partial completion without changing existing response fields.Persist receipts additively
Append a new JSONL record kind instead of rewriting event records:
{ "kind": "recipient_status_change", "recipient_status": { "event_id": "evt_...", "recipient": "agent-a", "actor": "agent-a", "status": "done", "created_at": "..." } }Replay it into an in-memory
event ID -> recipient -> latest statusindex. Existingeventandstatus_changerecords retain their exact meaning, so existing JSONL files load without migration. For analltarget, create recipient entries lazily as agents acknowledge or complete the item; Agora does not need an agent registry.Make first-party consumers safe
agora status --recipient agent-a <event-id> donethat uses the recipient endpoint; keep an unscopedagora statusglobal for compatibility and operator use.skills/agora-reportingto use recipient-scoped status for items obtained from the agent's inbox.alltarget, make its acknowledge/done actions recipient-scoped and render that actor's effective status. Event-wide closure should remain an explicit coordinator action.Compatibility
AGORA_URL,AGORA_AGENT,AGORA_THREAD, andAGORA_TOKENkeep their current meanings.Acceptance criteria
agent-aandagent-bappears in both open inboxes.all, with recipient records created lazily.openand repeatedstatusfilters use recipient-effective state before applyinglimit.400response.allwildcard, global closure, filtering-before-limit, persistence/replay, and existing-client behavior.make verifypasses.