Skip to content

ACP: expose explicit lifecycle primitives (session/cancel, lease release, session/load error code) #3327

@simonrosenberg

Description

@simonrosenberg

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.
  • Falls back to kill-and-respawn after a configurable timeout if the subprocess doesn't ack (issue Add ZIP download for conversation visualizer and fix directory reset issue #601 calls out that Codex's session/cancel has 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 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.

Requested API:

  • Structured 200 response shape on resume: { ok: true, session_loaded: bool, session_load_error?: string }. Today resume returns just { success: true }.
  • 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)
  • ACP spec: https://agentclientprotocol.com/ (session/cancel, session/load)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions