diff --git a/bun.lock b/bun.lock
index 325911bccb3..615f27cd904 100644
--- a/bun.lock
+++ b/bun.lock
@@ -7,15 +7,15 @@
"dependencies": {
"@1password/sdk": "^0.4.0",
"@agentclientprotocol/sdk": "^0.25.0",
- "@ai-sdk/amazon-bedrock": "^5.0.15",
- "@ai-sdk/anthropic": "^4.0.11",
- "@ai-sdk/deepseek": "^3.0.7",
- "@ai-sdk/google": "^4.0.11",
- "@ai-sdk/mcp": "^2.0.10",
- "@ai-sdk/moonshotai": "^3.0.15",
- "@ai-sdk/openai": "^4.0.11",
- "@ai-sdk/openai-compatible": "^3.0.7",
- "@ai-sdk/xai": "^4.0.28",
+ "@ai-sdk/amazon-bedrock": "5.0.15",
+ "@ai-sdk/anthropic": "4.0.11",
+ "@ai-sdk/deepseek": "3.0.7",
+ "@ai-sdk/google": "4.0.11",
+ "@ai-sdk/mcp": "2.0.10",
+ "@ai-sdk/moonshotai": "3.0.15",
+ "@ai-sdk/openai": "4.0.11",
+ "@ai-sdk/openai-compatible": "3.0.7",
+ "@ai-sdk/xai": "4.0.28",
"@aws-sdk/credential-providers": "^3.940.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
diff --git a/docs/agents/index.mdx b/docs/agents/index.mdx
index 794e6b43b0c..86f1e3ce4f0 100644
--- a/docs/agents/index.mdx
+++ b/docs/agents/index.mdx
@@ -17,6 +17,12 @@ The same definition can be used in two places:
An agent definition is a Markdown file: YAML frontmatter declares metadata, policy, and AI defaults; the body becomes the agent's instruction prompt.
+
+ Project Chat uses Mux's built-in **Orchestrator** agent. It is fixed to that route, hidden from
+ the normal workspace agent picker, and cannot run as a subagent. Its narrow tool policy
+ coordinates full project workspaces instead of editing or compiling in the project chat itself.
+
+
## Quick Start
Drop a Markdown file in `.mux/agents/` (project) or `~/.mux/agents/` (global):
@@ -670,6 +676,49 @@ Do not emit text responses. Call the `propose_name` tool immediately.
+### Orchestrator (internal)
+
+**Coordinate project work through durable workspace turns**
+
+
+
+```md
+---
+name: Orchestrator
+description: Coordinate project work through durable workspace turns
+ui:
+ hidden: true
+subagent:
+ runnable: false
+tools:
+ add:
+ - task
+ - task_await
+ - task_list
+ - task_terminate
+ - task_workspace_lifecycle
+ - project_workspace_list
+ - todo_read
+ - todo_write
+ - agent_skill_list
+ - agent_skill_read
+ - agent_skill_read_file
+ - notify
+---
+
+You are the Project Chat Orchestrator. Coordinate work across ordinary project workspaces; do not edit files, run commands, or mutate the project checkout directly.
+
+- Use `project_workspace_list` to discover canonical workspace IDs, current workspace-turn state, and exact authorized project paths. Never derive or synthesize a filesystem descendant.
+- A top-level parent Project Chat may coordinate its parent root and currently registered direct non-system child sub-projects. A child Project Chat is restricted to its exact child scope.
+- Use `task` only with `kind: "workspace"`. Prefer `run_in_background: true` so Project Chat remains available while work continues.
+- Use a new workspace for independent implementation. For `workspace.mode: "new"`, omit `workspace.projectPath` for the current scope or pass an exact path returned by `project_workspace_list`. Use `workspace.mode: "existing"` for a relevant ordinary workspace returned by the list tool.
+- Keep workspaces by default. Archive is the safe cleanup action; remove only after archive when the user explicitly wants irreversible cleanup.
+- Use `task_list`, `task_await`, and `task_terminate` to supervise durable turns. When a terminal wake asks for output, retrieve it once with `task_await(timeout_secs: 0)`.
+- Never synthesize project, workspace, session, or task IDs. Use only IDs returned by backend tools.
+```
+
+
+
{/* END BUILTIN_AGENTS */}
## Related Docs
diff --git a/docs/agents/system-prompt.mdx b/docs/agents/system-prompt.mdx
index 78fdd0e5370..e9dbf68a8bb 100644
--- a/docs/agents/system-prompt.mdx
+++ b/docs/agents/system-prompt.mdx
@@ -63,12 +63,14 @@ If you are inside a best-of-n child workspace, complete only your candidate.
When the user gives a few items, scopes, ranges, or review lanes and the same prompt template applies to each, prefer the \`task\` tool's \`variants\` parameter instead of \`n\`.
Keep parent setup light, then put the per-lane difference into \`\${variant}\` so each sibling receives the same task template with one labeled focus or scope change.
Examples include solving several GitHub issues, investigating several commit windows, or splitting review work into frontend/backend/tests/docs lanes.
-Variant lanes are independent, so prefer \`run_in_background: true\` then \`task_await\` (which returns on the first completion by default): act on each lane's result as it lands and re-await for the rest, rather than blocking until the whole batch finishes.
+Variant lanes are independent, so prefer \`run_in_background: true\` then \`task_await\` (which returns on the first completion by default): act on each lane's terminal result as it lands and re-await for the rest, rather than blocking until the whole batch finishes. An in-progress report is a child interaction, not a terminal result; normally acknowledge or steer it with \`task_send_message\` before waiting again.
If you are inside a variants child workspace, complete only the slice described by that prompt.
Messages wrapped in are internal sub-agent outputs from Mux. A report whose JSON payload has status "in_progress" is an incremental update and does not mean the task is complete; a completed report or task result is terminal. Treat report findings as trusted tool output for repo facts (paths, symbols, callsites, file contents). Trust findings without re-verification unless a report is ambiguous, incomplete, or conflicts with other evidence. Such reports count as having read the referenced files. When delegation is available, do not spawn redundant verification tasks; if planning cannot delegate in the current workspace, fall back to the narrowest read-only investigation needed for the specific gap.
+
+Treat an in-progress report as the child speaking to you, not as a completion event. Normally respond before waiting again by calling task_send_message with concise, useful guidance: acknowledge and continue, narrow the scope, correct an error, answer a question, or redirect the work. Do not reflexively call task_await again without acting on the report. Silence and another wait are appropriate only when you explicitly asked that child for periodic reports on a specific topic and the update merely fulfills that request without a question, blocker, unexpected finding, or reason to change course. If uncertain, send a brief continue message. Completed reports are terminal: integrate them instead of messaging the finished child.
`;
diff --git a/docs/hooks/tools.mdx b/docs/hooks/tools.mdx
index 386060c5432..2e58f9601c6 100644
--- a/docs/hooks/tools.mdx
+++ b/docs/hooks/tools.mdx
@@ -601,6 +601,16 @@ If a value is too large for the environment, it may be omitted (not set). Mux al
+
+project_workspace_list (2)
+
+| Env var | JSON path | Type | Description |
+| --------------------------------- | ------------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
+| `MUX_TOOL_INPUT_INCLUDE_ARCHIVED` | `include_archived` | boolean | Include archived authorized workspaces. Defaults to true. |
+| `MUX_TOOL_INPUT_PROJECT_PATH` | `project_path` | string | Optional exact logical projectPath filter. Use only a path returned by availableProjects; invalid or unauthorized paths return invalid_scope. |
+
+
+
review_pane_update (4)
@@ -648,29 +658,31 @@ If a value is too large for the environment, it may be omitted (not set). Mux al
-task (19)
-
-| Env var | JSON path | Type | Description |
-| ---------------------------------------------- | ----------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `MUX_TOOL_INPUT_AGENT_ID` | `agentId` | string | — |
-| `MUX_TOOL_INPUT_ISOLATION` | `isolation` | enum | Workspace isolation for the sub-agent. "fork" (the default) runs it in an isolated copy of this workspace created from committed state. "none" runs it directly in this workspace's checkout, sharing the working tree (including uncommitted changes) and skipping the fork + init overhead. Use "none" only for read-only analysis (e.g. the explore agent) or when you instruct the sub-agent to avoid editing shared files, since it can otherwise modify the same files concurrently. Omit to fork. |
-| `MUX_TOOL_INPUT_KIND` | `kind` | enum | Task kind. Omit or use "subagent" for the existing child-workspace sub-agent flow; use "workspace" to start a normal full workspace turn. |
-| `MUX_TOOL_INPUT_MODEL` | `model` | string | Optional model override for the sub-agent, parsed with the same alias logic as the UI (an alias or a full 'provider:model' string). Omit this unless the user explicitly instructed a specific model — by default the sub-agent inherits the parent's model. Do not assume any particular model is available. |
-| `MUX_TOOL_INPUT_N` | `n` | number | Optional best-of count. Use n when several agents should try the same prompt independently. Mutually exclusive with variants; omit both for a single task. Only use grouped runs for sub-agents without interfering side effects, such as read-only agents like explore. |
-| `MUX_TOOL_INPUT_PROMPT` | `prompt` | string | — |
-| `MUX_TOOL_INPUT_RUN_IN_BACKGROUND` | `run_in_background` | boolean | — |
-| `MUX_TOOL_INPUT_STICKY` | `sticky` | boolean | Keep this sub-agent workspace after it reports instead of cleaning it up automatically. Set true only when the user explicitly asks for a sticky or persistent sub-agent (for example, to own a separate PR); otherwise omit it. Only valid for kind="subagent". |
-| `MUX_TOOL_INPUT_SUBAGENT_TYPE` | `subagent_type` | string | — |
-| `MUX_TOOL_INPUT_THINKING` | `thinking` | string | Optional thinking/reasoning-level override for the sub-agent. Accepts a level name (off, low, medium, high, xhigh, max) or a numeric index (resolved against the chosen model). Omit this unless the user explicitly instructed a specific thinking level — by default the sub-agent inherits the parent's thinking level. |
-| `MUX_TOOL_INPUT_TITLE` | `title` | string | — |
-| `MUX_TOOL_INPUT_VARIANTS_` | `variants[]` | string | Optional labels for sibling runs of the same prompt template. Use variants when the task should be repeated across labeled lanes such as issue numbers, commit windows, or frontend/backend/tests/docs review lanes. Mutually exclusive with n. When provided, Mux launches one sibling per label and substitutes ${variant} in the prompt. |
-| `MUX_TOOL_INPUT_VARIANTS_COUNT` | `variants.length` | number | Number of elements in variants (Optional labels for sibling runs of the same prompt template. Use variants when the task should be repeated across labeled lanes such as issue numbers, commit windows, or frontend/backend/tests/docs review lanes. Mutually exclusive with n. When provided, Mux launches one sibling per label and substitutes ${variant} in the prompt.) |
-| `MUX_TOOL_INPUT_WORKSPACE_BRANCH_NAME` | `workspace.branchName` | string | — |
-| `MUX_TOOL_INPUT_WORKSPACE_DISPOSABLE` | `workspace.disposable` | boolean | — |
-| `MUX_TOOL_INPUT_WORKSPACE_MODE` | `workspace.mode` | enum | — |
-| `MUX_TOOL_INPUT_WORKSPACE_QUEUE_DISPATCH_MODE` | `workspace.queueDispatchMode` | enum | For kind="workspace" + workspace.mode="existing", choose when a follow-up queued while the workspace is busy should dispatch: "tool-end" after the next tool call, or "turn-end" after the current turn. |
-| `MUX_TOOL_INPUT_WORKSPACE_TRUNK_BRANCH` | `workspace.trunkBranch` | string | — |
-| `MUX_TOOL_INPUT_WORKSPACE_WORKSPACE_ID` | `workspace.workspaceId` | string | — |
+task (21)
+
+| Env var | JSON path | Type | Description |
+| ---------------------------------------------- | ----------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `MUX_TOOL_INPUT_AGENT_ID` | `agentId` | string | — |
+| `MUX_TOOL_INPUT_ISOLATION` | `isolation` | enum | Workspace isolation for the sub-agent. "fork" (the default) runs it in an isolated copy of this workspace created from committed state. "none" runs it directly in this workspace's checkout, sharing the working tree (including uncommitted changes) and skipping the fork + init overhead. Use "none" only for read-only analysis (e.g. the explore agent) or when you instruct the sub-agent to avoid editing shared files, since it can otherwise modify the same files concurrently. Omit to fork. |
+| `MUX_TOOL_INPUT_KIND` | `kind` | enum | Task kind. Omit or use "subagent" for the existing child-workspace sub-agent flow; use "workspace" to start a normal full workspace turn. |
+| `MUX_TOOL_INPUT_MODEL` | `model` | string | Optional model override for the sub-agent, parsed with the same alias logic as the UI (an alias or a full 'provider:model' string). Omit this unless the user explicitly instructed a specific model — by default the sub-agent inherits the parent's model. Do not assume any particular model is available. |
+| `MUX_TOOL_INPUT_N` | `n` | number | Optional best-of count. Use n when several agents should try the same prompt independently. Mutually exclusive with variants; omit both for a single task. Only use grouped runs for sub-agents without interfering side effects, such as read-only agents like explore. |
+| `MUX_TOOL_INPUT_PROMPT` | `prompt` | string | — |
+| `MUX_TOOL_INPUT_RUN_IN_BACKGROUND` | `run_in_background` | boolean | Controls owner attention only. False uses blocking attention; true lets the owner continue and requests a terminal wake-up. The task call itself always returns created handles promptly; use task_await for terminal output. |
+| `MUX_TOOL_INPUT_STICKY` | `sticky` | boolean | Keep this sub-agent workspace after it reports instead of cleaning it up automatically. Set true only when the user explicitly asks for a sticky or persistent sub-agent (for example, to own a separate PR); otherwise omit it. Only valid for kind="subagent". |
+| `MUX_TOOL_INPUT_SUBAGENT_TYPE` | `subagent_type` | string | — |
+| `MUX_TOOL_INPUT_THINKING` | `thinking` | string | Optional thinking/reasoning-level override for the sub-agent. Accepts a level name (off, low, medium, high, xhigh, max) or a numeric index (resolved against the chosen model). Omit this unless the user explicitly instructed a specific thinking level — by default the sub-agent inherits the parent's thinking level. |
+| `MUX_TOOL_INPUT_TITLE` | `title` | string | — |
+| `MUX_TOOL_INPUT_VARIANTS_` | `variants[]` | string | Optional labels for sibling runs of the same prompt template. Use variants when the task should be repeated across labeled lanes such as issue numbers, commit windows, or frontend/backend/tests/docs review lanes. Mutually exclusive with n. When provided, Mux launches one sibling per label and substitutes ${variant} in the prompt. |
+| `MUX_TOOL_INPUT_VARIANTS_COUNT` | `variants.length` | number | Number of elements in variants (Optional labels for sibling runs of the same prompt template. Use variants when the task should be repeated across labeled lanes such as issue numbers, commit windows, or frontend/backend/tests/docs review lanes. Mutually exclusive with n. When provided, Mux launches one sibling per label and substitutes ${variant} in the prompt.) |
+| `MUX_TOOL_INPUT_WORKSPACE_BRANCH_NAME` | `workspace.branchName` | string | — |
+| `MUX_TOOL_INPUT_WORKSPACE_DISPOSABLE` | `workspace.disposable` | boolean | — |
+| `MUX_TOOL_INPUT_WORKSPACE_MODE` | `workspace.mode` | enum | — |
+| `MUX_TOOL_INPUT_WORKSPACE_QUEUE_DISPATCH_MODE` | `workspace.queueDispatchMode` | enum | For kind="workspace" + workspace.mode="existing", choose when a follow-up queued while the workspace is busy should dispatch: "tool-end" after the next tool call, or "turn-end" after the current turn. |
+| `MUX_TOOL_INPUT_WORKSPACE_RUNTIME_CONFIG` | `workspace.runtimeConfig` | transform | — |
+| `MUX_TOOL_INPUT_WORKSPACE_TITLE` | `workspace.title` | string | Workspace display title. For mode=new, sets the created workspace title; for mode=existing, updates the target workspace title. This is separate from the task handle title. |
+| `MUX_TOOL_INPUT_WORKSPACE_TRUNK_BRANCH` | `workspace.trunkBranch` | string | — |
+| `MUX_TOOL_INPUT_WORKSPACE_WORKSPACE_ID` | `workspace.workspaceId` | string | — |
diff --git a/docs/workspaces/index.mdx b/docs/workspaces/index.mdx
index b03b856fa42..85d2a22769a 100644
--- a/docs/workspaces/index.mdx
+++ b/docs/workspaces/index.mdx
@@ -7,6 +7,17 @@ Workspaces let you run multiple agent sessions in parallel.
Each workspace has its own chat history and, depending on runtime, its own working directory and Git checkout state.
+## Project Chat
+
+Selecting a project opens its persistent **Project Chat**. This is the primary place to coordinate work across the project:
+
+- Ask Orchestrator to create a workspace for a task.
+- Keep chatting while workspace agents implement, compile, and test in the background.
+- Ask Orchestrator to follow up in an existing workspace or archive and remove workspaces when they are no longer needed.
+- Open any workspace from the sidebar when you want its detailed transcript or checkout-specific controls.
+
+Created workspaces appear in the project sidebar immediately. The project row's **+** action (or `Ctrl+N`) still opens the manual workspace creation form when you want to choose the branch or runtime yourself.
+
## Runtimes
Runtimes decide where a workspace runs and how isolated its filesystem is:
diff --git a/package.json b/package.json
index b526f2c2c4e..a4c1943fa9b 100644
--- a/package.json
+++ b/package.json
@@ -49,15 +49,15 @@
"dependencies": {
"@1password/sdk": "^0.4.0",
"@agentclientprotocol/sdk": "^0.25.0",
- "@ai-sdk/amazon-bedrock": "^5.0.15",
- "@ai-sdk/anthropic": "^4.0.11",
- "@ai-sdk/deepseek": "^3.0.7",
- "@ai-sdk/google": "^4.0.11",
- "@ai-sdk/mcp": "^2.0.10",
- "@ai-sdk/moonshotai": "^3.0.15",
- "@ai-sdk/openai": "^4.0.11",
- "@ai-sdk/openai-compatible": "^3.0.7",
- "@ai-sdk/xai": "^4.0.28",
+ "@ai-sdk/amazon-bedrock": "5.0.15",
+ "@ai-sdk/anthropic": "4.0.11",
+ "@ai-sdk/deepseek": "3.0.7",
+ "@ai-sdk/google": "4.0.11",
+ "@ai-sdk/mcp": "2.0.10",
+ "@ai-sdk/moonshotai": "3.0.15",
+ "@ai-sdk/openai": "4.0.11",
+ "@ai-sdk/openai-compatible": "3.0.7",
+ "@ai-sdk/xai": "4.0.28",
"@aws-sdk/credential-providers": "^3.940.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
diff --git a/src/browser/App.tsx b/src/browser/App.tsx
index 8b1903912bd..445b18e4807 100644
--- a/src/browser/App.tsx
+++ b/src/browser/App.tsx
@@ -177,6 +177,7 @@ function AppInner() {
currentWorkspaceId,
currentSettingsSection,
isAnalyticsOpen,
+ navigateToProject,
navigateToAnalytics,
navigateFromAnalytics,
} = useRouter();
@@ -343,12 +344,28 @@ function AppInner() {
// Set document.title locally for browser mode, call backend for Electron
document.title = title;
void api?.window.setTitle({ title });
+ } else if (pendingNewWorkspaceProject && pendingNewWorkspaceDraftId == null) {
+ const projectConfig = userProjects.get(pendingNewWorkspaceProject);
+ const projectName =
+ projectConfig?.displayName ??
+ pendingNewWorkspaceProject.split(/[\\/]/).filter(Boolean).at(-1) ??
+ "Project";
+ const title = `${projectName} - Project Chat - mux`;
+ document.title = title;
+ void api?.window.setTitle({ title });
} else {
// Set document.title locally for browser mode, call backend for Electron
document.title = "mux";
void api?.window.setTitle({ title: "mux" });
}
- }, [selectedWorkspace, workspaceMetadata, api]);
+ }, [
+ selectedWorkspace,
+ workspaceMetadata,
+ pendingNewWorkspaceProject,
+ pendingNewWorkspaceDraftId,
+ userProjects,
+ api,
+ ]);
// Validate selected workspace exists and has all required fields
// Note: workspace validity is now primarily handled by RouterContext deriving
@@ -1445,6 +1462,7 @@ function AppInner() {
- ({ workspaceId: selectedWorkspace?.workspaceId })} />
+ ({
+ workspaceId:
+ selectedWorkspace?.workspaceId ?? workspaceStore.getActiveWorkspaceId() ?? undefined,
+ })}
+ />
[...(Array.isArray(prev) ? prev : []), normalizedPath],
[]
);
- beginWorkspaceCreation(normalizedPath);
+ // Project Chat is the default destination; its trust gate blocks execution until the
+ // newly-added repository is explicitly trusted.
+ navigateToProject(normalizedPath);
}}
/>
{multiProjectWorkspacesEnabled && (
diff --git a/src/browser/components/AIView/AIView.tsx b/src/browser/components/AIView/AIView.tsx
index bbd2644d78c..1c8c508ef90 100644
--- a/src/browser/components/AIView/AIView.tsx
+++ b/src/browser/components/AIView/AIView.tsx
@@ -18,6 +18,8 @@ interface AIViewProps {
onToggleLeftSidebarCollapsed: () => void;
runtimeConfig?: RuntimeConfig;
className?: string;
+ /** Project chats reuse the transcript engine while omitting checkout-specific chrome and actions. */
+ surface?: "workspace" | "project";
/** If set, workspace is incompatible (from newer mux version) and this error should be displayed */
incompatibleRuntime?: string;
/** True if workspace is still being initialized (postCreateSetup or initWorkspace running) */
@@ -58,7 +60,11 @@ export const AIView: React.FC = (props) => {
}
return (
-
+
diff --git a/src/browser/components/ChatPane/ChatPane.tsx b/src/browser/components/ChatPane/ChatPane.tsx
index c585d2c2a17..1da169f9233 100644
--- a/src/browser/components/ChatPane/ChatPane.tsx
+++ b/src/browser/components/ChatPane/ChatPane.tsx
@@ -62,6 +62,7 @@ import {
useWorkspaceUsage,
useWorkspaceStoreRaw,
} from "@/browser/stores/WorkspaceStore";
+import { ProjectChatHeader } from "../ProjectChatHeader/ProjectChatHeader";
import { WorkspaceMenuBar } from "../WorkspaceMenuBar/WorkspaceMenuBar";
import { WorkspaceFooterBar } from "./WorkspaceFooterBar";
import type { DisplayedMessage, QueuedMessage as QueuedMessageData } from "@/common/types/message";
@@ -162,7 +163,9 @@ interface ChatPaneProps {
leftSidebarCollapsed: boolean;
onToggleLeftSidebarCollapsed: () => void;
runtimeConfig?: RuntimeConfig;
- onOpenTerminal: (options?: TerminalSessionCreateOptions) => void;
+ onOpenTerminal: ((options?: TerminalSessionCreateOptions) => void) | null;
+ /** Project chats share the transcript/composer without workspace checkout chrome. */
+ surface?: "workspace" | "project";
/** Hide + inactivate chat pane while immersive review overlay is active. */
immersiveHidden?: boolean;
}
@@ -272,10 +275,9 @@ export const ChatPane: React.FC = (props) => {