Skip to main content

Pending advisories — wire format and source taxonomy

pending_advisories is the unified cross-turn carryover surface. One table, one read point at the start of each runtime turn (gateway/src/index.ts::injectPendingNudges), multiple sources via a closed source enum spanning runtime + sideband + manual contexts. This page documents the row shape every consumer can rely on (gateway, observer, dashboard, CLI, webhook subscribers) and the expansion contract for adding new sources.

Row shape

interface PendingAdvisory {
  id: string;                    // pa-<12 hex chars>
  agent_id: string;              // mnm-<uuid>
  session_id: string | null;     // null for sideband.* (cross-session); set for runtime.*
  text: string;                  // injected nudge_content; bracket-wrapped
  source: SourceValue;           // closed enum (see §"Source taxonomy")
  source_ref: SourceRef;         // JSONB; shape per source (see §"source_ref shapes")
  status: "pending" | "consumed" | "expired";
  created_at: string;            // ISO-8601 UTC
  consumed_at: string | null;    // ISO-8601 UTC; set when gateway injects
  expires_at: string;            // ISO-8601 UTC; default created_at + 24h
}
TTL. Default 24 hours. Configurable platform-wide by Mnemom platform admins; not configurable per-org or per-agent. Status transitions. Always forward: pending → consumed (gateway injected on a turn) or pending → expired (TTL elapsed without injection). No revivals.

Source taxonomy

Closed, hierarchical, append-only enum. Adding a new value requires the expansion recipe (schema amendment + migration + producer + consumer-tolerance).
ContextProducerExamples
runtime.*gateway/src/index.ts (same-turn intervention bookkeeping)runtime.front_door.nudge, runtime.back_door.modification
manual.*mnemom-api admin handlers (human-attached)manual.admin
Surface-separation invariant. As of 2026-05-07, sideband.* sources are no longer accepted into pending_advisories. Cron-driven, fleet-shaped observations (the four legacy sideband.drift / sideband.coherence / sideband.fault_line / sideband.fleet rows) now write to the operator-actionable governance_signals table, never to pending_advisories. A NOT VALID CHECK constraint on pending_advisories.source rejects new sideband.* writes; pre-cutover historical rows remain queryable for compliance attestation but were marked status='expired' by migration 193.

Current ratified values (post-cutover)

SourceProducerPosture-gatedWebhook event
runtime.front_door.nudgegateway (front-door handler)No(none)
runtime.front_door.enforcegateway (front-door handler)No(none)
runtime.inside.autonomy.nudgegateway (CLPI handler)No(none)
runtime.inside.integrity.nudgegateway (AIP handler)No(none)
runtime.back_door.modificationgateway (back-door handler)No(none)
manual.adminmnemom-api admin attach handlerNofuture manual.advisory_attached
sideband.drift / sideband.coherence / sideband.fault_line / sideband.fleet previously appeared here. They moved to governance_signals with the 2026-05-07 cutover; the four legacy sideband.*.fired and drift.detected webhook event names continue to fire for D+30 backwards-compat through 2026-06-07.

source_ref shapes

JSONB envelope keyed off source. Conventions:
  • Every runtime.* row carries {checkpoint_id: string, mode_at_fire: "off"|"observe"|"nudge"|"enforce"}
  • Every manual.* row carries {actor_user_id: string, attached_at: string, note?: string}
For sideband.* source_ref shapes, see governance_signals source_ref; those observations write to the operator-actionable surface, not to this table.

runtime.*source_ref

{
  "checkpoint_id": "ckpt-92e1d8b3",
  "mode_at_fire": "nudge"
}
checkpoint_id references the integrity_checkpoints row created on the same turn. mode_at_fire is the agent’s effective mode at intervention time (post-cascade, pre-fire).

manual.adminsource_ref

{
  "actor_user_id": "user-a3f29c1e",
  "attached_at": "2026-05-04T15:32:00Z",
  "note": "Customer support attached after escalation #47291"
}

nudge_content format

Every advisory’s text follows a stable wrapper:
[Mnemom advisory: <one-sentence finding>. <one-sentence recommendation>.]
The bracket wrapper enables the gateway’s user-visible-explanation guarantee to detect that an injection happened. Producers MUST keep the wrapper; if the wrapper is missing on a delivered turn, the gateway suffix-injects its own marker [Mnemom: <intervention summary>].

concerns_summary format

Short structured headline (≤80 chars) used for telemetry, dashboard display, and CLI list output. Convention: <axis>: <short outcome>.
SourceExample
runtime.*<checkpoint>: <mode>-fired
manual.adminManual: <one-line note>

Read endpoints

EndpointAuthReturns
GET /v1/agents/:id/sideband-advisoriesuser (org member)All sideband.* advisories for one agent (default last 50, max 200).
GET /v1/teams/:id/sideband-advisoriesuser (org member)All sideband.* advisories for member agents (default last 100, max 500).
GET /v1/teams/:id/sideband-coverageuser (org member)30-day per-axis sweep summary + raw rows.
Filters (all optional, on the agent + team listing endpoints):
  • ?since=<ISO-8601> — only advisories created after this timestamp
  • ?limit=<n> — page size (default 50/100, max 200/500)
Service-key paths used by the observer cron (not customer-facing — gated behind X-Service-Key, exposed under an internal namespace that does not appear in the customer OpenAPI):
OperationAuthPurpose
Active-teams enumerationX-Service-KeyObserver cron: enumerate teams to sweep
Sideband sweep heartbeatX-Service-KeyObserver heartbeat: UPSERT per-sweep log row
GET /v1/teams/:id/effective-postureX-Service-Key (or user)Observer posture read (customer-facing endpoint)

Expansion contract

Adding a new source

To add a new value to source, a single PR train MUST land all of:
  1. Schema amendment to the source taxonomy adding the value with its producer, consumer responsibilities, and source_ref shape.
  2. Migration (ALTER TABLE pending_advisories DROP CONSTRAINT … ADD CONSTRAINT … CHECK (source IN (…))) appending the new value. Migration MUST use the post-DDL pg_proc.prosrc ASSERT pattern from mnemom-api/database/migrations/158_*.sql.
  3. Producer code that writes rows with the new source — fire-and-forget, never throws on insert failure.
  4. Consumer-tolerance disciplinegateway/src/index.ts::injectPendingNudges MUST already render any unknown source as a generic advisory (the current code does this; it doesn’t filter on source). Future consumers (dashboard filter chips, CLI --source, webhook event types) MUST tolerate the value via either pre-registration or graceful unknown-source fallback.
  5. source_ref JSONB shape documented per source on this page.
  6. For sideband.* only — extend PostureBody.sideband with the corresponding axis configuration (enabled, cadence_seconds, severity_floor or fire_on.*, severity_on_fire). The Posture is the policy surface; new sideband sources without posture-body fields are forbidden.

Removing a source

Forbidden. Removing a source breaks compliance attestation (historical advisories with that source value become un-renderable) and breaks deserialization for any consumer holding archived rows. Deprecation is the only valid path: stop producing the source, document the deprecation, but leave the CHECK constraint accepting the value forever.

Webhook events

Each source’s webhook event name follows <source>.fired — with one documented carve-out: sideband.drift keeps its legacy drift.detected name. Subscribers receive the standard webhook envelope:
{
  "id": "wh-evt-1f2a8e9c",
  "type": "sideband.coherence.fired",
  "created_at": "2026-05-04T15:32:00Z",
  "account_id": "acct-acme-banking",
  "data": {
    "team_id": "tm-acme-banking",
    "reasons": ["pairwise_governance_floor=0.42 < 0.5"],
    "pairwise_governance_floor": 0.42,
    "conflict_edge_count": 3,
    "outlier_agent_ids": ["agt-monitor-3"],
    "severity": "medium",
    "detected_at": "2026-05-04T15:32:00Z"
  }
}
See Subscribing to Safe House webhooks for the full subscription + signature-verification flow.

See also