obot.agent v0.1.0 (released 2026-07-11) gave working sessions a full lifecycle — session-init
opens, five skills capture state along the way, session-wrapup closes into a diary entry —
and the lead/sibling spawn pattern made sessions multi-agent operations: the 07-11 sessions ran
four to five concurrent named agents. What v0.1 did not ship is a way to see a session.
Mid-session state lives in files designed for agents (scratchpad, state.json, memory), and
the operational picture at wrapup is reconstructed from transcripts and then discarded.
#24 closes that gap with a read-only static generator that renders one self-contained HTML page from the sources the session framework already maintains — a live dashboard during the session, and at wrapup the same view frozen into a session report published on the hub and linked from the diary entry. The reports double as keynote material for the R/Pharma 2026 diary narrative (#22, #10). Goals, in priority order:
## Scaffold); everything else is render-only. No new state stores, no sync drift.Every input was verified against live data on 2026-07-11. Pinned fields are the parse contract (§4).
| Source | What it provides | Pinned fields / format |
|---|---|---|
~/.claude/jobs/<id>/state.json |
Per background session: identity, status, progress, links, output | name, color, state (working / needs‑input / done / failed), detail,
tempo, tokens, children[] (PR/issue links: kind, href, id),
output.result, createdAt, firstTerminalAt, updatedAt, cwd,
respawnFlags (model), intent (briefing text) |
claude agents --json [--all] --cwd <ws> |
Session index: interactive sessions (which have no state.json) + liveness of background ones |
kind (interactive/background), name, status (busy/idle), state, startedAt, sessionId, id, cwd |
Scratchpad .claude/session-notes/YYYY-MM-DD.md |
Priorities with check-state, mid-session todos, notes, scaffold candidates, per-agent session logs | Sections ## Overview (session-init; grouped checklists + <!-- session-init … --> marker),
## Todo, ## Notes, ## Scaffold (new, §7); ## Session log — … headings |
| Memory + diary | “Next session” panel | memory/next-session-todo.md body; newest diary/*.md “Next session: loose ends” section |
GitHub (batched gh sweep) |
Roadmap activity since session start | gh search issues/prs --owner jwildfire --updated ">=<start>" + project-board statuses
(obot Roadmap, user project 1). Derived and cached, never stored. |
Not available (and therefore out of scope for v1, §9): per-session cost in USD. The statusline
stdin JSON carries cost.total_cost_usd, but it is push-based, render-time-only, and interactive-only —
there is no persisted per-session cost store. state.json → tokens is exact and persisted, so
tokens are the v1 effort metric; a cost estimate (tokens × model rates, model from
respawnFlags) is a cheap follow-on once wanted.
The session-orchestration proposal (2026-07-04) designed the same territory before the session skills existed, and left four decisions open. Events resolved most of it; #24 absorbs the remainder. Disposition, decision by decision:
| 2026-07-04 decision | Disposition |
|---|---|
| D1 — track as a new hub Requirement, or fold into #18? | Overtaken The lifecycle/skills half shipped with obot.agent v0.1 (session skills absorbed via obot.agent#13 / hub #23); the spawn pattern is live practice. The visibility/tracking half is #24 — this requirement. |
D2 — who answers a lane's needs input:? |
Not absorbed Deliberately out of scope here (§9). The dashboard surfaces needs-input prominently (banner + agent pill); answering stays in the agents view or the lead session. The relay question moves to #18's design. |
| D3 — standing watcher or on-demand status? | Superseded by #24 The live dashboard is the standing watcher — a page humans glance at, replacing both the 30-second poll loop and “ask an agent to re-render the todo list.” One-shot generation still works for on-demand use. |
| D4 — one skill or two? | Superseded v0.1 shipped five session skills; the wrapup/diary
procedure the old D4 wanted separated exists as session-wrapup. |
Applied with sign-off (2026-07-11): the 2026-07-04 report now carries a status note (“absorbed by #24”) at the top of its README and page — annotated, per hub convention, never deleted. That closes the “session-orchestration D1–D4” item that had ridden the decisions backlog since 07-04.
A single zero-dependency Node script, tools/session-hub/session-hub.mjs, shipped in
obot.agent (the versioned home of the session skills it visualizes; same layout precedent as the
hub's scripts/render_diary.mjs). No server, no npm installs, no runtime state of its own.
collect normalize render
─────── ───────── ──────
jobs/*/state.json ─┐
claude agents --json ┼──► session model (JS object) ──► one self-contained HTML file
scratchpad sections ┤ · session identity + boundary · live: .claude/session-hub/live.html
memory + last diary ┤ · agents[] (pinned fields) · report: obot.roadmap/reports/sessions/
gh sweep (cached) ─┘ · priorities/todos/notes/scaffold YYYY-MM-DD[-N].html
· roadmap events since start
gh offline) renders as a per-panel notice line,
never a crash. The state.json parser pins exactly the fields in §2 and treats everything else as
opaque.<!-- session-init … -->
marker in today's scratchpad; agents in scope are jobs under the workspace cwd with
updatedAt ≥ start (plus anything still running); the gh sweep filters
updated ≥ start. Fallback when no marker exists: local midnight.gh sweep is cached (.claude/session-hub/cache/gh-sweep.json,
~5-minute TTL) so a 60-second regenerate loop doesn't hammer the API; the panel shows the sweep's timestamp.
Gilead-BioStats repos are excluded (SAML-blocked API).--watch regenerates on an interval (default 60 s) to
.claude/session-hub/live.html in the workspace (not a git repo — nothing to ignore); the page
auto-refreshes and is read in Chrome from file://.--report runs the same pipeline once, freezing live panels
into outcomes, and writes into the hub for publication.prefers-color-scheme, all CSS inline. Alignment with the shared keynote stylesheet
(obot.agent#15) once that lands.Exactly the mockup's inventory; each panel header names its source on the page itself, so the dashboard doubles as documentation.
| Panel | Source | Notes |
|---|---|---|
| Masthead + mode pill | generator args | Session name/date · workspace · Live/Report · generated-at stamp |
| Alert banner | agents model | Only when ≥1 agent is needs-input/failed (or a rate-limit pattern is detected in detail); otherwise absent |
| Stat tiles ×4 | derived | Priorities open/done · agents by state · total tokens · roadmap-event count |
| Priorities | scratchpad ## Overview | Groups preserved (agent-actionable / waiting on @jwildfire / parked); check-state + issue refchips linkified |
| Roadmap activity | gh sweep (cached) | Time-ordered events: PRs opened/merged, issues filed/closed, board moves, releases — since session start |
| Agents | state.json + claude agents --json | Per session: color dot (orange lead / green sibling convention), name, state pill, one-line detail, since/duration, token bar, model, PR/issue chips from children; output.result when done |
| Notes | scratchpad ## Notes | Timestamped, newest last |
| Scaffold improvements | scratchpad ## Scaffold | The one new capture channel (§7) |
| Next session | next-session-todo memory + last diary loose ends | Render-only |
At wrapup, session-hub.mjs --report renders the final state with report semantics: the mode pill flips,
the banner becomes an outcome strip, agents show final results (output.result), durations and total
tokens, priorities show final check-state, and roadmap activity becomes the session's definitive event timeline.
Output: obot.roadmap/reports/sessions/{slug}.html, where the slug mirrors the diary entry
(2026-07-11, 2026-07-11-2, …) — one report per diary entry, forever paired.
Diary integration (D2) is link-first: the wrapup commits the report and adds one line to the diary
entry (“📊 Session report” under the meta block). That needs zero changes to
render_diary.mjs — a Markdown link renders as-is. Promoting the report to a proper tab on the
diary page (and a chip on the diary index) is a contained render_diary.mjs follow-up, filed as its own
small hub issue, not blocking v0.2.
session-wrapup gains a report step: render --report, commit to
reports/sessions/, link from the diary entry (rides the existing diary commit under the standing
hub grant). Its scaffold-review step (step 3) becomes a sweep of the ## Scaffold list instead of
end-of-session recall.session-scaffold (new, D3): a thin capture skill mirroring
session-note — trigger “scaffold: …” / “add that to the scaffold list” — appending to
## Scaffold in the scratchpad. Continuous-improvement candidates get captured when the friction
happens, not reconstructed at wrapup.session-init: no functional change (the <!-- session-init … -->
marker it already writes becomes the session-boundary anchor, D4); its scratchpad skeleton adds the empty
## Scaffold section, and its kickoff summary mentions the dashboard command.tools/session-hub/README.md (usage, data contract, degradation behavior);
terminology stays aligned with docs/terminology.md (the Agents panel shows sessions —
the lead and spawned agents; subagents are invisible by design, they have no session state).Carried from the issue's Design placeholder; each was presented to @jwildfire with a recommendation as the gate on this design. All four resolved 2026-07-11 — recommendations accepted as written.
--watch regenerates every ~60 s; the page
auto-refreshes; Chrome reads it from file://. Zero dependencies, zero ports, nothing extra to
babysit — and it keeps the “read-only static generator” promise in the requirement. A local server's only real
win is flicker-free partial refresh, which invites interactivity scope creep; revisit only if the full-page
refresh annoys in practice (a scroll-position-restore snippet is the cheap mitigation).
reports/sessions/{diary-slug}.html + link-first diary integration.
Slug mirrors the diary entry so report and entry are permanently paired; the wrapup adds a one-line link in the
entry (no render_diary.mjs change needed now). The proper diary tab + index chip is a
contained follow-up issue on render_diary.mjs, post-v0.2.
## Scaffold capture UX: dedicated skill, or a session-note extension? Approved 2026-07-11session-scaffold skill. The existing pattern is one thin skill
per capture channel (session-note → Notes, session-update → Todo); a separate skill
keeps triggers unambiguous for both Jeremy and agents, and the wrapup sweep targets exactly one section.
Overloading session-note with a flag saves one file but muddies the trigger phrase.
<!-- session-init … --> marker in today's scratchpad; include jobs
updated since it (plus still-running carryovers); report slug takes the diary session number. This matches the
diary's per-working-session convention exactly, and handles both real cases from this week: two sessions in one
day (07-11 → -2) and one session spanning midnight (07-10 21:30 → 07-11 08:10). A pure calendar-day
boundary mishandles both.
reports/sessions/; trends,
token/cost rollups, and keynote-cut visualizations are a later requirement once a few weeks of reports exist.claude agents / the agents TUI — the hub is a workspace-level view,
not a process manager.| Risk | Mitigation |
|---|---|
state.json is a harness-internal format, not a documented API; fields may rename on CLI upgrades. |
Pin the §2 field list in one parser module; treat everything else as opaque; per-agent degradation (row renders with whatever fields survive + a notice); re-verify on CLI upgrades. Carried over verbatim from the issue's known-risk note. |
| Scratchpad format drift — it's prose maintained by several agents. | Tolerant section parser: recognized structures (checkboxes, timestamps, refs) get rich rendering; anything else passes through as plain text rather than being dropped. |
gh sweep: rate limits, offline, SAML-blocked orgs. |
Single batched sweep, cached with TTL; on failure the panel shows the last cache + its timestamp; Gilead-BioStats excluded by construction. |
| Report snapshots drift from the diary narrative (two records of one session). | They are different views by design — report = operational record (frozen data), diary = narrative. The shared slug + mutual link keeps them paired; neither restates the other. |
file://, check-states, agent states, and token counts match the underlying
sources at render time; the watch loop keeps it current unattended.state.json field, an entire scratchpad section,
or network access still yields a rendered page with per-panel notices — no crash, no blank page.reports/sessions/{slug}.html,
committed to the hub, linked from that day's diary entry, live on the Pages site — the first exemplar being
the session that implements this.## Scaffold, shows on the dashboard,
and the wrapup sweep processes the list.## Scaffold section; roadmap activity
remains derived + cached, never committed.
Decomposed via requirement-tasks into linked sub-issues after design sign-off — this preview
is for shape and sizing only. #24 is the driver requirement for the obot.agent v0.2 milestone.
| # | Repo | Task | Size |
|---|---|---|---|
| 1 | obot.agent | tools/session-hub/ generator: collectors + session model + live-mode renderer (--watch), degradation behavior, README | M |
| 2 | obot.agent | Report mode (--report, freeze semantics) + session-wrapup report step | S–M |
| 3 | obot.agent | session-scaffold skill + scratchpad skeleton (## Scaffold) + session-init/wrapup text updates; workspace wiring (symlink, allowlist) | S |
| 4 | obot.roadmap | reports/sessions/ home (README, index listing) + diary link convention; supersession note on the 2026-07-04 orchestration report | S |
| — | obot.roadmap | Follow-up (post-v0.2): diary tab/index chip in render_diary.mjs | S |