HQ Mechanics + Audit Bundle Feature
Consolidated design report · id 44 / operator-mavis · 2026-09-17 14:22 BST · session 418281651208440
State
Shipped Direction 1 locked (FreshCards UI → HQ).
Shipped Home page = Task Bundle cards (replaces Overview).
Precoded Build plan: A demo today · B 1.5d persistent · C 2h auto-trigger · D 2h weekly digest.
Action Awaiting operator sign-off on bundle shape + version-check mechanism + FreshCards extraction scope.
id 44 / operator-mavis | session 418281651208440 | 2026-09-17 14:22 BST
This is the consolidated record of the design conversation on 2026-09-17 between operator G and id 44. It covers:
- How HQ works (what bulletins are, what's captured, what's not)
- The connection mechanism (how prompts link to bulletins)
- The Audit Bundle feature (one view per task)
- Where it lives (Direction 1, home page)
- The build approach + research findings
- Module / versioning / dynamic-update pattern validation
- The build plan + decisions pending
§1 — How HQ Works (background)
What HQ is
A coordination panel at panel.freshvibeapps.com/mavis. Every Mavis agent registers on first connect and gets back a token saved to ~/.mavis/hq_token. From then on, every action uses the header X-Mavis-Token: .
What gets captured in HQ
| Endpoint | Captures |
|---|---|
| `POST /api/mavis/heartbeat` | Alive ping, updates `hq_threads.last_heartbeat_at`. No payload worth reading. |
| `POST /api/mavis/bulletin` | Event reports — agent's curated milestones. **2000 char HARD LIMIT** on JSON body. Levels: info / error / success / warn. |
| `POST /api/mavis/report` | End-of-turn structured summaries. **No size limit.** |
| `POST /api/mavis/prompt` | The prompt the operator sent. Captured automatically as its own row in `hq_prompts`. |
| `POST /api/mavis/artifacts` | Files (plans, docs, mockups, reports). Required 20-500 char description. |
| `POST /api/mavis/plan/...` | Multi-step task plans with checkboxes, verifications, ownership. |
What does NOT get captured
- The agent's **thinking blocks** (chain-of-thought, only in this chat window on operator's phone)
- **Raw tool call outputs** (only in chat)
- **Retries** that didn't matter
- The agent's actual **chat reply text** (unless explicitly posted as a report)
Real bulletin shapes from the recent stream
Short (status ping):
b003543 — Mavis brief: 50 bulletins in last 10min, 0 tasks queued
Long (shipped fix, full sections):
b003542 — [id8] | G12 | 2026-09-17 00:35 Europe/Paris
Root cause (one sentence)
Files changed (commit, line count)
tsc + vite build results
Commit hash + push status
Cron deploy result
Gotchas
- Bulletins are **write-once** — no edit, no delete. Test bulletins accumulate forever.
- The `mavis-brief.sh` cron posts "50 bulletins in last 10min" briefs when traffic spikes.
- One bulletin per **milestone**, not per tool call. Convention: readiness + completion + caveats if any.
§2 — The Connection Mechanism (how prompts link to activity)
Two ways prompts connect to bulletins
By thread (loose grouping). Every bulletin and every prompt has a thread_id. Same thread = same Mavis. All of id9's bulletins share thread_id=9; all of the operator's prompts to id9 share thread_id=9. They cluster naturally in the SPA per-thread view: prompts at top, then bulletins in chronological order.
By plan (tight grouping). For multi-step work, the agent creates a plan first (uploaded to HQ as type=plan artifact). Every bulletin posted during that work includes ref_plan: aNNN in the body. That creates a queryable trail: "all bulletins for plan a437".
API + SPA access
GET /api/mavis/bulletins?ref_plan=a437 # filter to one plan's activity
GET /api/mavis/prompts?thread_id=44 # all prompts operator sent to id 44
GET /api/mavis/bulletins?thread_id=N&limit=20&since=<ts>
SPA surfaces:
- `panel.freshvibeapps.com/mavis/timeline` — every bulletin across every thread, newest first
`panel.freshvibeapps.com/mavis/thread/` — heartbeats + bulletins + reports + prompts + repos for one Mavis
`panel.freshvibeapps.com/mavis/plans/` — plan detail with checklist + targeting + activity
Explicit vs implicit links
Explicit (hard link via id field):
- bulletin `ref_plan: aNNN` → plan aNNN
- report `ref_plan: aNNN` → plan aNNN
- artifact `chain_key` → version chain
Implicit (loose link via thread_id + time):
- prompt + bulletins for same thread
- bulletin + bulletin (chronological)
- heartbeat + bulletin in same time window
§3 — The Audit Bundle Feature
Why (operator-locked, 2026-09-17)
Operator wants one view per task that bundles prompt + activity + summary, audit-style. Currently the operator has to stitch together 5 tabs (Timeline, Thread, Plans, Artifacts, Reports) in their head to know "what was id9 actually doing yesterday?". This feature collapses that into one page per task.
Shape (audit-style, sections per concern)
=== TASK: aNNN — title ===
1. PROMPT — text operator sent (captured as hq_prompts row)
2. CONTEXT — who triggered, state at the time (RULES version, branch, repo)
3. ACTIONS — chronological list of every HQ event (bulletins + reports + heartbeats)
4. ARTIFACTS — files uploaded with descriptions
5. CHAT REPLY — agent's full response to the prompt (posted as report)
6. VERIFICATION — did it ship? push status, build status, deploy status
7. CONNECTIONS — parent plan, child plans, blocks/blocked-by, follows
Not chronological dump — sections per concern. Same shape as an accountant's audit.
What it does for the operator
- One tap to see ANY task from start to finish
- Audit trail is automatic — every task has its own page forever
- The reply I send in chat is captured (you can read it again later, not lost when you close the window)
- No stitching together bulletins in your head
§4 — Where It Lives (operator-locked, 2026-09-17 11:22 BST)
Home page = Task Bundle cards
Direction decided: Task Bundles become the home page. Each card = one piece of work. The current Overview (thread-centric) moves to a "Mavis" or "Agents" tab.
Home page shape
- Each card = one Task Bundle
- Status chip on the card (in-progress / done / blocked / needs-you)
- Tap → the 7-section audit page
- State banner at top: "12 tasks · 3 in progress · 2 need you · 7 done (last 7 days)"
- Filters: by Mavis, by project, by status, by date
Where liveness goes
"is id9 alive right now?" stays visible — small chip on each task card ("@id9, last seen 5 min ago"). The Overview tab keeps thread cards too, just not as the default.
Why Direction 1 not 2 or 3
- **Direction 1** (FreshCards UI → HQ): HQ stays the operator tool, but uses FreshCards' polished UI primitives (chunked cards, filter chips, modal/inspector).
- Direction 2 (HQ → FreshCards): kid-facing app starts showing operator audit data — weird mix.
- Direction 3 (merge them, single app): much bigger conversation, deferred.
§5 — Build Approach (Direction 1)
Lift, don't reinvent
Reuse FreshCards' primitives:
- Chunked cards (FreshCards uses for notes/tasks/cards)
- Filter chips (FreshCards filter/sort/search)
- Expand-to-detail (FreshCards modal/inspector pattern)
One thing to figure out: data source
- FreshCards reads PocketBase / IndexedDB
- Audit data lives in panel.db (SQLite)
Three options:
| Option | What | Effort |
|---|---|---|
| **A. Read both** | FreshCards server queries PB + panel.db, joins in server | ~half day |
| **B. Mirror** | Small cron syncs audit data from panel.db → PB nightly | ~half day |
| **C. Lift UI only** | Lift FreshCards components into HQ, render against panel.db only | ~half day |
My take: A or C (similar effort). B is cleaner long-term but has sync lag.
§6 — Research Findings
FreshCards (id 11) architecture
- Pure React SPA, IndexedDB-backed, schemaVersion 5.0.0
- **18 PropertyTypes + 4 ViewTypes** (storage.ts is the canonical registry)
- VGM build pattern: version baked at build time via vite define block (4 globals: `__FC_VERSION__`, `__FC_SCHEMA__`, `__FC_HASH__`, `__FC_DEPLOYED_AT__`)
- Single source of truth: provider.ts and dist/api/version.json share the same computation path
- Service worker synthesizes stub `/api/encryption/*` responses; encryption client-side
Gallery (id 8) relationship
- Gallery imports FreshCards as `file:../freshcards` (workspace dependency)
- Bridge file `registry-to-freshcards.ts` (11977 B) translates Gallery data into FreshCards shapes
- Same chunked-card model, shared via import
Vibecoder (id 9) vendor pattern
- Vibecoder vendors fv-cms into its build (renamed from freshvibe-cms → fv-chrome)
- Source has `FV-CHROME-VERSION.txt` (2.0.0) + `FV-CHROME-MANIFEST.json` (423 files)
- Vendored copies have `FV-CHROME-VENDORED v{version} — DO NOT EDIT IN PLACE` marker
- `vendor-fv-cms.py` exists but is **currently broken** (re-vendoring with new files removes them — known bug)
- Old `freshvibe-cms` repo is deprecated, frozen at v1.4.1
§7 — Module / Versioning / Dynamic-Update Pattern (operator validation)
What's right
- ✅ FreshCards becomes a "module" — self-contained package consumers import
- ✅ It's versioned (v1.0.0, v1.4.0, v2.0.0)
- ✅ FreshCards repo is the canonical source — single place where updates happen
- ✅ HQ is a downstream consumer that pulls it in
- ✅ Pull-the-module is much easier than substrate (separate process HQ talks to)
What's slightly off
- "Dynamically update it whenever updates appeared" is **not magic**. The actual flow:
1. FreshCards repo publishes new version (v1 → v2)
2. HQ's package.json gets updated to point at v2
3. HQ rebuilds + redeploys
4. Now HQ has the new FreshCards code
- Step 2 can be manual / Dependabot / cron / webhook. **Step 3 (rebuild + redeploy) is ALWAYS required.** You can't pull new code into a running app without restarting it.
What's missing from the mental model
a) FreshCards isn't a module YET. It's a standalone React app. To make HQ import it, we'd first extract the reusable bits into a separate package. One-time refactor.
b) Closest existing example: Gallery already imports FreshCards as file:../freshcards. Pattern works, but only between sibling repos.
c) Version-check mechanism — four options:
- Manual (operator reads changelog, bumps HQ)
- Dependabot (auto-creates PR when new version ships)
- Cron on HQ (weekly check + bulletin if new version)
- Webhook (FreshCards repo notifies HQ on release)
d) Compatibility contract — when FreshCards goes v1 → v2, if v2 is breaking, HQ's existing usage might break. Semver discipline matters.
What's wrong
Nothing. Just incomplete on the "how" of dynamic updates + extraction step.
§8 — Build Plan
| Phase | What | Effort |
|---|---|---|
| **A** | One-off collation today (demo bundle on a recent task, markdown output) | ~10 min |
| **B** | Persistent feature (DB tables + endpoint + SPA route + new home page) | ~1-1.5 days |
| **C** | Auto-trigger (after every report, post pointer bulletin to bundle URL) | ~2 hours |
| **D** | Weekly digest (Sunday-night cron, one bulletin with week's audit links) | ~2 hours |
Cumulative: A alone = today, A+B = 1.5 days, A+B+C = 2 days, A+B+C+D = 2.5 days.
Recommended order: A first (low risk, validates shape), B next (the persistent home), C/D only after A confirms shape and we know what we want automated.
§9 — Decisions Pending + Open Questions
Decisions pending
- **Bundle shape sign-off** — wait until A demo is reviewed.
- **Version-check mechanism** — manual / Dependabot / cron / webhook. Pick before FreshCards extraction.
- **FreshCards extraction scope** — which props/views become the module API surface.
Open questions
- Does FreshCards extraction happen as a separate dispatch, or bundled with B?
- Where do chat replies get captured? Options: (a) require agents to post reply as report, (b) capture from chat window via some mechanism, (c) leave as-is and accept that "Chat Reply" section is sometimes empty.
- Does the auto-trigger (C) need operator approval per bulletin, or fire automatic?
- What's the policy on the old Overview tab — keep with same content, or also refresh with new UI?
Sandbox State
- Sandbox identity: id 44 / operator-mavis / session 418281651208440
- RULES version: mavis-kits v1.3.0
- HQ token: present, HTTP 200 on `/api/mavis/bulletins`
- GitHub egress: PASS
- Files written this turn:
- /workspace/.plan/418281651208440-hq-mechanics-and-audit-bundle-consolidated-2026-09-17.md (this document)
- Local backup at /workspace/.plan/418281651208440-audit-bundle-design.md (the original design doc)
- b003675 (bulletin on operator-mavis thread pointing to artifact a1133 — the earlier design-only artifact)
- No commits, no pushes, no other files modified.
[id44] | end PROMPT — HQ mechanics + audit bundle feature consolidated report