Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions DEVLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13312,3 +13312,39 @@ production Playwright build passed all 45 desktop, mobile, accessibility, and
application journeys. Before/after evidence plus the UX health report are
preserved under `docs/audits/agent-studio-2026-07-15/`. Production verification
follows after tagging and deployment.

---

## 2026-07-15 — Mission Control agent fleet management

Promoted Mission Control from a read-only agent summary to the primary fleet
control plane. The new `/agents` and `/agents/[id]` routes own global profile
creation/editing, the single execution-runtime assignment, MCP-client rollups,
workspace filtering and bind/unbind actions, recent work, and permission-aware
archive/removal. Legacy Agent Studio URLs redirect to the new routes.

Binding writes name one workspace explicitly, re-check owner/admin membership
server-side, record the same durable audit/activity events as workspace writes,
and preserve archived bindings for reversible re-adoption. Workspace Agent
settings are now policy-only (capacity, capability overrides, routing,
engagement, and approval); Instance Admin is governance-only (request approval,
instance sharing, force-disable, audit/recovery) and links to Mission Control
for routine management. Instance admins get smart removal there, while profile
owners get archive and workspace admins can only unbind.

The global shell now labels ordinary aggregate pages as read-only and the Agent
fleet as a Global control plane. Navigation, account settings, workspace copy,
current guides, router integration coverage, and Playwright lifecycle/redirect
coverage were updated to reflect Define → Bind → Operate, with Govern reserved
for instance administrators.

Follow-up coverage moved the older multi-workspace binding and mobile smoke
contracts onto the Mission Control fleet routes. MCP-client creation is now
shown per binding only when the caller can administer that workspace, matching
the server-side credential authorization boundary.

Verification: typecheck and lint passed (existing repository warnings only);
the full Vitest gate passed 1,320 tests with one intentional live-connector
skip; the production build succeeded; the affected mobile and multi-workspace
specs passed 13 journeys; and the complete serial Playwright gate passed all 47
desktop, mobile, accessibility, and application journeys.
22 changes: 14 additions & 8 deletions docs/agents/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ lifecycle of an agent, the implicit heartbeat that comes from webhook delivery,
and where to find each piece of the surface in the app.

::: info Configuration map
Use **Agent Studio** for identity, prompt, and the single primary execution
runtime; **Workspace → Agent roster** for binding policy; **Workspace → Agent
access** for one or more MCP client credentials; **Instance Administration**
for global governance; and **Mission Control** for read-only operations.
Use **Mission Control → Agents** to define identities, choose the single primary
execution runtime, bind workspaces, and monitor recent work; **Workspace → Agent
policy** for local capacity/routing/engagement/approval; **Workspace → Agent
access** for one or more MCP client credentials; and **Instance Administration**
for approval, sharing, force-disable, audit, and recovery.
:::

## Two ways to run agent work
Expand Down Expand Up @@ -163,13 +164,14 @@ opening each agent's detail page.

A typical onboarding sequence:

1. **Define the identity.** Agent Studio → New profile starts with Hermes,
1. **Define the identity.** Mission Control → Agents → New profile starts with Hermes,
Claude, Codex, or custom. Set `name`, `profileKey`, description, avatar,
provider, and execution engine once.
2. **Choose the primary runtime.** A profile can select zero or one execution
host. Active workspace bindings inherit later identity/execution edits.
3. **Bind it to a workspace.** The workspace roster owns local capacity,
routing eligibility, engagement, approval, and capability overrides.
3. **Bind it to a workspace.** Mission Control owns binding and unbinding;
workspace settings own local capacity, routing eligibility, engagement,
approval, and capability overrides.
4. **Declare capabilities.** Lowercase, free-form. Common entries match
priority names (`urgent`, `high`) for `PRIORITY_MATCH` and label names
(`infra`, `frontend`) for `CAPABILITY_MATCH`.
Expand Down Expand Up @@ -370,7 +372,11 @@ without leaving the current page.
- **Agent detail page** — `/w/<slug>/agents/<id>`. Pipeline (assigned-but-not-
yet-acked → in-progress → recent), timeline (assignment, ack, transition,
comment events), uptime sparkline, webhook delivery health.
- **Settings → Agents** — `/w/<slug>/settings/agents`. Create, edit, archive.
- **Mission Control → Agents** — `/agents`. Create and edit profiles, attach
runtimes and MCP clients, bind/unbind workspaces, inspect recent runs, and
archive or safely remove identities.
- **Settings → Agent policy** — `/w/<slug>/settings/agents`. Workspace-local
capacity, routing eligibility, engagement, approval, and capabilities.
- **Settings → Workspace** — `/w/<slug>/settings/workspace`. The dispatch
knobs that govern agent selection.
- **Settings → Dispatch Rules** — declarative routing, evaluated before
Expand Down
47 changes: 24 additions & 23 deletions docs/agents/profiles-and-bindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ template. It's owned by a user and lives outside any workspace.
`profileKey` is **unique per owner**, so two different users can each
own a `victor` without colliding.

