# Agent Read Model API Split

> Status: target design
> Canonical path: `specs/agent/read-model-api.md`
> Scope: AgentTeam Server browser-facing agent read models, REST API shape, runtime-state hydration, agent candidate lookup, frontend query-state boundaries, and read-path cost constraints.
> Related specs: [product overview](../product/overview.md), [identity](../identity/identity.md), [message read path performance boundaries](../message/read-path-performance-boundaries.md), [API handler adapter](../api/handler-adapter.md), [AgentTeam Server API Profile Routing](../api/server-profile-routing.md), [machines](../product/machines.md)
> Non-goals: agent creation/update/delete semantics, daemon internal protocol, role asset workflows, and private-agent collaborator policy.

## 1. Purpose

Browser-facing agent reads must be split by product intent instead of sharing one
large workspace-wide projection. The central rule is:

> Agent read cost must scale with the explicit visible page, requested agent IDs,
> machine ID, channel ID, search prefix, or diagnostic target set. It must not
> scale with all workspace agents merely because a surface needs names, avatars,
> runtime badges, mention candidates, or one agent detail page.

The old single-list shape is convenient but unsafe because it mixes:

- stable directory identity;
- user-specific management/review permissions;
- machine binding;
- runtime state;
- current instance;
- model/runtime configuration;
- latest runtime failure diagnostics;
- candidate lookup for unrelated UI surfaces.

New read paths must choose the smallest read model that matches the user intent.

## 2. Agent Read Model Classes

### 2.1 Directory Summary

`AgentDirectoryItem` is the stable, low-churn directory projection used by
sidebar rows, searchable pickers, machine overview rows, and lightweight member
surfaces.

Allowed fields:

| Field | Purpose |
| --- | --- |
| `id` | Stable agent principal ID. |
| `name` | Stable public agent handle, currently backed by `agents.name`. |
| `memberId` | Hidden member relation key for message/identity joins. |
| `displayName` | Human-visible label, currently backed by member display name. |
| `avatarUrl` | Optional visual identity. |
| `ownerUserId` | Owner relation when the viewer may need owner affordances. |
| `machineId` | Machine relation for routing or grouping. |
| `canManage` | Viewer-specific management affordance. |
| `canReview` | Viewer-specific review affordance when needed by the surface. |

`canReview` must be omitted unless the requesting surface needs review
affordances. Default directory and search reads must not join review/share state
solely to populate an optional review control.

Directory summaries must not include:

- `status`, `activity`, `activityDetail`, or `currentInstance`;
- latest runtime failure summaries;
- LLM config, API key state, model profile, headers, or runtime setup errors;
- output target, review context, role package assets, or diagnostics-only metadata.

### 2.2 Runtime Snapshot

`AgentRuntimeSnapshot` is the dynamic runtime projection for explicitly
requested agents. It contains only the current runtime facts needed to paint live
status:

| Field | Purpose |
| --- | --- |
| `agentId` | Requested agent ID. |
| `machineId` | Runtime machine relation. |
| `machineStatus` | Current online/offline machine state. |
| `autoWakeEnabled` | Server-owned wake policy bit. |
| `status` | Current agent activity status mirror. |
| `activity` | Current activity kind. |
| `activityDetail` | Short activity detail. |
| `currentInstance` | Optional live instance summary. |

Runtime snapshots are request-bound by agent IDs. They must not be used as a
workspace-wide agent directory.

### 2.3 Agent Detail

`AgentDetail` is the single-agent projection used by `/agents/:agentId` and
settings surfaces. It may include fields that are too heavy or too sensitive for
directory lists, such as runtime configuration, model profile identifiers,
permission mode, source provenance, review capability, and current management
state.

Agent detail must be loaded by requested agent ID. Opening one agent page must
not enumerate all workspace agents.

### 2.4 Diagnostics

Diagnostics projections include latest runtime failure, runtime failure detail,
review/debug metadata, and other operator or owner inspection fields.

Diagnostics reads must be explicit and bounded by:

- one agent ID;
- a requested agent ID batch with a hard limit;
- an operator-visible diagnostic scope with pagination or continuation.

Diagnostics must not be included in default directory or first-paint reads.

### 2.5 Intent Candidates

Candidate projections are used by explicit picker/search/autocomplete intent,
for example adding agents to a channel, selecting a machine agent, resolving a
mention, or choosing an execution target.

Candidate reads must be bounded by the surface:

- search prefix and hard limit;
- channel ID for addable channel agents;
- machine ID for machine-scoped agents;
- source scope and prefix for mention candidates;
- viewer-owned fast path where ownership makes visibility deterministic.

Candidate endpoints must not fetch all workspace agents and filter client-side.

## 3. REST API Contract

Browser agent collection, detail, runtime, and diagnostics endpoints belong
under the owning agent API package. Surface-specific endpoints stay under the
package that owns that surface, for example channel membership candidates under
the channel API package and machine-scoped agent pages under the workspace
machine API package. Route registration files compose handlers and adapters
only; handler files own behavior through narrow consumer-side interfaces.
Request and response wire contracts belong in `internal/api/dto`; store models
must not be reused as HTTP DTOs.

### 3.1 Legacy List

`GET /api/agents` and the compatibility alias `GET /api/v1/agents` remain
compatibility endpoints during migration. They should be treated as legacy once
the split endpoints exist.

Rules:

- New browser surfaces must not add new dependencies on `GET /api/agents` or
  `GET /api/v1/agents`.
- Existing callers must migrate to a narrower endpoint when touched for agent
  read behavior.
- The legacy endpoint must expose observability that distinguishes `scope`,
  workspace, route source, frontend build, and bounded caller hints when
  practical.
- Browser API requests should include bounded `X-AgentTeam-Frontend-Build` and
  `X-AgentTeam-Route-Source` headers. Server logs for the legacy list must sample
  high-volume traffic and must hash user/member identifiers rather than writing
  raw human identifiers.
- After hot callers have migrated, deployments should return a small
  `410 Gone` response for `GET /api/agents` and `GET /api/v1/agents`. This
  retired route behavior must not affect narrower agent read models,
  `POST /api/agents`, or `GET /api/v1/agents-for-current-member`.
- Legacy endpoint metrics must use low-cardinality labels. Raw agent IDs, full
  URLs, search text, user input, and other unbounded values must not be metric
  labels. High-detail investigation belongs in sampled logs, traces, or bounded
  diagnostic views.

### 3.2 Directory Endpoints

Recommended directory endpoints:

```http
GET /api/agents/directory?scope=owned|manageable|workspace&limit=50&cursor=...
GET /api/agents/search?q=<prefix>&scope=owned|manageable|workspace&limit=20
GET /api/v1/workspaces/:workspaceId/machines/:machineId/agents
```

Directory endpoints must enforce a product limit. Workspace-scoped directory
pages may exist for explicit agents directory browsing, but they must paginate
and must not be required by route-independent workspace shell first paint.

`GET /api/v1/workspaces/:workspaceId/machines/:machineId/agents` must start from
the requested machine ID. It must not call a workspace-wide agent list and
filter by machine client-side or server-side after broad hydration.

### 3.3 Runtime Snapshot Endpoint

The runtime snapshot endpoint is:

```http
POST /api/agents/runtime-snapshots
{ "agentIds": ["agt_1", "agt_2"] }
```

Rules:

- Empty ID lists return an empty result without touching storage.
- Duplicate and blank IDs are ignored after normalization.
- The request has a hard maximum number of IDs.
- The SQL shape starts from requested IDs and joins outward.
- Missing or unauthorized IDs are omitted rather than causing workspace-wide
  fallback.

### 3.4 Detail Endpoint

Recommended detail endpoint:

```http
GET /api/agents/:agentId
```

The detail endpoint returns one `AgentDetail` for the requested agent. It may
include static detail and the current runtime snapshot when that is cheaper than
a second request, but it must still start from the requested agent ID.

The detail endpoint must not include long diagnostics by default. Latest runtime
failure summary may be included only if the query remains requested-ID-bound and
the field is required for the default detail page. Otherwise it belongs in a
diagnostics endpoint.

### 3.5 Diagnostics Endpoints

Recommended diagnostics endpoints:

```http
GET /api/agents/:agentId/runtime-failure
POST /api/agents/runtime-failures
{ "agentIds": ["agt_1", "agt_2"] }
```

Diagnostics endpoints must be lazy. They are allowed from agent detail,
inspection panels, machine troubleshooting surfaces, or explicit operator
diagnostics. They must not be required by sidebar, workspace shell bootstrap,
channel member panels, or generic picker first paint.

### 3.6 Candidate Endpoints

Recommended candidate endpoints:

```http
GET /api/channels/:channelId/addable-agents?q=<prefix>&limit=20
GET /api/agents/mention-candidates?scopeType=channel|thread|task&scopeId=...&q=<prefix>&limit=20
GET /api/v1/workspaces/:workspaceId/machines/:machineId/agent-candidates?q=<prefix>&limit=20
```

Candidate endpoint semantics are surface-specific. A generic workspace-wide
candidate endpoint is allowed only when the UI is explicitly browsing workspace
agents and the response is paginated.

The external `limit` on candidate endpoints means at most that many authorized
candidates are returned. Implementations must not apply `LIMIT` before
authorization in a way that returns fewer candidates merely because unauthorized
rows consumed the window. When more authorized candidates may exist, responses
must expose continuation state such as `hasMore` or a cursor.

## 4. Frontend Contract

### 4.1 Workspace Shell

Workspace shell first paint must not require `GET /api/agents`.

Allowed first-paint sources:

- workspace bootstrap without agent directory;
- channels and DM channels;
- unread or conversation summaries that do not require all workspace agents;
- current route-specific lazy agent reads.

If a shell route needs agent data, it must load the narrow read model for that
route:

- `/agents/:agentId`: one agent detail plus runtime snapshot for that ID;
- `/machines`: machine list plus machine-scoped agent pages;
- agents sidebar: paged directory summaries;
- channel member panel: channel members plus runtime snapshots only for visible
  channel agents;
- create channel / add member: addable candidate endpoint only after picker
  intent;
- trigger/task/mention surfaces: prefix candidate endpoint only after intent.

### 4.2 Runtime Updates

Realtime `agent:activity`, `agent:status`, `agent:session`, and
`agent:wake_status` events must patch runtime query state or visible agent rows
without invalidating workspace-wide directory lists.

Agent lifecycle events may invalidate directory pages only when the affected
surface is active. If the event names the affected agent ID, clients should patch
or remove that agent from known pages before falling back to refetch.

Workspace reconnect and socket room replay must not blindly invalidate all agent
directory queries. Reconnect recovery may refetch the active route's narrow agent
queries and visible runtime snapshots.

### 4.3 Query State Composition

Frontend state composes separate query states:

- directory pages keyed by `(workspaceId, scope, filter, cursor)`;
- agent detail keyed by `(workspaceId, agentId)`;
- runtime snapshots keyed by `(workspaceId, sorted agentIds)` or patched into
  visible row state;
- diagnostics keyed by `(workspaceId, agentId)` or requested diagnostic batch.

The UI must not rely on a single full `Agent[]` query as the source for every
surface.

## 5. Storage And Query Cost Rules

### 5.1 Directory Queries

Directory queries must:

- use a product limit or requested relation key;
- select only directory fields;
- avoid joining `agent_instances` unless the route explicitly requests runtime;
- avoid loading latest runtime failures;
- batch permission flags in the same bounded query or a bounded follow-up query.

### 5.2 Runtime Queries

Runtime queries must:

- start from requested agent IDs;
- join `agents`, `machines`, and `agent_instances` only for those IDs;
- omit unavailable rows rather than fallback to a broad list;
- keep maximum requested IDs low enough for predictable SQL shape.

### 5.3 Detail Queries

Detail queries must:

- start from one requested agent ID;
- prove workspace membership and visibility before returning data;
- include only the detail fields needed for the default detail surface;
- keep diagnostics lazy unless a requested-ID-bound diagnostic join is proven
  cheap and necessary.

### 5.4 Candidate Queries

Candidate queries must:

- apply prefix, surface, owner, machine, channel, source scope, or other product
  bounds before expensive hydration;
- use hard limits whose response semantics are authorized candidates, not raw
  pre-authorization rows;
- authorize the bounded candidate set in batch;
- return partial/continuation state rather than falling back to all workspace
  agents.

## 6. Compatibility And Migration

Migration should proceed by replacing hot consumers before changing legacy
response shape.

Recommended order:

1. Add `AgentDirectoryItem`, `AgentDetail`, runtime snapshot, diagnostics, and
   candidate DTOs.
2. Add requested-ID and paged directory store methods with query-shape tests.
3. Move agent detail route off the workspace-wide list.
4. Move machine surfaces to machine-scoped agent pages.
5. Move channel add-member, mention, trigger, task, and picker surfaces to
   candidate endpoints.
6. Move sidebar to paged directory summaries.
7. Add legacy endpoint observability for migration tracking.
8. Fail tests when new browser code imports `listAgents` outside approved legacy
   adapters.

Legacy `GET /api/agents` and `GET /api/v1/agents` remain registered only to
return `410 Gone` for older builds and API compatibility. They must not be part
of new first-paint or realtime recovery paths.

## 7. Verification

Required verification for implementation changes:

- SQL-shape or query-count tests for each new store method.
- One-item vs many-workspace-agents tests proving requested-ID reads do not grow
  with unrelated agents.
- Frontend tests proving workspace shell first paint does not call
  `GET /api/agents`.
- Realtime tests proving runtime events patch runtime state without invalidating
  workspace-wide agent directory queries.
- Route ownership tests for new REST endpoints.
- Observability showing legacy `/api/agents` and `/api/v1/agents` request rate,
  latency, scope, caller surface, frontend build, and retired-route status
  during migration.
