# Session Capabilities

Capabilities describe what a created session can receive, emit, invoke, expose, and release.

Rendered page: https://agentrelay.com/docs/session-capabilities
Markdown endpoint: https://agentrelay.com/docs/markdown/session-capabilities.md

---

Capabilities live on the session, not the harness definition.

The harness tells Relay what a session can do after it creates or attaches to that session. Two sessions from the same harness may have different capabilities because they were created with different provider settings, transports, permissions, or connection state.

## Capability Shape

```ts
type AgentSessionCapabilities = {
  messaging: {
    receive: true;
    send?: boolean;
    attachments?: Array<'text' | 'image'>;
  };

  delivery: {
    modes: DeliveryMode[];
    queue?: boolean;
  };

  events: {
    emits: AgentSessionEventType[];
  };

  actions?: {
    invoke?: boolean;
    expose?: boolean;
  };

  lifecycle: {
    release: boolean;
    pause?: boolean;
    resume?: boolean;
    fork?: boolean;
    snapshot?: boolean;
  };
};
```

Declare a capability only when you implement it. In particular, set `lifecycle.release: true` only when your
session also returns a `release()` method, and set it to `false` (omitting `release()`) otherwise.

Minimum capability:

```ts
const minimum: AgentSessionCapabilities = {
  messaging: { receive: true },
  delivery: { modes: ['immediate'] },
  events: { emits: ['status.changed'] },
  lifecycle: { release: false },
};
```

## Messaging Capabilities

```ts
messaging: {
  receive: true;
  send?: boolean;
  attachments: Array<'text' | 'image'>;
}
```

If a session can receive messages, it can receive channel messages, direct messages, group DMs, and thread replies. Relay does not need a separate `channels` capability.

The useful capability question is what content the session can consume and whether it can send messages back through Relay.

## Delivery Capabilities

```ts
delivery: {
  modes: ['immediate', 'next-tool-call', 'on-idle'];
  queue: true;
}
```

Delivery modes declare supported boundaries. `queue` means the harness can accept work now and deliver it later while still reporting receipts.

Do not add a separate `interrupt` boolean. `immediate` is the interrupting delivery mode. Other modes are boundary-aware.

## Event Capabilities

```ts
events: {
  emits: [
    'status.changed',
    'tool.called',
    'tool.completed',
    'transcript.chunk',
    'file.changed',
    'terminal.output',
  ];
}
```

Events are explicit because observability varies by provider. Claude Code hooks, Codex session notifications, app-server APIs, and custom workers will not all expose the same raw data.

Relay can still normalize supported observations into common event types.

## Action Capabilities

```ts
actions: {
  invoke: true;
  expose: true;
}
```

Use `invoke` when the session can call SDK actions through Relay, usually through MCP tools. Use `expose` when the session or harness can register actions that other participants can invoke.

Capabilities should not list every action name. Action-level availability belongs to the action registry, caller policy, and `availableTo` selectors.

## Lifecycle Capabilities

```ts
lifecycle: {
  release: true;
  pause: true;
  resume: true;
  fork: true;
  snapshot: true;
}
```

Set `release` to `true` only when the session returns a matching `release()` method; set it to `false`
otherwise. The rest are optional because not every provider can pause, resume, fork, or snapshot a session.

## Status States

Status is reported through events, not identity.

```ts
type AgentStatus =
  | 'starting'
  | 'active'
  | 'idle'
  | 'waiting'
  | 'blocked'
  | 'paused'
  | 'releasing'
  | 'released'
  | 'offline'
  | 'failed';
```

Useful meanings:

| Status | Meaning |
| --- | --- |
| `starting` | The harness is creating or attaching to the session. |
| `active` | The session is actively working or generating output. |
| `idle` | The session is ready for more work. |
| `waiting` | The session is waiting on a tool, user input, network, or external result. |
| `blocked` | The session cannot continue without intervention. |
| `paused` | The session is intentionally paused. |
| `releasing` | Release has started but is not complete. |
| `released` | The session boundary has been released. |
| `offline` | The adapter cannot currently reach the session. |
| `failed` | The session or harness failed unexpectedly. |

