Specs the 3-layer fix for silently-hung agents (JRS lost ~2.5h today): L1 bounded
tool execution, L2 non-blocking A2A rollout, L3 platform stall-watchdog with
probe->restart across all tenants. CTO design-approved; awaiting build sign-off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements Phase 1 of the unified-requests-inbox RFC: a single `requests`
subsystem that generalizes `user_tasks` (agent→user worklist asks) and
`approval_requests` (the destructive-action gate) into one inbox keyed by
`kind ∈ {task, approval}`, where requester and recipient may each be a user
OR an agent. Responding is asynchronous — the requester never blocks; a
REQUEST_RESPONDED event signals it to pick the answer up on its next tick.
Schema (migrations/20260610120000_requests.{up,down}.sql, idempotent):
- `requests`: kind, requester_{type,id}, org_id, recipient_{type,id}, title,
detail, status (pending|info_requested|done|rejected|approved|cancelled),
responder_{type,id}, priority, created/updated/responded_at. recipient_id /
requester_id are plain TEXT with NO FK (a party may be a user, not a
workspaces row). Indexes for inbox, org-pending, and outgoing reads.
- `request_messages`: the More-Info / "chat about this" thread (FK → requests,
ON DELETE CASCADE), indexed by (request_id, created_at).
- Idempotent backfill (ON CONFLICT (id) DO NOTHING) copies historical
user_tasks (kind=task; dismissed→rejected) and approval_requests
(kind=approval; denied→rejected, escalated→pending) into the unified inbox
so the tabs show pre-cutover items.
Store (internal/handlers/request_store.go): RequestStore mirrors UserTaskStore
— per-request over global db.DB, events.EventEmitter for testability, sentinel
errors. Create / Get / Messages / ListInbox / ListOutgoing / ListPendingForOrg
(LEFT JOIN workspaces for the agent name) / Respond (validates action↔kind:
approval→approved|rejected, task→done|rejected) / RequestInfo / AddMessage
(flips info_requested when the recipient asks back) / Cancel. Mutations
broadcast REQUEST_CREATED / REQUEST_RESPONDED / REQUEST_MESSAGE anchored on the
agent party so the canvas/inbox is signalled.
Handler (internal/handlers/requests.go) + routes
(internal/router/router.go), mirroring the approvals/user-tasks auth split:
- wsAuth (workspace token): POST /workspaces/:id/requests, GET .../requests
(outgoing), GET .../requests/inbox, plus agent-side
.../requests/:requestId/{get,respond,messages,cancel}.
- AdminAuth (canvas user): GET /requests/pending?kind=task|approval (the tabs),
and /requests/:requestId/{get,respond,messages,cancel}. The org_id anchor is
resolved via orgRootID (org_scope.go) — the workspaces table has no org_id
column.
Events (internal/events/types.go): adds REQUEST_CREATED, REQUEST_RESPONDED,
REQUEST_MESSAGE to the taxonomy + AllEventTypes; drift snapshot
(types_test.go) updated. Canvas TS mirror is a later phase (not touched here).
Tests (internal/handlers/requests_test.go): 20 cases via the existing sqlmock
harness — create (task + approval, agent recipient), inbox vs outgoing,
get+thread, respond (valid + invalid action-for-kind + not-found), More-Info
message → info_requested (recipient flips, requester doesn't), cancel, org
pending list + kind filter, recipient routing.
This is P1 of the unified-requests RFC. Follow-ons: P2 MCP tools + approval
shims, P3 canvas Tasks/Approvals tabs UI, P4 idle-nudge worker. Not included
here.
RFC: docs/design/rfc-unified-requests-inbox.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A workspace can now manage the asks it raised (not just create them),
mirroring how it would manage its own resources:
REST (WorkspaceAuth, scoped by workspace_id so an agent only touches tasks
it raised):
- GET /workspaces/:id/user-tasks — list own tasks (any status)
- PATCH /workspaces/:id/user-tasks/:taskId — update own {title,detail,status}
- DELETE /workspaces/:id/user-tasks/:taskId — delete own task
MCP (in-workspace a2a bridge, available to every agent):
- list_user_tasks() — read own asks + status
- update_user_task(user_task_id, title?, detail?, status?)
- delete_user_task(user_task_id)
These complement the existing request_user_action (create) and the user-side
/resolve. Confirms the design: any workspace (not just platform) can create
and manage tasks; the Home list stays org-wide. Handler tests cover
list/update/delete (+ not-found). go build + vet clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New `user_tasks` primitive — things an agent asks the *user* to do (e.g.
"Review the draft"). Any workspace can raise one; they surface in the
concierge Home Tasks list org-wide. Mirrors the approvals subsystem.
Backend (workspace-server):
- migration 20260607000000_user_tasks (id, workspace_id, title, detail,
status pending|done|dismissed, timestamps).
- handlers/user_tasks.go — Create (POST /workspaces/:id/user-tasks),
ListAll (GET /user-tasks/pending, AdminAuth, cross-workspace),
Resolve (POST /workspaces/:id/user-tasks/:taskId/resolve done|dismissed).
- events USER_TASK_REQUESTED / USER_TASK_RESOLVED (+ drift-test snapshot).
- router wiring mirroring the approvals auth split.
- MCP tool `request_user_action(title, detail?)` on the in-workspace a2a
bridge — available to EVERY agent, not gated like send_message_to_user.
- user_tasks_test.go (create/resolve happy + validation paths).
Canvas: concierge Home Tasks tab now reads /user-tasks/pending (org-wide)
with Done/Dismiss → resolve, replacing the interim schedules wiring; live
tab count.
Design SSOT: docs/design/rfc-user-tasks.md.
Follow-up (next commit): workspace-scoped read/update/delete of own tasks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Architecture RFC for an always-on per-tenant platform agent that holds the
platform-management MCP natively, joins A2A as a first-class kind='platform'
participant at the org root, and is the user's default concierge. Captures the
SSOT mapping, the platform-as-root + re-parenting model, the two-MCP runtime,
and the server-side approval gate. Pre-implementation; needs CTO sign-off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>