Skip to content

🤖 feat: add workspace turn task handles#3600

Merged
ThomasK33 merged 17 commits into
mainfrom
task-workspace-qzx9
Jun 19, 2026
Merged

🤖 feat: add workspace turn task handles#3600
ThomasK33 merged 17 commits into
mainfrom
task-workspace-qzx9

Conversation

@ThomasK33

Copy link
Copy Markdown
Member

Summary

Adds full-workspace task handles behind task(kind="workspace"), letting a parent turn launch a normal workspace turn, await its final assistant message, list it, interrupt it non-destructively, and reprompt the same owner-created workspace with a fresh handle.

Background

The existing task tool was tied to sub-agent workspaces and agent_report lifecycle semantics. Full workspace turns need durable await/interrupt handles without setting sub-agent fields or making those workspaces disposable descendants by default.

Implementation

  • Adds a durable per-owner TaskHandleStore for workspace_turn records under the owner session.
  • Extends task, task_await, task_list, and task_terminate schemas/handlers for wst_... handles.
  • Adds TaskService.createWorkspaceTurn, stream-end/error finalization, foreground waiter hardening, owner-scoped existing-workspace reprompts, and non-destructive interruption.
  • Propagates muxMetadata through AIService.streamMessage() so final stream events correlate back to the handle.
  • Shares final-message ref schema/type and adds disposable workspace cleanup after durable terminal settlement.

Validation

  • Simplify workflow: workflow_run simplify --base origin/main --fix applied a hardening commit before squashing.
  • Targeted tests: bun test src/common/utils/tools/toolDefinitions.test.ts src/node/services/taskHandleStore.test.ts src/node/services/tools/task.test.ts src/node/services/tools/task_await.test.ts src/node/services/tools/task_list.test.ts src/node/services/tools/task_terminate.test.ts src/node/services/taskService.test.ts src/node/services/aiService.test.ts --test-name-pattern 'workspace task|workspace-turn|muxMetadata|routeProvider|task_await|task_list|task_terminate|TaskHandleStore'.
  • Metadata/static validation: bun test src/node/services/aiService.test.ts --test-name-pattern 'muxMetadata|routeProvider'.
  • Required local gate: MUX_ESLINT_CONCURRENCY=1 make static-check.

Dogfooding

  • Ran a dev-server sandbox and browser-recorded task(kind="workspace") end-to-end.
  • Initial background workspace turn returned wst_24dfb4ba9e for workspace 73594a39a5; task_await completed with final text fixed workspace turn completed.
  • Existing-workspace reprompt reused workspace 73594a39a5, returned wst_fc14f6a93a, and completed with final text fixed workspace turn reprompt completed.
  • Evidence captured in the Mux conversation attachments and local sandbox artifacts under /home/coder/.mux-tmp/workspace-turn-dogfood-fixed/.

Risks

This touches core task orchestration and stream settlement. Regression risk is moderate around task waits and workspace lifecycle, mitigated by focused tests for schema validation, owner scoping, stream completion/error handling, foreground wait backgrounding, disposable cleanup, and tool handler behavior.

Pains

Dogfooding exposed a correlation gap: muxMetadata was persisted on the user message but not propagated into initial stream metadata, so final stream events could miss handle correlation. This PR fixes propagation and keeps an active-handle fallback for resilience.


📋 Implementation Plan

Implementation Plan: Generalized Task Handles for Full Workspace Turns

Goal

Make the task abstraction capable of launching and managing more than sub-agent workspaces. A caller should be able to start a normal full workspace turn, receive a stable task handle, later task_await that handle to get the final assistant turn-complete message, interrupt it through task tooling, and later reprompt a workspace it created earlier by passing that existing workspace ID back through the task abstraction. This must work without conflating full workspaces with sub-agent agent_report lifecycle semantics.

Evidence and current-state facts

Verified through direct repo inspection and Explore reports:

  • src/node/services/tools/task.ts currently parses agentId / subagent_type, then calls TaskService.create({ kind: "agent", ... }) for each single, n, or variants launch.
  • src/node/services/taskService.ts currently defines TaskKind = "agent"; TaskService.create() persists a child workspace entry with parentWorkspaceId, taskStatus, taskPrompt, agentId / agentType, and starts it via workspaceService.sendMessage().
  • Sub-agent task completion is built around agent_report: TaskService.handleStreamEnd() looks for agent_report / implicit report, finalizeAgentTaskReport() persists subagent report artifacts in ancestor session dirs, resolves waiters, and may clean up the child workspace.
  • src/node/services/tools/task_await.ts already multiplexes several stable handle families: descendant agent task IDs, bash: background processes, and wfr_... workflow runs.
  • src/node/services/tools/task_terminate.ts already dispatches by ID kind: workflow runs are interrupted, bash tasks are terminated through the background process manager, and agent task IDs call terminateDescendantAgentTask().
  • Normal workspaces emit stream-start / stream-end through AIService; StreamEndEvent includes workspaceId, messageId, final parts, and metadata in src/common/orpc/schemas/stream.ts.
  • WorkspaceService.create() accepts creation-time tags, which gives an atomic way to tag created workspaces for task-handle recovery.

Recommendation

Implement a durable workspace-turn task handle as a sibling of current sub-agent task IDs, not as a special case of sub-agent parentWorkspaceId / taskStatus.

Hard invariant: workspaces created for workspace_turn handles remain normal workspaces. They must not set parentWorkspaceId, taskStatus, agent_report lifecycle fields, sub-agent patch metadata, or cleanup-on-report behavior. The handle record owns await/stop state; the workspace remains user-visible and inspectable.

The key design rule: a workspace task handle identifies one assistant turn in one workspace, not the workspace forever. A full workspace can accumulate multiple turn handles over time when the owner reprompts it. Return both for each turn:

{
  "taskId": "wst_abc123",
  "workspaceId": "ws_789",
  "status": "running"
}
  • taskId / handle ID: stable await/stop target.
  • workspaceId: the normal full workspace the user can inspect and continue using.

Approach comparison and LoC estimates

Recommended approach: durable workspace_turn handles

Net product LoC estimate: ~950–1,450 LoC.

Includes schemas, handle store, owner-scoped existing-workspace targeting, stream correlation, task tool/task_await/task_list/task_terminate integration, unit tests, and dogfood helpers. This is more code than the minimal route, but keeps sub-agent and full-workspace semantics cleanly separated.

Not recommended: reuse sub-agent workspace fields with taskKind: "turn"

Net product LoC estimate: ~350–600 LoC.

This would extend TaskKind to "agent" | "turn", persist a child workspace with parentWorkspaceId, suppress agent_report, and finalize on stream-end. It is tempting because it reuses waitForAgentReport(), but it overloads fields whose existing meaning is “descendant sub-agent that owes agent_report and can be cleaned up.” It risks accidental auto-recovery prompts, parent auto-resume coupling, patch artifact assumptions, and destructive task_terminate behavior for what should be a durable full workspace.

Data model

Add a workspace-turn task handle record

Create a small durable store, likely under the caller workspace session dir:

  • New module: src/node/services/taskHandleStore.ts (or src/node/services/workspaceTaskHandleStore.ts).
  • Backing files: one JSON file per handle under ~/.mux/sessions/<ownerWorkspaceId>/task-handles/<handleId>.json.
  • Prefer per-handle files over a single aggregate file to avoid concurrent rewrite races and to make corrupt-record self-healing local to one handle. Use existing session-dir path helpers and the repo’s self-healing style for corrupted/missing records.

