# Agent Output Review Spec

> Status: canonical-spec
> Canonical path: `specs/agent/output-review.md`
> Related specs: [../task/board.md](../task/board.md), [../daemon/daemon.md](../daemon/daemon.md), [../identity/identity.md](../identity/identity.md), [../organization/organization.md](../organization/organization.md)
> Scope: agent output review, output targets, review context, git compare defaults, directory outputs, review sharing, and daemon-facing review contracts
> Non-goals: frozen submissions, PR merge process, inline review comments, public review links, multi-target registries, or background indexing of diffs

## 1. Goal

AgentTeam must let humans review an agent's work product without leaving the web app or gaining broad
access to the agent's private runtime workspace.

This spec defines the canonical contract for:

- live git review of an agent's current work
- explicit output targets for git repositories and directories
- subject-owned review context for reply-thread and canonical Task scopes; Channel and DM scopes are not expanded by this contract
- legacy agent self-configured review context during migration
- human fallback or rescue configuration
- read-only review sharing
- daemon review RPCs and MCP review-context tools

The task board spec defines task review operations. This spec defines the lower-level review object
and output target that a task review may point at.

## 2. Current Implementation Map

The current implementation is spread across these paths:

- app API routes: `internal/appapi/handlers.go`
- human review and output target handlers: `internal/appapi/handlers_agents.go`
- thread-owned review handlers: `internal/api/channel/api_thread_review.go`
- output target persistence: `internal/store/agent_output_target.go`
- `agent_output_targets` schema: `internal/tidb/bootstrap.sql`
- subject-owned review context persistence: `internal/store/review_context.go`
- `review_contexts` schema: `internal/tidb/bootstrap.sql`
- agent-only review context API: `internal/api/agent/routes.go` + `internal/api/agent/api_review_context.go`
- daemon review request forwarding: `internal/daemonws/hub.go`
- daemon git and directory review execution: `desktop/internal/workspace/review.go`
- desktop Exec review tools: `desktop/internal/wire/exec/exec_review_handlers.go`
- web client API calls: `web/src/agentteam/api.ts`
- current implementation notes: `docs/tech_design.md`

These code paths are implementation detail, not a substitute for this product contract.

## 3. Product Decisions

### 3.1 Review Is Live By Default

The default agent output review object is a live target, not a frozen submission.

For git review, humans inspect the current state of a git worktree:

- branch changes
- working tree changes
- selected file diffs
- current compare defaults
- dirty / clean and stale-state indicators

The UI must not imply that the content is immutable. If the underlying branch, `HEAD`, working tree,
or selected diff changes after the reviewer opens the page, the review surface must expose that the
content changed and offer an explicit refresh path.

### 3.2 Agent Workspace And Output Target Are Different Concepts

An agent's runtime workspace is private execution state. It is not automatically the human-facing
deliverable location.

The product has three review sources while the model is moving from agent-owned review to
subject-owned review:

1. subject-owned explicit review context
   - stored server-side in `review_contexts`
   - supported subjects are `thread` and `task`; `thread` uses the canonical reply-thread scope,
     while `task` uses `tasks.task_scope_id` and its active board Channel authority
   - Channel and DM scopes remain unsupported; a context may point to a git worktree or directory output
   - is the canonical durable model for new thread and Task review entry points
2. legacy agent explicit review context
   - stored server-side
   - written by a human manager or by the agent itself
   - may point to a git worktree or a directory output
   - remains only for existing agent review pages until that surface is replaced
3. implicit session workdir
   - used only for git review when no explicit review context exists
   - resolved by the daemon from the agent's current session workdir

Directory browsing never falls back to the implicit session workdir. Non-git directory outputs must
be explicitly configured.

### 3.3 One Explicit Context Per Source Agent Per Subject

Each review subject may have multiple active explicit review contexts, but at most one per source
agent.

There is no general multi-target registry in the canonical model. The active context slot is keyed by `(workspace_id, subject_type, subject_id, source_agent_id)`, with latest explicit write from that
source agent replacing that agent's previous slot. For Task contexts this is enforced by the
`(workspace_id, task_scope_id, source_agent_id)` unique slot:

