Skip to content

Implement ACP draft session lifecycle with thread-bound bind - #51

Merged
soorya-u merged 3 commits into
mainfrom
feat/acp-draft-session-lifecycle
Jul 12, 2026
Merged

Implement ACP draft session lifecycle with thread-bound bind#51
soorya-u merged 3 commits into
mainfrom
feat/acp-draft-session-lifecycle

Conversation

@soorya-u

@soorya-u soorya-u commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add thread-bound ACP draft sessions via bindAgent, persisting sessionId and agentLocked on threads
  • Route catalog operations through bound sessions, reject chat on unbound threads, and close sessions on thread delete
  • Filter listAgents to healthy agents with TTL caching; update web composer to bind on agent select and hide agent column when locked
  • Sync OpenSpec for phase 1 session lifecycle and archive the completed change

Closes #44

Test plan

  • bun check
  • bun check:types
  • bun test:unit
  • bun test:integration
  • Manual: open draft thread, select agent, verify catalog loads from bound session
  • Manual: send first message, verify agent picker locks to selected agent
  • Manual: delete bound thread, verify ACP session is closed

Made with Cursor

Summary by CodeRabbit

  • New Features
    • Agents are now checked for availability before appearing in the agent list.
    • Selecting an agent creates and preserves a draft session for the thread, reusing it across catalog actions and prompts.
    • Agent, model, mode, effort, and persona selections are now tied to the current thread.
    • Threads lock agent changes after the first message.
    • Added clearer handling for unavailable agents, invalid bindings, and session errors.
    • The picker reflects locked threads and hides agent switching when changes are no longer allowed.

Bind agents to draft threads before chat, persist sessionId and agent lock state, route catalog through bound sessions, and sync OpenSpec for phase 1 session lifecycle.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel

vercel Bot commented Jul 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cyrus Ready Ready Preview, Comment Jul 12, 2026 1:23pm

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces thread-bound ACP draft sessions created during agent binding, persisted session and lock state, thread-scoped catalog access, cached agent health filtering, Result-based coordinator errors, session cleanup/recovery, and client-side binding with locked picker behavior.

Changes

ACP draft session lifecycle

Layer / File(s) Summary
Contracts and persisted session state
shared/schemas/..., shared/connections/..., shared/database/..., openspec/...
Adds bind-agent and thread-scoped catalog contracts, persists sessionId and agentLocked, and standardizes typed repository/coordinator errors.
Bound runtime and coordinator flow
apps/cli/src/core/agents/..., apps/cli/src/core/threads/coordinator.ts, apps/cli/__tests__/integration/draft-session-lifecycle.test.ts
Creates, reuses, resumes, switches, closes, and prompts through thread-bound sessions with lock enforcement and lifecycle tests.
Controller and prompt error flow
apps/cli/src/handlers/controller/..., apps/cli/src/utils/..., apps/cli/__tests__/integration/wiring.test.ts
Threads bound-session inputs through handlers, maps coordinator errors to ORPC errors, validates chat bindings, closes sessions before deletion, and handles Result-wrapped prompt streams.
Agent health filtering
apps/cli/src/core/agents/health.*, apps/cli/src/commands/agents/doctor.ts
Adds TTL-cached ACP health checks and returns only healthy agents from agent listing.
Client binding and locked picker
shared/hooks/..., apps/web/src/components/chat/composer/agent-model-picker.tsx
Binds agents through mutations, loads catalogs by thread, removes local agent overrides, and hides agent selection after locking.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • soorya-u/cyrus#8 — Introduces the AgentRuntime and ThreadCoordinator foundations refactored here.
  • soorya-u/cyrus#36 — Overlaps the run-turn prompt-stream and interruption handling path.
  • soorya-u/cyrus#40 — Overlaps the CLI agent doctor and health-check flow.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements the listed Phase 1 requirements: bindAgent, persisted session state, thread-scoped catalog RPCs, locking, cleanup, health filtering, and UI binding.
Out of Scope Changes check ✅ Passed The changes are broadly aligned with the draft-session lifecycle work and its supporting tests, schemas, handlers, and UI updates.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the PR’s main change: implementing ACP draft session lifecycle with thread-bound agent binding.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/acp-draft-session-lifecycle

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/cli/src/handlers/controller/projects.ts (1)

35-45: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Close thread sessions before deleting a project. deleteThreadsForProject only removes DB rows, so any bound ACP sessions for that project’s threads keep running. projectsHandlers needs ControllerDeps/runtime here so it can close each thread session before the bulk delete.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/cli/src/handlers/controller/projects.ts` around lines 35 - 45, Update
projectsHandlers and its deleteProject flow to receive ControllerDeps/runtime,
then close every bound ACP thread session for the project before calling
deleteThreadsForProject. Preserve the existing project deletion and
repository-error handling, and ensure session cleanup occurs before the bulk
thread-row deletion.
🧹 Nitpick comments (3)
shared/hooks/src/connection/use-agent-catalog.ts (1)

99-105: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use effortsQueryKey and personaQueryKey variables in onSuccess for consistency.

modelsQueryKey is reused at line 99, but effortsQueryKey (line 68) and personaQueryKey (line 79) are bypassed in favor of inline RTC_OPERATION_KEYS calls. This is a DRY inconsistency.

♻️ Proposed refactor
 	onSuccess: (data) => {
 		queryClient.setQueryData(modelsQueryKey, { models: data.models });
-		queryClient.setQueryData(RTC_OPERATION_KEYS.getEfforts(threadId), {
+		queryClient.setQueryData(effortsQueryKey, {
 			efforts: data.efforts,
 		});
-		queryClient.setQueryData(RTC_OPERATION_KEYS.getPersona(threadId), {
+		queryClient.setQueryData(personaQueryKey, {
 			personas: data.personas,
 		});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shared/hooks/src/connection/use-agent-catalog.ts` around lines 99 - 105,
Update the onSuccess cache updates in the agent catalog hook to use the existing
effortsQueryKey and personaQueryKey variables instead of calling
RTC_OPERATION_KEYS.getEfforts(threadId) and
RTC_OPERATION_KEYS.getPersona(threadId) inline. Keep the existing data payloads
and modelsQueryKey update unchanged.
openspec/changes/archive/2026-07-12-acp-draft-session-lifecycle/specs/acp-draft-session/spec.md (1)

65-68: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix grammar: "requires bound" → "requires a bound".

The scenario heading "Catalog requires bound session" uses nonstandard grammar.