Proposed record:

export type TaskHandleKind = "agent_task" | "workspace_turn";

export interface WorkspaceTurnTaskHandleRecord {
  kind: "workspace_turn";
  handleId: string;
  ownerWorkspaceId: string;
  workspaceId: string;
  turnId: string;
  status: "queued" | "starting" | "running" | "completed" | "interrupted" | "error";
  createdAt: string;
  updatedAt: string;
  createdWorkspace: boolean;
  disposableWorkspace: boolean;
  title?: string;
  prompt?: string;
  model?: string;
  agentId?: string;
  thinkingLevel?: ThinkingLevel;
  messageId?: string;
  finalMessage?: {
    messageId: string;
    // Durable store may keep a bounded/sanitized copy for recovery, but tool
    // output should return a reference by default to avoid duplicating text in context.
    parts?: CompletedMessagePart[];
    metadata: StreamEndEvent["metadata"];
  };
  reportMarkdown?: string;
  error?: string;
}

Do not create records for current sub-agent tasks immediately unless useful for uniform lookup. Existing agent task IDs can remain backed by config/session report artifacts, and task_await can resolve them via current paths.

Ownership for reprompting existing workspaces

A workspace-turn handle is per-turn, but the owner also needs durable permission to target the same full workspace again. Use an owner-scoped check rather than parentWorkspaceId:

  • A workspace is repromptable by an owner workspace if either:
    • the workspace config has tags["mux.taskOwnerWorkspaceId"] === ownerWorkspaceId, or
    • the owner’s handle store contains at least one workspace_turn record with createdWorkspace: true and the same workspaceId.
  • The owner relationship is used only for task-tool scoping. It does not make the target workspace a sub-agent or descendant workspace.
  • Reprompting an existing workspace creates a new WorkspaceTurnTaskHandleRecord with a new handleId and turnId, createdWorkspace: false, and the same workspaceId.
  • Do not allow arbitrary workspace IDs in MVP. workspace.mode: "existing" is accepted only for workspaces owned/created by the caller workspace under the rules above. Foreign workspaces return invalid_scope / schema-level tool error without leaking transcript content.

Ownership tag security and provenance

Treat workspace ownership tags as internal capability/recovery metadata, not as freely trusted user metadata.

  • mux.taskOwnerWorkspaceId, mux.taskHandleId, and mux.taskTurnId are minted only by TaskService.createWorkspaceTurn() when it intentionally creates an owned workspace.
  • Generic workspace fork/clone/import flows must not copy mux.task* ownership tags into unrelated workspaces. If a workspace is forked outside the workspace-turn creation path, strip these tags or ignore them for ownership.
  • User/agent-visible tag mutation must not be able to forge task ownership. If the app exposes generic tag editing, protect/reserve the mux.task* namespace or make TaskHandleStore.isWorkspaceOwnedBy() ignore user-mutated task tags.
  • TaskHandleStore.isWorkspaceOwnedBy() should prefer owner handle records. Tag-only ownership is a recovery path: log the recovery, verify the tagged workspace still exists, and materialize a recovered ownership record before allowing reprompt.
  • Copied or forged tags alone must not let another workspace reprompt, await, terminate, or otherwise control a workspace it did not create.

Workspace tags for recovery

When task(kind: "workspace", workspace: { mode: "new" | "fork" }) creates a full workspace, create it with tags:

{
  "mux.taskHandleId": handleId,
  "mux.taskOwnerWorkspaceId": ownerWorkspaceId,
  "mux.taskTurnId": turnId
}

For later existing-workspace reprompts, keep the original owner tag stable. Do not overwrite it per turn; each new prompt gets its own handle record.

This lets startup/recovery code rediscover created workspaces even if handle persistence races with workspace creation. Do not rely only on tags for await results; the handle record remains source-of-truth for status and final message.

Reconciliation rules:

  • Handle persisted but sendMessage fails: mark the handle error and keep/delete the workspace according to createdWorkspace + rollback policy.
  • Workspace created but handle persistence fails: rollback/archive/delete the just-created workspace, or recreate the missing handle from tags during recovery if rollback is impossible.
  • Tags and handle disagree: prefer the owner-scoped handle record for await/stop state, log the mismatch, and surface a recovery note in task_await(timeout_secs: 0).

Public tool contract

task input schema

Keep existing sub-agent behavior as the default. Add a discriminant that does not break old calls:

kind?: "subagent" | "workspace"; // default "subagent"

For kind: "subagent":

  • Preserve existing agentId / subagent_type, n, variants, model, thinking, isolation, foreground/background behavior.

For kind: "workspace", support creating a new full workspace and reprompting owner-created existing workspaces:

{
  kind: "workspace",
  prompt: string,
  title: string,
  run_in_background?: boolean,
  model?: string | null,
  thinking?: string | null,
  workspace?: {
    mode?: "new" | "fork" | "existing", // default "new" or "fork" as decided below
    workspaceId?: string | null,          // required for "existing"
    branchName?: string | null,
    trunkBranch?: string | null,
    disposable?: boolean | null
  } | null
}

Recommended MVP defaults:

  • workspace.mode defaults to "new" / fork-from-current-project behavior.
  • workspace.mode: "existing" requires workspace.workspaceId and only targets a workspace previously created/owned by the caller workspace.
  • Existing-workspace reprompting requires the target workspace to be idle in MVP. Use WorkspaceService.sendMessage(..., { requireIdle: true, startStreamInBackground: true, ... }) so the handle maps to one immediate turn instead of an ambiguous queued future message.
  • Set disposable: false by default so a “full workspace task” remains inspectable after interruption/completion. For existing-workspace reprompts, force createdWorkspace: false and disposableWorkspace: false regardless of the input.
  • Do not support n / variants for workspace tasks in the first PR. If kind === "workspace" and n or variants are present, return a clear schema/refinement error. Workspace fanout can be a follow-up after handle semantics are stable.

task result schema

Extend queued/running/completed schemas to allow workspace fields:

{
  status: "queued" | "starting" | "running" | "completed",
  taskId: string,
  workspaceId?: string,
  handleKind?: "agent_task" | "workspace_turn",
  messageId?: string,
  finalMessageRef?: {
    messageId: string,
    model?: string,
    agentId?: string,
    finishReason?: string,
    usageSummary?: { inputTokens?: number; outputTokens?: number; totalTokens?: number },
    partCount?: number,
    textCharCount?: number
  },
  reportMarkdown?: string,
  note?: string
}

Avoid duplicating the same final-message content in tool output. For workspace-turn results, reportMarkdown is the default model-facing text projection of the final assistant message. finalMessageRef carries durable identity plus compact bounded metadata only; it does not include raw CompletedMessagePart[] by default. If no final text exists, use a short non-empty fallback such as “Workspace turn completed without final text output.”

task_await result schema

Add optional fields to completed results while preserving current completed shape for sub-agent, bash, and workflow results:

workspaceId?: string;
handleKind?: "agent_task" | "workspace_turn";
messageId?: string;
finalMessageRef?: {
  messageId: string;
  model?: string;
  agentId?: string;
  finishReason?: string;
  usageSummary?: { inputTokens?: number; outputTokens?: number; totalTokens?: number };
  partCount?: number;
  textCharCount?: number;
};

