Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
365 lines
18 KiB
Markdown
365 lines
18 KiB
Markdown
# 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:<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](../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
|