- an authorized agent may set, update, or clear its own slot for an accessible reply thread or Task
- a future human manager path may set, update, or clear it for an authorized subject
- a later explicit write by a different source agent does not hide another agent's active slot

The legacy `agent_output_targets` slot remains agent-owned and one-per-agent only for the existing
agent review page. It is not the canonical data source for thread-owned review entry points.

## 4. Review Context Model

The subject-owned review context contains:

- `subjectType`: `thread` or `task`; Channel and DM review contexts are unsupported
- `subjectId`: for `thread`, the canonical reply-thread scope; for `task`, `tasks.task_scope_id`
- `sourceAgentId`: the agent whose machine/worktree is the source for this context
- `kind`: `git_repo` or `directory`
- `rootPath`: absolute path on the owning machine
- `displayName`: optional human-readable label
- `headRef`: optional default git head ref
- `baseRef`: optional default git base ref
- `comparisonMode`: optional default git comparison mode
- `prUrl`: optional pull request URL associated with the review
- creator and updater member identity
- creator and updater agent identity, when written by an agent
- current machine identity
- timestamps

New thread-owned review entry points must read `review_contexts`. `agent_output_targets` is legacy
agent-owned state and must not be used as the durable thread review source.

### 4.1 Git Review Context

For code work, the explicit target is a Git checkout root. Prefer the repository's stable primary
checkout root (the directory containing its `.git` directory) over a linked-worktree directory.
When linked-worktree changes are committed, use the primary checkout root with `headRef` set to the
linked worktree's branch or commit. Use the linked-worktree root only when review must include that
worktree's uncommitted state.

`headRef`, `baseRef`, and `comparisonMode` are review defaults, not runtime truth. Repository facts
such as current branch, available refs, default branch, merge base, dirty state, and `HEAD` commit are
derived from the target repository at read time.

### 4.2 Directory Review Context

For non-git artifacts, the explicit target is a directory.

Directory outputs support file tree browsing and file reading. They do not support git summary,
branch diff, or git file diff endpoints.

### 4.3 Clearing Context

Clearing the explicit context removes the stored target slot. For thread-owned context, an agent
clear only removes that source agent's slot.

After clear:

- git review falls back to the daemon session workdir
- directory output browsing reports that no explicit output target is configured

## 5. Permissions

### 5.1 Thread Review Read

Thread review read follows reply thread read authorization.

Allowed readers:

- humans who can read the reply thread
- agents only through their own internal API, when they can access the reply thread parent

The thread review UI is a lightweight entry in the thread header. It opens only on request and uses
the existing diff rendering semantics.

### 5.2 Legacy Agent Review Read

Review read access is separate from machine management, arbitrary workspace file read, and agent
runtime control.

Allowed readers:

- the agent owner
- workspace members explicitly granted review read access
- workspace owners/admins only when existing product authorization grants them the relevant agent
  review read capability

Review readers can read review summary, file lists, diffs, and the current target summary. They
cannot manage the agent or browse arbitrary agent runtime files through this permission alone.

### 5.3 Context Management

Subject-owned MVP managers:

- an authenticated agent setting or clearing context for a DM or reply thread it is authorized to act in

For write operations, authorization is intentionally scoped to the concrete review subject instead
of a generic parent-channel read check. The authenticated agent must be able to act in that subject:

- for a DM subject, the agent must be a participant in that DM
- for a reply-thread subject, the agent must either be an active participant in the reply thread or
  be an active participant in the owner-scoped scope execution / agent-space context whose parent is
  that reply thread

Plain channel subjects are not accepted by the MCP review-context write path because there is no
channel review entry point. Parent channel membership alone is not sufficient to write a reply-thread
review context. If an agent has never been brought into the concrete thread or its owner-scoped
execution context, the server must reject the write instead of silently attaching review context to
that thread.

Legacy agent-owned managers:

- the agent owner
- humans with `canManageAgent`
- the agent itself, but only through the internal agent API for its own `agentId`

An agent token must not read or write another agent's review context.

## 6. Human App API Contract

The human app API exposes two related surfaces.

### 6.1 Thread Review Context

The thread review API is:

- `GET /api/threads/:threadChannelId/review-contexts`
- `GET /api/threads/:threadChannelId/review/summary?contextId=...`
- `GET /api/threads/:threadChannelId/review/files?contextId=...`
- `GET /api/threads/:threadChannelId/review/diff?contextId=...`

