# Deterministic Agent Watches (/docs/ai/agent-watches)



<StoryFrame label="Watch contract" title="The agent can ask. Only authority can activate. Only evidence can decide." accent="gold">
  <StoryFact label="Activation">
    Explicit owner or manager approval
  </StoryFact>

  <StoryFact label="Decision">
    Version-pinned evaluator over fresh authorized data
  </StoryFact>

  <StoryFact label="Bound">
    Three active watches; no more than 24 hours
  </StoryFact>
</StoryFrame>

Agent watches monitor a supported condition for a bounded period. An agent may
propose a watch, but it cannot activate one or decide whether the condition is true.

```mermaid
stateDiagram-v2
  [*] --> Proposed: agent or user proposes
  Proposed --> Active: owner/manager approves + immediate evaluation
  Active --> Active: pending / unavailable
  Active --> Satisfied: condition true
  Active --> TerminalUnsatisfied: condition can no longer become true
  Active --> Expired: final authorized evaluation not satisfied
  Active --> Canceled: operator, access, or entitlement revoked
```

## Four outcomes [#four-outcomes]

<StateGrid label="Watch evaluation outcomes">
  <StateCard code="satisfied" title="Condition verified" tone="commit">
    Close the watch and commit exactly one notification intent.
  </StateCard>

  <StateCard code="terminal_unsatisfied" title="No longer possible" tone="halt">
    Close without presenting a false success signal.
  </StateCard>

  <StateCard code="pending" title="Not true yet" tone="gold">
    Keep the watch active and schedule its next evaluation.
  </StateCard>

  <StateCard code="unavailable" title="Evidence unavailable" tone="muted">
    Make no truth claim; retry inside the approved window.
  </StateCard>
</StateGrid>

Expiry performs one final authorized evaluation. A condition satisfied exactly at
the deadline still notifies.

## Supported evaluators [#supported-evaluators]

* `agent.session.state@v1` watches the owning session for one of the registered
  states.
* `workflow.run.status@v1` watches one same-tenant run for one of the requested
  statuses.

Evaluator key and version are an exact pair. Upgrading server code does not rewrite
existing watches; a temporarily missing pinned evaluator yields `unavailable`.

## Bounds and authority [#bounds-and-authority]

* cadence: 1, 5, 15, or 60 minutes;
* window: 30 minutes, or 1, 2, 6, 12, or 24 hours;
* maximum: 3 active watches per tenant, actor, and session;
* at least one notification action is required;
* only session owners/managers may propose, approve, inspect, list, or cancel;
* `agent_watches` entitlement and live target access are checked at proposal,
  approval, every tick, and delivery.

Scoped chat tokens cannot delegate watch management. Identical active conditions
collapse to one watch. Revoked access cancels before target data is read, and revoked
notification authority suppresses a queued delivery with an auditable receipt.

## HTTP operations [#http-operations]

Every call uses normal authentication plus `Ductor-Agent-Protocol-Version: 1`:

| Operation | Path                                                                            |
| --------- | ------------------------------------------------------------------------------- |
| Propose   | `POST /api/v1/chat/sessions/{sessionID}/watches/proposals`                      |
| Approve   | `POST /api/v1/chat/sessions/{sessionID}/watches/proposals/{proposalID}/approve` |
| List      | `GET /api/v1/chat/sessions/{sessionID}/watches`                                 |
| Inspect   | `GET /api/v1/chat/sessions/{sessionID}/watches/{watchID}`                       |
| Cancel    | `POST /api/v1/chat/sessions/{sessionID}/watches/{watchID}/cancel`               |

The MCP-backed `propose_agent_watch` tool can only propose. Activation remains an
explicit authenticated session API decision.

## Durability and notifications [#durability-and-notifications]

Approval and cap enforcement use a serializable transaction. Due workers use fenced,
skip-locked leases; a stale worker cannot commit a decision. Watch transition,
immutable evaluation, and notification outbox insert are atomic. Delivery retries
reuse a deterministic transaction id.

`in_app` uses the governed notification dispatcher. Optional `wake_session` emits a
content-free best-effort wake only after durable notification work succeeds; clients
reread the ledger. Evidence contains hashes, reason codes, timestamps, verification,
and freshness—not model reasoning.

<Callout title="The model explains; it never decides">
  A model may phrase the proposal or explain a completed result. Replay, truth, state transition,
  and notification authority never depend on an LLM call.
</Callout>

## Operations [#operations]

Apply migration 480 before enabling durable chat. Repeated `unavailable` commonly
means source storage failure, missing evaluator registration, or stale/unverified
evidence. Never repair watch rows manually; restore the dependency or cancel through
the API.