Idle states are especially important for delivery because `on-idle` messages should wait for one of the safe states configured by the harness.

## Event Type List

```ts
type AgentSessionEventType =
  | 'status.changed'
  | 'status.idle'
  | 'status.active'
  | 'status.blocked'
  | 'status.waiting'
  | 'status.offline'
  | 'tool.called'
  | 'tool.completed'
  | 'tool.failed'
  | 'tool.output'
  | 'message.received'
  | 'message.sent'
  | 'delivery.accepted'
  | 'delivery.delivered'
  | 'delivery.deferred'
  | 'delivery.failed'
  | 'action.invoked'
  | 'action.completed'
  | 'action.failed'
  | 'action.denied'
  | 'transcript.chunk'
  | 'file.changed'
  | 'command.started'
  | 'command.completed'
  | 'command.failed'
  | 'terminal.output'
  | 'terminal.screen'
  | 'usage.updated'
  | 'session.started'
  | 'session.released'
  | 'session.resumed'
  | 'session.forked'
  | 'log'
  | 'error';
```

The event list is intentionally broader than the minimum. A harness can support the subset that is real for its environment.

## Full Session Event Union

```ts
type TranscriptChunk = {
  id: string;
  at: Date;
  role: 'agent' | 'user' | 'system' | 'tool';
  content: string;
  sequence: number;
  metadata?: Record<string, unknown>;
};

type AgentSessionEvent =
  | { type: 'status.changed'; status: AgentStatus; previousStatus?: AgentStatus; reason?: string }
  | { type: 'tool.called'; run?: string; tool: string; input?: unknown }
  | { type: 'tool.completed'; run?: string; tool: string; output?: unknown; durationMs?: number }
  | { type: 'tool.failed'; run?: string; tool: string; error: string; retryable?: boolean }
  | { type: 'tool.output'; run?: string; tool: string; output: string }
  | { type: 'message.received'; messageId: string; deliveryId?: string }
  | { type: 'message.sent'; messageId: string }
  | { type: 'delivery.accepted'; deliveryId: string; messageId: string }
  | { type: 'delivery.delivered'; deliveryId: string; messageId: string }
  | { type: 'delivery.deferred'; deliveryId: string; messageId: string; availableAt: Date | string; reason?: string }
  | { type: 'delivery.failed'; deliveryId: string; messageId: string; reason: string; retryable?: boolean }
  | { type: 'action.invoked'; action: string; invocationId: string }
  | { type: 'action.completed'; action: string; invocationId: string; durationMs?: number }
  | { type: 'action.failed'; action: string; invocationId: string; error: string; retryable?: boolean }
  | { type: 'action.denied'; action: string; invocationId: string; reason: string }
  | { type: 'transcript.chunk'; chunk: TranscriptChunk }
  | { type: 'file.changed'; path: string; operation: 'create' | 'update' | 'delete'; diff?: string }
  | { type: 'command.started'; command: string; cwd?: string }
  | { type: 'command.completed'; command: string; exitCode?: number; durationMs?: number }
  | { type: 'command.failed'; command: string; error: string; exitCode?: number }
  | { type: 'terminal.output'; stream?: 'stdout' | 'stderr' | 'combined'; text: string }
  | { type: 'terminal.screen'; text: string; rows?: number; cols?: number }
  | { type: 'usage.updated'; tokens?: number; costUsd?: number; metadata?: Record<string, unknown> }
  | { type: 'session.started'; sessionId: string }
  | { type: 'session.released'; sessionId: string; reason?: string }
  | { type: 'session.resumed'; sessionId: string }
  | { type: 'session.forked'; sessionId: string; parentSessionId: string }
  | { type: 'log'; level: 'debug' | 'info' | 'warn' | 'error'; message: string }
  | { type: 'error'; error: string; code?: string; retryable?: boolean };
```

## Capability Negotiation

When Relay registers a session, it should use capabilities to decide:

- whether the session can receive the message content type
- which delivery modes are valid
- whether the session can call action tools
- whether the session can expose actions
- which event predicates can be satisfied by live session observations
- whether release, pause, resume, fork, or snapshot operations are available

Capability errors should be explicit and structured. They should not become silent message loss.