MVP context-budget rule: a workspace-turn await result must not include both full reportMarkdown text and raw CompletedMessagePart[]. The default response returns reportMarkdown + compact finalMessageRef. A future explicit detail mode may fetch raw parts, but when it does, it must suppress or truncate reportMarkdown so there is only one substantive copy of the final message in context.

Stop/terminate contract

MVP decision: extend task_terminate for workspace_turn handles with workflow-like interruption semantics, not destructive deletion.

  • Current sub-agent behavior remains destructive and backward-compatible.
  • workflow runs remain interrupted/resumable.
  • workspace_turn calls aiService.stopStream(workspaceId, { abandonPartial: false }), marks the handle interrupted, wakes waiters, and returns status: "interrupted" with a note that the full workspace is preserved.
  • Update tool schema/docs so callers understand that task_terminate may interrupt/preserve durable work for workflow and workspace-turn handles instead of discarding it.
  • Add a dedicated non-destructive task_stop as a follow-up only if product wants clearer vocabulary; do not block the MVP on a new tool.

Internal architecture

Add a resolver layer for awaitable handles

Introduce a small resolver module used by task_await, task_list, task_terminate, and future task_stop:

export type ResolvedTaskHandle =
  | { kind: "agent_task"; taskId: string }
  | { kind: "workspace_turn"; record: WorkspaceTurnTaskHandleRecord }
  | { kind: "bash"; processId: string }
  | { kind: "workflow_run"; runId: string };

Resolution rules:

  1. Prefix bash: stays bash.
  2. Prefix wfr_ stays workflow run.
  3. Prefix wst_ or a persisted record match resolves workspace-turn handle.
  4. Otherwise fall back to existing descendant agent-task lookup.

Scope rules:

  • Workspace-turn handles are valid only when record.ownerWorkspaceId === callerWorkspaceId or when the owner is an ancestor if nested ownership is intentionally supported. For MVP, require exact owner match unless there is a strong reason to support descendants.
  • Existing workspace reprompting is valid only when TaskHandleStore.isWorkspaceOwnedBy(callerWorkspaceId, workspaceId) succeeds via tags or prior createdWorkspace: true records.
  • Return invalid_scope instead of leaking existence/details for foreign handles or foreign workspaces.

Turn-completion correlation

The handle must correlate with one specific assistant turn. Do not guess “the next stream-end in that workspace,” and do not assume every low-level stream-end event is semantically “turn complete” until verified.

Preferred implementation:

  1. Persist handleId + turnId before calling sendMessage().
  2. Thread explicit internal correlation fields through the send path: taskHandleId, turnId, and the resulting messageId once known. Use typed internal options or typed muxMetadata; avoid untyped ad-hoc blobs.
  3. Add or reuse an event that means assistant message committed / turn complete. If existing stream-end fires only after the final assistant message is durable, it can be used. If it can fire at intermediate tool-step boundaries, add a new event from AgentSession after the final assistant message is committed.
  4. Treat StreamEndEvent.parts as an optimization. The durable source of truth is the committed child workspace chat message identified by messageId; use history lookup by messageId when needed.
  5. Maintain an in-memory active map keyed by workspaceId + turnId / taskHandleId so stream errors and aborts can mark the correct handle even if terminal error events do not carry full metadata.

Fallback if explicit metadata plumbing is too large: capture the correlated stream-start event and persist messageId, guarded by a per-workspace launch lock and timeout. This is more race-prone and should be avoided for the first serious implementation.

Workspace-turn lifecycle

Create a new method, e.g. TaskService.createWorkspaceTurn(args):

  1. Validate caller workspace and project trust, matching existing task creation trust posture.
  2. Parse model/thinking overrides with existing parseTaskAiOverrides behavior.
  3. Generate handleId with a stable prefix such as wst_ plus config.generateStableId().
  4. Generate turnId.
  5. Resolve the target workspace:
    • For workspace.mode: "new" | "fork", create a normal workspace through WorkspaceService.create() using current project/runtime/trunk defaults, or a focused helper if fork-from-current-workspace is required.
    • For workspace.mode: "existing", validate that workspaceId exists, is owner-scoped to the caller, and is currently idle.
  6. Persist the handle record as starting / running before starting the stream. For existing-workspace reprompts, set createdWorkspace: false and preserve the existing workspace tags/config.
  7. Call workspaceService.sendMessage(workspaceId, prompt, options, { startStreamInBackground: true, requireIdle: true, agentInitiated: true, ...correlation }).
  8. If accepted, persist messageId once available and return running handle metadata immediately for background mode; for foreground mode, call the same await path as task_await.
  9. If send fails before acceptance, mark handle error and surface the error.
  10. On correlated assistant-message-committed / turn-complete event, persist final message and mark completed.
  11. On stream error/abort for matching turn, mark error or interrupted, wake waiters.

Awaiting workspace-turn handles

Add TaskService.waitForWorkspaceTurn(handleId, options) or implement directly in a WorkspaceTurnTaskAdapter.

Behavior:

  • Fast path completed/error/interrupted from persisted handle record.
  • If timeout_secs === 0, return current snapshot without waiting.
  • If running, register an in-memory waiter keyed by handleId and resolve on handle finalization.
  • On timeout, return current running / starting status with elapsed time.
  • On app restart, if a handle is running but no stream is active, reconcile:
    • If final message with matching turnId / messageId can be found in history, mark completed.
    • If there is a partial or the workspace can resume, keep running and optionally return a note that the workspace turn may need manual resume.
    • If known stream error persisted, mark error.

Parent delivery semantics

MVP decision: do not auto-resume the parent for background workspace-turn completion.

  • If a foreground task call or task_await is waiting, return the result directly.
  • If a background workspace-turn completes with no waiter, make it discoverable through task_list and task_await by ID; the original task result already gave the handle.
  • Do not append <mux_subagent_report> because trusted-subagent semantics are not identical.
  • A distinct synthetic parent message such as <mux_workspace_turn_result> can be a follow-up if dogfooding shows discoverability is poor, but it should not be part of the MVP unless product explicitly wants parent auto-handoff.
  • Auto-resume should remain a follow-up and, if added, must use “parent idle and no active background handles” gates to avoid surprise extra turns.

Concurrency policy

MVP decision: workspace-turn handles count against the same global task parallelism budget used for agent tasks (maxParallelAgentTasks) unless implementation uncovers a strong reason for a separate setting.

Implementation notes:

  • Active statuses for the limit: starting and running.
  • MVP behavior when the limit is reached: return a clear retryable error instead of queueing. This avoids making normal workspaces look like sub-agent tasks and avoids ambiguous queued reprompts into existing workspaces.
  • Do not allow unlimited full workspace streams; this is both a cost-control and UX predictability requirement.
  • Add tests proving workspace-turn launches respect the parallelism budget and do not starve existing sub-agent tasks.

File-by-file implementation plan

Phase 1 — Types, schemas, and store

Files:

  • src/common/utils/tools/toolDefinitions.ts
  • src/common/types/stream.ts or existing message/stream type exports if needed
  • src/node/services/taskHandleStore.ts (new)
  • Tests near the new store or src/node/services/taskService.test.ts

