Annotations Channel
Reference for the ahp-session:/<uuid>/annotations channel — per-session annotations anchored to file ranges within a session turn. Clients (and the agent host) mutate annotations by dispatching the client-dispatchable annotations/* state actions, which the write-ahead reducer applies identically on both peers.
JSON Schema: state.schema.json
State Types
AnnotationsSummary
Lightweight per-session summary of the annotations channel, surfaced on {@link SessionSummary.annotations} so badge UI can render annotation / entry counts without subscribing to the channel itself.
| Field | Type | Description |
|---|---|---|
resource | URI | The subscribable annotations channel URI for the owning session (typically ahp-session:/<uuid>/annotations). Surfaced explicitly even though it is derivable from the session URI so badge UI does not need to know the derivation rule. |
annotationCount | number | Total number of {@link Annotation} entries in the channel. |
entryCount | number | Total number of {@link AnnotationEntry} entries across every annotation. |
AnnotationsState
Full state for a session's annotations channel, returned when a client subscribes to an ahp-session:/<uuid>/annotations URI.
| Field | Type | Description |
|---|---|---|
annotations | Annotation[] | Annotations in this channel, keyed by {@link Annotation.id}. |
Annotation
A conversation anchored to a specific file produced by a specific turn, optionally narrowed to a range within that file.
{@link turnId} anchors the annotation to the file versions that turn produced, so a later turn that rewrites the same file does not silently invalidate the annotation's anchor — clients can resolve {@link resource} and {@link range} against the turn's changeset. When {@link range} is omitted the annotation is anchored to the entire file.
Every annotation MUST contain at least one {@link AnnotationEntry}. An {@link AnnotationsSetAction} that creates an annotation therefore carries its mandatory first entry, and removing the last remaining entry collapses the annotation via {@link AnnotationsRemovedAction} rather than leaving an empty annotation behind.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Stable identifier within the annotations channel. Assigned by the client that dispatches the creating {@link AnnotationsSetAction}. |
turnId | string | Yes | Turn that produced the file versions this annotation is anchored to. Matches a {@link Turn.id} on the owning session. |
resource | URI | Yes | The file the annotation is anchored to. |
range | TextRange | No | Range within {@link resource} the annotation is anchored to. When omitted the annotation is anchored to the entire file. |
resolved | boolean | Yes | Whether the annotation has been resolved. Newly created annotations are always unresolved (false); a client marks an annotation resolved (or re-opens it) by dispatching an {@link AnnotationsUpdatedAction} carrying the updated flag (or an {@link AnnotationsSetAction} when replacing the whole annotation). |
entries | AnnotationEntry[] | Yes | Entries in this annotation, in dispatch order (oldest first). MUST contain at least one entry. |
_meta | Record<string, unknown> | No | Producer-defined opaque metadata, surfaced to tooling but not interpreted by the protocol. |
AnnotationEntry
A single entry within an {@link Annotation}.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Stable identifier within the enclosing annotation. Assigned by the client that dispatches the {@link AnnotationsEntrySetAction} (or the enclosing {@link AnnotationsSetAction}) introducing the entry. |
text | StringOrMarkdown | Yes | Entry body. A bare string is rendered as plain text; pass { markdown: "…" } to opt into Markdown rendering. See {@link StringOrMarkdown}. |
_meta | Record<string, unknown> | No | Producer-defined opaque metadata, surfaced to tooling but not interpreted by the protocol. |
Actions
Mutate AnnotationsState. Scoped to an annotations channel URI via the enclosing ActionEnvelope.channel.
JSON Schema: actions.schema.json
annotations/set
Upsert an {@link Annotation} in the annotations channel — adds a new annotation, or replaces an existing one identified by {@link Annotation.id}.
Dispatched by a client to create an annotation (together with its mandatory first entry) or to re-anchor / resolve an existing one; the dispatching client assigns the {@link Annotation.id} and the id of any new entry. When replacing, the full annotation payload (including its {@link Annotation.entries | entries} list) is substituted; producers SHOULD prefer {@link AnnotationsEntrySetAction} for per-entry edits, and {@link AnnotationsUpdatedAction} to resolve / re-anchor an existing annotation, to keep wire updates small.
| Field | Type | Description |
|---|---|---|
type | ActionType.AnnotationsSet | |
annotation | Annotation | The new or replacement annotation. MUST contain at least one entry. |
annotations/updated
Partially update an existing {@link Annotation}'s own properties — a narrow alternative to {@link AnnotationsSetAction} for the common case of resolving / re-opening or re-anchoring an annotation without resending its {@link Annotation.entries | entries}.
Targets one annotation by its {@link annotationId}. Only the fields present on the action are written; omitted fields leave the corresponding {@link Annotation} property unchanged. The annotation's {@link Annotation.entries | entries}, {@link Annotation.id | id}, and {@link Annotation._meta | _meta} are never touched — dispatch {@link AnnotationsSetAction} to replace those, to clear {@link range} (re-anchor to the whole file), or {@link AnnotationsEntrySetAction} / {@link AnnotationsEntryRemovedAction} to edit individual entries.
If {@link annotationId} does not match any current annotation the action is a no-op.
| Field | Type | Required | Description |
|---|---|---|---|
type | ActionType.AnnotationsUpdated | Yes | |
annotationId | string | Yes | The {@link Annotation.id} of the annotation to update. |
turnId | string | No | Re-anchors the annotation to the file versions this turn produced. Matches a {@link Turn.id} on the owning session. Omit to leave the current {@link Annotation.turnId} unchanged. |
resource | URI | No | Re-anchors the annotation to this file. Omit to leave the current {@link Annotation.resource} unchanged. |
range | TextRange | No | Narrows the annotation to this range within {@link resource}. Omit to leave the current {@link Annotation.range} unchanged; this action cannot clear an existing range — dispatch {@link AnnotationsSetAction} to re-anchor to the whole file. |
resolved | boolean | No | Marks the annotation resolved (true) or re-opens it (false). Omit to leave the current {@link Annotation.resolved} state unchanged. |
annotations/removed
Remove an {@link Annotation} from the channel by its id.
Dispatched to delete an entire annotation and every entry it contains. Because the protocol forbids empty annotations, a client that wants to remove the last remaining entry dispatches this action — collapsing the annotation — rather than {@link AnnotationsEntryRemovedAction}.
| Field | Type | Description |
|---|---|---|
type | ActionType.AnnotationsRemoved | |
annotationId | string | The {@link Annotation.id} of the annotation to remove. |
annotations/entrySet
Upsert an {@link AnnotationEntry} within an existing annotation — adds a new entry, or replaces one identified by {@link AnnotationEntry.id}. The dispatching client assigns the {@link AnnotationEntry.id} of a new entry. If {@link annotationId} does not match any current annotation the action is a no-op.
| Field | Type | Description |
|---|---|---|
type | ActionType.AnnotationsEntrySet | |
annotationId | string | The {@link Annotation.id} the entry belongs to. |
entry | AnnotationEntry | The new or replacement entry. |
annotations/entryRemoved
Remove a single {@link AnnotationEntry} from an annotation without collapsing the annotation itself. Used when more than one entry remains — to remove the last entry a client dispatches {@link AnnotationsRemovedAction} instead, since the protocol forbids empty annotations.
If either {@link annotationId} or {@link entryId} does not match the current state the action is a no-op.
| Field | Type | Description |
|---|---|---|
type | ActionType.AnnotationsEntryRemoved | |
annotationId | string | The {@link Annotation.id} the entry belongs to. |
entryId | string | The {@link AnnotationEntry.id} to remove. |