.kilo/plans/agent-manager-multi-project-shipping-gaps.md
Status as of 2026-07-27. The multi-project sidebar renders and works end to end in the self-test harness (both projects list worktrees/sessions/sections, section creation persists, worktree delete works, project switching restores selection, live session upsert verified). Full unit suite green (3444 pass / 0 fail), typecheck, lint, knip, arch caps all pass. Everything is uncommitted in the worktree.
This document lists what is still missing, ranked by whether it should block shipping.
Two audiences matter for the blocking decision:
kilo-code.new.experimental.multiProject, default off. Rough edges here are
acceptable if they cannot corrupt data.initializeState and onRequestState only called refreshSessions() when the
managed state contained at least one session. With zero managed sessions the
backend listing never ran, sessionsLoaded never reached the webview, and both
the WORKTREES and SESSIONS sections stayed on skeletons forever (the worktree
gate requires worktreesLoaded() && sessionsLoaded()). Any user whose state file
lost its sessions — exactly what the earlier session-persist bugs caused — hit a
permanently empty-looking sidebar. Reproduced in the harness on a worktree-root
workspace and fixed by making both refreshes unconditional. Root-caused via
stage-by-stage init logging on 2026-07-27; do not reintroduce a content-based
guard here.
The config binding rework requires every updateConfig sender to carry a binding id,
and configUpdateFailed currently wipes the draft for the failed scope. Any existing
sender without a binding (permission dock, model picker, auto-approve, onboarding)
now fails or loses the user's unsaved edits.
expected is optional in the overlay schema, writer, and
handler, so a client without a binding writes unconditionally again instead of
getting a 400. The webview half (draft retention on configUpdateFailed, and the
audit of every updateConfig sender) is still open.updateConfig sender and classifying it.feat(vscode): config write revision bindings) so it can be reverted alone.Partially addressed: untrusted projects are now filtered out of the consent list
(P0-4), and config scope switching plus project-scoped indexing.enabled writes
were restored after the rework had hardcoded the tab to global scope (the earlier
P1-8 gap). The remaining blocker is the read path below.
fetchAndSendIndexingStatus issues PUT /indexing/consent (a write) on a plain
status refresh, defaulting to enabled: false for any project not in local
globalState. On a fresh machine/profile, the first status read turns indexing off
for users who had it on. A refresh can also target the wrong project
(requestIndexingStatus resolves from the current session's directory). Separately,
the Indexing tab lists untrusted projects and would let a user enable indexing for a
repo the trust system has not approved.
packages/opencode (shared upstream code, needs kilocode_change markers) or the
cloud repo.feat(vscode): per-project indexing consent).Everything currently sits uncommitted in one worktree. The review's recommendation
stands: git reset --soft origin/main and stage by path into three commits —
multi-project, config bindings, indexing consent. The multi-project commit message
must not promise per-project config/indexing behavior that lands in the other two.
projectIdFor hashes the canonical path verbatim while samePath folds case, so two
casings of one repo produce two project ids, two contexts, and two state managers
racing to write the same .kilo/agent-manager.json (last write wins, worktrees and
sessions vanish).
projectIdFor on darwin/win32; reject addProject when
samePath matches any registered root.canonicalizePath already realpaths existing
paths; the fallback branch must fold case too.Two VS Code windows each have their own ProjectContexts with independent generation
counters feeding one shared ProjectRouteService. Panel B registering a project at
generation 0 while panel A is at 2 unregisters panel A's routes; closing one panel
drops routes the other still needs.
panelId + projectId + sessionId), generations issued
by the service, ambiguity computed across all panels.gh pr noise in repos without remotes — Bug 5gh pr view fails every 15s per worktree forever, logs before the dedupe, and
pollOnce rejections are unhandled.
lastHash dedupe; void this.pollOnce().catch(log) in
schedule; per-root remote probe with a single error emission; skip PR pollers for
remote-less projects in ProjectPollers.sync.createLocalTabs migrates persisted
localSessionIDs into a single bucket that tabKey() never reads once the
catalog arrives. Migrate single to the pinned project id on first state apply.restoreProjectTarget skips tab bookkeeping (P1-3): a restored session has no
tab. Call selectLocal() first, add the tab, then select.ensurePendingTab runs before restore (P1-4): switch adds a "New Session"
draft that restore may contradict. Move it after restore.setActiveTarget writes
.kilo/agent-manager.json on every selection change. Debounce or persist on
deactivation/dispose only.resolveProjectRoot process spawning (P1-19).Tracked as #12685 (section parity) and #12686 (sidebar drag-and-drop).
AgentManagerApp keeps the legacy renderBody (now SidebarBody.tsx) and
ProjectSidebarBody.tsx as a reduced reimplementation. The reimplementation already
caused one full outage of multi-project mode (missing DragDropProvider crashed the
webview render). Missing versus legacy: worktree ordering, drag-and-drop reorder and
move-to-section, grouping, busy/navHint/shortcut badges, section auto-rename, stats
skeletons.
SidebarBody.tsx is deleted.ProjectScope plus the this.state/this.context getters make the target project
invisible at call sites; any continuation escaping ALS silently falls back to the
active project. provider-lifecycle.ts shows the intended end state (explicit deps).
Keep threading ctx explicitly into the remaining handler groups; add a dev-mode log
in the context getter when a project-stamped message resolves without a scope.
worktrees(), managedSessions(), selection() etc. are single-valued with
memKey()/tabKey() and two "current project" accessors that disagree during the
switch window. Long-term: one createProjectStore(projectId) per project. For now
the gating added to the remember effect contains the known race.
upsertSession, byDirectory, fresh-skip
re-post) but not E2E-verified: the harness backend's basic-auth credentials could
not be re-extracted after a window reload, so external session creation was not
exercised live. Verify manually: kilo a new session in a registered repo from a
terminal and watch it appear in that project's sidebar.SidebarBody/TabBar stories is the
cheapest safety net.