Steps:

  1. Add tool schema discriminant for kind: "subagent" | "workspace" while preserving old task args.
  2. Add workspace-turn fields to task/tool result schemas.
  3. Implement TaskHandleStore with:
    • upsert(record)
    • get(ownerWorkspaceId, handleId)
    • list(ownerWorkspaceId, statuses?)
    • update(ownerWorkspaceId, handleId, mutator)
    • isWorkspaceOwnedBy(ownerWorkspaceId, workspaceId) for existing-workspace reprompt scope checks
    • listWorkspaceTurns(ownerWorkspaceId, workspaceId) for history/debugging of repeated prompts
    • self-healing reads for absent/corrupt records.
  4. Add focused tests for schema acceptance/rejection and store persistence.

Quality gate:

  • Run targeted schema/store tests.
  • Run make typecheck if types are touched broadly.

Phase 2 — Workspace-turn creation

Files:

  • src/node/services/tools/task.ts
  • src/node/services/taskService.ts
  • src/node/services/workspaceService.ts if a creation helper is needed
  • src/node/services/tools/task.test.ts
  • src/node/services/taskService.test.ts

Steps:

  1. Split createTaskTool execution into sub-agent and workspace branches after validation.

  2. Add TaskService.createWorkspaceTurn() instead of overloading TaskService.create(kind: "agent").

  3. In createWorkspaceTurn():

    • Resolve parent metadata and project trust.
    • Pick workspace creation strategy for mode: "new" | "fork": normal new/forked workspace from the parent project.
    • For mode: "existing", validate ownership with TaskHandleStore.isWorkspaceOwnedBy() and reject busy targets with a clear idle-required error.
    • Generate handle and tags atomically for new/forked workspaces; for existing workspaces, generate a new handle without mutating ownership tags unless missing-tag recovery is explicitly needed.
    • Persist handle before sending the prompt.
    • Start stream in background with correlation metadata and requireIdle: true.
  4. Foreground run_in_background: false uses the same wait primitive with a reasonable timeout/backgrounding behavior matching current task tool semantics.

  5. Return workspaceId in all workspace task results.

  6. Return invalid_scope / clear tool errors for attempts to reprompt arbitrary workspaces that were not created by the caller workspace.

Quality gate:

  • Targeted task.test.ts for tool arg dispatch.
  • Targeted taskService.test.ts for create path and failure rollback.

Phase 3 — Turn-completion correlation and finalization

Files:

  • src/node/services/workspaceService.ts
  • src/node/services/agentSession.ts
  • src/node/services/aiService.ts if stream metadata originates there
  • src/common/orpc/schemas/stream.ts if event schema needs explicit fields
  • src/node/services/taskService.ts
  • src/node/services/taskService.test.ts

Steps:

  1. Add internal send correlation fields (taskHandleId, turnId) either as explicit internal options or as typed muxMetadata.
  2. Ensure stream-start and terminal events preserve enough correlation to match the handle; if stream-end is not equivalent to assistant-message-committed, add a dedicated committed/turn-complete event from AgentSession.
  3. Add a workspace-turn finalization path that is independent from existing sub-agent parentWorkspaceId checks:
    • Find record by workspaceId + turnId / taskHandleId.
    • Read the committed child assistant message by messageId as source of truth; use event parts only as a fast path.
    • Build reportMarkdown by concatenating text parts or using a non-empty fallback.
    • Persist finalMessage metadata and bounded parts/message reference.
    • Mark status completed and resolve waiters.
  4. Add stream-error/abort handling for matching handles through the active map:
    • aborted / user stop => interrupted.
    • provider/runtime errors => error with message.
  5. Add restart reconciliation for stale running handles, at least enough for task_await(timeout_secs: 0) to report a useful status and not hang.

Quality gate:

  • Targeted turn-completion tests for:
    • plain text final message after durable commit,
    • tool-using multi-step turn does not finalize before the correlated final assistant message is committed,
    • empty final text fallback,
    • stream error/interruption.

Phase 4 — Await/list/stop/terminate integration

Files:

  • src/node/services/tools/task_await.ts
  • src/node/services/tools/task_list.ts
  • src/node/services/tools/task_terminate.ts
  • Follow-up only: src/node/services/tools/task_stop.ts if a separate stop tool is later desired
  • src/common/utils/tools/toolDefinitions.ts
  • Corresponding tests under src/node/services/tools/

Steps:

  1. Add resolver logic for workspace-turn handles before the descendant agent-task fallback.
  2. Extend task_await:
    • explicit task_ids can include wst_... handles,
    • omitted IDs include active workspace-turn handles owned by the current workspace,
    • completed workspace-turn returns reportMarkdown, workspaceId, messageId, finalMessage, elapsed time, and refetch note.
  3. Extend task_list to show active/completed workspace-turn handles with handleKind, workspaceId, status, title, and createdAt.
  4. Extend task_terminate for workspace_turn:
    • interrupt the stream and preserve workspace by default,
    • update handle status and wake waiters,
    • return interrupted with a note that the workspace remains available.
  5. Preserve all existing bash/workflow/sub-agent behavior and update task_terminate docs to describe kind-specific semantics.

Quality gate:

  • Run targeted task_await.test.ts, task_list.test.ts, and task_terminate.test.ts. Add task_stop.test.ts only in a follow-up that adds the separate tool.

Phase 5 — UX polish and discoverability

Files:

  • src/node/services/taskService.ts
  • src/browser/features/Tools/TaskToolCall.tsx
  • src/browser/features/Tools/TaskToolCall.test.tsx
  • Storybook stories under src/browser/features/Tools/TaskToolCall.stories.tsx

Steps:

  1. Render workspace-turn task results distinctly from sub-agent reports, including a workspace link if available.
  2. Add frontend display support for workspaceId, messageId, and handleKind in task tool cards.
  3. Ensure task_list output is clear enough that background workspace-turn handles are discoverable without parent auto-resume.
  4. Keep synthetic parent-history delivery as a follow-up; if added later, use a distinct wrapper/parser, never <mux_subagent_report>.
  5. Ensure mobile/narrow rendering handles long workspace names and task IDs without overflow.

Quality gate:

  • Targeted UI tests/stories.
  • Mobile-width Storybook/agent-browser check if UI changes are made.

Testing strategy

Unit and integration tests

