Time stone

Sentinel

Watches, never speaks, never reasons.

fire decision <= 100 ms LLM calls in watch loop: 0 arming a Sentinel arms: nothing

The discipline

The Time stone keeps the cheapest seat in the gauntlet. A Sentinel is a deterministic observer: it looks at the world, compares what it sees against a rule, and either fires or stays quiet. That is the whole job.

Sentinels escalate facts, not opinions. A fire says "this condition held at this moment," never "this seems important." Interpretation is a paid act, and paid acts belong to the Council, not the watcher.

A Sentinel that needs an LLM to decide something is interesting is a Worker wearing the wrong label. The watch loop makes exactly zero LLM calls, and the fire decision lands in 100 milliseconds or less.

Enabling a Sentinel only permits observation and recording. Nothing downstream wakes up, nothing spends, nothing acts. Watch is cheap; the expensive stones each demand their own, separate permission.

Why so strict

An observer that can think will eventually think about things it was never asked to watch. The gauntlet holds because every stone stays in its lane, and the Sentinel's lane is the narrowest of all: see, compare, record, shut up.

The eight stock Sentinels

Eight watchers ship in the box. Each is configured in the Sentinels config file, located via SENTINELS_CONFIG_PATH (default /etc/gateway/sentinels.yaml).

file-inbox-watch

stock sentinel

Watches a directory.

Fires on new or changed files in a watched directory. Uses a 1 second PollWatcher by default on macOS.

silence-watch

stock sentinel

Watches the quiet.

Fires only when BOTH conditions hold: no audit event for threshold_hours AND the backlog directory is non-empty. Silence alone is not a fact; silence with pending work is.

queue-depth-watch

stock sentinel

Watches a number over HTTP.

An HTTP plus jsonpath threshold poller: it fetches an endpoint, extracts a value with a jsonpath such as $.active_total, and fires when the value crosses the configured threshold.

watch-health-watch

stock sentinel, meta

Watches the watchers.

The meta Sentinel: fires if any other sentinel is quarantined, hard-killed, or its hash chain is broken. It excludes itself, because a watcher reporting on its own health is how horror stories start.

ledger-delta-watch

experimental

Watches the money.

Experimental. Fires on a spend spike of threshold_pct over baseline, with min_baseline_usd guarding against tiny denominators turning pennies into alarms.

anomaly-watch

experimental

Watches failure rates.

Experimental. Tracks the escalation failure rate against an EWMA baseline and fires when the rate drifts too far above it. Pure statistics, still no LLM.

completion-verify-watch

stock sentinel

Watches the receipts.

Fires on acked outbox rows whose worker_provenance is missing or is not VerifiedExact. A completed action without verified provenance is a fact worth escalating.

precedent-integrity-watch

stock sentinel

Watches the record.

Treats the Council sessions/index.jsonl as an append-only byte stream and verifies count, bytes, and the SHA-256 prefix. If the precedent log was rewritten, this Sentinel says so.

The watch API

Everything a Sentinel sees is readable over HTTP. Reads are public; mutations are admin; arming is a different principal entirely.

Read endpoints

Endpoint
What it returns
GET /watch/list/{tenant}
The Sentinels configured for a tenant.
GET /watch/temperature/{tenant}
A heat score: clamp01(0.7 * fires_1h / 5 + 0.3 * fires_24h / 24). Bands: cold below 0.15, warm below 0.6, hot at 0.6 and up.
GET /watch/audit/{tenant}
The audit feed. limit defaults to 50 and is capped at 500; before_id is the pagination cursor.
GET /watch/stats
Aggregate watch statistics.
GET /watch/verify-chain/{tenant}
Verifies the fire hash chain within a 5 second budget, then returns 504 if it runs out of time. A broken chain returns 200 with ok:false: a broken chain is a finding, not a fault.
GET /watch/ui-snapshot/{tenant}
Admin plus canary only. The UI snapshot, including action_production_armed.
GET /watch/outbox/pubkey
The PUBLIC Ed25519 verifying key for outbox directives. Its kid is sidecar-v1-{first8 hex of sha256(pubkey)}. Public means public: anyone may verify, only the signer can sign.

Admin mutations

Endpoint
What it does
POST /watch/force-wake/{sentinel}
Triggers a synthetic diagnostic fire for one Sentinel. Returns 409 if the Sentinel is quarantined or hard-killed. If the request body omits a tenant, it defaults to sovereign.
DELETE /watch/quarantine/{sentinel}
Clears quarantine and hard-kill state for one Sentinel.

Who may call what

Auth class
Rules
Public read
The read endpoints above (except ui-snapshot) need no credential.
Admin bearer
Mutations and ui-snapshot require a bearer token: WATCH_ADMIN_TOKEN or BOOTSTRAP_TOKEN. An empty token means every admin route answers 401.
Arm principal
Arming is a separate principal entirely. No watch token, admin or otherwise, is an arming credential.
Canary tenant
WATCH_CANARY_TENANT pins the canary tenant; other tenants get 403 with single_tenant_violation.
Error format
Errors are RFC 9457 problem+json.

The three gates

Three separate switches stand between a config file and real-world action. Each gate is explicit, and opening one never opens the next.

Gate
What it permits
1. Load / enable a Sentinel
Permits observation and recording. Nothing more. Arming a Sentinel arms nothing.
2. Enable the dispatcher
WATCH_DISPATCHER_ENABLED lets escalations flow into Council work. This gate may spend money.
3. Arm the producer
A hardware ceremony, not a config value. This is the only gate that touches the action path.

Gate one is not gate two is not gate three

Loading or enabling a Sentinel is not enabling the dispatcher, and enabling the dispatcher is not arming the producer. If you only flipped the first switch, the gauntlet is watching, recording, and spending nothing.

Quarantine and probation

A Sentinel that cannot fire cleanly gets benched, then eased back under supervision. The numbers are fixed, not vibes.

Rule
Value
Trip threshold
fails_to_trigger = 2 quarantines a Sentinel.
Backoff ladder
60, 300, 1800, 3600 seconds.
Hard-kill
After 5 quarantine cycles within 1 hour.
Exit hysteresis
3 consecutive successes plus a wait of at least 2x the current backoff before a Sentinel leaves quarantine.
Probation
10 minutes of log-only operation after reinstatement. Probation fires carry a [PROBATION] prefix.

Polling and deep Sentinels tick on their cooldown schedule. Fast Sentinels do not tick at all; they are kicked externally when their event source stirs.

Fine print, read before arming anything

  • [PROBATION] fires are NOT filtered by the CDC sweep and can reach paid Council work unless the dispatcher is disabled. Probation is log-only in spirit, not in billing.
  • watch_fires is hash-chained for tamper evidence but is NOT individually signed, and it is never pruned: the log is unbounded by design, so disk is your problem, not the Sentinel's.
  • force-wake with no tenant in the body defaults to the sovereign tenant. Diagnostic fires land where you aimed them only if you aimed them.
  • Polling and deep Sentinels tick on cooldown; Fast Sentinels are kicked externally. A Fast Sentinel that never fires may simply never have been kicked.