Agent / Expose, do not reimplement
Availability and authorization are different questions
A capability can depend on the current Model:
ClearedCompleted: {
name: 'clear_completed',
description: 'Delete every completed todo',
available: model => model.todos.some(todo => todo.completed),
authorize: ({ principal }) => isOwner(principal),
}These predicates answer different questions:
available(model)
"Does this action exist in the application's current state?"
authorize(...)
"May this principal invoke it?"Availability runs first. An unavailable capability is omitted from discovery and refused by name before authorization is evaluated. That prevents the authorization result for an action that does not currently exist from becoming an information leak.
One invocation reads one Model snapshot for available, input mapping, and
authorize, so policy does not accidentally reason over several different
application states.
The invocation pipeline
Every call goes through the same contract-owned pipeline regardless of adapter:
1. resolve capability
2. check available(model)
3. decode protocol input
4. authorize principal
5. construct application Message
6. dispatch Message
7. if declared, wait for correlated completionAnything refused before dispatch sends no Message.
That separation is important: adapters do not get to decide what counts as valid input, whether an action is available, or whether the caller is allowed. They only translate their protocol into this pipeline.
Policy belongs beside the capability
authorize receives the principal, decoded input, Model snapshot, and transport.
That lets the contract reject an agent call early with a typed error.
For state that also has an authoritative server boundary, early agent policy is not a replacement for server enforcement. For example, the todo app uses the same owner predicate in the agent contract and in the Sync/Durable journal:
agent boundary
-> refuse unauthorized request early
server journal
-> enforce authorization authoritativelyThose checks are complementary. A client-side agent contract improves the interface; the trusted boundary still owns trust.