Add/extend:

  • src/common/utils/tools/toolDefinitions.test.ts or existing schema tests:
    • workspace task args accepted without agentId,
    • n / variants rejected for workspace kind in MVP,
    • result schemas accept workspaceId / finalMessage.
  • src/node/services/taskHandleStore.test.ts:
    • create/read/update/list,
    • corrupt record self-healing,
    • owner scoping,
    • isWorkspaceOwnedBy() succeeds for caller-created workspaces and fails for foreign workspaces,
    • handle records are preferred over tags, and tag-only recovery materializes/logs a recovered ownership record,
    • copied/forged mux.task* tags alone do not grant ownership to another workspace.
  • src/node/services/tools/task.test.ts:
    • sub-agent behavior unchanged,
    • workspace branch calls createWorkspaceTurn,
    • workspace.mode: "existing" passes workspaceId through for owner-created targets,
    • foreground/background results shape.
  • src/node/services/taskService.test.ts:
    • create workspace-turn persists handle and tags,
    • reprompting an owner-created existing workspace creates a second handle with the same workspaceId, new turnId, and createdWorkspace: false,
    • workspace-turn creation returns a clear retryable error when the parallelism budget is exhausted,
    • busy existing workspace reprompt is rejected with a clear idle-required error,
    • send failure marks handle error,
    • assistant-message-committed / turn-complete event finalizes the handle,
    • stream-error/abort marks error/interrupted,
    • restart reconciliation does not hang.
  • src/node/services/tools/task_await.test.ts:
    • wait until workspace-turn completion,
    • completed workspace-turn output does not duplicate full final text in both reportMarkdown and raw CompletedMessagePart[],
    • repeated turns in the same workspace are awaited by distinct wst_... handles,
    • timeout_secs: 0 snapshot,
    • invalid_scope for foreign owner,
    • omitted IDs include active owned workspace-turn handles.
  • src/node/services/tools/task_terminate.test.ts:
    • workspace-turn interrupt preserves workspace,
    • returned status/docs make preservation clear,
    • sub-agent/batch/bash/workflow behavior unchanged.
  • src/browser/features/Tools/TaskToolCall.test.tsx if result rendering changes.

Regression tests for existing behavior

Explicitly keep or add tests proving:

  • workspace-turn-created workspaces do not have parentWorkspaceId, taskStatus, agent_report recovery fields, or subagent patch metadata,
  • no agent_report recovery prompt is sent for a workspace-turn handle,
  • workspace-turn finalization uses the committed assistant message by messageId,
  • existing task calls without kind still require agentId and use sub-agent flow,
  • existing sub-agent foreground/background waits still use agent_report,
  • workflow-owned descendant filtering in task_await is unchanged,
  • generic fork/clone/import does not copy or trust mux.task* ownership tags,
  • user/agent-visible tag mutation cannot forge ownership for reprompt/await/terminate,
  • existing-workspace reprompting is allowed only for workspaces owned/created by the caller workspace,
  • busy existing-workspace reprompting fails clearly instead of queueing an ambiguous future turn,
  • task_terminate remains destructive for current sub-agent task IDs.

Dogfooding plan

Use an isolated Mux root/workspace so the run is reproducible and safe.

Setup

  1. Start a sandbox dev server / desktop app using the repo’s sandbox skill or scripts.
  2. Create/open a parent workspace in a trusted test project.
  3. Enable any required experiment flag if the implementation hides workspace-turn tasks behind one.
  4. Open devtools/logs for the parent and child workspace sessions.

Scenario A — background full workspace task

  1. From the parent chat, invoke a workspace task in background:
    • prompt: ask for a small read-only repository summary,
    • expected result: taskId with wst_ prefix and workspaceId.
  2. Verify in UI:
    • parent task tool card shows handle and workspace link,
    • new full workspace appears in sidebar,
    • child workspace streams as a normal chat, not as a sub-agent report workflow.
  3. Run task_await([taskId]) from parent.
  4. Verify result includes workspaceId, messageId, compact finalMessageRef, and reportMarkdown without also duplicating raw CompletedMessagePart[].
  5. Take screenshots of parent result, child workspace, and awaited result.
  6. Record a short video from launch through await completion.

Scenario B — reprompt the created workspace

  1. Reuse the workspaceId from Scenario A and invoke task(kind: "workspace", workspace: { mode: "existing", workspaceId }, ...) from the original parent workspace.
  2. Verify the returned result has a new taskId / turnId but the same workspaceId.
  3. Await the new handle and confirm the child workspace transcript now contains both normal turns.
  4. Try the same workspaceId from an unrelated workspace and verify invalid_scope / clear rejection without transcript leakage.
  5. Capture screenshots and video of the reprompt, second await result, and scope rejection.

Scenario C — interruption

  1. Launch a workspace task with a prompt that runs long enough to interrupt.
  2. Use task_terminate([taskId]) from the parent (or task_stop only if a follow-up adds it).
  3. Verify:
    • stream stops,
    • task_await(timeout_secs: 0) returns interrupted,
    • full workspace remains visible/inspectable,
    • no sub-agent agent_report recovery prompt appears.
  4. Capture screenshots and video.

Scenario D — restart recovery

  1. Launch a background workspace-turn task.
  2. Restart the app during or after completion.
  3. Re-open parent workspace and call task_await([taskId], timeout_secs: 0).
  4. Verify completed results are re-fetchable or running/interrupted state is reported without hanging.
  5. Capture screenshots/video.

Scenario E — scope guard

  1. From an unrelated workspace, attempt task_await([workspaceTurnTaskId]).
  2. Verify invalid_scope without leaking final message content.
  3. Capture screenshot.

Validation commands

During implementation, run targeted tests after each phase, then final validation:

bun test src/node/services/tools/task.test.ts
bun test src/node/services/tools/task_await.test.ts
bun test src/node/services/tools/task_terminate.test.ts
bun test src/node/services/taskHandleStore.test.ts
bun test src/node/services/taskService.test.ts
bun test src/browser/features/Tools/TaskToolCall.test.tsx # if UI touched
make typecheck
make lint
make static-check

If local resource constraints hit ESLint worker OOM, use the known repo workaround:

MUX_ESLINT_CONCURRENCY=1 make static-check

Acceptance criteria

  • Existing sub-agent tasks remain backward-compatible in tool args, results, awaiting, listing, and termination.
  • A caller can invoke task(kind: "workspace", ...) and receive a stable taskId plus a normal workspaceId.
  • The same caller can pass the returned workspaceId back with workspace.mode: "existing" and get a new stable turn handle for the same full workspace.
  • Reprompting an existing workspace from a different caller is rejected as invalid_scope / clear tool error.
  • The new workspace is a full visible workspace and is not forced into agent_report completion semantics.
  • task_await([taskId]) returns the final assistant turn-complete message as a single substantive payload: default reportMarkdown text plus messageId/compact finalMessageRef metadata, without also duplicating raw CompletedMessagePart[] in the same output.
  • Tool output observes the no-duplicate-payload rule: exact final-message content is emitted once per result unless an explicit future detail mode asks for raw parts and suppresses/truncates the text projection.
  • Completed workspace-turn results are durably re-fetchable with task_await(timeout_secs: 0) after context compaction or app restart.
  • Workspace-turn handles can be interrupted/stopped from the caller workspace.
  • Interrupting/stopping a workspace-turn handle preserves the full workspace by default.
  • Foreign workspaces cannot await, list, stop, or terminate handles they do not own.
  • Background workspace-turn completion is discoverable by the parent without using <mux_subagent_report>.
  • Dogfooding evidence includes screenshots and video recordings for initial workspace creation, existing-workspace reprompting, interruption, restart recovery, and scope guard scenarios.

Key risks and mitigations

