dashboardAgent.sweepWatches ran the watch sweep, the investigation sweep, the
batch re-arm and eval retention together. Watch is about to ship as its own
stacked PR, so the job is split where the cut will be: dashboardAgent.maintenance
keeps the investigation sweep and eval retention, dashboardAgent.watchMaintenance
takes watch expiry, delivery and the batch re-arm. Same cadence, and each backstop
keeps its own try/catch.
A retried card submit was only repairable while the first attempt's watch was
still active. Once it had fired, expired, or answered in one shot, the retry
re-evaluated the condition and created a second operation.
A watch_submissions ledger, keyed (chat_id, client_request_id), is now written
before the condition is read and carries the outcome once there is one. A retry
looks it up first: a recorded outcome is replayed, a different draft under the
same id conflicts, and only a pending row proceeds - converging on the watch id
reserved up front rather than creating another.
The message-size checks ran after the body had been read, so a request without a
content-length was buffered and parsed in full before being refused. An ingress
cap on the agent's paths now counts the bytes as they arrive, and the chat proxy
reads its body with a ceiling instead of reading it whole first.
The page load reported unread wakes only, so a fresh browser whose watch was
created elsewhere and hasn't fired yet never started polling: the wake landed
without a toast or a dot until a reload. The loader now returns the active-watch
presence too, in one read per page load.
The batch took the 500 soonest-expiring watches of a group every tick, so a group
larger than the cap could leave the rest unchecked until the first 500 expired.
The group is now ordered least-recently-checked first, with a generated
cadence_minutes column and an index so the due predicate no longer re-parses the
spec JSON per tick.
The chip factory and slot vocabulary, the evergreen docs chips, the per-page slot
table and the signal chips each get their own module; registry.ts is now the
public face that re-exports them. A pure move.
dashboardAgentWatchChecks.ts keeps the dispatcher, the failure envelope and the
previous-facts reader, and re-exports the run, queue, error and health families
plus the shared reader contract. A pure move; every import path is unchanged.
watch-tick.ts keeps the two task definitions and the per-watch check; the wake
delivery, the condition lifecycle, the group tick and the db/session/callback
adapters each move to their own module. A pure move: watch-tick.ts re-exports
everything it exported before.
tools.ts now only assembles ready adapters, in the same frozen key order: the
HTTP/JWT client, result curation, the docs client, the source-read ledger,
evidence canonicalisation, investigation persistence, and the api/navigation/
watch/alert tool groups each own their own module. A pure move.
The prompt-prefix fingerprints are unchanged, which is what keeps the head-start
and agent prefixes byte-identical.
The docs claimed every turn is judged; the sampled rate, the redaction rule, the code-mode
skip, the opt-out and the retention period are now written down in one place.
Payloads, outputs, query rows and file contents are replaced by their shape before a turn
reaches the judge, and the row now keeps the derived verdict only.
The wake narration and watch investigation cases move verbatim to
watch-actions.test.ts; the scaffolding both test files use moves to
test-support.ts. Same 146 passed / 2 skipped.
Pure move. The wake narration and the consented investigation now live in
watch-actions.ts, reached from one call in onAction; the runtime they share with
the agent's own hooks moves to agent-runtime.ts. Every export is re-exported from
dashboard-agent.ts, so no import path changes.
The gallery pages, their fixtures and the screenshot script only exist to
look at the agent UI, so they ship separately. The fixtures two unit tests
read — the report view models and the page contexts — stay here.
Head start passed `system` as a bare string, so Anthropic neither wrote nor read
the cache: the ~18.4k-token prefix was billed in full and the agent's step 2 then
paid for a fresh write. Both breakpoints now carry a 1-hour TTL.
The two prefixes were also not identical — `get_run` sat in a different position in
the agent's tool set than in the canonical schema-only one, so they could never have
shared a cache. Reordered, and a fingerprint over system text + tool definitions is
logged per model call alongside the provider's cache-write, cache-read and uncached
input token counts, so a future drift is visible instead of silent.
A single read could put ~65k tokens in the transcript, paid for on every later
turn. The truncation notice points at the line range, and the range is now applied
before the cap so a read past the ceiling returns the lines asked for.