Observify

reference
REFERENCE

Observify docs

Every route this file serves, and how to read the think-like-fable kit atlas it hosts under /reports/. Generated from the live route table — not hand-maintained prose.

Overview

Observify is one Python stdlib file (observify.py, no dependencies) serving an always-on dashboard over every live agentic session on this machine, plus the read-only infra panes (wolfplex Pi, Plex, Holt), a static game-build showcase, the think-like-fable kit-report atlas, and — as of this build — the ~/.agent_sessions export store and this reference.

run python3 observify.py [--port 8787] [--window 30] [--exclude <session-id>]
gate python3 observify.py --selftest — the build-failing invariant gate; exit 0 = green.
compile check python3 -m py_compile observify.py

Sessions render SERVER-SIDE on first load (the HTML already contains real data), then the browser polls the matching /api/... endpoint every few seconds and re-renders in place — the same card markup either way. Cost figures carry a ⚠ until Wolf verifies the rate table; unverified numbers are never presented as fact. ?raw=1 (or true/yes) on any HTML route suppresses Observify's injected auto-refresh badge, so a raw/byte-for-byte fetch of a page is never rewritten.

Live dashboard

The landing page. One card per live session across every runtime (Claude Code, Codex, Grok, Antigravity), plus a pinned Pi/Holt observation bar and the game-build card. Server-rendered, then polled.

GET/

The SSR live-sessions dashboard.

paramkindmeaning
minutesint, optionaloverride the active-session window in minutes (default is the server’s --window, currently 30); sessions idle longer than this drop off the grid

reach it the ◈ Observify brand link in every masthead

GET/api/sessions

JSON: {sessions, totals, window_min, pi, processes} — powers the poll loop behind /. A session whose transcript went quiet past the window but whose Claude Code process is still open stays in the list, carrying open: true and idle_seconds.

paramkindmeaning
minutesint, optionalsame window override as /

reach it fetched automatically by / every few seconds; not linked directly

GET/healthz

Liveness probe: {"ok": true}.

paramkindmeaning
none

Session detail

One session, drilled into: a paginated event stream ("book"), metrics & trends, a subagent/shell flow map, and a complete-session Kit Journey displayed reverse-chronological/newest first, with linked Atlas descriptions and transcript evidence; its subagent/shell tree remains collapsed below. Internal scoring still follows causal oldest-to-newest order. Codex sessions get two more tabs: Instructions and Implementation. All sub-tabs are click-only — none of them read a URL hash.

GET/session/<id>

The full detail page: Stream / Metrics & trends / Flow / Kit tabs (+ Instructions / Implementation for Codex).

paramkindmeaning
none

reach it click any card on /

GET/api/session/<id>

Incremental event JSON that feeds the live Stream tab.

paramkindmeaning
sinceint, optionalcursor — return only events after this index (0 = from the start)
windowstring, optional"main" (default) or a subagent window id from this response’s windows field
implementation_sinceint, optionalcursor for the Codex Implementation tab’s incremental events
GET/api/session/<id>/analytics

Chart data (cost/token/turn trends) for the Metrics & trends tab.

paramkindmeaning
none
GET/api/session/<id>/kit

Normalized complete-session Kit Journey events: tools, files, skills, lessons, apparatus, delegation, verified Atlas links, state, and bounded transcript evidence. The human view is reverse-chronological/newest first; scoring consumes causal oldest-to-newest order. Pass the returned revision as ?revision=… to receive a tiny unchanged marker when no grounded dependency moved.

paramkindmeaning
none
GET/api/session/<id>/orchestration

The subagent + background-shell tree retained beneath the Kit Journey.

paramkindmeaning
none
GET/api/session/<id>/atlas

One Codex instruction/atlas entry’s content, for the Instructions tab.

paramkindmeaning
layerstringwhich instruction layer to read
pathstringthe entry’s path within that layer
GET/api/session/<id>/shell/<shell_id>

Cached output for one background shell.

paramkindmeaning
none
GET/api/session/<id>/process/<pid>

One authorized background OS process’s detail + output tail.

paramkindmeaning
none
POST/api/session/<id>/process/<pid>/stop

Stop that process — TOCTOU-guarded: refuses if the PID’s argv identity changed since it was authorized.

paramkindmeaning
none
GET/api/session/<id>/inbox

Pending and delivered interjection messages for this session.

paramkindmeaning
none
POST/api/session/<id>/inbox/dismiss

Dismiss one delivered inbox message.