Risk Mitigation
Race matching terminal events to a handle Thread explicit taskHandleId / turnId through internal send metadata; avoid “next stream-end” inference.
Conflating full workspaces with sub-agent tasks Use a separate durable handle store and workspace_turn kind; do not set parentWorkspaceId just to reuse agent_report code.
Multi-step tool turns finalize too early Finalize only on the correlated assistant-message-committed / turn-complete signal; tests must cover tool-using turns.
Context pollution from duplicated final message Return reportMarkdown + compact finalMessageRef by default, not full text plus raw parts; any future raw-parts mode must suppress/truncate the text projection.
Empty final text Persist final message reference/parts as source of truth and provide non-empty fallback reportMarkdown.
Destructive termination surprises MVP task_terminate interrupts/preserves workspace-turn workspaces by default; separate task_stop is follow-up vocabulary only.
Ambiguous queued reprompts into busy existing workspaces MVP requires existing targets to be idle and uses requireIdle: true; queued existing-workspace turns can be a follow-up with explicit queue correlation.
Restart recovery ambiguity Persist handle before sending, tag created workspace atomically, reconcile stale running handles on await/startup.
Forged/copied ownership tags Treat mux.task* tags as reserved internal recovery metadata; prefer handle records, strip/ignore tags on generic copies, and test forged tags.
Scope leakage Owner-scoped handle lookup; return invalid_scope for foreign handles.
Tool schema complexity Preserve old default path; initially reject n/variants for workspace tasks.

Rollout strategy

  1. Hide workspace-turn tasks behind an internal experiment flag if desired.
  2. Land in small vertical slices:
    • handle store + schemas,
    • create + stream finalize,
    • await/list/terminate,
    • UI/dogfood polish.
  3. Keep the first user-facing version conservative: new/forked workspace creation plus owner-scoped existing-workspace reprompting only, no arbitrary existing workspace targeting, no busy-workspace queueing, no workspace fanout, and non-destructive interruption semantics.
  4. Follow-up opportunities:
    • support queued reprompts into busy existing workspaces with explicit queue/turn correlation,
    • support broader mode: "existing" policies if product wants cross-workspace delegation,
    • support workspace task fanout,
    • expose richer UI affordances for jumping to final messageId,
    • unify bash/workflow/agent/workspace handles behind a single adapter module.

Generated with mux • Model: openai:gpt-5.5 • Thinking: xhigh • Cost: $93.61

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@mintlify

mintlify Bot commented Jun 19, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
Mux 🟢 Ready View Preview Jun 19, 2026, 11:42 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 42d66960cd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/taskService.ts Outdated
Comment thread src/node/services/taskService.ts Outdated
Comment thread src/node/services/tools/task_await.ts
@ThomasK33 ThomasK33 force-pushed the task-workspace-qzx9 branch from 42d6696 to 239e536 Compare June 19, 2026 12:01
@ThomasK33

Copy link
Copy Markdown
Member Author

Addressed the Codex findings in the latest push:

  • PRRT_kwDOPxxmWM6K0pfl / stream abort: added stream-abort handling for active workspace-turn handles so UI stops settle the handle as interrupted and free queue slots.
  • PRRT_kwDOPxxmWM6K0pfq / global parallelism: added global workspace-turn listing and included active workspace turns in task creation and queued-task scheduler slot accounting.
  • PRRT_kwDOPxxmWM6K0pfv / detached awaits: workspace-turn awaits detached by min_completed now return a live snapshot instead of an error.

Validation after the fixes:

  • bun test src/common/utils/tools/toolDefinitions.test.ts src/node/services/taskHandleStore.test.ts src/node/services/tools/task.test.ts src/node/services/tools/task_await.test.ts src/node/services/tools/task_list.test.ts src/node/services/tools/task_terminate.test.ts src/node/services/taskService.test.ts src/node/services/aiService.test.ts --test-name-pattern 'workspace task|workspace-turn|muxMetadata|routeProvider|task_await|task_list|task_terminate|TaskHandleStore|createWorkspaceTurn counts active workspace turns'
  • MUX_ESLINT_CONCURRENCY=1 make static-check

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review
Please take another look.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 239e536577

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/taskService.ts Outdated
Comment thread src/node/services/taskService.ts Outdated
@ThomasK33 ThomasK33 force-pushed the task-workspace-qzx9 branch from 239e536 to e49aed4 Compare June 19, 2026 12:12
@ThomasK33

Copy link
Copy Markdown
Member Author

Addressed the second Codex round in the latest push:

  • PRRT_kwDOPxxmWM6K04p6 / background-task guard: workspace-turn stream-end finalization now runs only after the root-workspace active descendant/workflow guard has had a chance to auto-resume for outstanding work; active descendants keep the handle running.
  • PRRT_kwDOPxxmWM6K04p_ / foreground waits and slot accounting: active workspace-turn slot counting now mirrors agent tasks by excluding workspace turns whose workspace is currently blocked in a foreground wait.

Validation after this round:

  • bun test src/common/utils/tools/toolDefinitions.test.ts src/node/services/taskHandleStore.test.ts src/node/services/tools/task.test.ts src/node/services/tools/task_await.test.ts src/node/services/tools/task_list.test.ts src/node/services/tools/task_terminate.test.ts src/node/services/taskService.test.ts src/node/services/aiService.test.ts --test-name-pattern 'workspace task|workspace-turn|muxMetadata|routeProvider|task_await|task_list|task_terminate|TaskHandleStore|createWorkspaceTurn counts active workspace turns|active workspace turn count'
  • MUX_ESLINT_CONCURRENCY=1 make static-check

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review
Please take another look.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e49aed4e3c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/taskService.ts Outdated
Comment thread src/node/services/taskService.ts
@ThomasK33 ThomasK33 force-pushed the task-workspace-qzx9 branch from e49aed4 to ad8e1b3 Compare June 19, 2026 12:22
@ThomasK33

Copy link
Copy Markdown
Member Author

Addressed the third Codex round in the latest push:

  • PRRT_kwDOPxxmWM6K1EZN / unrelated stream metadata: active-handle stream-end fallback now ignores stream events carrying other muxMetadata types (for example compaction), so only metadata-less workspace-turn streams use the fallback.
  • PRRT_kwDOPxxmWM6K1EZU / accepted pre-stream failures: task(kind="workspace") now passes onAcceptedPreStreamFailure and settles the handle as error, waking waiters and freeing slots if startup fails after the user message is accepted.

Validation after this round:

  • bun test src/common/utils/tools/toolDefinitions.test.ts src/node/services/taskHandleStore.test.ts src/node/services/tools/task.test.ts src/node/services/tools/task_await.test.ts src/node/services/tools/task_list.test.ts src/node/services/tools/task_terminate.test.ts src/node/services/taskService.test.ts src/node/services/aiService.test.ts --test-name-pattern 'workspace task|workspace-turn|muxMetadata|routeProvider|task_await|task_list|task_terminate|TaskHandleStore|createWorkspaceTurn counts active workspace turns|active workspace turn count|createWorkspaceTurn marks accepted pre-stream failures'
  • MUX_ESLINT_CONCURRENCY=1 make static-check

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review
Please take another look.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ad8e1b3787

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/taskService.ts Outdated
@ThomasK33 ThomasK33 force-pushed the task-workspace-qzx9 branch from ad8e1b3 to a8f27aa Compare June 19, 2026 12:31
@ThomasK33

Copy link
Copy Markdown
Member Author

Addressed the fourth Codex round in the latest push:

  • PRRT_kwDOPxxmWM6K1NsI / stale active handles: active workspace-turn slot counting now verifies each persisted running/starting handle is still live in-process or streaming; stale persisted handles are settled as interrupted before they can consume parallelism slots.

