18 KiB
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:
Issue -> Task -> Run
Issueis the top-level work item or theme inside one workspace.Taskis an executable unit under an issue.Runis 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:
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
Storeinterface for concrete persistence adapters
It must not own:
- HTTP, gRPC, OpenAPI, or protobuf contracts
- Electron IPC or desktop preload details
tshroom membership, visitor share tokens, or invite capabilitytuttidprocess 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:
.
./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.
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 ActionableItems, 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:
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:<runID> 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.
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:
- Add this architecture document.
- Scaffold
packages/workspace/issueswith models, service, store interface, and focused service tests against a fake store. - Scaffold
packages/workspace/issue-managerwith contracts, adapter interfaces, i18n defaults, and workbench registration types.
The next local-first slice wires tuttid to the shared contracts:
tuttidSQLite adapter and OpenAPI routes under/v1/workspaces/{workspaceID}- generated
@tutti-os/client-tuttid-tsissue-manager methods
Later slices can add:
- full React node UI migration from
tsh tsh-serveradapter from room-scoped APIs to the shared packagetsh-desktopadapter from existing IPC to the shared workbench feature