paramkindmeaning
for_tsstring, JSON bodythe message’s timestamp being dismissed
POST/api/session/<id>/interject

Queue a text message into a live session’s inbox (file-write only; never touches the process).

paramkindmeaning
textstring, JSON bodythe interjection text, capped at 4000 chars

Infra · Pi / Plex / Holt

Read-only observation of wolfplex (the Raspberry Pi 5), its Plex server, and the Holt graduation/orchestration run — every value arrives over one cached, read-only SSH batch per tier; Observify never opens a live SSH session from inside an HTTP handler.

GET/pi

wolfplex Pi detail console. Tabs: orchestration (default) / software / services / hardware / plex / hailo.

paramkindmeaning
#panelhash anchor, optionale.g. /pi#hardware, /pi#software — read once via location.hash on load to pick the initial tab

reach it the pinned Pi card in the obs-bar on /; cross-links from /holt and /reports/

GET/api/pi/detail

JSON {html, pending, updated} fragment, refreshed every 15s behind /pi.

paramkindmeaning
none
GET/api/pi/software

Cache-only JSON inventory behind the Software tab: exhaustive installed packages, applications, services, models, containers/images when Docker access permits, curated operational stack, Holt security toolkit, source freshness, and explicit collection gaps. The HTTP handler never waits for SSH.

paramkindmeaning
pageint, default 11-indexed result page, clamped to the filtered page count
per_pageint, default 50, max 100rows per API page; the Software UI always requests exactly 50
qstring, optionalcase-insensitive substring search over normalized software metadata
categoryallowlisted string, default allfilter by the categories returned in the payload
ecosystemallowlisted string, default allfilter by the ecosystems returned in the payload
stateallowlisted string, default allfilter by literal active / failed / loaded / idle / unknown and related states
sortname|version|category|ecosystem|state, default namedeterministic whole-result sort
dirasc|desc, default ascsort direction
GET/pi/plex

Exhaustive Plex server detail. Nested tabs: libraries / activity / pass / users / server — click-only, no hash support.

paramkindmeaning
none

reach it the Open deep Plex route link on /pi

GET/api/pi/plex

JSON fragment behind /pi/plex.

paramkindmeaning
none
GET/holt

Holt graduation/orchestration dashboard (wolfplex :8899). Tabs: education / terminus / corpus / kit atlas / commands / questions.

paramkindmeaning
#panelhash anchor, optionale.g. /holt#commands, /holt#questions — read once via location.hash on load to pick the initial tab, matching /pi's pattern

reach it the Holt dashboard link on /pi and the obs-bar

GET/api/holt/detail

JSON {html, pending, updated} fragment behind /holt, refreshed every 15s.

paramkindmeaning
fresh1|true|yes, optionalsynchronous cache-bust — re-reads live+holt over SSH right now instead of trusting the ≤180s passive TTL; used by the refresh badge's manual button and this page's own first load
GET/api/holt/questions

Server-paginated JSON over every taught question in qa_bank.jsonl since the start of Holt's education. Never loads the 98MB source whole; embeddings are stripped remotely and never appear in the response.

paramkindmeaning
pageint, default 11-indexed page number, clamped to the actual page count
perint, default 50, max 200rows per page
phaseall|undergrad|grad|unlabeled, default allundergrad/grad come from matching against curriculum.json/grad_curriculum.json; unmatched questions are honestly 'unlabeled', never guessed
qstring, optionalcase-insensitive substring search over the question text
sortline|ts|phase|course|q, default linesorts the WHOLE filtered set server-side, not just the current page
dirasc|desc, default ascsort direction

Game build

A static showcase for the "I Drink Your Milkshake" Godot build — scene tree, pipeline, school path, render gallery. No live polling.

GET/game

The game-build page.

paramkindmeaning
none

reach it the 🥤 Game Build card on /

GET/game-assets/<name>

One static asset (image) referenced by that page.

paramkindmeaning
none

Kit reports

The think-like-fable kit atlas, served two ways: the hand-authored static cover at /reports/, and a dynamic, Observify-rendered index of every file on disk at /reports/browse.

GET/reports/

Serves reports/index.html verbatim — the hand-authored think-like-fable cover page.

paramkindmeaning
none

reach it the ◈ Kit Report nav link

GET/reports/browse

Observify’s own kit-health index: mirror-sync / scar-ledger / build-health / entabeni-state stat tiles, then a card grid of every file directly under reports/ (HTML, JSON, Markdown, text — and anything else on disk there, including build scripts and hidden state files).

paramkindmeaning
none