Validation after this round:

  • bun test src/common/utils/tools/toolDefinitions.test.ts src/node/services/taskHandleStore.test.ts src/node/services/tools/task.test.ts src/node/services/tools/task_await.test.ts src/node/services/tools/task_list.test.ts src/node/services/tools/task_terminate.test.ts src/node/services/taskService.test.ts src/node/services/aiService.test.ts --test-name-pattern 'workspace task|workspace-turn|muxMetadata|routeProvider|task_await|task_list|task_terminate|TaskHandleStore|createWorkspaceTurn counts active workspace turns|active workspace turn count|createWorkspaceTurn marks accepted pre-stream failures'
  • MUX_ESLINT_CONCURRENCY=1 make static-check

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review
Please take another look.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a8f27aa49e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/taskService.ts
@ThomasK33 ThomasK33 force-pushed the task-workspace-qzx9 branch from a8f27aa to 3fd4b4b Compare June 19, 2026 12:43
@ThomasK33

Copy link
Copy Markdown
Member Author

Addressed the fifth Codex round in the latest push:

  • PRRT_kwDOPxxmWM6K1V5h / parent auto-resume: parent stream-end active-work detection now includes active workspace-turn handles owned by the parent, so background wst_... handles are included in the existing auto-resume task_await prompt before the parent can take the no-work return.

Validation after this round:

  • bun test src/common/utils/tools/toolDefinitions.test.ts src/node/services/taskHandleStore.test.ts src/node/services/tools/task.test.ts src/node/services/tools/task_await.test.ts src/node/services/tools/task_list.test.ts src/node/services/tools/task_terminate.test.ts src/node/services/taskService.test.ts src/node/services/aiService.test.ts --test-name-pattern 'workspace task|workspace-turn|muxMetadata|routeProvider|task_await|task_list|task_terminate|TaskHandleStore|createWorkspaceTurn counts active workspace turns|active workspace turn count|createWorkspaceTurn marks accepted pre-stream failures|parent stream-end auto-resumes'
  • MUX_ESLINT_CONCURRENCY=1 make static-check

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review
Please take another look.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3fd4b4b2fd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/taskService.ts
@ThomasK33 ThomasK33 force-pushed the task-workspace-qzx9 branch from 3fd4b4b to ebddde6 Compare June 19, 2026 12:55
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Addressed the latest finding:

  • finalizeWorkspaceTurnFromStreamEnd now refuses to complete a handle from a stream-end message previously marked deferred, so a recheck race cannot finalize pre-handoff text after descendants/workflows happened to settle.

Validation:

  • bun test src/node/services/taskService.test.ts --test-name-pattern "deferred stream-end|deferred pre-handoff|recovers stale completed|workspace-turn|terminal settlements"
  • MUX_ESLINT_CONCURRENCY=1 make static-check

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0aefdf33dd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/taskService.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Addressed the latest finding:

  • Deferred workspace-turn marker writes now run under the same per-handle settlement lock and re-read the current record while locked.
  • Terminal handles are skipped, so a deferred marker cannot resurrect a handle that was interrupted/completed concurrently.

Validation:

  • bun test src/node/services/taskService.test.ts --test-name-pattern "deferred marker|deferred stream-end|deferred pre-handoff|workspace-turn"
  • MUX_ESLINT_CONCURRENCY=1 make static-check

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b2c9458adf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/taskService.ts
Comment thread src/node/services/taskService.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Addressed the latest findings:

  • Workspace-turn stream abort settlement now only treats explicit user aborts as terminal; system/startup aborts leave the handle running so resumed streams can complete it.
  • Workspace-turn stream errors now settle as failed unless they are in the recoverable set (aborted, context_exceeded, runtime_start_failed), so terminal errors such as unknown, retry_failed, empty_output, and stream_truncated do not leave handles running forever.

Validation:

  • bun test src/node/services/taskService.test.ts --test-name-pattern "system stream aborts|stream aborts mark|terminal stream errors|recoverable stream errors|workspace-turn"
  • MUX_ESLINT_CONCURRENCY=1 make static-check

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 896b61b12a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/taskService.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Addressed the latest finding:

  • Terminal workspace-turn stream errors now recover a persisted active handle by workspaceId when the in-memory active map is empty (for example after startup auto-retry resumes a turn after restart).
  • Recoverable errors still leave the handle running for retry/resume.

Validation:

  • bun test src/node/services/taskService.test.ts --test-name-pattern "terminal stream errors|recoverable stream errors|system stream aborts|workspace-turn"
  • MUX_ESLINT_CONCURRENCY=1 make static-check

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9d72dc2113

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/taskService.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Addressed the startup auto-retry race by treating pending/queued/preparing workspace turns as live during stale-handle sweeps, with a regression test covering countActiveWorkspaceTurns preserving the running handle.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 945f617180

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/agentSession.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Addressed the workflow-result metadata issue by filtering assistant stream metadata to the workspace-turn correlation marker only. Added a startup auto-retry regression test proving workflow-result metadata is not stamped onto the assistant stream while workspace-turn metadata still propagates.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 704b373b9b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/taskService.ts Outdated
Comment thread src/node/services/taskService.ts
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Addressed both latest findings:

  • Recoverable workspace-turn stream errors now settle as errors once no retry/preparing/streaming recovery is in flight, while pending retries keep the handle running.
  • Agent-task stream-end now treats task-owned workspace-turn handles like other background awaitables and blocks agent_report finalization until the task awaits them.

Validation: targeted TaskService regressions, startup metadata tests, WorkspaceService pending-turn test, and MUX_ESLINT_CONCURRENCY=1 make static-check.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 564c236a1d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/taskService.ts Outdated
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Addressed the deterministic retry-signal finding. AgentSession now exposes a stream-error recovery decision promise that resolves once a recovery retry is visibly preparing or once terminal auto-retry scheduling/abandonment has completed; TaskService awaits that signal before deciding whether a recoverable workspace-turn error is still live or should settle.

Validation: targeted TaskService regressions, targeted AgentSession startup metadata regressions, WorkspaceService pending-turn regression, and MUX_ESLINT_CONCURRENCY=1 make static-check.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: 49c3c4a146

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33 ThomasK33 added this pull request to the merge queue Jun 19, 2026
Merged via the queue into main with commit c263729 Jun 19, 2026
37 of 41 checks passed
@ThomasK33 ThomasK33 deleted the task-workspace-qzx9 branch June 19, 2026 19:22
@mux-bot mux-bot Bot mentioned this pull request Jun 19, 2026
jim-fung pushed a commit to jim-fung/mux that referenced this pull request Jul 1, 2026
## Summary

