Dashboard, Audit, and Compilation Monitoring

For new readers

This page documents the monitoring layer of the self-model reference implementation — the pages and processes a human reviewer actually looks at while the system runs. The safety and ethics requirements state, in the abstract, that a designated human reviewer must be able to inspect state, freeze compilation, and roll back content. This page is where those requirements become a running dashboard, a real freeze/rollback code path, and a real checkpoint file on disk.

The dashboard: what it shows, and what it deliberately doesn't

dashboard/dashboard_server.patlang (758 lines) is a native PatLang HTTP server, structurally the same shape as PatLang's own task_dashboard.patlang example — GET / serves an HTML+JS monitoring page, GET /data serves live JSON — extended with this project's own conversation_output queue topic, since knowing what's alive doesn't show the system actually doing anything. Its own header is explicit about a scope boundary that matters for the safety story: the dashboard shows what components claim to be and what they last said, not independently verified — that verification job belongs to the auditor below, kept deliberately separate per Safety Section 6.2, which requires auditability and in-loop transparency to be independent of each other. The dashboard only ever reads; nothing it displays is fed back into any component's input.

A real scoping bug shaped how the dashboard finds a Perception instance to talk to (dsvr_find_perception). Signal discovery is not scoped per instance — it is a single, global presence beacon every PatLang process on the machine announces onto, regardless of which manifest spawned it, and Perception instance names (perception_1, perception_2, …) are reused identically across every running instance. With two full system instances running side by side — routine for this project — the dashboard used to return whichever perception_* discovery happened to find first, with no relationship to "the Perception instance that belongs to this dashboard." Confirmed live: a chat message sent to a small, isolated test dashboard never reached that test's own Perception instance at all — it silently went to a real, unrelated instance's Perception, which answered a message its own user never sent it. The fix is dsvr_own_perception_ports, an explicit list of this dashboard's own Perception ports passed in at spawn time by launch.patlang (see the orchestration page), preferred over the old unscoped scan, which now survives only as a fallback for an isolated dashboard with no manifest to scope against.

Freeze and resume, called by a human from the dashboard

This is the direct implementation of Safety Section 4.2: a designated human reviewer must be able to halt compilation — freeze Abstraction and Procedural Memory updates — without halting the system's ability to respond, so a suspected corruption event can be contained without a full outage. dsvr_handle_control answers a control request carrying a target component name and an action of either freeze or resume; anything else returns a plain error. It resolves the target through signal discovery, then calls safe_signal_query(port, "freeze_compilation", "") or "resume_compilation" — the same defensive, dead-target-tolerant signal call used throughout this project's monitoring code, discussed below. This is a real, callable control surface reached from the dashboard's own served page, not a description of a feature.

The auditor: an out-of-loop status trail

audit/auditor.patlang implements Safety Section 3.2 directly: external audit access to instrumentation signals must be available to a designated human reviewer at all times during any deployment. Every poll_interval_ms, auditor_poll_all discovers every currently-announced component via signal_discover_all, queries each one's status signal, and appends a timestamped record to a durable audit_log topic — a trail a human reviewer can inspect at any time, and, since Milestone 6.1, what the dashboard itself now reads instead of live-querying every component on every page load. The component's own header states the boundary this exists to preserve: this process only ever reads other components' status and writes its own separate log; nothing it produces is consumed by any in-loop component, which is what keeps audit access and in-loop transparency (Safety Section 6.2) genuinely independent rather than nominally so.

It uses safe_signal_query, not plain signal_query, for the same reason shutdown.patlang does (see the orchestration page): querying every currently-discovered component is exactly the pattern that had already crashed the dashboard once a component died in the gap between discovery and query, and the auditor was equally exposed, just not yet caught doing it. A separate, purely operational bug is also documented directly in the code: PatLang's line-based file reading appears to scale worse than linearly with file size — a 642KB audit log (3149 rows) took over 17 seconds to read, while a 46KB conversation-output log (342 rows) took 120ms, a 14x size difference producing roughly a 145x time difference. The fix bounds growth at the source: auditor_prune_log prunes the log to its most recent 300 rows whenever it exceeds 500, the same "a live monitor only actually needs a recent window" reasoning the dashboard's own status cache already uses.

Compilation monitoring: the Tay precedent, made concrete

audit/compilation_monitor.patlang implements Safety Section 2.3/2.4 — compilation rate and content must be monitored, with defined thresholds for automatic throttling or human review when update patterns deviate from established baselines, including apparent coordination of inputs driving compilation. The header names the reason this exists as a specific, documented precedent, not a hypothetical: Microsoft's Tay (2016), flooded by coordinated adversarial input within about an hour.

The monitor watches every admitted entry on compilation_log (a non-consuming read; Abstraction and Procedural Memory are the real consumers of that topic, not this monitor) and tracks, over a sliding window of the last 20 admissions, what fraction came from a single interlocutor — cm_max_concentration, a crude but real proxy for one source flooding compilation. Once the window holds at least 5 entries and concentration exceeds 0.7, cm_raise_alert does two things at once, deliberately: it publishes a human-visible alert to compilation_alerts, and it calls freeze_compilation on both the Abstraction and Procedural Memory ports directly — the header is explicit that this is never silently automatic-only, per this project's own design choice that automatic throttling and human review are not alternatives to each other.

A real false-positive was found and fixed here too: bootstrap's own curated seeding is, by design, a single source admitting many examples in a row, and a real full-system run auto-froze both Abstraction and Procedural Memory immediately after bootstrap completed, before any real user had said anything, because bootstrap's nine seed examples sharing one interlocutor looked identical to the coordinated-flood pattern this monitor exists to catch. The fix excludes the bootstrap identity (tc_bootstrap_interlocutor) from the sliding window entirely — the same bootstrap exclusion procedural_memory.patlang's own compilation gate already needed, on the same reasoning: legitimate curated seeding is not a live conversation and must not be treated as one. features/compilation_monitor.feature tests both directions concretely: one interlocutor flooding 5 admitted examples in a row both raises an alert and freezes Abstraction; the bootstrap identity admitting 5 examples in a row raises neither.

Rollback to a checkpoint

Safety Section 4.3 requires a designated human reviewer to be able to roll back Abstraction or Procedural Memory content to a prior checkpoint if compilation from a suspected adversarial episode is detected. lib/checkpoints.patlang implements this concretely: checkpoint_create(name) writes a timestamp-named snapshot file under a checkpoints directory, and checkpoint_rollback(name, checkpoint_id) reloads it, discarding anything admitted since. Both Abstraction and Procedural Memory expose this as a real signal handler — when rollback do checkpoint_rollback(abstraction_store_name(), event_data) end in components/abstraction.patlang, and the matching handler in components/procedural_memory.patlang — each documented as requiring the component's own process to restart after replying, since PatLang has no way to swap out an already-loaded in-memory store mid-process. This genuinely works: a real checkpoint file, checkpoints/abstraction_1789038185840.json, exists on disk from an actual run of the system, holding the admitted category list ([["Oslo","travel"]]) at the moment it was taken — evidence this is a working mechanism, not a stub.

See also

For the narrative account of how the Perception-scoping bug, the audit-log scaling problem, and the bootstrap false-positive were actually found, see the project journey page. See safety and ethics for the full Section 3–4 requirements this page implements, and Abstraction for the compilation gate this monitor watches and the checkpoint store it rolls back.