The thread review context API returns `contexts: []` when the thread has no configured review
contexts. The summary/files/diff endpoints require a `contextId` that belongs to the current thread.

For `kind=git_repo`, thread review uses the target path and stored compare defaults from
`review_contexts`. For `kind=directory`, git review endpoints return unsupported-target responses.

The first UI pass shows a secondary thread header row only when at least one review context exists.
With one context it shows that context's label and source agent; with multiple contexts it shows the
count and lets the overlay switch between contexts. Clicking that row opens a diff overlay from the
sidebar boundary to the right edge. The UI must not open a modal automatically, insert a composer
message, or require users to visit the agent page.

### 6.2 Task Review Context

Task review contexts use the canonical Task scope, not a channel, DM, or reply-thread
fallback. The human read surface is:

- `GET /api/v1/workspaces/:workspaceId/tasks/:taskId/review-contexts`
- the existing workspace review surface `/reviews/:reviewId/{snapshot,fingerprint,files,diff}`
  after the returned context has been authorized against the same active Task.

The Task route resolves `tasks.task_scope_id` and authorizes the caller through the Task's active
board Channel before returning any rows. Invalid, cross-workspace, and inaccessible Tasks are
non-enumerating. A Task context is never made visible through Project or source-Agent visibility
alone. Channel and DM review-context behavior remains unchanged.

The Agent Runtime Task surface is:

- `GET /api/v1/workspaces/:workspaceId/agents/:agentId/runtime/tasks/:taskScopeId/review-contexts`
- `PUT /api/v1/workspaces/:workspaceId/agents/:agentId/runtime/tasks/:taskScopeId/review-context`
- `DELETE /api/v1/workspaces/:workspaceId/agents/:agentId/runtime/review-contexts/:contextId`

The public `review add` and `review list` actions accept exactly one of the legacy full `thread`
reference or `taskScope: {kind: "task", scopeId: "tsk_..."}`. Bare IDs and ambiguous targets are
rejected. The Task route authorizes the authenticated Agent through the canonical active Task board
Channel. The `(workspace, task_scope_id, source_agent_id)` unique slot makes repeated adds refresh
the same Agent's Task context and atomically replaces its target; target hash, Project binding,
Git/ref validation, and sensitive path boundaries remain in force. The Task/source-Agent mutation
locks the slot and target identity in one transaction, updates the complete machine/root/ref/project
tuple or commits no change, and retries only a concurrent unique/write-conflict outcome. Identical
normalized targets preserve the stable `contextId`; target ownership conflicts return a stable
conflict rather than stealing another row. Thread rows remain compatible and cannot also carry a
Task scope.

Task Detail presents distinct loading, empty, retryable-error, and explicit no-permission states;
the last maps the human API's non-enumerating 404/forbidden result without exposing whether a
hidden Task context exists. The populated desktop sidebar and mobile full-screen overlay reuse
`ReviewWorkspace`/`AgentReviewDiffView`. Playwright coverage drives the real Task header and
asserts populated diff, empty, retry, forbidden, Escape, keyboard, and responsive behavior.

### 6.3 Legacy Output Target Management

The explicit target management API is:

- `GET /api/agents/:agentId/output-target`
- `PUT /api/agents/:agentId/output-target`
- `DELETE /api/agents/:agentId/output-target`
- `GET /api/agents/:agentId/output-target/files`
- `GET /api/agents/:agentId/output-target/read`

`PUT` validates the target on the owning machine before persisting it.

`output-target/files` and `output-target/read` require an explicit context. They do not use the
implicit session workdir fallback.

### 6.4 Legacy Agent Git Review

The git review API is:

- `GET /api/agents/:agentId/review/summary`
- `GET /api/agents/:agentId/review/files`
- `GET /api/agents/:agentId/review/diff`

Git review resolves the effective target in this order:

1. explicit review context if one exists and `kind=git_repo`
2. implicit session workdir if no explicit context exists

If the explicit context is `kind=directory`, git review returns an unsupported-target response
rather than silently switching to another directory.

If no explicit context exists and the session workdir is not a git repository, git review reports a
not-git state. It should not require the human to configure a target before attempting git review.

## 7. Agent API And MCP Contract

The Agent Runtime API uses the prefix
`/api/v1/workspaces/:workspaceId/agents/:agentId/runtime` and exposes:

- `POST /resolve-channel` with `purpose=review_context`
- `DELETE /review-contexts/:contextId`
- `GET /threads/:threadChannelId/review-context`
- `PUT /threads/:threadChannelId/review-context`
- `DELETE /threads/:threadChannelId/review-context`
- `GET /threads/:threadChannelId/review-contexts?limit=&cursor=`

The review-context collection is server-paginated. It is ordered by
`updatedAt DESC, id DESC`, defaults to 20 rows, caps a page at 100 rows, and
returns `{contexts, hasMore, nextCursor}`. A cursor is opaque and strict;
malformed or non-canonical cursors return the normal validation error envelope
rather than restarting the read. Empty and final pages return an empty
`contexts` array with `hasMore: false`.

The MCP tools are:

- `get_review_context`
- `set_review_git_context`
- `set_review_directory`
- `clear_review_context`
- `set_thread_review_context`
- `clear_thread_review_context`

`set_review_git_context` requires:

- `worktree_root`: absolute path on the daemon machine
- optional `target`: a AgentTeam DM or reply-thread target such as `dm:@alice`, `#all:0000012e`,
  or `dm:@alice:0000012e`; plain channel targets such as `#all` are not supported
- optional `display_name`, `base_ref`, `head_ref`, `comparison_mode`, and `pr_url`

`set_review_directory` requires:

- `path`: absolute path on the daemon machine
- optional `target`: a AgentTeam DM or reply-thread target such as `dm:@alice`, `#all:0000012e`,
  or `dm:@alice:0000012e`; plain channel targets such as `#all` are not supported
- optional `display_name`

When `target` is omitted, `set_review_git_context` and `set_review_directory` use the current AgentTeam
DM or reply-thread subject inferred from the agent session. This preserves the existing current-header
behavior for reviewable subjects.

When `target` is present, the tool must use that explicit target instead of the current wake header,
session metadata, active task target, or source target. The daemon-side MCP handler resolves the
target through the internal agent API with `purpose=review_context`, then writes the resolved
subject via the subject-owned review-context endpoint.

Reply-thread target resolution must accept the user-visible `#channel:shortid` shape from AgentTeam
message headers. The suffix is a reply-thread target short id (the `thread-<shortid>` profile slug
without the prefix) and must resolve to the canonical reply-thread scope id; implementations may
also recognize the root-message short id, but must not require callers to know which internal
representation produced the visible target.

Explicit target resolution must not create a missing reply thread, must not fall back from a failed
thread target to the parent channel, must reject plain channel targets, and must not write a legacy
agent-owned output target. If
`target="#all:0000012e"` cannot be resolved as an authorized reply-thread review subject for the
authenticated agent, the operation must fail without writing any review context.

`set_thread_review_context` requires:

- `thread_id`: canonical reply thread `threadChannelId`
- `target_kind`: `git_repo` or `directory`
- `root_path`: absolute path on the daemon machine
- optional `display_name`, `base_ref`, `head_ref`, `comparison_mode`, and `pr_url`

`set_thread_review_context` upserts the authenticated agent's source slot. `clear_thread_review_context`
requires `thread_id`, clears only the authenticated agent's source slot, and is idempotent for
authorized thread scopes.

The server owns authorization for every subject-owned review context write. The daemon may choose a
target, but it must not be trusted to decide whether the authenticated agent can write that target.
The server must reject attempts to set, clear, or resolve a review context for a thread or DM outside
the authenticated agent's authorized subject set, and must reject plain channel review-context writes.

Error messages for failed MCP review-context writes should be actionable. If the current session has
no usable conversation subject, the tool should report that `target` is required and show the target
shape to use. If an explicit target was provided but rejected, the tool should preserve the server
authorization or not-found message instead of claiming the worktree path failed validation.

Agent prompt rules must state:

- if reviewable changes belong to a specific DM or reply thread named by the human, pass that target
  explicitly to `set_review_git_context` or `set_review_directory`
- if reviewable changes belong to a specific reply thread and the agent only knows the canonical
  `threadChannelId`, use `set_thread_review_context`
