Render Markdown, add ask_human context, simplify inbox responses, group inbox by task

This commit is contained in:
Ian Gulliver
2026-10-14 10:37:04 -07:00
parent e9fd7de556
commit ca2644374d
25 changed files with 950 additions and 283 deletions
+7 -7
View File
@@ -57,22 +57,22 @@ Agent activity becomes events with `agent_session_id` set: `user.message`, `agen
## Inbox
Agents ask questions with the `ask_human` MCP tool; a malformed call (no `why_blocked`, fewer than two options, a recommendation that isn't one of the options, an unknown attachment kind, a foreign `thread_id`) is rejected with a message saying what to fix, and nothing is filed. Each `report` call and each agent error (a turn that ends in error without a human interrupt, a process that dies mid-turn) also files an item.
Agents ask questions with the `ask_human` MCP tool. Each question carries a `context` with the background the human needs to answer without reading the transcript; the UI renders it, the question, and the options as Markdown. A malformed call (no `context` or `why_blocked`, fewer than two options, a recommendation that isn't one of the options, an unknown attachment kind, a foreign `thread_id`) is rejected with a message saying what to fix, and nothing is filed. Each `report` call and each agent error (a turn that ends in error without a human interrupt, a process that dies mid-turn) also files an item.
`GET /api/inbox` is one query over open, unsnoozed items, sorted by priority (`blocked`, `gate`, `review`, `fyi`) then age; `project_id` and `task_id` narrow it to a project or task view, and `state` and `include_snoozed` widen it. Blocking questions and errors are `blocked`; reports and non-blocking questions are `fyi`.
`GET /api/inbox` is one query over open, unsnoozed items, oldest first; `project_id` and `task_id` narrow it to a project or task view, and `state` and `include_snoozed` widen it. Blocking questions and errors are `blocked`; reports and non-blocking questions are `fyi`. The inbox view groups items by task, each group headed by project and task: tasks with a blocked item come first, then tasks sort by project name and by their oldest item. Within a task, items are in time order.
`POST /api/inbox/responses` answers any number of items in one submission. Response types and what they do to the item:
| type | allowed on | item becomes |
|---|---|---|
| `option` (with optional note in `text`) | questions | `answered` |
| `free_form` | questions | `answered` |
| `question` (a follow-up from the human) | questions | stays `open` |
| `redirect` | any | `redirected` |
| `option` | questions | `answered` |
| `other` (free text in `text`: an answer, a question back, or new instructions) | questions, errors | `answered` |
| `snooze` | any | stays `open`, hidden until `snooze_until` |
| `dismiss` | reports, errors | `dismissed` |
Answers reach the agent as one message, listing each answer under the question or report it responds to, once the session is idle and none of its blocking questions are still waiting for the human. A follow-up question tells the agent to reply with `ask_human` using the item's `thread_id`; the new item supersedes the open one in that thread. Every item change is an `inbox.created` or `inbox.updated` event carrying the whole item, including who resolved it.
In the UI a question offers its options plus Other, whose text box is the only text field; a report has only an OK button, which dismisses it at once; an error offers Dismiss, Reply (`other`) and Snooze.
Answers reach the agent as one message, listing each answer under the question or error it responds to, once the session is idle and none of its blocking questions are still waiting for the human. An `other` answer to a question tells the agent that if it asks something back, it should answer with `ask_human` using the item's `thread_id`. Every item change is an `inbox.created` or `inbox.updated` event carrying the whole item, including who resolved it.
## Events