# Workspace Issue Manager This document records the reusable issue-manager architecture shared by `tutti` and `tsh`. The TypeScript package `packages/workspace/issue-manager` is the current public frontend/package surface for this boundary. Complementary daemon-side or Go domain packaging may continue to evolve behind the same host-adapter model. ## Direction The issue manager is a workspace-domain capability that should be implemented as reusable packages plus host adapters. The shared domain model is: ```text Issue -> Task -> Run ``` - `Issue` is the top-level work item or theme inside one workspace. - `Task` is an executable unit under an issue. - `Run` is one agent execution attempt for a task. Workbench integration should expose this capability as an issue manager node. The node kind should be `issueManager`, and React-facing names should use `IssueManagerNode` / `IssueManagerWorkbenchFeature`. ## Package Split Use two thick packages: ```text packages/workspace/issues packages/workspace/issue-manager ``` ### `packages/workspace/issues` This Go package owns transport-agnostic issue-manager behavior: - issue, task, run, output, and context-reference models - status, priority, pagination, and search normalization - title-only issue and task list search; content does not produce list matches - task status projection onto issue summaries - run lifecycle orchestration - a `Store` interface for concrete persistence adapters It must not own: - HTTP, gRPC, OpenAPI, or protobuf contracts - Electron IPC or desktop preload details - `tsh` room membership, visitor share tokens, or invite capability - `tuttid` process wiring - concrete MySQL or SQLite queries `tuttid` should adapt this package to local SQLite-backed storage. `tsh-server` should adapt it to the existing remote control-plane storage and room policy surface. ### `packages/workspace/issue-manager` This TypeScript package owns the reusable workbench feature surface: - shared DTOs and service adapter interfaces - reusable OpenAPI fragment under `openapi/issue-manager.v1.yaml` - issue-manager view state and workbench node registration helpers - React-facing issue manager node components and controller logic - default i18n resources and UI-system-backed Tailwind utility styling - host-agnostic rich-text context reference handling - host-agnostic run lifecycle UI flow It must not read product globals such as `window.tutti`, `legacy TSH preload globals`, or any host-specific preload surface. Hosts provide explicit adapters instead. Expected exports: ```text . ./contracts ./core ./i18n ./services ./ui ./workbench ``` `./core` owns stateless primitives and feature foundation. `./services` should be the default dependency-injection entry for hosts. It owns service/session creation plus shared state orchestration. `./ui` should consume those services and stay focused on rendering, DOM interaction, and imperative UI bridges. Issue and task content editors consume host-provided `@` rich-text trigger providers through the workbench node seam. The default issue-manager picker uses the shared rich-text mention palette with top-level Agent and App tabs, aligned with Agent GUI mention behavior: Agent mentions resolve `agent-target` providers, App mentions resolve `workspace-app` providers, and the menu opens as soon as the `@` trigger is typed, before a search term exists. Files and issue references should stay on their explicit reference actions instead of appearing in this `@` picker, and the Task Center app itself should be excluded from issue-manager app mentions. Issue run creation is target-first. UI, CLI, and AgentGUI sidecar flows pass `agentTargetId`/`--agent-target-id` as the launch authority; the daemon derives and persists the provider for display, filtering, and legacy compatibility. An explicit manual create-and-run operation commits its `Run` and prepared launch intent in one SQLite transaction. `PrepareRun` performs validation and constructs any required implicit top-level Task without writing it. The SQLite admission adapter inserts that Task, claims it, writes the Run and intent, and updates Issue projections atomically. If intent admission fails, no implicit Task, Run, or projection may survive. The intent carries stable Agent session/client-submit identities and an immutable prompt plus attachment-path snapshot, and is leased before external delivery. Prepared and leased payloads pin their managed files independently of mutable ContextRefs. Confirmed delivery marks the intent dispatched; authoritative evidence that no Agent turn started settles the intent, Run, task/Issue projections, and topic activity in one transaction. A delivery-unknown result returns it to `prepared`, and startup/periodic Issue execution recovery retries the same payload and identities. Deleting the Issue or claimed Task is rejected while its intent is `prepared` or `leased`, so cascade deletion cannot remove the recovery record or attachment pin during Agent delivery. These launch-intent rows, lease helpers, and recovery semantics belong to the generic workspace Issue boundary; Tutti Mode adds its execution fences and checkpoint policy on top of them. Daemon-managed Issue attachments follow the same atomicity rule at the aggregate boundary: bytes are staged first, while the Issue, image ContextRefs, and topic activity commit together. Startup file reconciliation executes before Agent Host and background workers start, then removes only managed files that never gained a committed ContextRef or were left by a failed cleanup. File contents are synced on every platform; directory-entry syncing is used where the operating system exposes a portable directory `fsync` and is a no-op on Windows. ContextRef removal shares the Issue mutation lock with Run admission. Automatic Runs also hold a transient source-file pin from their durable claim until the Agent adapter finishes copying the image; an explicit prepared or leased launch intent provides the corresponding durable pin for retryable manual delivery. ## Plan Conversion And Execution Orchestration Issue Manager accepts two distinct planning origins: - A provider-native Plan can still be converted through the traditional AgentGUI **Break into an Issue** flow. Its source is `traditional_plan`. - An accepted Tutti Mode Plan task graph is materialized by the daemon-owned workspace workflow service. Its source is `tutti_mode_plan`. Tutti Mode Plan does not masquerade as a provider interaction and does not send an ephemeral renderer draft through the traditional conversion path. The Agent creates immutable `tutti-mode-plan/v1` revisions through the Tutti CLI; the user decides daemon-owned checkpoints; and tuttid projects tasks only from the accepted current task-graph revision. See [Workspace Workflows And Tutti Mode Plan](./workspace-workflows.md). For the Tutti-owned path, the Markdown revision already contains the Issue-level reasoning/orchestration profile, auto/fixed token budget, task assignments, model choices, execution directories, and dependencies. The workflow service derives read-only `ActionableItem`s, then invokes Issue Manager with one atomic Issue-and-task-graph request. The SQLite adapter commits the Issue, all Tasks, and topic activity atomically; task failure rolls the Issue back, and update events are published only after commit. A deterministic workflow operation and Issue ID make repeated decisions and waiters idempotent. The reserved Issue namespace is owned by the daemon `workspaceworkflow` business model rather than the reusable workspace Issue package, so generic Issue consumers cannot become a second Tutti workflow authority. AgentGUI neither parses an Agent message into this graph nor calls Issue creation itself. After materialization, `tutti_mode_plan` graphs are read-only through every generic Issue Manager mutation surface. Issue/task/context/run/dispatch, profile, and budget writers return the typed conflict reason `tutti_issue_managed`, including the source Session recovery target. Manual and `traditional_plan` Issues keep their existing mutation behavior. Only the source Agent may use `tutti plan issue mutate`, with the exact active checkpoint, graph revision, and stable request ID. Source graph changes are transactional and revisioned. A successful mutation increments `graphRevision`, rebinds the active checkpoint to that revision, retires any prepared or dispatched Goal Review and reviewer wake for the stale graph, and returns the revision required by the next exact-set schedule. Reusing the same request ID and payload replays the committed result; different content is a conflict. Obsolete tasks are logically superseded rather than deleted: their task row, Runs, outputs, and audit history remain queryable, while active projections and scheduling exclude them. Completed tasks are immutable, and a running task must first reach a terminal settlement before supersession. `sourceSessionId` is the durable planning link for both origins. Issue and Task headers can open that source Session, while successful atomic creation projects one idempotent, credential-free workspace-Issue mention back into the source timeline. Tutti Mode Plan additionally keeps its durable workflow, immutable revisions, checkpoints, and the operation's `issueId`; those records represent review provenance and do not compete with the Issue as the execution entity. A `tutti_mode_plan` Issue is a managed read model only when it also carries a non-empty `sourceSessionId`; the UI fails closed when either marker is missing. Managed Issue and Task surfaces expose no generic create, edit, delete, move, run, acceptance, or context-reference mutations. Their only modification entrypoint opens the exact source Session, appends a localized Issue/Task mention to the existing composer draft, and focuses the composer without submitting. The Issue Manager host adapter owns that exact-Session launch; the shared package never reaches into AgentGUI or replaces an existing draft. Completion uses a three-step acceptance ladder: ```text agent_claimed -> auto_checked -> user_accepted ``` That ladder and the generic dispatch frontier remain authoritative for manual and `traditional_plan` Issues. A `tutti_mode_plan` Issue instead belongs to its daemon execution aggregate: materialization creates no Run, `autoAccept` has no dispatch authority, and only the source Agent's checkpoint/revision-fenced exact-set schedule command may admit Runs. The admission transaction creates the selected Runs, task projections, and recoverable launch intents together; it never silently fills capacity with another eligible task. Budget capacity uses the same shared evaluator as generic dispatch and reserves estimated allowance for same-Issue active Runs. Issue runs settle live from the canonical root-turn settlement fan-out: the Issue-run observer sits on the opt-in settle list (root-provider-lifecycle adapters report no settled state patch, so the general session-state observers never see live settles) and matches the settled turn against the run's initiating `issue-run:` submit, so an unrelated turn settling in the delegate conversation can never complete the run. The fallback reconciler only declares a run dead when the session has been event-silent past the grace window — a streaming session with a lagging (empty) persisted active-turn id is alive, not abandoned. A successful Run moves its task to `pending_acceptance`/`agent_claimed`; it is only the executor's completion claim. For manual and `traditional_plan` Issues, explicit user acceptance reaches `user_accepted`. For a `tutti_mode_plan` Issue, the source Agent must review the durable settlement checkpoint and either schedule the exact next set or explicitly acknowledge it. Either command accepts the reviewed successful task transactionally; failed and canceled task projections remain terminal. Repeated terminal completion, settlement repair, schedule, and acknowledge mutations are idempotent. Parallelism stays honest at two boundaries. Materializing a plan normalizes the durable `parallelizable` flag against dependencies: a task that depends on a member of its own consecutive parallelizable group can never run alongside it, so the misleading flag is stripped (dependencies are never touched — they are the safe side of the contradiction). At dispatch, a successor whose direct dependencies ran in isolated per-run worktrees receives their exact branch names in its launch prompt ("Dependency outputs"), so parallel results are merged forward instead of stranding on `tutti/task/*` branches; the planning guide pairs this with the convention of an integration task after every parallel group. For generic dispatch, a task whose durable `autoAccept` flag is set skips the human gate: run completion is accepted programmatically through the same `UpdateTask` path a manual acceptance takes, so dispatch advance and the whole-issue completion check stay identical. Tutti-owned execution treats that field as compatibility metadata only. Sending a `pending_acceptance` task back to `not_started` (rework) resets its acceptance to `agent_claimed` and immediately re-opens the dispatch frontier — the daemon re-dispatches without waiting for an unrelated event. Tutti-owned terminal Runs never enter the generic notifier or auto-dispatch paths. Each terminal Run appends an ordered durable execution checkpoint for the source Agent. Once all tasks are terminal and every terminal Run has a settlement checkpoint, an `all_tasks_terminal` checkpoint advances the execution to `pending_goal_review`; completion cannot bypass that review boundary. The npm package name should be `@tutti-os/workspace-issue-manager`. It participates in the shared public npm release group documented in [npm Package Release](../conventions/npm-package-release.md). The OpenAPI fragment is a transport contract for hosts that expose the shared issue-manager capability over HTTP. Host daemons keep their own aggregate API entrypoints and compose the fragment instead of duplicating issue-manager paths, parameters, and schemas. `tuttid` composes it from `services/tuttid/api/openapi/tuttid.v1.yaml`. ## Scope Model The shared packages use `workspaceId` as the stable scope. `tutti` can pass the local workspace id directly. `tsh` keeps `roomId` in its host adapter for collaboration, authorization, sharing, and visitor access, then maps requests into the shared workspace-scoped model. Room-specific behavior must remain outside the shared core. Examples of host-owned `tsh` behavior: - room membership checks - visitor share token validation - room invite capacity and share links - room member display names and avatars ## Context References Context references are first-class domain entities. Rich text content may render and edit references, but storage and service logic should not rely on parsing rich text as the only source of truth. This keeps search, delete, upload, prompt construction, and future audit behavior stable. The shared model should support references attached to either an issue or a task. A task-level reference is used for agent execution context. Issue-level references are available for higher-level planning and task creation flows. Hosts may accept inline image attachments while creating an Issue and project them into ContextRefs after validating and durably storing the bytes. Issue Run launches inherit supported Issue-level image references and append Task-level image references, preserving text and image blocks as provider-neutral Agent prompt content. Host adapters own byte storage and safe-path policy; the shared domain continues to own only the ContextRef relationship. Managed attachments cross host and VM boundaries as opaque ContextRefs. Public responses mark them with `accessKind: managed_attachment`, identify them by workspace, Issue, and ContextRef IDs, and omit the daemon's absolute storage path. The host-provided ContextRef opener retrieves validated bytes through the Issue Manager API, materializes them inside its own trusted attachment store, and opens that local copy. Ordinary project references keep `accessKind: workspace_path` and continue through the host's workspace file adapter. This keeps daemon-local paths out of reusable package contracts and prevents a VM renderer from trying to open a path from another machine. The service layer classifies access from the attachment file adapter's managed path policy; transport mappers never infer ownership from caller-controlled IDs or MIME values. The reusable TypeScript contract is discriminated: `workspace_path` carries a required path, while `managed_attachment` cannot carry one. A legacy path reference with no `accessKind` remains accepted as a workspace path so existing external hosts keep their file-opening behavior. The daemon exposes a distinct top-level start-and-launch use case for manual Issues. It resolves managed ContextRefs before creating the durable Run and then delegates session/turn creation through Agent Host. Attachment lookup failure must not create or launch an incomplete Run. Planned Issue materialization intentionally uses a request schema without inline attachments. ## Agent Run Boundary The shared packages own run lifecycle state, not concrete agent startup. The TypeScript feature receives an `agentRunner` adapter. The Go package stores and transitions run records. Hosts decide how to start Codex, Claude Code, OpenClaw, Hermes, or any other supported provider. This boundary lets `tutti` and `tsh` share the issue-manager UX while keeping their runtime/session integration separate. ## Landing Slices The boundary-setting slice established the reusable package shape. The current repository already includes the published TypeScript issue-manager package and this section tracks the broader cross-host rollout model: 1. Add this architecture document. 2. Scaffold `packages/workspace/issues` with models, service, store interface, and focused service tests against a fake store. 3. Scaffold `packages/workspace/issue-manager` with contracts, adapter interfaces, i18n defaults, and workbench registration types. The next local-first slice wires `tuttid` to the shared contracts: - `tuttid` SQLite adapter and OpenAPI routes under `/v1/workspaces/{workspaceID}` - generated `@tutti-os/client-tuttid-ts` issue-manager methods Later slices can add: - full React node UI migration from `tsh` - `tsh-server` adapter from room-scoped APIs to the shared package - `tsh-desktop` adapter from existing IPC to the shared workbench feature