SuperAPI AI Directory

session-management

Agents & models

View repository

Updated 11-03 10:56

About this skill

Session Management Design Distillation

Use this skill when the change scope hits any of the following:packages/services/src/session/taskIndexRepo.ts、packages/services/src/zcode-agent/zcodeTaskIndexSyncer.ts、zcodeTaskServiceAdapter.ts、packages/ui sidebar / task list / project tree,apps/zcode-cli/packages/adapters/src/storage/session-store/*, sessions-index protocol, workspace / project / roots data structures, two-database migration.

Authoritative spec:docs/superpowers/specs/2026-09-09-virtual-project-design.md and docs/ui/task-grouped-view.md. The 2026-10-13 group restoration ruling overrides the historical Group-merges-into-Project plan. This skill does not repeat the spec; it only covers "why" and "how to avoid pitfalls."

1. One-sentence principle

Session facts belong to the CLI, organizational state belongs to the Host; each field has exactly one writer.

CLI  db.sqlite      存在性(软删除 time_deleted)、title+title_source、time_created/updated、last_outcome、正文、goal、fork、trace
Host tasks-index    project_id、pinned、archived、unread_at(+CAS)、sort_order、projects/roots/hosts、定时/闲时;控制端另存跨设备分组成员与顺序
                    cli_* 列 = CLI 事实的只读镜像,唯一写者 taskIndexSyncer
禁止:CLI 写 app 库;Host 写 db.sqlite;Host 对任何 cli_* 列写 Date.now() 或从 snapshot 推导的值

Why

Before the refactor, there were 30+ bugfix comments related to title / status (rg "Bugfix" packages/services/src/zcode-agent/zcodeTaskIndexSyncer.ts packages/services/src/session/taskIndexRepo.ts), and almost all of them fall into two categories:

  1. The Host writes session facts on behalf of the CLI:updated_at = Date.now()(rename / status change) diverges from the CLI's time_updated , causing sort jitter, and the UI is forced to trust only the sessions-index's lastActivityAt; rename writes locally first and then best-effort notifies the CLI, logging only a warning on failure, so the two sides disagree on "whether the title is manual";task_status is inferred from the snapshot, so a cold start always yields completed, and residual running states can only be absorbed by UI rules.
  2. The CLI decides the row set on behalf of the Host: in 3.4.0, a sessions-index snapshot missing summary caused historical tasks to disappear from the Grouped view (docs/superpowers/specs/2026-07-20-sidebar-task-row-authority-design.md); a CLI cold seed can resurrect sessions the app has already deleted, and the app can only suppress them via tombstone.

Conclusion: the question is not "which database is better" but "who has first-hand knowledge of this fact." Only the CLI knows what happened in a session; only the Host/UI knows what the user did to the list; the CLI is one process per workspace and may be offline, so a cross-workspace list must be persisted on the Host side (onDynamicWorkspaceEvent comments explicitly forbid launching an Agent for the list).

2. Invariants (check each one before changing code)

# Invariant Typical symptom when violated
I1 workspaceKey = workspaceIdentity?.trim() || workspacePath only expresses identity isolation; execution paths always use workspacePath / root_path remote identity gets written into cwd (3.4.2 incident)
I2 the session's bound directory (cwd) never changes after creation; moving a project only changes project_id, moving between groups only changes members and order after cold start, provider / memory / permissions cannot be found
I3 the project's other roots serve only as additionalDirectories for skills / commands / subagents / writable scope; AGENTS.md, settings, MCP, hooks, plugins, memory, permissions are resolved only along the bound directory chain executable configuration from another directory leaks into the current session; same-name MCP/hook conflicts
I4 the CLI is unaware of projects; the protocol only has a directory list the CLI having a project concept = design drift
I5 the row set is determined by the app database; sessions-index only supplements real-time fields (phase, lastActivityAt, pending interaction) tasks disappear from the list / come back to life
I6 sort time only uses the mirror of the CLI's creation/activity time; keep running/prewarming priority, stable sorting by creation time within the running layer, and creation/update time preference in the normal layer; pin / archive / rename / unread do not change time jumps to the top after renaming
I7 Unread uses CAS (expectedUnreadAt), only Host explicitly unreadSignal creates unread Mobile read is overridden by desktop; full table flickers on every round completion
I8 Title priority: user override (title_source=custom) > model generated > first prompt > placeholder "New session"; snapshot never overrides override Manual title reverts
I9 Delete = CLI soft delete (time_deleted),app deleted is a mirror; cold seed / listSessions / TUI must filter Deleted session resurrects
I10 Both DB schemas only add, never delete or change semantics of old columns; new columns nullable or with defaults;ON CONFLICT(workspace_key, task_id) 's unique constraint must be preserved Old version crashes after downgrade
I11 Derived directory hash (sessions/{hash}、agent-config/{provider}/{hash}, CLI memory) input is the real directory + identity, unrelated to project, not migrated Config / memory lost after upgrade
I12 Mobile remote-control bridge / queue / snapshot key only use workspaceKey; desktop continuous and mobile replayable boundaries unchanged Remote control crosses sessions
I13 Host organization state exists only in the original column, no copy; old column deleted / title_overridden is "intent pending handoff to CLI", full-table reconciliation on every startup, cannot only fill NULL Deletes / renames during downgrade are lost after re-upgrade
I14 sessions-index snapshot absence does not equal deletion; deletion can only come from session.removed or sessionsIndex/reconcile returning deleted Task resurrects or is mistakenly deleted after event loss
I15 Restoring a CLI DB backup must disable foreign keys and verify row count / hash;message/part to session is ON DELETE CASCADE, a bare DELETE empties the body All messages lost after restore
I16 Additional directories are resolved uniformly by Host and carried through create / explicit resume / V4 subscribe cold recovery; what is excluded is session cwd, not the primary directory; no refresh while a running instance exists Desktop opening historical tasks cannot get additional directories; directory scope of old sessions is messed up after changing the primary directory
I17 Empty-ownership sessions are classified exactly once on first discovery (bit 8); no automatic project creation; unclassified ones are visible in task area, timeline or groups. Missing app row = unclassified [], missing project / read failure = explicit error Removed workspace resurrects; session later gets quietly moved into a project; TUI new session cannot be opened

3. Key decisions quick reference (see spec section 3 for details)

  • 2026-10-10 remote decision: local/remote projects are flat, Host does not act as grouping; remote creates a single-directory project first then creates the session, no project-less creation or drag-to-append directories allowed. Unified project ordering follows controller-side sidebar preferences. See docs/superpowers/specs/2026-10-10-session-manage-upstream-integration.md。
  • Sidebar = global pinned area (session + pinned projects) → project area → task area (project_id IS NULL, any cwd);~/.zcode/workspace/* only serves as the default execution directory for global new tasks. Filtering includes by project / by group / recently updated; real directories are not nodes, the old two-level switcher is not restored.
  • Project: local 0..n roots of the same host + one primary directory, remote exactly one fixed directory; the same directory can belong to multiple projects; new session cwd = primary directory; when local has 0 roots, automatically create ~/.zcode/workspace/<ts> and add as primary root.
  • Deletion of local projects and authoritative online remote projects = soft-delete the container; sessions retain their ownership and remain visible on the timeline; when creating a new project whose roots set is exactly equal, prompt to restore; no permanent deletion is provided. Offline deletion of a remote project only removes the local entry and cache, and does not trigger remote soft deletion or restoration.
  • Removing a root from a local project / changing the primary directory does not affect already-created sessions, scheduled tasks, idle tasks, or Bots (they fix cwd and attached directories at creation time); roots changes take effect for a running session on its next resume.
  • Skills / subagents with the same name from different roots coexist, with qualified names assigned by the CLI <rootName>/<name>。
  • Grouping: the controlling Host uniquely holds cross-device groups, and phones reuse that Host; groups are not created per workspace, and groups are not converted into projects. Grouping does not change cwd, projects, CLI time, configuration, or permissions. Reads are read-only; single-node moves re-check source/anchor within the transaction, and absence does not delete; system tasks are grouped only on first occurrence and are not pulled back after manual removal.
  • Display preference: a valid new-version view takes priority; otherwise the old organizeBy is used; old grouped defaults to grouping by group. Newly created tasks fix the draft's group placement, which is submitted idempotently after the first send, without guessing during navigation.
  • Remote: project and task organizational state stays in the remote app database; locally, persist host connection records, project entries the user has added, and basic information caches. When offline, entries cannot be expanded, renamed, or have roots edited; project operations only allow removing the local entry and cache, while remote projects and sessions are retained, with no pending-sync deletion queue. Remote allows only one fixed root determined at creation, with a host limit of 20, and saved hosts are auto-connected at startup.
  • Reconnection only refreshes entries that still exist locally; late responses must not upsert removed entries; setting.json is derived from the local entry list and does not reverse-rebuild removed entries. Re-adding a remote project must be actively chosen by the user, or explicitly added by a successful online creation; deleting a local host only removes the connection and its associated entries.
  • The settings page's agent configuration management is organized by real directory (roots deduplicated and flattened); scheduled / idle / Bots select virtual projects.

4. Data and migration discipline

  • A versioned migration runner is shared by both databases (extracted from apps/zcode-cli/.../session-store/migration-runner.ts):schema_migration(id, checksum, app_version), checksums are immutable, unknown ids are ignored.
  • Each migration explicitly declares touchedTables; the runner backs up only these tables in the same read transaction (ATTACH + CREATE TABLE AS SELECT); backups are only for recovery from migration failure, and the normal flow only produces them without consuming them, with no whole-database snapshot (local whole database 770MB vs table-level 16MB).
  • Each migration must verify the content hash of all tables before and after the fixture, allowing only touchedTables to change. The App uses E2E or repeatable runtime acceptance and no longer adds business unit tests; CLI migrations retain independent unit tests.schema.baseline.sql is diffed by CI as additions only.
  • Compatibility retirement in three phases: V1 reads new columns only and old columns are written but not read; V2 writes schema_floor and stops dual writes; V3 physically deletes retired columns / time_archived and rebuilds primary keys. The group tables have been restored to runtime state and cannot be deleted per the old plan. Do not introduce "permanent" compatibility branches at any phase.
  • Downgrade relies on additive schemas, not backups; unowned sessions left over from the first migration, newly added by older versions, or first discovered by the CLI are adjudicated only once: if the directory uniquely matches a non-deleted project under the same host, they are assigned to it; if there are zero or multiple matches, they are kept with NULL and enter the task area, and no project is created automatically. The only exception is the first migration of a remote Source Server: the remote itself has no old workspace manifest, so a single root project is created based on the historical task directories (virtual project spec 2026-10-15 ruling). Old groups, empty groups, system groups, colors, members, and order continue to be read; the old Group → Project conversion is canceled, and workspace → Project is retained.
  • project_id IS NULL is a valid uncategorized state, not a pending-migration marker. At startup, first identify host_id IS NULL rows newly added by older versions, then fill in identity and first assignment in the same transaction; when a new version creates or first inserts a row, it atomically writes identity and assignment. Processed NULL rows are not automatically assigned due to startup, reconciliation, or the addition of a project with the same directory; globally newly created tasks explicitly remain uncategorized.
  • Migration reuses the same SQL / Repo; the startup gate initializes business services only after the Host Worker completes; wait asynchronously for the lock, publish ready only after the migration transaction commits, and fail-closed on failure (retry / open the backup directory). Do not create another migration implementation.

5. Pre-change checklist

  1. Who is thesole writerof this field? If you want to write it on the other side, first go back to Section 1 and reassess; usually it should be changed to sending a command (renameSession / deleteSession) and then waiting for the mirror.
  2. Does the timestamp you write come from a CLI event or Date.now()? The latter is only allowed to write Host organizational state's own time columns (pinned_at、meta_updated_at) and is not allowed in any ordering basis.
  3. Will sessions-index decide whether a row exists or disappears? Not allowed.
  4. Was workspacePath used for identity equality checking or key creation? Remote must include workspaceIdentity。
  5. Are new columns / tables nullable or defaulted, and have schema.baseline.sql and migration's touchedTablesbeen updated, and is there per-table hash verification evidence?
  6. Changed CLI configuration discovery logic? It is only allowed to be consumed in the three places: skills / subagents / path policy additionalDirectories; the rest remain based only on workingDirectory。
  7. For behavior changes involving title / status / unread / archive / delete, do e2e tests cover both intersections of "desktop + mobile remote control" and "local + SSH/WSL"?
  8. For schema changes, has the e2e "upgrade old fixture → read/write through old code paths (including modifying existing rows: delete, rename) → upgrade again" been run?
  9. For code that touches delete / title, are the three stages "intent bit → lazy handoff → mirror write-back" maintained, as well as orphan handling?

6. Reference implementation and documentation

  • Current facts:docs/task/task-sqlite-index.md、docs/v4-refactor/14-sessions-index.md、docs/ui/task-title-authority.md、docs/ui/task-unread-state.md、docs/task-list-refresh-semantics.md、docs/wsl-task-status-inconsistency.md(root cause of subscriptions being silently replaced).
  • Historical decisions:docs/superpowers/specs/2026-07-13-v4-sidebar-task-state-authority-design.md、2026-07-20-sidebar-task-row-authority-design.md、docs/local-workspace-path-cold-resume-identity.md。
  • Current groups and views:docs/ui/task-grouped-view.md、docs/ui/sidebar-task-view-switcher.md。
  • Deprecated (retained only for downgrade understanding):docs/grouped-task-workspace-bootstrap.md、apps/zcode-cli/docs/design/v2/multi-workspace.md。