Long-lived auto-cleanup PR maintained by the **Auto-Cleanup Agent**.
Each pass lands at most one extremely low-risk, behavior-preserving
cleanup drawn from recently merged `main` activity, then advances the
checkpoint below. The prior long-lived PR (coder#3559) merged into `main`;
this is its successor.

Auto-cleanup checkpoint: 2af29df

## This pass

Considered the one new `main` commit since the previous checkpoint —
coder#3603 (`feat: add built-in loop skill`). That change adds the advertised
`loop` built-in skill (`src/node/builtinSkills/loop.md`), regenerates
`builtInSkillContent.generated.ts` (generated — off-limits), and extends
the agent-skills discovery test expectations. The
skill-content/generated surface offers no safe hand-edit, so the cleanup
was drawn from the production sibling of that area: the skill-discovery
service `agentSkillsService.ts`.

Landed one behavior-preserving cleanup in `agentSkillsService.ts`:
`readSkillDescriptorFromDir` open-coded the same invalid-skill
diagnostic push — `options?.invalidSkills?.push({ directoryName, scope,
displayPath: skillFilePath, message, hint })` — at six failure sites
(missing/unreadable SKILL.md, SKILL.md-is-a-directory, oversized file,
read failure, invalid descriptor, parse failure). Those six sites share
identical identity fields (`directoryName` / `scope` / `displayPath`)
and differ only in `message` + `hint`. All six now delegate to a single
function-local `pushInvalidSkill(message, hint)` closure that captures
the function-constant identity fields. Behavior-preserving: the pushed
object shape and every per-site `message`/`hint` are unchanged, and the
closure short-circuits on `options?.invalidSkills` exactly as the inline
calls did.

## Branch contents (net diff against `main`)

<details>
<summary>Cleanups carried by this PR (not yet merged)</summary>

- **`agentSkillsService.ts` — dedupe invalid-skill diagnostics.**
coder#3603's new built-in skill lives in the agent-skills discovery area;
`readSkillDescriptorFromDir` open-coded the same
`options?.invalidSkills?.push({ directoryName, scope, displayPath:
skillFilePath, message, hint })` at six failure sites that share
identical identity fields and differ only in `message`/`hint`. All six
now delegate to a single function-local `pushInvalidSkill(message,
hint)` closure. Behavior-preserving (identical pushed shape, messages,
hints, and `options?.invalidSkills` short-circuit).
- **`workspaceGoalService.ts` — dedupe session file path resolution.**
The three private accessors `getFilePath` / `getHistoryFilePath` /
`getBoardFilePath` each open-coded the same `assert(...non-empty
workspaceId)` guard and `path.join(getSessionDir(...), <FILE_CONST>)`.
All three now delegate to a single `resolveSessionFilePath(workspaceId,
fileName)` helper. Behavior-preserving (identical guard + join; accessor
names and callers unchanged).
- **`task.ts` — dedupe plan-agent explore-only error.** The plan-agent
restriction error `In the plan agent you may only spawn agentId:
"explore" tasks.` was open-coded twice (the workspace-turn guard and the
per-launch agent-id guard). Both now reference a single module-level
`PLAN_AGENT_EXPLORE_ONLY_ERROR` constant. Behavior-preserving (identical
string output).
- **`task_await.ts` — dedupe workspace-turn error fallback.** coder#3600's
new workspace-turn await paths open-coded the fallback error message
`"Workspace turn failed"` three times (`<record>.error ?? "Workspace
turn failed"`). All three now reference a single module-level
`WORKSPACE_TURN_DEFAULT_ERROR` constant. Behavior-preserving (identical
string output).
- **`goalToolUtils.tsx` / `SetGoalToolCall.tsx` — dedupe turn-count
pluralization.** coder#3595's new goal toolcards open-coded `` `${n} turn${n
=== 1 ? "" : "s"}` `` in both `formatGoalTurns` and the set_goal
toolcard's `formatOptionalTurnCap`. Both now call a shared
`pluralizeTurns(count)` helper. Behavior-preserving (identical string
output).
- **`heartbeat.ts` — dedupe heartbeat tool success result
construction.** coder#3596's new `heartbeat` tool open-coded the same `{
success: true, action, configured, settings, summary }` success object
three times (`get` / `set` / `unset`), each re-calling `summarize({
action, settings })`. All three now route through a single
`buildSuccessResult(action, settings)` helper that derives `configured`
from `settings`. Behavior-preserving: the per-branch `configured` values
(`settings != null`, `false`, `true`) all equal `settings != null` given
the non-null `Ok` payload type for `set`.
- **`taskService.ts` — dedupe startup recovery candidate listing.**
coder#3594's `TaskService.initialize` open-coded the `awaiting_report` /
`running` candidate filters twice (initial computation and
post-interrupt refresh). Both call sites now reuse a single
`listStartupRecoveryCandidates(sourceConfig)` closure returning `{
awaitingReportTasks, runningTasks }`, matching the existing
`listQueuedTasks` pattern. Behavior-preserving.
- **`ChatPane.tsx` — dedupe send-queued-immediately in-flight guard
clear.** coder#3592 introduced `sendQueuedImmediatelyInFlightRef` to block
duplicate send-now attempts; releasing that guard ("clear it only if it
still points at this attempt") was open-coded twice — once after a
failed interrupt result and once in the `catch` arm. Both call sites now
invoke a single callback-local closure `clearInFlightGuardIfCurrent()`.
Behavior-preserving: the failed-result site's combined
`!interruptResult.success && ref === id` becomes `if
(!interruptResult.success) clearInFlightGuardIfCurrent()` (logically
identical); the `catch` site is a verbatim substitution.
- **`WorkflowRunner.ts` — dedupe `parallelAgents` child-failure abort
handling** into a single batch-local closure
`applyChildFailureToBatch(error)`, shared by the per-run task closure
and the bulk-create path.
- **`ChatInput/index.tsx` — dedupe edit-mode attachment toast** string
`"Attachments cannot be added while editing a message."` into a single
module-level constant `EDIT_MODE_ATTACHMENT_ERROR_MESSAGE`, replacing
three inline copies across the add-attachment / paste / drop guards.

</details>

<details>
<summary>Prior passes (landed via coder#3559, merged)</summary>

- Replaced an inline terminal-status check in
`WorkflowService.interruptRunOnAbort` with the
`isTerminalWorkflowRunStatus` helper.
- Deduplicated the `workspaceId`-only metadata JSON Schema repeated by
three id-targeted host actions in `workspaceHostActions.ts` into
`WORKSPACE_ID_ONLY_INPUT_SCHEMA`.
- Extracted a shared `BlockedBadge` component in
`WorkflowDefinitionToolCall.tsx`.
- Deduplicated the repeated `argsSchema.properties must be an object`
error string in `workflowArgs.ts`.
- Deduplicated mocked-module specifier strings in
`useModelsFromSettings.test.ts`.
- Removed a redundant `toggleWorkflowExpanded` wrapper in
`WorkflowRunToolCall.tsx`.
- Deduplicated `SAVE_AUTOMATION_ERROR_MESSAGE` /
`REMOVE_AUTOMATION_ERROR_MESSAGE` in `AutomationModal.tsx`.
- Replaced a duplicated `"wfr_"` literal in `WorkflowRunner.ts` with
`isWorkflowRunTaskId()`.

</details>

## Validation

- `make static-check` — ESLint, both `tsgo` typecheck configs, and
Prettier all pass on the rebased tree. The only failing step is
`fmt-shell-check`, which requires `shfmt` (not installed in this
environment); no shell scripts were touched, so CI (which has `shfmt`)
is unaffected.
- `bun test src/node/services/agentSkills/agentSkillsService.test.ts` —
20 pass, 0 fail (covers skill discovery + the invalid-skill diagnostics
path this pass deduped); `builtInLoopSkill.test.ts` — 1 pass.

---

_Generated with `mux` • Model: `anthropic:claude-opus-4-8` • Thinking:
`xhigh`_

<!-- mux-attribution: model=anthropic:claude-opus-4-8 thinking=xhigh -->

---------

Co-authored-by: mux-bot[bot] <264182336+mux-bot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant