Skip to content

feat(acp): emit plan session updates from todos store - #1030

Merged
tautvydasLiekis merged 14 commits into
masterfrom
feat/acp-plan-updates
Sep 4, 2026
Merged

feat(acp): emit plan session updates from todos store#1030
tautvydasLiekis merged 14 commits into
masterfrom
feat/acp-plan-updates

Conversation

@tautvydasLiekis

@tautvydasLiekis tautvydasLiekis commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What

Emits stable-v1 ACP plan session updates (sessionUpdate: "plan") directly from the Todo store:

  • Every Todo write (create_todos / update_todos / mark_todo / clear_todos, including Ferment-synced Todos) recomputes the most-specific non-empty scope as a full-replacement PlanEntry[] snapshot.
  • Identical snapshots are suppressed; clearing a narrow scope falls back to a populated broader scope instead of flashing an empty checklist.
  • loadSession uses the same scope selection for restored Todos; sessions without restored Todos emit nothing.
  • A newly opened empty session survives an agent reload by recreating its in-memory manager with the same ID; it remains unpersisted until a real turn, avoiding abandoned-session files.
  • Mapping per stable v1 (pending | in_progress | completed): in_progress prefers activeForm; blocked maps to pending while PlanEntry._meta["kimchi.dev"] preserves todoStatus: "blocked" and the optional note.
  • Scope metadata rides Plan._meta["kimchi.dev"].scope; spec-compliant clients ignore it.
  • Notifications are session-scoped and unsubscribed on close, shutdown, and error paths.

Why

Zed renders ACP plan updates natively as a live checklist in the agent panel, so IDE users can watch ordinary, plan-mode, and Ferment-generated Todo progress without separate lifecycle-specific ACP integrations. The Todo store remains the single checklist source; ACP v2 can replace only the wire encoder with plan_update.

Example

☐ write tests
► wiring emission            ← in_progress, from activeForm
☐ deploy                     ← blocked status and note in entry metadata
✓ done task
Todo checklist rendered in Zed

Validation

  • src/modes/acp/plans.test.ts: 11 tests covering mapping, dedupe, scope fallback, session isolation, restore, teardown, and send-failure isolation.
  • Focused Todo/Ferment/ACP seam suite: 269/269 passed.
  • src/modes/acp/server.test.ts: 227/227 passed, including empty-session reload without JSONL junk and invalid-ID rejection.
  • tests/e2e/acp/plan-updates.test.ts: 4 real-binary scenarios covering create/update/clear, cross-session isolation, ordinary Todos under --plan, and the Ferment phase/step lifecycle.
  • Full ACP E2E suite: 28/28 passed.
  • pnpm run check and pnpm run build:binary passed.

Co-Authored-By: Kimchi noreply@kimchi.dev

ACP clients (Zed renders these natively) receive a stable-v1 plan
sessionUpdate with the full PlanEntry snapshot whenever a todo write
lands in the session store, plus one initial snapshot on resume.
Blocked todos map to a neutral [blocked] marker (no failed status in
v1); scope metadata rides Plan._meta["kimchi.dev"].scope.

Co-Authored-By: Kimchi <noreply@kimchi.dev>
@tautvydasLiekis tautvydasLiekis added the new feature Introduces a new feature label Aug 14, 2026
@kimchi-review

kimchi-review Bot commented Aug 14, 2026

Copy link
Copy Markdown

Kimchi Code Review

Property Value
Commit a751fca
Author @tautvydasLiekis
Files changed 0
Review status Completed
Comments 1 (1 warning)
Duration 51s

Summary

📊 Review Score: 92/100 (overall code quality — 0 lowest, 100 highest)
⏱️ Estimated effort to review: 3/5 (1 = trivial, 5 = very complex)

🧪 Tests: yes — Strong coverage across three layers: unit tests for buildPlanEntries/buildPlanUpdate in plan-mapper.test.ts, integration tests in server.test.ts for emission, session isolation, close/shutdown, and loadSession resume behavior, plus an E2E test in tests/e2e/acp/plan-updates.test.ts exercising create_todos/update_todos/clear_todos tool calls and full-replacement plan snapshots.

📝 Found 1 issue(s). See inline comments for details.

What to expect

Kimchi will analyze the changes in this pull request and post:

  • A summary of the overall changes
  • Inline comments on specific lines with findings categorized by issue type

The review typically completes within a few minutes. This comment will be updated once the review is ready.

Interact with Kimchi
  • @getkimchi review — re-trigger a full review on the latest commit
  • @getkimchi summary — regenerate the PR summary
  • @getkimchi ignore — skip this PR (no review will be posted)
  • Reply to any inline comment to ask follow-up questions or request clarification
Configuration

Reviews are configured by your organization admin.
Review instructions, excluded directories, and severity thresholds can be adjusted per repository in the Kimchi dashboard.


Powered by Kimchi — AI-powered code review by CAST AI

@kimchi-review kimchi-review 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.

📊 Review Score: 92/100 (overall code quality — 0 lowest, 100 highest)
⏱️ Estimated effort to review: 3/5 (1 = trivial, 5 = very complex)

🧪 Tests: yes — Strong coverage across three layers: unit tests for buildPlanEntries/buildPlanUpdate in plan-mapper.test.ts, integration tests in server.test.ts for emission, session isolation, close/shutdown, and loadSession resume behavior, plus an E2E test in tests/e2e/acp/plan-updates.test.ts exercising create_todos/update_todos/clear_todos tool calls and full-replacement plan snapshots.

📝 Found 1 issue(s). See inline comments for details.

Comment thread src/modes/acp/server.ts
models: buildSessionModelState(configOptions),
}
} catch (err) {
unregisterAcpPrompter(session.sessionId)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️🔧 Maintainability

The newSession error handler calls unsubscribePlanSync() but does not call the session's base unsubscribe() before disposing. The loadSession error handler correctly unsubscribes both, so this asymmetry is inconsistent and can leave a live session subscription attached to a disposed session if creation fails after session.subscribe() is wired.

💡 Suggestion: Store the session record in a local before cleanup (or retrieve it once) and call both record.unsubscribe() and record.unsubscribePlanSync() in the newSession catch block, matching the cleanup in the loadSession error path.

@tautvydasLiekis
tautvydasLiekis marked this pull request as ready for review August 14, 2026 11:42
@readme-ai-writer

readme-ai-writer Bot commented Aug 14, 2026

Copy link
Copy Markdown

Documentation Changes Added

Page Section Action Summary
coding-acpGuides📝 UpdatedAdd documentation for ACP plan updates — live todo progress displayed as a checklist in supported IDEs.

🔗 View all changes in ReadMe


Actions

  • Merge documentation branch with PR merge
  • Delete documentation branch with PR close

If neither actions are selected, on PR close/merge the docs branch in ReadMe will remain open.

@readme-ai-writer

readme-ai-writer Bot commented Sep 4, 2026

Copy link
Copy Markdown

📚 No documentation changes were needed for this PR.

Comment thread src/modes/acp/plans.ts

@vmizg vmizg left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Simpler model looks correct. A few edge cases worth considering:

  1. No dedupe. The todo-sync bridge writes multiple scopes/no-op updates in quick succession; clients may get flooded with identical full-replacement plans. The old tracker JSON-deduped consecutive identical plans.

  2. Empty-scope flashes. When the bridge clears a step scope on STEP_COMPLETED, the tracker emits entries: [] even if the phase scope still has content.

  3. Restore snapshot picks one scope. restoredPlanSnapshot emits only the most specific non-empty scope, then live writes from other scopes can switch the client's plan view unexpectedly.

  4. Missing Ferment-lifecycle E2E coverage. The old tests/e2e/acp/plan-updates.test.ts drove the real binary through activate_ferment_phase → start_ferment_step → complete_ferment_step → complete_ferment_phase; the new version only exercises direct create_todos/update_todos/clear_todos calls. Given the architecture now treats the todo store as the source of truth, this is a coverage gap rather than a logic bug, but worth adding back if you want integration confidence for the bridge-to-tracker path.

@tautvydasLiekis
tautvydasLiekis added this pull request to the merge queue Sep 4, 2026
Merged via the queue into master with commit e1cb3ff Sep 4, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new feature Introduces a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants