You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 7bc9a86
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: docs/specs/chat.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -178,4 +178,5 @@ MSW fixtures typed from `api/openapi.yaml`.
178
178
**Start a task in <firstproject>**, **What's running?**, **Pause a schedule**). Each fills the
179
179
composer, unsent. They go once the thread has a message.
180
180
- The native identity strip in the header details is supplied by the sessions slice.
181
+
- A stalled turn (no task activity past its allowance, see [orchestration health](orchestration-health.md#stalled-turns)) shows the turn recovery panel instead of leaving only the spinner: "No activity for 25m. It may still finish. Stop or restart?" (with "while a tool runs" when the last event started a tool call), a **stop** button, and **restart session** when the thread is restartable. A stalled turn stays separate from a failed one: the next task event clears the panel, and nothing is resent. The owner also gets an attention item for it. Tested with MSW only.
181
182
- A failed stop on the main thread shows the turn recovery panel below the conversation, with **restart session** behind an in-app confirmation that says the native context is lost (see [sessions](sessions.md#react-status)). The stop error now carries the server's reason. Tested with MSW only.
Copy file name to clipboardExpand all lines: docs/specs/sessions.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -31,6 +31,7 @@ History before `e776537` describes the retired Python backend. Where other text
31
31
task attempt.
32
32
- Stop turn cancels the A2A task with `CancelTask` (the kagent client and `kagenttest` both have it). It takes no lock (cancel is the one that takes the per-session send lock); the one-open-turn index and the fenced writes keep it consistent with a send. A delivery still `recorded` is cancelled without any kagent call. The task is cancelled outside any transaction; the delivery ends `cancelled`, the queue is held, and the partial reply and the stop note go into one message in one transaction (the note's id is derived from the delivery, so the delivery loop and the stop call settle the same turn once). Results: `stopped`, `finished` (the task had already ended, and its real outcome is recorded), `no_open_turn`. A task that is not visible yet or still running after the cancel is `409` and nothing changes; a kagent error is `502`. An `uncertain` delivery counts as open while it is inside its hold; one past the hold is skipped, since it no longer blocks sends and the delivery loop fails it, so it cannot mask a newer running turn. The stop is not repeated blindly.
33
33
- Restart (`POST /sessions/{id}/restart`, issue #262) is the way out of a turn that stop cannot confirm, such as a runtime wedged inside kagent that never answers `CancelTask` (kagent will not suspend a Session with a non-terminal task either). The body carries a caller-chosen `request_id`, the idempotency key. Under the session's send lock Mainloop first writes the intent (`session_restarts`, state `requested`, naming the kagent Session to delete), then, with no transaction open, reads the Session and deletes it only if kagent still shows it, and reads it again when the delete reply still shows it running. kagent answering NotFound, `DELETED` or `DELETING` (a delete it has accepted, which cannot be undone and admits nothing) counts as gone. One transaction under the send lock then makes the binding forget that Session (fenced on its id, so a newer Session is never touched; a new create request id, and the standing context cleared), settles every `sending`, `delivered` or `uncertain` delivery sent to it as `failed` with "the native session was restarted before this turn finished; it was not sent again", admits the oldest `queued` message, and marks the intent `done` with the failed ids. Failed, not uncertain: Mainloop knows the Session is gone, so the message cannot still run, and nothing waits out `SESSION_UNCERTAIN_HOLD_SECONDS`. A `recorded` message has not been sent and is left for the new Session. The next message creates a new kagent Session that carries the recent conversation (the replacement path above); the native context is lost. The response is `restarted` (or `no_native_session` when the binding had none) with `failed_message_ids`. Repeating the same `request_id` returns the recorded outcome and deletes nothing; a different id while a restart is open continues the open one. A delete kagent does not confirm is `502` and keeps the intent `requested`; a repeat, or the `resume-session-restarts` sweep (every 30 seconds, for intents idle a minute), continues it the same way. Only the main thread and standalone native sessions can be restarted: a workspace session (its runtime belongs to the workspace service), a task-owned session (the task's stop path force-deletes a runtime that never answers, see [orchestration health](orchestration-health.md#unconfirmed-stops)), an archived, cancelled or failed session, and a main thread whose provider switch is pending are `409`. `NativeSessionInfo.restartable` says whether it applies. Nothing is restarted automatically and no prompt is replayed. Tested against the fake kagent only; not measured against a live kagent.
34
+
- A delivered turn whose task shows no activity past its allowance is marked stalled; it is shown with stop and restart and raised as attention, and the next event clears it. It is an observation, not a delivery state: see [orchestration health](orchestration-health.md#stalled-turns).
34
35
- A delivered or uncertain turn whose task kagent no longer has (`GetTask` not found, or no task shows the message) is checked against its kagent Session: when kagent shows the Session gone (NotFound, deleting or deleted), the turn fails at once with "the kagent session no longer exists, so this turn cannot finish; it was not sent again" instead of waiting out the uncertain hold, and the queue moves on. A task that is missing while its Session still exists keeps the uncertain path.
35
36
- Cancel closes the session's open deliveries except task-owned `sending` and `uncertain` rows,
36
37
whose claims and unresolved outcomes are retained for reconciliation without replay. In the same
@@ -213,7 +214,7 @@ Labels: **implemented** means built in `web/` and covered by component or route
213
214
214
215
-**Implemented:** session rows (on the project page); the `/agents` form, reachable by URL only (Claude Code or Codex, Ctrl/Cmd+Enter submits, blocked while offline); and the session page. The page offers **Stop turn** only while the native state reports a turn in flight, and never on archived, failed or cancelled sessions, **Cancel session** through an in-app confirmation (never `window.confirm`), and **Clear** for completed, failed and cancelled sessions, which returns to the main chat. It also shows the task link, workspace badges, summary or error, the identity strip, and the not-found, server error and unreachable states.
215
216
-**Implemented:** after any session action, the page re-reads the session, its native state and attention before it shows the result. A `409` therefore shows the current state.
216
-
-**Implemented:** the turn recovery panel in every chat (main thread and session chat). While a turn is in flight, the session is `restartable` and the last stop failed, it says the turn could not be stopped and offers **restart session**. The in-app confirmation (never `window.confirm`) says the native context is lost, the open turn ends as failed and is not sent again, and the next message starts a new native session with the recent conversation. One request id is kept per restart until it succeeds, so a retry after a lost reply or a `502` continues the same restart. Tested with MSW only.
217
+
-**Implemented:** the turn recovery panel in every chat (main thread and session chat). While a turn is in flight it shows for a stalled turn (how long it has been quiet, with **stop**, and **restart session** when the session is `restartable`), and for a restartable session whose last stop failed (it says the turn could not be stopped and offers **restart session**). The in-app confirmation (never `window.confirm`) says the native context is lost, the open turn ends as failed and is not sent again, and the next message starts a new native session with the recent conversation. One request id is kept per restart until it succeeds, so a retry after a lost reply or a `502` continues the same restart. Tested with MSW only.
217
218
-**Implemented:** while the session page first loads, placeholder rows with a row's height sit under the loading text. The text is what a screen reader announces; the placeholders are hidden from it.
218
219
-**Wording:** the identity strip's live word is the agent's own state, shown verbatim. It shows `live` when the agent is ready, otherwise whatever state string the agent reports (for example `busy` or `suspended`, and any other word it sends), or `unknown` when there is no state yet. A failed or unconfirmed last delivery takes priority and shows as `last message failed` or `last message unconfirmed`, so it never reads `live`. A cancelled session shows `cancelled` the same way, even when the agent still reports ready. When the backend cannot be reached the word is `unreachable`, which the page adds itself; an agent never reports it.
219
220
-**Implemented:** the session chat in the page's chat slot (`SessionChatSlot`). It shows the session's own conversation, with the same timeline, delivery notices, retry and composer as the main thread. It reads `GET /sessions/{id}/native` and `GET /sessions/{id}/conversation`, and sends to `POST /sessions/{id}/message`. Its unsent draft is kept for that session only. While a turn is in flight the header shows the turn's state and a **stop** button. A cancelled session's header reads `cancelled`, never the agent's own `ready`, and the **stop** button is hidden when the native state is unknown or malformed. Its pending requests show as cards at the end of the conversation (below); it does not show the topic index.
0 commit comments