reach it the open viewer flow, or link to it directly

GET/reports/<name>

Open one report file.

paramkindmeaning
raw1 / true / yes, optional.json/.md/.markdown/.txt normally render inside Observify’s own viewer chrome (JSON gets syntax highlighting, Markdown gets prose rendering) — ?raw=1 forces the raw bytes instead. .html files (and any other extension) always serve raw, with a best-guess content type.

reach it cards on /reports/browse

Agent sessions store

The view added in this build. Reads /Users/wolf/.agent_sessions/*.json directly — one record per finished agentic run, kept current by session_export.py --watch. Fully server-rendered like / and /reports/browse; there is no separate JSON API.

GET/sessions

Every session record, newest capture_time first: executor, model, session_name, token totals (+ prompt/completion breakdown), duration, human capture time, and content (snippet, expands to full past 240 chars). Grouped stat tiles by executor, plus a client-side executor filter (no query params, no reload). The 3 seed test fixtures (claude_test_01, codex_test_02, grok_test_03) render de-emphasized with a TEST FIXTURE pill — shown, never hidden or crashed on.

paramkindmeaning
none

reach it the ◈ Agent Sessions nav link

Capture log

CAPTURE DATA, not another work session: the observer/router’s own log of what it captured and how it routed it, read live from ~/.observer-watch/journal.jsonl + state.json + heartbeat. This is deliberately distinct from Agent sessions store above — that view is WORK sessions; this one is the capture ENGINE watching them. See the Watch apparatus section below for what actually produces this data.

GET/capture

The Capture Log page: a CAPTURE SUMMARY (router liveness, captured vs prefiltered, synthetic vs real, by-verdict, by-brain with p50/p95 latency, cost-of-observing, last-capture-age, RECONCILE against state.json) plus the paginated, sortable per-event ledger.

paramkindmeaning
pageint, default 11-indexed page number, clamped to the actual page count
perint, default 50, max 200rows per page
sortts|source|verdict|brain|latency|cost|synthetic, default tssort column — ts+desc (newest-first) is the cheap offset-indexed path; any other combination fully parses the event set once
dirasc|desc, default descsort direction

reach it the ◈ Capture nav link

GET/api/capture

JSON {summary, log} — the exact same numbers the HTML page renders, for programmatic/verification access.

paramkindmeaning
pageint, default 1same as /capture
perint, default 50, max 200same as /capture
sortstring, default tssame as /capture
dirasc|desc, default descsame as /capture

Memory pipeline

The machine’s RAM as a kinetic mission console: four strata (GPU-MAPPED / ACTIVE RAM / COMPRESSED+SWAP / EVICTED), one glowing contact per live session tree, fed by the observer fleet’s 5-minute memory ledger. Ceiling lines show the ARMED advisory values mirrored from ~/.observer-watch/ceiling-checker.py (alert-only — nothing on this page can kill or throttle anything). A timeline scrubber replays every recorded cycle; LIVE mode re-polls the JSON every 30s.

GET/memory

The console page. Server-renders the newest cycle as a static strata board, then the canvas engine animates cycle-to-cycle interpolation, births/evictions, WARN pulses, PANIC strobes + swap-descent trails, the swap-pressure gauge, and the aggregate rail. Hover/tap a contact for its full RSS/footprint history sparkline. Honors prefers-reduced-motion.

paramkindmeaning
none

reach it the ◈ Observability → Memory nav link

GET/api/memory.json

JSON {available, generated_at, machine, ceilings, cycles, panics} — bounded parse of ~/.observer-watch/memory-ledger.tsv (newest 400 cycles; the system row’s overloaded columns are unpacked server-side) plus memory-panics.tsv (absent file = honestly zero alerts). Memoized on file signatures, so the 30s LIVE poll is nearly free.

paramkindmeaning
none

Watch apparatus — the observer’s senses and brain

The always-on observation layer that FEEDS this dashboard and wakes a human (or a Claude session) only when warranted. Everything lives in ~/.observer-watch/, runs detached (survives every window close), and costs zero tokens at idle: the brain is the local gpt-oss:20b via ollama.

Senses — one watcher per machine × runtime, each tailing where that runtime’s activity lands, emitting events to w-<name>.out feed files:
claude (this Mac) — every Claude Code transcript under ~/.claude/projects/: human steers, outcomes, errors
codex (this Mac) — codex rollouts under ~/.codex/sessions/
grok (this Mac) — the ~/.grok session store (sqlite, read-only polling)
agy (this Mac) — ~/.gemini/antigravity-cli/history.jsonl
inbox — this dashboard’s interjection channel to the observer
pi-claude — Claude transcripts on the Pi (quiet by design: Holt runs natively there)

Prefilter: a feed line becomes an EVENT only if it matches l123-pos.txt (one ERE per line, case-insensitive: caps, gate failures, interjections, human-steer + anger register) and clears l123-neg.txt (benign-context exclusions). The filter files are the canonical copies — edit them, never inline regexes.

Brain: observer-router.py routes each event STATELESSLY through local gpt-oss:20b (free, ~5k tokens/event) → verdict IGNORE / LOG / PUSH / WAKE. Confidence < 0.6 escalates to ONE haiku call; total failure PUSHES the raw event — a dead brain must never look like a quiet night. Repeat-defect counters upgrade any 3rd occurrence to PUSH. Native workloads no transcript watcher can see (Holt/grad on the Pi, the Mac B relay build, kit drift) are polled every 300s as synthetic events through the same pipe.

Outputs: journal.jsonl (every event + verdict), state.json (counters), PUSHES.log + a macOS notification per PUSH, wake-<ts>.md per WAKE (the prepared briefing a fresh Claude session picks up), heartbeat (liveness).

Re-entry: /continue observer VERIFIES this apparatus is alive and reads the journal since last seen — it does not rebuild the watch inside an expensive long-lived window (L149).

This page

Generated from the live route table in make_handler().do_GET/do_POST every time it is requested — not hand-maintained prose that can drift from the code.

GET/docs

This reference.

paramkindmeaning
none

reach it the ◈ Docs nav link

Kit atlas guide — think-like-fable

/reports/ opens on the think-like-fable cover: "one method, four kits" — frozen July 18, 2026, 148 scars at that freeze. It is the production orchestration kit that runs Observify’s own build fleet, reviewed here rather than hand-narrated.

⬢ CANONICAL

The God Kit

The master, at ~/Projects/Kit/think-like-fable — 96 files, 148 L-scars, 12 principles, 16 commands, the narrative canon, NotebookLM sources, two sub-kits. Single-writer: the harvester allocates L-ids, Wolf greenlights, ship-kit.sh mirrors it everywhere (L34-guarded). Truth accumulates here first.

open god-kit.html →
⬡ EDITION · v2 ZIP

The Orchestration Kit

The deployable subset — regenerated from canonical by build-kit-v2.sh on every ship, never hand-edited, so it cannot drift. Kernel + skills + tools + templates (78 files): what a fresh engagement drops in to boot a fleet with /begin.

open orchestration-kit.html →
⬠ FIRST DERIVED INSTANCE

The Entabeni Kit

On Mac B at ~/Desktop/EntabeniRepos/think-like-entabeni — derived for the Entabeni engagement. Own id-space (25 EL-scars, zero L-ids; EL→L promotion is deliberate), a 4-command subset, 6 engagement profiles grounding each repo, small-window mode pinned. Proof the method generalizes.

open entabeni-kit.html →
⬔ SECOND DERIVED INSTANCE

The Terminus Kit

Local at ~/Desktop/Completely Sorted/Snorkel/terminus/think-like-terminus — derived for the Snorkel review workspace. Own id-space (12 TL-scars, zero L-ids), the 4-command subset (/begin, /continue, /dogfood, /dogfood-it), no fleet apparatus — one unit at a time, human-gated. De-mirrored from /ship; watched via /drift.

open terminus-kit.html →

The other atlas pages (each one click from the cover’s nav):

GET/reports/kit-atlas.html

Kit Atlas — the file-by-file map of every kit.

paramkindmeaning
none
GET/reports/scar-codex.html

Scar Codex — the full L-scar lesson ledger (148 at freeze; check /reports/scars.json for the live count).

paramkindmeaning
none
GET/reports/kit-drift.html

Kit Drift — mirror and derived-kit drift status; what /drift adjudicates.

paramkindmeaning
none
GET/reports/terminus-findings.html

Terminus Review — Findings from the Terminus engagement review.

paramkindmeaning
none

How to read it: start at the cover (/reports/) for the four-kit map and the scar-ledger tick strip; open Kit Atlas for the file-by-file breakdown of any one kit; use /reports/browse when you want the raw/live state Observify renders itself — drift.json, scars.json, files.json — instead of the hand-authored narrative; and check Kit Drift whenever a mirror looks behind, since that page (and /drift) is the adjudicator, not this one.

updated just nownext 3m 00s