📝 Proposed fix
-#### Scenario: Catalog requires bound session
+#### Scenario: Catalog requires a bound session
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@openspec/changes/archive/2026-07-12-acp-draft-session-lifecycle/specs/acp-draft-session/spec.md`
around lines 65 - 68, Update the scenario heading associated with getModels to
read “Catalog requires a bound session,” correcting the grammar while leaving
the scenario steps unchanged.

Source: Linters/SAST tools

openspec/changes/archive/2026-07-12-acp-draft-session-lifecycle/design.md (1)

35-41: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add language specifier to fenced code block.

The code block at line 35 has no language specified, triggering a markdownlint MD040 warning.

📝 Proposed fix
-```
+```text
 bindAgent → pool.getRuntime(agentName)  // initialize once
          → closeSession if thread had different agent/session
          → newSession({ cwd: project.cwd })
          → UPDATE threads SET agent_name, session_id
          → return { models, modes, efforts, personas, capabilities }
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @openspec/changes/archive/2026-07-12-acp-draft-session-lifecycle/design.md
around lines 35 - 41, Add the text language specifier to the fenced code block
containing the bindAgent lifecycle flow in the design document, preserving all
existing block content unchanged.


</details>

<!-- cr-comment:v1:1116ad05b14f1b64eccf72e0 -->

_Source: Linters/SAST tools_

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @apps/cli/src/core/agents/runtime.ts:

  • Around line 336-363: Update requireSession to cache and reuse the in-flight
    session-opening promise keyed by threadId, so concurrent cold-cache callers
    await one openOrCreateRuntimeSession operation. Remove the pending entry after
    completion or failure, preserve the existing sessions cache and error behavior,
    and ensure failed opens do not leave stale promises.

In @apps/cli/src/core/threads/coordinator.ts:

  • Around line 49-133: Normalize all AgentRuntime failures in Coordinator methods
    before returning: update bindAgent, getModels, getModes, getEfforts,
    getPersonas, setModel, setMode, setEffort, and setPersona to wrap
    requireSession, createBoundSession, and session-setter awaits with
    Result.tryPromise or equivalent handling. Map rejected runtime/session
    operations to a dedicated coordinator error for session or runtime
    unavailability, preserving the existing typed Result contract and cleanup
    behavior.

In @shared/hooks/src/connection/use-agent-catalog.ts:

  • Around line 130-146: Add an error guard to the auto-bind useEffect using the
    bind mutation’s existing error state, alongside bindAgentPending, so failed
    automatic binds do not trigger repeated requests while boundSessionId is unset
    and agentLocked is false. Keep selectAgent’s manual retry behavior intact by
    relying on its existing mutation-state reset.

In @shared/schemas/src/rtc/catalog.ts:

  • Around line 18-26: Update BindAgentOutputSchema to include the required
    capabilities field using the established schema type, and update the bindAgent
    worker output to populate it alongside models, modes, efforts, and personas.
    Ensure the implementation matches the contracts in the ACP draft-session and
    wire-schema specifications; if capabilities remains intentionally deferred,
    revise those specifications instead.

Outside diff comments:
In @apps/cli/src/handlers/controller/projects.ts:

  • Around line 35-45: Update projectsHandlers and its deleteProject flow to
    receive ControllerDeps/runtime, then close every bound ACP thread session for
    the project before calling deleteThreadsForProject. Preserve the existing
    project deletion and repository-error handling, and ensure session cleanup
    occurs before the bulk thread-row deletion.

Nitpick comments:
In @openspec/changes/archive/2026-07-12-acp-draft-session-lifecycle/design.md:

  • Around line 35-41: Add the text language specifier to the fenced code block
    containing the bindAgent lifecycle flow in the design document, preserving all
    existing block content unchanged.

In
@openspec/changes/archive/2026-07-12-acp-draft-session-lifecycle/specs/acp-draft-session/spec.md:

  • Around line 65-68: Update the scenario heading associated with getModels to
    read “Catalog requires a bound session,” correcting the grammar while leaving
    the scenario steps unchanged.

In @shared/hooks/src/connection/use-agent-catalog.ts:

  • Around line 99-105: Update the onSuccess cache updates in the agent catalog
    hook to use the existing effortsQueryKey and personaQueryKey variables instead
    of calling RTC_OPERATION_KEYS.getEfforts(threadId) and
    RTC_OPERATION_KEYS.getPersona(threadId) inline. Keep the existing data payloads
    and modelsQueryKey update unchanged.

</details>

<details>
<summary>🪄 Autofix (Beta)</summary>

Fix all unresolved CodeRabbit comments on this PR:

- [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended)
- [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `d0aa1913-5ed1-44e8-993b-346fdf8fcaa4`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between ed9680b38c7147626b30186c5511e5b5998399c2 and e68ae18edccfdc7de099d1bebfa43fbd54c795ae.

</details>

<details>
<summary>📒 Files selected for processing (51)</summary>

* `apps/cli/__tests__/integration/draft-session-lifecycle.test.ts`
* `apps/cli/__tests__/integration/wiring.test.ts`
* `apps/cli/src/commands/agents/doctor.ts`
* `apps/cli/src/core/agents/health.test.ts`
* `apps/cli/src/core/agents/health.ts`
* `apps/cli/src/core/agents/runtime.test.ts`
* `apps/cli/src/core/agents/runtime.ts`
* `apps/cli/src/core/threads/coordinator.ts`
* `apps/cli/src/errors/coordinator.ts`
* `apps/cli/src/handlers/controller/agents.ts`
* `apps/cli/src/handlers/controller/catalog/effort.ts`
* `apps/cli/src/handlers/controller/catalog/mode.ts`
* `apps/cli/src/handlers/controller/catalog/model.ts`
* `apps/cli/src/handlers/controller/catalog/persona.ts`
* `apps/cli/src/handlers/controller/chat.ts`
* `apps/cli/src/handlers/controller/index.ts`
* `apps/cli/src/handlers/controller/projects.ts`
* `apps/cli/src/handlers/controller/threads.ts`
* `apps/cli/src/utils/error.ts`
* `apps/cli/src/utils/run-turn.test.ts`
* `apps/cli/src/utils/run-turn.ts`
* `apps/cli/tsconfig.json`
* `apps/web/src/components/chat/composer/agent-model-picker.tsx`
* `openspec/changes/acp-draft-session-lifecycle/tasks.md`
* `openspec/changes/archive/2026-07-12-acp-draft-session-lifecycle/.openspec.yaml`
* `openspec/changes/archive/2026-07-12-acp-draft-session-lifecycle/design.md`
* `openspec/changes/archive/2026-07-12-acp-draft-session-lifecycle/proposal.md`
* `openspec/changes/archive/2026-07-12-acp-draft-session-lifecycle/specs/acp-draft-session/spec.md`
* `openspec/changes/archive/2026-07-12-acp-draft-session-lifecycle/specs/acp-provider-cli/spec.md`
* `openspec/changes/archive/2026-07-12-acp-draft-session-lifecycle/specs/acp-session-router/spec.md`
* `openspec/changes/archive/2026-07-12-acp-draft-session-lifecycle/specs/conversation-persistence/spec.md`
* `openspec/changes/archive/2026-07-12-acp-draft-session-lifecycle/specs/wire-schemas/spec.md`
* `openspec/changes/archive/2026-07-12-acp-draft-session-lifecycle/tasks.md`
* `openspec/specs/acp-draft-session/spec.md`
* `openspec/specs/acp-provider-cli/spec.md`
* `openspec/specs/acp-session-router/spec.md`
* `openspec/specs/conversation-persistence/spec.md`
* `openspec/specs/wire-schemas/spec.md`
* `shared/connections/src/contracts/controller.ts`
* `shared/constants/src/operation-keys.ts`
* `shared/database/src/models/threads.ts`
* `shared/database/src/repositories/conversations.ts`
* `shared/database/src/repositories/threads.ts`
* `shared/database/src/utils/error.ts`
* `shared/hooks/src/connection/use-agent-catalog.ts`
* `shared/hooks/src/connection/use-controller-threads.ts`
* `shared/hooks/src/stores/agent-catalog.ts`
* `shared/schemas/src/rtc/catalog.ts`
* `shared/schemas/src/rtc/common.ts`
* `shared/schemas/src/rtc/threads.test.ts`
* `shared/schemas/src/rtc/threads.ts`

</details>

<details>
<summary>💤 Files with no reviewable changes (2)</summary>

* openspec/changes/acp-draft-session-lifecycle/tasks.md
* shared/hooks/src/stores/agent-catalog.ts

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment thread apps/cli/src/core/agents/runtime.ts
Comment thread apps/cli/src/core/threads/coordinator.ts
Comment thread shared/hooks/src/connection/use-agent-catalog.ts
Comment thread shared/schemas/src/rtc/catalog.ts
soorya-u and others added 2 commits July 12, 2026 18:50
Cover bind, lock, and delete flows with a dev-stack RPC script, extend integration coverage for session cleanup, and align e2e harness auth origins with localhost development.

Co-authored-by: Cursor <cursoragent@cursor.com>
Deduplicate concurrent session opens, map runtime failures to coordinator errors, include bind capabilities in the wire schema, stop auto-bind retry loops on error, and close bound sessions when deleting a project.

Co-authored-by: Cursor <cursoragent@cursor.com>
@soorya-u
soorya-u merged commit 6dfd1f7 into main Jul 12, 2026
7 checks passed
@soorya-u
soorya-u deleted the feat/acp-draft-session-lifecycle branch July 12, 2026 15:00
soorya-u added a commit that referenced this pull request Jul 13, 2026
Reflect merged PRs #51 and #52 in OpenSpec artifacts: thread worktree cwd for attachments, composer agent-loading patterns, and feed vs git diff distinction for permissions.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 1: ACP draft session lifecycle

1 participant