Manage profiles in **Agent Studio** at **`/settings/agents`** (the global,
account-level agents page). Its detail view reports runtime, workspace-binding,
and MCP-client readiness and is the only place identity/execution fields are
edited. This is also where your profiles surface on
[Mission Control](/guide/mission-control.html#my-agents).
Manage profiles in **Mission Control → Agents** at **`/agents`**. Its detail
view reports runtime, workspace-binding, MCP-client readiness, and recent work;
it is the primary place to create, edit, connect, bind, archive, or safely
remove an identity. The former `/settings/agents` and
`/settings/agents/[id]` routes redirect here so bookmarks remain valid.

Updating a profile synchronizes its identity and execution fields into every
active workspace binding. Binding policy such as capacity and capability
Expand All @@ -49,19 +49,17 @@ workspace, then layers per-workspace **policy** on top:
| Engagement mode | `engagementMode` | Per-binding default; null = inherit `Workspace.assignmentEngagementMode` |
| Approval gate | `requireApprovalBeforeStart` | Per-binding override of the workspace approval gate |

Bind, set policy, and unbind from the workspace agents page at
**`/w/[slug]/settings/agents`**. The **catalog** there lists the
profiles available to bind (ones you own, plus instance-shared ones)
that aren't already bound. The actions — `bind`, `setPolicy`, `unbind`,
and `remove` — are workspace-admin-gated.
Bind and unbind from a profile's **Workspace bindings** section in Mission
Control. The workspace selector only offers workspaces where the caller is an
owner or admin, and the server repeats that authorization check before every
write. Workspace settings at **`/w/[slug]/settings/agents`** retain only
workspace policy: capacity, capability overrides, routing eligibility,
engagement mode, and approval gates.

::: tip Unbind vs Delete
**Unbind** archives the binding (reversible) — runs, chats, and history are
preserved, and re-binding the same profile reuses the archived row.
**Delete** (the trash action beside Unbind) is a _smart remove_: it
hard-deletes a genuinely unused agent (no runs, comments, keys, or
assignments) and otherwise archives it — so a deletion can never cascade
away an agent's run history.
preserved, and re-binding the same profile reuses the archived row. Workspace
admins can unbind but cannot delete the global identity.
:::

## Tier 3 — Instance policy
Expand All @@ -75,19 +73,22 @@ Two governance levers belong to the instance admin (see
- **`disabledAt`** — a force-disable. A disabled profile (and all its
bindings) refuses dispatch and chat. Distinct from `archivedAt`
(deletion).
- **Remove** — Instance Admin → Agents has a **Remove** action
(`agents.profiles.remove`) beside Enable/Disable: it deletes a profile no
workspace has bound, and archives one that still has bindings (so those
agents aren't orphaned). Archived profiles drop out of the admin list and
the bind-catalog.
- **Approve or reject** — pending profile requests remain instance-governed.

Instance Admin → Agent governance intentionally does not duplicate routine
profile management. Each row links to Mission Control, where an instance admin
gets the safe **Remove profile** action (`agents.profiles.remove`): it deletes a
profile no workspace has bound, and archives one that still has bindings so
agents and history are not orphaned. Profile owners who are not instance admins
get **Archive profile** instead.

## Requesting a profile

Defining a _new_ profile from scratch is instance-admin-only — but any
member can **request** one:

1. A member calls `agentProfile.request` (the **Request a profile**
action on the workspace agents page) with a `profileKey`, name, and
1. A member calls `agentProfile.request` (the **Request profile** action in
Mission Control → Agents) with a `profileKey`, name, and
base capabilities. This creates a **pending** profile
(`requestedById` = the requester, `approvedAt` = null).
2. A pending profile is **not bindable** — it's hidden from every
Expand All @@ -106,7 +107,7 @@ runtime, while each workspace binding may have any number of linked `ApiKey`
credentials for Codex, Claude, Hermes, or other trusted MCP clients.

Create, inspect, rotate, revoke, and remove those credentials at
**`/w/[slug]/settings/access`**. Agent Studio aggregates the linked clients by
**`/w/[slug]/settings/access`**. Mission Control aggregates the linked clients by
binding and deep-links into Agent access with the correct workspace agent
preselected. The former `/settings/clients` and
`/w/[slug]/settings/clients` inventory routes redirect to Agent access.
Expand Down
4 changes: 2 additions & 2 deletions docs/agents/runtimes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ mode) and `runtimeMode` describes _how_ the agent stays online, `Runtime`
describes _where the work physically happens_.

::: info Runtime versus MCP client
An Agent Studio profile selects **zero or one primary execution runtime**.
A Mission Control agent profile selects **zero or one primary execution runtime**.
Codex, Claude, Hermes, and custom MCP clients connect with separate linked
credentials in the workspace's **Agent access** page; a binding can have many
of those clients. Adding an MCP client never changes the profile's runtime.
Expand Down Expand Up @@ -60,7 +60,7 @@ columns.
`LOCAL_DAEMON` rows. The detail page also shows whether the runtime
declares `terminal`, `filesystem`, and `git` access, plus the latest
sanitized runtime version/environment metadata when the host reports it.
- **Agent Studio profile detail** — readiness summary plus an editable primary
- **Mission Control agent profile detail** — readiness summary plus an editable primary
Runtime assignment that synchronizes to active workspace bindings.
- **Mission Control agents tab** — compact `RuntimeChip` next to the
runtime-mode pill.
Expand Down
18 changes: 12 additions & 6 deletions docs/guide/instance-admin.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Instance Admin

`/admin` is the instance-operator surface — the controls that span
*every* tenant on a Forge instance, rather than one workspace. It renders
_every_ tenant on a Forge instance, rather than one workspace. It renders
in its own graphite shell (distinct from the warm-paper workspace shell)
to make it obvious you've stepped up a level.

Expand All @@ -10,10 +10,10 @@ to make it obvious you've stepped up a level.
The `/admin` area is gated on `User.instanceRole === INSTANCE_ADMIN`.
There are exactly two instance roles:

| `InstanceRole` | Grants |
|---|---|
| `InstanceRole` | Grants |
| ---------------- | ------------------------------------------------------ |
| `INSTANCE_ADMIN` | The `/admin` shell + every `instanceAdmin.*` procedure |
| `MEMBER` | Default; no instance-level access |
| `MEMBER` | Default; no instance-level access |

This is **orthogonal to workspace roles**. Being `OWNER` of a workspace
doesn't make you an instance admin, and an instance admin isn't
Expand Down Expand Up @@ -79,10 +79,16 @@ instance admin can approve or reject (`agentProfile.listPending` →
`instanceShared` or force-disable one. See
[Agent profiles & bindings](/agents/profiles-and-bindings.html#requesting-a-profile).

Routine profile management does not live in the graphite admin shell. Each
governance row links to **Mission Control → Agents**, where authorized instance
admins can edit and safely remove a profile. Removal permanently deletes only
an unreferenced profile; otherwise it archives the definition and preserves
workspace bindings and history.

## Where to next

- [Mission Control](/guide/mission-control.html) — the cross-workspace
*user* home (read-only), separate from this operator surface.
- [Mission Control](/guide/mission-control.html) — cross-workspace operations
plus the agent fleet control plane, separate from instance governance.
- [Agent profiles & bindings](/agents/profiles-and-bindings.html) — the
approval + instance-sharing flow admins govern.
- [Runtimes](/agents/runtimes.html) — the compute hosts the instance
Expand Down
33 changes: 17 additions & 16 deletions docs/guide/mission-control.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# Mission Control

Mission Control is the cross-workspace home — the screen at `/` when you
sign in. Where a workspace shows you one tenant's world, Mission Control
shows you *all of them at once*: your assigned work, your agents, your
runtimes, and live activity, each row stamped with the workspace it
belongs to.
Mission Control is Forge's cross-workspace operating layer. The overview at
`/` shows every workspace at once; **Agents** at `/agents` is the global fleet
control plane for defining identities, binding them into workspaces, and
monitoring live use.

::: tip Chord
<kbd>g</kbd> <kbd>h</kbd> brings you home to Mission Control from
Expand All @@ -20,15 +19,17 @@ three global destinations, the workspace switcher, and links into
account-level settings:

- **Mission Control** (`/`) — this page.
- **Agents** (`/agents`) — global profiles, execution connections, workspace
bindings, recent runs, and safe archive/removal controls.
- **Inbox** (`/inbox`) — your mentions and assignments, aggregated
across workspaces.
- **Activity** (`/activity`) — the live cross-workspace run + event feed.

Everything in the concourse is **read-only**. The cards show you what's
happening and link you *into* the right workspace; the actual
writes — transitioning an issue, replying to a comment, dispatching an
agent — happen once you've clicked through into that workspace's shell.
This is deliberate: a single mutation always has one unambiguous tenant.
The overview, Inbox, and Activity remain **read-only across workspaces**. The
Agents destination is the deliberate exception and carries a **Global control
plane** scope label. Its binding writes always name one workspace explicitly
and require an owner/admin membership there; workspace policy edits still
happen inside that workspace's settings.

## The cards

Expand All @@ -39,12 +40,12 @@ client-side from the `global.*` tRPC routers.

Four tiles across the top:

| Tile | Shows |
|---|---|
| **Assigned to me** | Count of issues assigned to you, across every workspace |
| **Open issues** | Open-issue count summed across all your workspaces |
| Tile | Shows |
| --------------------- | -------------------------------------------------------- |
| **Assigned to me** | Count of issues assigned to you, across every workspace |
| **Open issues** | Open-issue count summed across all your workspaces |
| **Agent runs · live** | Active `AgentRun` count, plus how many agents are online |
| **Runtimes online** | `online / total` runtimes you've registered |
| **Runtimes online** | `online / total` runtimes you've registered |

### My work

Expand All @@ -62,7 +63,7 @@ rollup (`open · live · members`). Click to enter the workspace. The

Your agent **profiles** (global definitions you own), each with an
online pip, `@profileKey`, the workspaces it's bound into, and its
provider. Click through to the profile detail under `/settings/agents`.
provider. Click through to the profile detail under `/agents`.
See [Agent profiles & bindings](/agents/profiles-and-bindings.html) for
the profile → binding model.

Expand Down
6 changes: 3 additions & 3 deletions src/app/(app)/admin/agents/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ export default function AdminAgentsPage() {
return (
<AdminPage
activePath="/admin/agents"
crumbs={["Instance", "Agent policy"]}
crumbs={["Instance", "Agent governance"]}
eyebrow="Instance"
title="Agent policy"
subtitle="Share profiles to every workspace · force-disable across the instance"
title="Agent governance"
subtitle="Approve requests · control instance sharing · force-disable across every workspace"
>
<AdminAgents />
</AdminPage>
Expand Down
31 changes: 31 additions & 0 deletions src/app/(app)/agents/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { redirect } from "next/navigation";
import { GlobalShell } from "@/components/global-shell/global-shell";
import { getGlobalShellData } from "@/components/global-shell/data";
import { AgentDetailContent } from "@/app/(app)/settings/agents/[id]/agent-detail-content";

/** Mission Control profile detail and binding control surface. */
export default async function AgentFleetDetailPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const [data, { id }] = await Promise.all([getGlobalShellData(), params]);
if (!data) redirect("/signin");

return (
<GlobalShell
user={data.user}
workspaces={data.workspaces}
activePath="/agents"
crumbs={["Forge", "Mission Control", "Agents", "Profile"]}
scope="control"
contentClass="overflow-hidden"
>
<AgentDetailContent
id={id}
isInstanceAdmin={data.user.instanceRole === "INSTANCE_ADMIN"}
workspaces={data.workspaces}
/>
</GlobalShell>
);
}
34 changes: 34 additions & 0 deletions src/app/(app)/agents/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { redirect } from "next/navigation";
import { GlobalShell } from "@/components/global-shell/global-shell";
import { getGlobalShellData } from "@/components/global-shell/data";
import { AgentsContent } from "@/app/(app)/settings/agents/agents-content";

/** Mission Control fleet: define, bind, and operate global agent identities. */
export default async function AgentFleetPage({
searchParams,
}: {
searchParams: Promise<{ workspace?: string }>;
}) {
const [data, query] = await Promise.all([getGlobalShellData(), searchParams]);
if (!data) redirect("/signin");

return (
<GlobalShell
user={data.user}
workspaces={data.workspaces}
activePath="/agents"
crumbs={["Forge", "Mission Control", "Agents"]}
title="Agent fleet"
subtitle="Define identities, connect execution, bind workspaces, and monitor live use."
eyebrow="Mission Control"
scope="control"
contentClass="overflow-hidden"
>
<AgentsContent
isInstanceAdmin={data.user.instanceRole === "INSTANCE_ADMIN"}
workspaces={data.workspaces}
initialWorkspaceId={query.workspace}
/>
</GlobalShell>
);
}
3 changes: 2 additions & 1 deletion src/app/(app)/settings/access/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1322,7 +1322,8 @@ function Intro({ baseUrl }: { baseUrl: string }) {
</div>
<div className="text-meta mt-2 rounded-md border border-ember/30 bg-ember/5 px-3 py-2 text-muted-foreground">
This page is the credential source of truth: create, inspect, rotate, revoke, and delete
every MCP client here. Primary execution runtimes stay with the identity in Agent Studio.
every MCP client here. Primary execution runtimes stay with the identity in Mission Control
→ Agents.
</div>
</Section>
);
Expand Down
Loading
Loading