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
Follow-up from agent-canvas#601 and the lifecycle-UI PR that ships against it. Canvas now exposes Resume + "Stop generating" + a take-ownership modal + a session-load-failed banner — but each of those papers over a missing primitive on the agent-server side. Tracking the gaps here.
What canvas wants
1. A true cancel, distinct from pause
Today ConversationClient exposes only pauseConversation() (POST /api/conversations/{id}/pause) and runConversation() (POST /api/conversations/{id}/run). The ACP spec has a separate session/cancel notification that Claude Code handles cleanly mid-stream, emitting a partial result. Canvas would surface that as "Stop generating" (keep the conversation alive, just abort the current turn) vs. the current "End conversation" / pause flow (mark the conversation paused, navigate away).
Requested API:
POST /api/conversations/{id}/cancel — sends session/cancel to the ACP subprocess, waits N seconds for ack, returns 200 on clean cancel or a structured error on timeout. Does NOT flip execution_status to PAUSED — the next user message should resume the same turn loop.
Today canvas labels the in-stream stop button "Stop generating" but the backend call is the same /pause — so the UX distinction is purely cosmetic until this lands.
2. A lease-release endpoint
owner_lease.json (~/.openhands/agent-canvas/conversations//) blocks a fresh agent-server boot from claiming an interrupted conversation. Leases auto-expire after DEFAULT_LEASE_TTL_SECONDS = 45.0, but if the user knows the previous owner is dead, they should be able to claim ownership immediately. Today the canvas-side "Take ownership" modal can only advise "wait briefly and retry" because there's no endpoint to delete the lease.
Requested API:
DELETE /api/conversations/{id}/owner_lease — atomically clears owner_lease.json if the holder's PID is no longer alive on the current host, otherwise 409s with the holder's PID/host. Mirrors the in-process ConversationLease._is_pid_alive check.
3. A structured session/load error code
When ACP's session/load fails (e.g. user wiped ~/.claude/projects/<hash>/), the agent-server should surface the failure with a stable error code on /api/conversations/{id}/run, not just a free-form string. Canvas needs to distinguish "session/load failed, falling back to a fresh upstream session" from generic errors so it can show a banner instead of a toast — and right now canvas substring-matches acp_session_load_failed / session/load, which is brittle.
Or, alternatively: emit a session_load_failed agent event on the conversation event stream so the UI can render the banner from the event rather than the resume response.
4. Surface ACP metadata on ConversationInfo
Per #601: the SDK already stores session_id, acp_agent_name, and acp_agent_version in agent_state but doesn't surface them in the conversation GET. Canvas has to walk the event stream to find them, which is wasteful and forces the lifecycle UI to make assumptions about ACP vs non-ACP conversations.
Requested API:
ConversationInfo.acp_session_id: str | None
ConversationInfo.acp_agent_name: str | None
ConversationInfo.acp_agent_version: str | None
Priority
1 and 3 are the most user-visible — they're the ones the agent-canvas lifecycle PR has to fake. 2 and 4 are nice-to-haves that would let canvas drop heuristics.
References
agent-canvas#601 (lifecycle UI gap)
agent-canvas lifecycle PR (link will be added once merged)
Follow-up from agent-canvas#601 and the lifecycle-UI PR that ships against it. Canvas now exposes Resume + "Stop generating" + a take-ownership modal + a session-load-failed banner — but each of those papers over a missing primitive on the agent-server side. Tracking the gaps here.
What canvas wants
1. A true
cancel, distinct frompauseToday
ConversationClientexposes onlypauseConversation()(POST/api/conversations/{id}/pause) andrunConversation()(POST/api/conversations/{id}/run). The ACP spec has a separatesession/cancelnotification that Claude Code handles cleanly mid-stream, emitting a partial result. Canvas would surface that as "Stop generating" (keep the conversation alive, just abort the current turn) vs. the current "End conversation" / pause flow (mark the conversation paused, navigate away).Requested API:
POST /api/conversations/{id}/cancel— sendssession/cancelto the ACP subprocess, waits N seconds for ack, returns 200 on clean cancel or a structured error on timeout. Does NOT flipexecution_statustoPAUSED— the next user message should resume the same turn loop.session/cancelhas had bugs).Today canvas labels the in-stream stop button "Stop generating" but the backend call is the same
/pause— so the UX distinction is purely cosmetic until this lands.2. A lease-release endpoint
owner_lease.json(~/.openhands/agent-canvas/conversations//) blocks a fresh agent-server boot from claiming an interrupted conversation. Leases auto-expire afterDEFAULT_LEASE_TTL_SECONDS = 45.0, but if the user knows the previous owner is dead, they should be able to claim ownership immediately. Today the canvas-side "Take ownership" modal can only advise "wait briefly and retry" because there's no endpoint to delete the lease.Requested API:
DELETE /api/conversations/{id}/owner_lease— atomically clearsowner_lease.jsonif the holder's PID is no longer alive on the current host, otherwise 409s with the holder's PID/host. Mirrors the in-processConversationLease._is_pid_alivecheck.3. A structured
session/loaderror codeWhen ACP's
session/loadfails (e.g. user wiped~/.claude/projects/<hash>/), the agent-server should surface the failure with a stable error code on/api/conversations/{id}/run, not just a free-form string. Canvas needs to distinguish "session/load failed, falling back to a fresh upstream session" from generic errors so it can show a banner instead of a toast — and right now canvas substring-matchesacp_session_load_failed/session/load, which is brittle.Requested API:
{ ok: true, session_loaded: bool, session_load_error?: string }. Today resume returns just{ success: true }.session_load_failedagent event on the conversation event stream so the UI can render the banner from the event rather than the resume response.4. Surface ACP metadata on
ConversationInfoPer #601: the SDK already stores
session_id,acp_agent_name, andacp_agent_versioninagent_statebut doesn't surface them in the conversation GET. Canvas has to walk the event stream to find them, which is wasteful and forces the lifecycle UI to make assumptions about ACP vs non-ACP conversations.Requested API:
ConversationInfo.acp_session_id: str | NoneConversationInfo.acp_agent_name: str | NoneConversationInfo.acp_agent_version: str | NonePriority
1 and 3 are the most user-visible — they're the ones the agent-canvas lifecycle PR has to fake. 2 and 4 are nice-to-haves that would let canvas drop heuristics.
References
session/cancel,session/load)