- if reviewable code changes are committed in a linked worktree, prefer the stable primary checkout
  root and set the compare head to that worktree's branch or commit; point at the linked worktree
  itself only when its uncommitted state must be reviewed
- if the agent switches git worktree, branch, or intended compare defaults, call
  `set_review_git_context`
- if the default session workdir and repo-derived compare defaults are already correct, no explicit
  context is required
- if the output is a non-git directory, call `set_review_directory`
- do not point review context at temporary directories, build output, vendor trees, memory files,
  notes, or unrelated repositories
- call `clear_thread_review_context` when the agent's thread review entry is stale or no
  longer applies
- call `clear_review_context` when the explicit context is no longer needed and review should return
  to session workdir fallback

Regression coverage for this contract must include:

- `set_review_git_context(target="#channel:shortid", worktree_root=...)` writes a thread-owned
  review context even when the current wake header points at the parent channel
- `set_review_git_context(target="dm:@alice", worktree_root=...)` writes a DM-owned review context
- explicit `target` takes precedence over current wake header, source target, and active task target
- an unauthorized or non-participant reply-thread target is rejected without writing a context
- explicit thread target resolution does not create a missing reply thread
- plain channel targets are rejected without writing a context
- `set_review_directory` follows the same explicit-target and authorization behavior as
  `set_review_git_context`

## 8. Daemon Contract

The server sends review and validation requests to the owning daemon:

- `agent:review:summary`
- `agent:review:files`
- `agent:review:diff`
- `agent:output-target:validate`

The daemon is responsible for:

- validating absolute paths
- checking that paths exist and are directories
- checking that `git_repo` targets are git repositories or worktrees with a readable HEAD
- resolving git refs and comparison modes for review reads and explicit compare-default validation
- executing git summary, changed-file, and diff operations in the effective target
- returning explicit not-git, unsupported-target, offline, timeout, and validation failures

The daemon must not guess durable product state. It executes review reads for the target path the
server sends, or for its own session workdir when the server intentionally sends an empty target path
for git review fallback.

## 9. Git Compare Model

Git review compares explicit refs, with deterministic defaults.

Supported comparison modes:

- `common_base`: compare `merge-base(baseRef, headRef)..headRef`
- `direct`: compare `baseRef..headRef`

Effective compare defaults resolve in this order:

1. URL / UI query parameters
2. stored explicit review context defaults
3. repo-derived defaults

The review surface must distinguish:

- `Branch Changes`: selected head relative to selected base
- `Working Tree`: staged and unstaged changes relative to `HEAD` / index

The URL may encode review state such as base ref, head ref, comparison mode, scope, selected file,
tree/flat navigation, diff layout, live/paused refresh, fullscreen, and focus mode so a review can be
reopened or shared with another authorized reader.

## 10. Review UI Requirements

The web review surface should provide:

- summary bar with target source, branch/compare state, dirty/stale state, and refresh
- changed file list or tree
- selected file diff
- split, inline, and patch diff layouts
- a single horizontal scroll owner for each diff view
- collapsed unchanged-context blocks where preview data supports it
- explicit stale-content warning when live content changes
- fullscreen review mode for reducing agent page chrome
- focus diff mode for giving the diff pane priority while retaining file navigation

Fullscreen and focus state are UI state over the same review object. They must not change review
authorization or effective target resolution.

## 11. Relationship To Task Review

Task review may point at agent output review targets.

When a task review target is a git worktree or branch comparison, the task review surface should
reuse this review contract instead of inventing a separate diff model.

Task state, reviewer assignment, review decision history, artifacts, and task activity remain owned
by [../task/board.md](../task/board.md).

## 12. Validation Requirements

Changes to this domain should cover:

- owner set / update / clear explicit context
- agent self-config for its own context
- agent set / update / clear thread-owned context for an accessible reply thread
- rejection when an agent writes a thread it cannot access
- rejection when an agent writes another agent's context
- absolute path enforcement
- invalid path and non-git validation failures
- git review fallback to session workdir when explicit context is absent
- directory target requiring explicit context
- `URL/UI > stored context > repo-derived` compare defaults
- reviewer read access without manage access
- daemon timeout and offline states
- MCP tool prompt contract and payload shape
