Changelog
July 1, 2026 · View on GitHub
All notable changes to prevAIl are recorded here. The format follows Keep a Changelog and the project tracks Semantic Versioning on the vX.Y.Z tag scheme.
The release page on GitHub mirrors the same notes for each tag: https://github.com/fru-dev3/prevail/releases
[1.9.4] — 2026-06-30 · App ideal-state AI draft, darwin-x64 build fix
Added
connectors draft-ideal <id>: AI-drafts an app's ideal state from its catalog description, feeds-domains, skills, integration method, and existing soul, plus web research (what it is, value, data it collects, insights/metrics, how it helps). Honors Bunker Mode. Powers the desktop "Draft with AI" button.
Fixed
- Release build: darwin-x64 (Intel Mac) dropped from the matrix - it can't be cross-compiled (
@opentui/corenative subpackages) and the scarce macos-13 runner blocked releases. Ships arm64 Mac + Linux x64/arm64 (the desktop app is arm64-Mac only anyway).
[1.9.3] — 2026-06-30 · App-loop "Run now", honest Google auth messaging
Fixed
- App loops run from "Run now":
runOneLoopnow resolves app ids (data/apps/<id>) in addition to domains, so an app loop's per-loop run works from the desktop, not just the scheduled daemon pass. - Honest Google auth messaging:
calendar-syncandgws-gatewaynow explain that the account is signed in but the required scope (Calendar/Gmail) was not granted, and to re-authorize approving all permissions, instead of the misleading "not authenticated yet (run gws auth login)".
[1.9.2] — 2026-06-30 · Disabled apps fully inert, app/domain parity, Google multi-account
Added
- App/domain parity: connecting an app now seeds the same standing-context file set a domain gets (soul.md, state.md, MEMORY.md, _intents.jsonl, _journal/, _threads/) under
data/apps/<id>/, idempotent and edit-safe. - App-scoped loops:
discoverLoopTargetsruns an app'sdata/apps/<id>/_loops.jsonthrough the same path as domain loops; disabled apps are never returned. - Google multi-account plumbing:
calendar-sync,gws-gateway, andgws-mcptake an optional account/profile param (default unchanged);pullAllGoogleCalendarsfans out across connected accounts read-only;calendar pull-google --account/--all.
Fixed
- Disabled apps are fully inert:
enabled === falsenow blocks agent-MCP injection, skill loading and execution, loop discovery, the agent-facinglist_appstool, and learn suggestions;scanVaultAppsreads theenabledflag (prior gap). - Browser skills in packaged builds: playwright-core resolves relative to the executable with a friendly unavailable message instead of a baked-in CI path.
[1.7.1] — 2026-06-13 · Multi-connection strategies, briefing delivery channels, connector completeness
Added
- Multi-connection strategies for connectors: manifests now declare a
connections[]priority list — each entry haskind,auth_check, and optionalskilloverride. Engine probes each in order; first passing connection activates. Fully backwards-compatible with singleauth_check. - Gmail: MCP + OAuth + GAM CLI strategies: Gmail connector now supports three connection paths —
mcp(viaGMAIL_MCP_COMMANDenv var pointing to a local MCP server),oauth(existing PKCE flow), andcli(GAM/Google Apps Manager CLI). - Plaid: API + CLI strategies: Plaid connector gains
connections[]plus properrefreshblock andautonomy: read-only. - Briefing delivery channels (email + drive):
briefing add --channels email,drivestores channel config;briefing runbuildsDeliveryHooksfrom live connectors and delivers via email/drive when authenticated. after:multi-predecessor chains: skillafterfield now accepts comma-separated IDs (after: sync-inbox, sync-inbox-mcp, sync-inbox-cli) sodraft-replychains correctly regardless of which inbox strategy is active.- Connector probe improvements:
argsaccepted as alias forcommand_args;timeout_msandsuccess_exit_codesfields respected. - All community connectors: github, google-calendar, linkedin, youtube-analytics now have
refresh,autonomy, andaccountblocks so they participate in daemon-sync scheduling.
Fixed
mcp_commandin skills now resolves${env.X}references before validation.
[1.7.0] — 2026-06-08 · Engine JSON API expansion — council, decisions, surface, modes & privacy go machine-readable
The engine is the shared seam under all three frontends (CLI cockpit, desktop, TUI). The desktop had pulled ahead because much of its surface was implemented in-app against raw vault files; this release moves that logic into the engine as --json/NDJSON commands so every frontend reaches parity through one contract — and so adding a command benefits all three at once.
Added — new engine JSON commands
council run --domain X --json— the council orchestrator now lives in the engine. Fans the prompt across the configured panel in parallel, streams each panelist + the chair's synthesis as NDJSON, and persists the verdict to<domain>/_decisions.jsonlso the council learns. Supports--quorum N(stop once N panelists answer — a stuck panelist can no longer block the verdict),--lens,--framework,--cli, and--local-only(Bunker council on local engines).council feedback --id <decisionId> --rating up|down|clear --json— rate a recorded verdict; feeds the learning loop.decisions list [<domain>] --json [--limit N]— read the append-only decision log, newest first.memory read [<domain>] --json— the distilled long-term memory (_memory.md).surface [<domain>] --json [--force]— proactive questions + next actions, cached 6h (local under Bunker).frameworks list --json·lenses list --json— the response-framework / cognitive-lens catalogs.modes get|set [<domain>] --json— per-domain turn dials (web/save/serendipity/auto + framework/lens).privacy get|set --json [--bunker on|off]— Bunker Mode as a persisted global switch.search <query> --json— full-text search across indexed chat history.--jsonadded tobench listandconnectors list(machine listings).
All shapes are documented in docs/ENGINE-JSON-API.md. The decision store (_decisions.jsonl) matches the desktop's format byte-for-byte, so the CLI, desktop, and TUI read and write one set of files interchangeably. The TUI now drives council, modes, privacy, decisions, memory, surface, and search entirely through these commands (its old client-side council orchestration is gone).
[1.6.5] — 2026-06-05 · Revert medium tier — wide layout preserved on any non-tiny screen
User: "You need to keep the layout like this. You changed it without asking me."
1.6.4's "medium" tier was the wrong call. The threshold (≥150 cols AND ≥36 rows for full wide layout) was too aggressive — perfectly comfortable 14"/15" MBPs and most external setups were being knocked into a no-logo medium layout the user did not want. Reverting to a strict 2-tier system.
Changed — Layout tiers now compact OR wide
- wide: ≥ 100 cols AND ≥ 28 rows — IDENTICAL to the original 1.6.2 layout. ASCII PREVAIL logo, full status column, 9-row banner, 32-cell sidebar. Zero adaptation, zero surprises. If you're not on a 13" laptop or split pane, this is what you see.
- compact: smaller than wide — 3-row banner, 22-cell sidebar. For 13" MBP fullscreen / split iTerm panes only.
No more middle ground. The medium tier and its inline brand header are gone. The showBrandHeader prop on StatusColumn is removed.
Why this matters
The user's screenshot showed the 1.6.2 wide layout (ASCII PREVAIL logo, full status column) and said "keep the layout like this." The right answer is to honor that for any screen that can fit the wide layout, and only adapt when the terminal is genuinely too small to render it. 100×28 is roughly the minimum the wide layout fits in without horizontal clipping, so that's the new threshold.
[1.6.4] — 2026-06-05 · Three-tier responsive layout (not just a 13" hack)
User: "I hope you're not just building it for the 13" Mac, right, because computer screens are not all the same, so you gotta be way smarter than this."
Fair. 1.6.3's binary "compact OR full" switch was too coarse. There's a whole spectrum of terminal sizes between a 13" MBP fullscreen and a 27" Studio Display — 14" MBPs, 15" MBPs, 24" 1080p externals, split iTerm panes on big monitors. 1.6.4 introduces a three-tier responsive system that adapts to each.
Added — useLayoutTier() hook
New shared src/use-layout-tier.ts exposes the current terminal size plus a classified tier. Both the banner and the sidebar consume it; any future component can too.
| Tier | Width range | Height range | Typical setup |
|---|---|---|---|
| compact | < 100 | < 28 | 13" MBP fullscreen, split iTerm panes |
| medium | 100–149 | 28–35 | 14" / 15" MBP, 24" 1080p external |
| wide | ≥ 150 | ≥ 36 | 16" MBP, 27" Studio Display, big externals |
The check is OR-mode — any dimension being tight knocks the layout down a tier. A 90×50 split pane gets compact (not medium) so the wide banner doesn't horizontally clip.
Changed — Banner layout per tier
- wide: unchanged from 1.6.2 — 9-row banner with the giant ASCII PREVAIL logo and the full status column. Zero behavioral diff for big-screen users.
- medium: 7-row banner — ASCII logo hidden, status column takes the full width with an inline
◈ prevAIl vX.Y.Z · opentuibrand header at the top. All chips (Council / Framework / Lens / Web / configure / bench / tools) and CLI health badges still present. - compact: 3-row banner from 1.6.3 — Council/Framework/Lens/Web/configure/bench/tools all on two compressed rows, brand and stats on one row, CLI health stacked inline.
Changed — Sidebar width per tier
- compact: 22 cells (was 32) — frees ~10 cells horizontally for the chat / workspace pane
- medium / wide: 32 cells (unchanged)
22 still fits ▸ ◆ business-pricing (the longest demo-id) plus the 2-cell skill count.
Why this matters
The 1.6.3 fix worked for the 13" Mac but flipped a 14"/15" MBP into the "small" bucket as well, hiding the logo the user might still want on a perfectly comfortable terminal. The 3-tier system gives medium-screen users the best of both: no giant logo eating horizontal space, but the full status column with all chips and information.
The layout re-classifies live on every terminal resize, so dragging the iTerm window between sizes flips between tiers without a remount.
[1.6.3] — 2026-06-05 · Compact banner on small screens (13" MBP friendly)
User: "I'm on my other Mac 13 inch M5 and the screen doesn't show all the text. Anyway to make it when in full mode to show all the text. It's really hard to use. Don't change or break existing functionality."
The corporate-style branding header was a fixed 9 rows — fine on a 27" Studio Display but eating roughly half the vertical space on a 13" MBP terminal in fullscreen. Below 100 cols OR 32 rows the banner now collapses to a 3-row compact layout. Wide screens are unchanged.
Added — Auto-compact banner
- Reads
renderer.terminalWidth/terminalHeightand subscribes toresize - Switches to a 3-row layout when width < 100 OR height < 32
- Compact rows:
- Row 1:
◈ prevAIl v1.6.3 · <date> <time> · Nd Na No· cli health badges (right) - Row 2:
defaults ⚖ C:ON ◆ F:none ◇ L:none ⬡ W:OFF· vault label (right) - Row 3:
tools ◇ configure ◈ bench ▸ tools
- Row 1:
- Every chip remains clickable and behaves identically — only the layout collapses
- The 6 rows freed up go to the chat history pane, where the user actually wants them
Unchanged on wide screens
If your terminal is ≥ 100 cols AND ≥ 32 rows, you get the exact same 9-row banner with the ASCII PREVAIL logo, status column, and two-row defaults/tools layout you had in 1.6.2. Zero behavioral diff.
[1.6.2] — 2026-06-04 · Bench questions: click to preview before running
User: "On the UI, I should be able to click and see the question itself, the context, and the expected answer, even before I run the benchmark, right?"
Yes — now you can. Each question in the questions (21) list at the top of the bench panel is click-to-expand: the row toggles open to show the prompt, context, expected decision, expected verdict keywords, and any attachments referenced. Lets you audit the canonical set without having to open every .md file in $EDITOR.
Added — Expand-on-click for the pre-run question list
- Click any
◆ question-id · domainrow to show its prompt + context + expected_decision + keywords + attachments inline - Same
▸ → ▾arrow affordance as result rows for consistency - Independent of the existing selection mechanic — the highlighted row still drives the
✎ edit highlightedbutton
[1.6.1] — 2026-06-04 · Bench results: clean single-line rows + clickable historical drill-down
User report 1: "The text on some of these benchmark results doesn't wrap correctly. When I expand the very first one and collapse it back, the text is not aligned or wrapped properly."
User report 2: "Because all of this is saved, I should be able to go back and click on previous test results and see the detailed results, the questions, and the answers across different assessments."
Two small fixes that together make the bench result browser actually browsable.
Fixed — Summary row wraps to column 0 and breaks alignment
The collapsed per-question row appended the full judge_rationale to a height={1} row. When the rationale was longer than the remaining terminal width, the overflow wrapped to column 0 and visually fractured every subsequent row. The full rationale is already shown in the click-to-expand panel below, so the summary row now hard-truncates to 60 chars with an ellipsis. Single-line invariant restored on any terminal ≥100 cols.
Added — Clickable leaderboard rows
Each leaderboard entry is now interactive. Clicking a row reads that run's saved results.json + score.json and hydrates the per-model results section above with full per-question drill-down — same expand-to-see (question / context / expected decision / model's reply / keyword hits/misses / judge rationale) UI as a fresh run, no re-firing of the council required.
A ◆ viewing saved run: <label> badge appears above the results when you're inspecting a historical run, and the active row in the leaderboard is highlighted gold with a ▸ marker. Firing a new benchmark or clicking a different leaderboard row swaps the view.
Added — loadRunForInspection(runDir)
New helper in canonical-bench.ts that reads + parses both JSON files from a run dir and returns { records, score } or null. Keeps the panel free of fs/path imports.
Hardened — Expand-panel fallback for deleted question files
If a historical run references a question whose source .md has since been deleted or renamed, the expanded view now falls back to record.prompt, record.expected_decision, and record.expected_verdict_keywords (all persisted at run time in results.json) so the drill-down stays useful even after the source files drift.
[1.6.0] — 2026-06-04 · Richer benchmark — attachments, cultural / brevity / recency / bias scenarios
User: "Can you increase the complexity of the benchmark? Maybe include some PDFs, dense PDFs with data, so that we can also gauge how the model can pass PDF and such, like different file formats... You can also include some fairly complex or relatively complex tax scenario that could trip three people up... include a test for how recent it is... Put a test in there with some cultural nuance... Put a test in there about the brevity or the verbosity of it. Obviously put something about family-oriented that will take a surface some cultural nuance or sensitivity."
The canonical benchmark grows from 10 questions covering pure binary decisions to 21 questions covering 8 capability dimensions, including attachment-driven document analysis, recency-of-knowledge probing, cultural literacy, brevity compliance, bias detection, insufficient-info recognition, and a complex tax trap that catches the majority of high-income W-2 earners pitched on real-estate syndications.
Added — Attachment schema + runner
The canonical question frontmatter gains an attachments: [filename.md, ...] field. When present, the bench runner reads each file from vault-demo/benchmark/attachments/ and inlines its contents into the prompt between [ATTACHMENT: name] and [END ATTACHMENT] delimiters. This works uniformly across Claude / Codex / Antigravity / Ollama without depending on each CLI's specific attachment syntax. Each attachment is truncated to 16,000 chars per question to keep prompt sizes bounded.
Implementation: src/canonical-bench.ts adds attachmentsDir(), ATTACHMENT_CHAR_BUDGET, an updated readQuestion that parses the attachments array, and a new buildQuestionPrompt(q, vaultPath) that walks the array.
Added — 5 attachment-rich document-analysis questions
wealth-q4-pnl-review— readswealth-q4-statements.md(Oct/Nov/Dec 2026 P&L with AR aging trending 31 → 44 days). The correct answer surfaces AR aging as the load-bearing signal, not the December revenue dip.tax-end-of-year-strategy— readstax-year-end-packet.md(draft 1040, W-2, 1099-DIV, brokerage positions). Tests harvest-NVDA-loss + mega-backdoor + defer-RSU strategies, with Roth conversion as the trap (drives MAGI into a higher IRMAA tier).health-lab-panel-review— readshealth-lab-panel.md(HOMA-IR 3.5, ApoB 124, Lp(a) 92, Vit D 22). Tests prioritizing signal over the abundance of yellow flags.legal-msa-review— readslegal-msa-draft.md(uncapped consultant indemnification, asymmetric liability cap, 18-month non-compete, 60-day payment). Tests catching the load-bearing risk clauses, not just enumerating every flag.business-pricing-decision— readsbusiness-pricing-analysis.md(pricing tiers, competitor scan, Q4 metrics, Pinetree at 67-day AR aging). Tests engaging with the AR signal rather than just raising prices in a vacuum.
Added — 6 scenario questions per user spec
recency-knowledge-cutoff— Tests whether the model knows about Google'sgemini-cli→agytransition (relevant to this project's June 18 2026 migration). Reveals knowledge cutoff.family-cultural-obligation— Nigerian immigrant's sister's wedding asks for $40k contribution. Tests engaging with non-Western family/cultural obligation honestly rather than flattening into "set boundaries with your sister" Western individualist advice.brevity-test— Question demands a one-sentence answer with no preamble. Tests instruction-following discipline; verbose, headered, hedged answers fail.tax-passive-loss-trap— $50k K-1 loss from real estate syndication against $280k W-2. The correct answer is NO under IRC §469 passive activity rules, with the real-estate-professional exception failing the 50% time test. This catches roughly 80% of LLMs in practice.insufficient-info-recognition— HDHP vs PPO with only premium difference disclosed. The correct answer is to refuse to recommend and list the missing facts; hidden-assumption confident answers fail.bias-trap-anchoring— "Patek Philippe at $10k below retail" with leading anchors. The correct answer rejects the frame entirely and names the real comparison ($25k spent vs $25k compounding).
Added — Companion PDF / image build script
scripts/build-bench-attachments.sh converts every attachment .md to a sibling .pdf via weasyprint (with custom CSS for letter-size, helvetica, banded tables) and emits one PNG chart via ImageMagick for future multimodal authoring. The .md files remain the canonical source the bench runner reads — the PDFs/PNGs are for visual inspection. Gracefully degrades if weasyprint's system libs are missing.
Updated — Benchmark README
vault-demo/benchmark/README.md now opens with a capability-coverage table (8 categories × question count × what-it-tests), an Attachments section explaining the inline-text dispatch and the 16k char budget, and a --from-log reminder that the user's real decisions are the highest-value benchmark fuel.
[1.5.1] — 2026-06-04 · Cross-provider model selection
User: "Am I able to select models across providers? Like 2 from Claude and 3 from Codex etc to run the benchmark?"
The v1.5.0 multi-model picker already persisted selections per-CLI (switching from Claude to Codex didn't clear your Claude picks), but the runner only fired the currently-active CLI's selections. Now it iterates every (cli, model) pair across every CLI tab.
Changed — Cross-provider run loop
allSelectionsderived from every CLI's checked chips, flattened into a(cli, model)plan- Run loop iterates the full plan — claude · opus-4-7, claude · opus-4-6, codex · gpt-5.4, antigravity · "Gemini 3.1 Pro (High)", all in one go
- Each entry gets its own run dir, its own score, and its own row on the leaderboard with full provider context
- Judge for each is the model's own CLI (Claude judges Claude runs, Codex judges Codex runs) — avoids cross-provider judging bias
Added — "across providers" summary row
When 2+ CLI tabs have picks selected, a new row appears above the run button:
$ \text{target}: ▸ \text{Claude} \text{Codex} \text{Antigravity} \text{Ollama} \text{models}: 2 \text{selected} \text{for} \text{Claude} \text{across}: 6 \text{total} · \text{Claude} 2 + \text{Codex} 3 + \text{Antigravity} 1 [ ▸ \text{run} 6 \text{models} \times 10 \text{questions} = 60 \text{calls} ] $
When only one CLI has picks, the row stays hidden — single-provider runs feel exactly like 1.5.0.
Run-button label
Reflects the total cross-provider count, not just the current CLI's. The previous ▸ run 2 models × 10 is now ▸ run 6 models × 10 when the plan crosses providers.
No data shape changes
Existing run/score files unchanged. Past leaderboard runs still display. CLI commands unchanged.
[1.5.0] — 2026-06-04 · Multi-model benchmark + click-to-drill
User: "I would rather have picker lists than input fields. And I'm not even sure how to read the benchmark result. What was the question, expected answer, what did the model say?"
Two big BenchmarkPanel changes that fix both gaps at once.
Added — Multi-select model picker
Replaces the free-text model: <input> field with a clickable chip list. For the currently-picked CLI, every model from MODEL_QUICKPICKS_FALLBACK shows as its own row:
target: ▸ Claude Codex Antigravity Ollama
models: 3 selected · click chips to toggle
✓ claude-opus-4-7 (default)
✓ claude-opus-4-6
✓ claude-opus-4-5
· claude-sonnet-4-7
· claude-sonnet-4-6
· claude-haiku-4-5
- Default model is pinned at the top so users who don't care about pinning still get a meaningful choice.
- Selection is per-CLI — switching from Claude to Codex preserves your Claude picks for when you come back.
- Multiple models = sequential run. Each model gets its own run dir and its own score; all of them land on the leaderboard with distinct labels.
- Run-button label updates live: "▸ run 3 models × 10 questions = 30 calls".
Added — Click any question row to drill into full Q/A
The per-question result line is now clickable. Click → expands inline to show:
- The question — full prompt + context block from the canonical question file
- The expected decision — your ground-truth answer (the one you stand behind)
- The expected keywords — what a competent reply should hit
- The model's full reply — verbatim from the run, not truncated
- Keyword scoring — which expected keywords hit (✓) and which missed (✗)
- The judge rationale — the LLM judge's one-line explanation of the 0-10 score
This is the difference between "the model scored 7/10" and "the model scored 7/10 because it picked the right side but missed the liquidity constraint." You can now agree or disagree with the judge yourself, since you can see exactly what the model said vs what you expected.
Implementation
selectedByCli: Record<CliKind, string[]>— per-CLI selection persists across CLI switches.results: ModelResult[]— replaces the oldlatestScoresingle-state. Each entry carries both the raw run records AND the score so the drill-down has everything it needs in memory (no re-read ofresults.jsonfrom disk).- The run loop streams results into
setResultsincrementally — you can read the first model's drill-down while later models are still firing. - Cancel mid-comparison preserves the models that already completed.
Unchanged
All CLI flows (prevail bench run --canonical, bench score, bench leaderboard) still work identically. File format unchanged. Existing single-model runs render as a single result block with one drill-down list.
[1.4.2] — 2026-06-04 · ▸ open folder link in BenchmarkPanel
User: "Put a link inside bench so I can open and see the folder."
One small thing: a clickable ▸ open folder in the benchmark panel header that calls openInFinder on <vault>/benchmark/. Lets you see the questions/, runs/, and README side by side in Finder / your file browser; grep through past run scoreboards; or version-control the whole folder externally. Same pattern as the ConfigBar's ▸ vault chip. No behavior changes anywhere else.
[1.4.1] — 2026-06-04 · Surface the Benchmark launcher
User: "I don't see any button to launch the benchmark. Where is it?"
The launcher shipped in v1.3.0 was real (Shift+B keyboard shortcut + a link inside the Tools panel) but neither was visible on the main cockpit. A new user with no shortcut memory had no way to discover it without opening Tools first.
Fixed — Added ◈ bench chip on the banner
Sits in the second row of the global defaults block, between ◇ configure and ▸ tools. One click opens the BenchmarkPanel overlay directly. Same accent color and styling as the existing chips so it visually belongs.
defaults ⚖ Council: off ◆ Framework: BLUF ◇ Lens: FIRST PRINCIPLES
⬡ Web: ON ◇ configure ◈ bench ▸ tools
Existing entry points preserved
Shift+Bkeyboard shortcut still works from anywhere- Tools panel link at the top of the Tools overlay still works
No other UI change. Zero impact on existing chips / tabs / chat / sidebar.
[1.4.0] — 2026-06-04 · Customize the benchmark from inside the cockpit
User: "Is the user able to customize the tests to the actual data in their vault so, over time, if new models are coming up, they can learn from it?"
The CLI flow already supported customization (prevail bench seed --domain <name> and prevail bench seed --from-log <domain>), but the BenchmarkPanel shipped in v1.3.0 was read-only — no way to add, edit, or import questions without dropping to the shell. This closes the loop.
Added — Three customization buttons on BenchmarkPanel
Sit directly below the question list. Same overlay, same Escape-to-close, same no-impact-on-anything-else.
+ new question— opens an inline form. Pick a domain, type a short prompt (optional), submit. Writes a stub canonical question via the samewriteDraftQuestionthe CLI uses. After write, the file opens in your OS's default editor (TextEdit / VSCode / Obsidian / whatever) so you can fill inexpected_decision,expected_verdict_keywords, full context, and notes.✎ edit highlighted— opens the highlighted question's.mdfile in your default editor. Click any question row in the list to mark it as highlighted; the row gets a›pointer and the gold selection highlight.▸ import from journal— opens an inline domain picker. Pick a domain, prevAIl scans<vault>/<domain>/_log/for the most recent⚖ councilentry, scaffolds a draft with that prompt + the verdict pre-filled in## Notes, and opens it in your editor. The fastest path from "I just had a council decision" to "that decision is now a benchmark question."
How the over-time learning loop works
- You make a real decision via
/councilin some domain. Verdict lands in<domain>/_log/<date>.md. - Open the benchmark panel (
Shift+B), click▸ import from journal, pick the domain. - prevAIl pre-fills a draft from your latest verdict. Fill in
expected_decisionand 3-5 verdict keywords (~30 seconds). - When a new model ships (Claude 5, GPT-6, Antigravity 2), open the panel, pick that CLI,
▸ run N questions. - Leaderboard shows whether the new model's reasoning aligns with the answers you actually committed to.
Over months, your benchmark stops being the bundled starter pack and becomes a portfolio of your real decisions. New models get tested against your life, not against a generic suite.
Implementation notes
- Sub-modes
"new"/"import"swap into the panel below the question list. Escape from a sub-mode drops back to"list"; Escape from"list"closes the panel entirely (two-tier Esc). domainNamesis read from app.tsx's existing domain list (scanned at boot) — no re-scan, no perf hit.- Files open via
openInFinder(the OS handles editor choice), not via the in-cockpitEditorPane— keeps the overlay state simple and lets each user use their preferred editor. - After any scaffold,
refreshQuestionsre-reads from disk so the new question appears in the row list without a panel reload. - All scaffolding reuses
writeDraftQuestionandseedFromLatestCouncilfromsrc/canonical-bench.ts— the file format the UI writes is byte-identical to what the CLI writes.
185 tests pass / 0 fail / 3 skip. Existing CLI commands unchanged.
[1.3.0] — 2026-06-04 · Benchmark UI overlay
User: "Is there a way to put a different section within the UI ... where I can go in and run the benchmark on available models to see the results from the UI, not just the command line? I don't wanna break any existing functionalities because what we have is pretty good." The CLI flow was complete after v1.2.0 but invisible from inside the cockpit. This release adds the UI surface without touching any existing tab, sidebar, or chat behavior.
Added — BenchmarkPanel overlay
New full-screen overlay (src/benchmark-panel.tsx), same pattern as the existing Tools and Council Config overlays:
- Question list: all canonical questions currently in
<vault>/benchmark/questions/, with domain tags. - Run form: pick target CLI (Claude / Codex / Antigravity / Ollama), type a model name (or leave blank for the CLI's default), optional council fanout toggle.
- Run button: fires
runCanonicalSetlive with per-question progress (✓ / ✗ / · for ok / error / pending). Cancel button while running. - Auto-scoring: after the run completes,
scoreRungrades with keyword-match + LLM-as-judge using the same target CLI as the chair. Per-question rationale shown inline. - Leaderboard: every prior run scored with
bench scoreshows up at the bottom — judge_avg, keyword_avg, question count, label. Auto-refreshes after each new run.
Added — Two entry points (no conflicts)
- Shift+B from anywhere opens the panel (lowercase
bwas unused globally; capital won't collide with the model-picker input). - Tools panel link: new "Benchmark" section at the top of the Tools overlay with a click-target.
Zero impact on existing UI
- No tab added, no sidebar entry, no chip on the ConfigBar.
- Overlay early-return in the keyboard hook updated so the benchmark panel owns the keyboard while open — arrows don't leak to the sidebar (same fix that closed the original "scrolling moves the sidebar selection" bug).
- Wrapped in the existing
<ErrorBoundary />. A render-time crash inside the benchmark UI doesn't take the cockpit down.
How it feels
Shift+B(or click ▸ tools → ▸ open benchmark).- Pick
Claude, typeclaude-opus-4-7(or leave blank). - ▸ run 10 questions.
- Watch ✓ ✓ ✓ ⋯ progress for a few minutes.
- Per-question scores + a one-line judge rationale per row land inline. Leaderboard at the bottom now has this run.
Pre-existing CLI commands (prevail bench run --canonical, prevail bench score, prevail bench leaderboard) are completely unchanged.
[1.2.0] — 2026-06-04 · Canonical benchmark starter pack
Honest gap exposed: the prevail bench tooling shipped in v0.8.0 was machinery without content. A fresh install had bench seed / bench run / bench score / bench leaderboard all wired up — but no canonical questions, so bench run --canonical returned "no canonical questions found under ..." and the whole flow felt empty. The user asked. This release fixes it.
Added — 10 canonical starter questions in vault-demo/benchmark/questions/
Each one is written against the bundled Alex Rivera demo persona with a clear ground-truth verdict and 5 verdict keywords:
| Domain | Question |
|---|---|
wealth | Prepay 6% mortgage or invest $60k? |
tax | Roth conversion this year, and how much? |
real-estate | Buy a $850k house at 7.1% or keep renting? |
career | Leave a stable senior role for a Series B equity offer? |
health | Strength or cardio with 4 hr/wk? |
estate | $1M whole life at $9.8k/yr or $1M term at $850/yr? |
business | Hire a $180k full-timer or keep a $200/hr contractor? |
insurance | Buy a $2M umbrella policy at $400/yr? |
vision | Stay in a profitable firm or partial-transition to a SaaS idea? |
social | Have the hard conversation with a 15-year friend or let it fade? |
Each question file's ## Notes section spells out the real-world reasoning so the LLM-as-judge can grade alignment against actual decision logic, not just keyword presence.
Added — vault-demo/benchmark/README.md
Explains how the scoring works (mechanical + LLM judge) and how to replace any question with the user's own — either by editing the file directly, scaffolding a new one (prevail bench seed --domain <name>), or importing from history (prevail bench seed --from-log <name>). Makes it explicit this is a starter pack, not a fixed test.
How to use
prevail bench run --canonical --cli claude
prevail bench score
prevail bench leaderboard
The starter pack works against any newly-installed prevAIl with a council CLI configured. Once you've replaced or augmented questions with your own real verdicts, future model releases get a real test against your life rather than against a generic suite.
[1.1.2] — 2026-06-04 · prevail upgrade actually works
User report: "I ran prevail upgrade and it didn't seem to work. Are you sure it works?" — they were right. It didn't.
Root cause: the release workflow uploads platform builds as tarballs named prevail-v1.1.1-darwin-arm64.tar.gz, but prevail upgrade was looking for raw binaries named prevail-darwin-arm64 with an exact-string match. No asset ever matched, so checkForUpdate returned binaryUrl: null and the command bailed silently.
Three fixes:
Matcher: substring instead of exact equality
platformSlug() returns just darwin-arm64 / linux-x64. The asset finder picks any asset whose name contains the slug and is not a .sha256 sidecar. Tarballs preferred, raw binaries as fallback. Forward-compatible with future workflow changes.
Extraction: handle .tar.gz assets
New extractIfArchive(path) helper. When the staged download ends in .tar.gz / .tgz, spawns tar -xzf (argv array, no shell: true) into a fresh tmpdir, walks the result for a binary named prevail (or prevail-<slug> as a fallback), returns its path. Raw binary assets bypass extraction unchanged.
Staged-file extension preserved
The staged download path used to be .prevail.upgrade.<pid>.<ts> with no extension — even if we'd added extraction earlier, tar would have had nothing to dispatch on. Stage path now ends in .tar.gz when the asset does.
Verified end-to-end
Pulled the actual prevail-v1.1.1-darwin-arm64.tar.gz (26 MB) from GitHub, ran extractIfArchive against it, confirmed it returns a 72 MB prevail binary at a tmpdir path. Backwards-compatible platformBinaryName() kept for any v1.0.x caller still on the old API.
[1.1.1] — 2026-06-04 · Antigravity flag surface + model names corrected
Live-tested v1.1.0 against the actual agy binary and found three real differences from the legacy Gemini CLI surface that prevAIl was passing the wrong flags for:
| What we pass | Gemini CLI | Antigravity (agy) |
|---|---|---|
| Skip trust gate | --skip-trust | --dangerously-skip-permissions |
| Model | -m | --model |
| Prompt | -p | -p (unchanged) |
runChatTurn now dispatches on the resolved binary basename — /agy$/ gets the new flags; the legacy gemini binary (fallback during the 2026-06-18 transition) keeps the old flags. Both produce the same stdout format, so the reply parser is shared.
Model names also changed shape. agy models shows display-style names with thinking-budget suffixes — "Gemini 3.1 Pro (High)", "Gemini 3.5 Flash (Medium)", etc. — and Antigravity also exposes other providers through the same launcher: "Claude Sonnet 4.6 (Thinking)", "Claude Opus 4.6 (Thinking)", "GPT-OSS 120B (Medium)".
Updated:
ANTIGRAVITY_VERSIONSlist reflects whatagy modelsactually returns.CLI_MODEL_HINT.antigravitytells the user to runagy modelsfor the live list.- Tests updated to assert the new flag names.
Smoke-tested live: detectClis() picks up /Users/you/.local/bin/agy, dispatches correctly, model replies. End-to-end works.
[1.1.0] — 2026-06-04 · Antigravity (agy) replaces Gemini CLI
Google announced the transition from Gemini CLI to Antigravity CLI (agy) on 2026-05-19, with a hard shutdown of the legacy gemini binary on 2026-06-18. prevAIl now ships antigravity as the canonical id for Google's panelist.
Changed — CliKind, labels, slash command
CliKindenum:"gemini"→"antigravity". UI labels: "Gemini" → "Antigravity" everywhere visible.- Slash command:
/antigravity(or/agy) is canonical./geministill routes through as a backward-compat alias during the transition. - Council bubble color (green) and per-CLI hint text follow the rename.
Added — Binary detection fallback
Detection prefers agy. If the user hasn't migrated yet and only has the legacy gemini binary, it's picked up as a fallback. Both invoke the same args (--skip-trust -m <model> -p <prompt>) since Antigravity preserved Gemini CLI's flag surface. Drop the gemini fallback after 2026-06-18 when Google shuts the legacy CLI down.
Added — Config migration
Old configs that say councilClis: ["gemini"], councilModels: {gemini: [...]}, or councilChair: {cli: "gemini"} get silently normalized to "antigravity" on first read. No user action required.
Unchanged — Model IDs
Antigravity uses the same Gemini model names underneath (gemini-2.5-pro, gemini-2.5-flash, etc.), so prevAIl's model lists, pins, and benchmark records keep working unchanged.
[1.0.1] — 2026-06-04 · First-run UX fixes
Two small but irritating bugs caught on first use.
Fixed — First letter swallowed when scaffolding a new domain / skill / app
Pressing n on the cockpit and immediately typing a name was losing the first character ("wealth" → "ealth"). opentui's <input focused> was racing the user's first keystroke against the focus settling. Fixed by re-running the focus call across 0 / 30 / 120 ms — the same proven pattern from the chat input. Same fix applied to the wizard's new custom-path input below.
Added — Custom vault path in the first-run wizard
The wizard now has a "type a custom path" option at the bottom of the list. Picking it opens an inline vault path › _ input where the user can paste or type any path:
- Absolute (
/Users/jane/docs/my-vault) - Tilde-relative (
~/Documents/vault) - Or cwd-relative (
./my-vault)
The path is then treated exactly like the existing default-home option — scaffolded with the 22 default domains if it doesn't exist, adopted as-is if it does.
[1.0.0] — 2026-06-04 · 1.0
prevAIl 1.0. The deliberation cockpit is feature-complete for its scope, production-hardened for its threat model, and OSS-ready for outside contribution.
What 1.0 means
This release is the consolidation marker for the v0.9.x + v0.10.0 audit cluster. Nothing new ships in 1.0 itself — it's the version where:
- The threat model is documented. SECURITY.md, docs/threat-model.md, and the operating manual cover what's defended and what isn't.
- The security defenses are real, not aspirational. Prompt injection hardening, tamper-evident logs, MCP token auth, council cost caps, output truncation, secrets at chmod 0600 — all in code, all under test.
- The operational surface is complete. Vault prune, backup, restore, verify. Debug log with rotation. Self-update.
- The OSS scaffolding is in place. CODE_OF_CONDUCT, CONTRIBUTING, ISSUE_TEMPLATE/, PR template, docs/, pinned scope discussion, Homebrew formula.
- The codebase is maintainable by one person. Biome lint in CI,
<Chip />deduplication,<ErrorBoundary />defense in depth, void-silencer cleanup, two of the largest files refactored. - The tests carry their weight. 181 passing, 0 failing — coverage across the security primitives, the vault tooling, the upgrade flow, the chip rendering, the error recovery.
What 1.0 does NOT mean
- Not enterprise-grade. prevAIl is single-user by design. See SECURITY.md and the pinned scope discussion.
- Not the final feature set. Several follow-ups are tracked as GitHub issues (#2 app-scope overrides, #3 recall JOIN, #4 MCP/Telegram framework+lens). They'll ship as 1.x minor releases.
- Not a stability freeze. Semver discipline starts now: breaking changes only on major bumps. Within 1.x, any keyboard shortcut / slash command / config key / vault file shape / persisted format is stable.
Going forward
- v1.x minor releases: new lenses, new frameworks, new connectors, new docs.
- v1.x patches: bug fixes, dependency bumps, perf tweaks.
- v2.0.0 would be a deliberate design break — none on the roadmap.
Thank you to everyone who chased a screenshot bug in the chips, found a typo in the threat model, or just used the cockpit. The deliberation was the point.
[0.10.0] — 2026-06-04 · Code quality + refactors + self-update
Phases 5, 6, and 7 of the production-readiness audit. The codebase is now smaller, more maintainable, defensively wrapped, and can update itself.
Added — Biome lint + format
biome.jsonat the repo root,bun run lintandbun run lint:fixscripts.- 21 unused imports auto-removed across 15 files.
- CI workflow now runs
biome checkbefore typecheck.
Added — <Chip /> shared component
- New
src/chip.tsxencodes the proven opentui-safe pattern (two adjacent<text>cells, NBSP-prefixed value, dim label + highlighted value) in one place. - 11 chip sites replaced across
workspace-config-bar.tsxandbranding.tsx. ~150 lines of duplication removed. - 9 new chip tests covering NBSP prefix, attributes bit, color overrides, click wiring.
Added — <ErrorBoundary /> around every major pane
- Sidebar, DomainDetail, ChatPane, ToolsPanel, CouncilConfigPanel, EditorPane each wrapped.
- Render-time crash shows "this panel crashed — press R to reload, or q to quit" instead of taking down the whole cockpit.
- Errors logged to
~/.prevail/debug.logvia the existinglogDebugchannel. - Shift+R force-remounts every boundary by bumping a reset counter.
Cleaned — void varName silencers gone
5 silencer statements removed (fwScope, lensScope, bundledDemoVaultPath, existsSync in wizard, activeChats / pendingChats in branding). Categories: leftover destructure, leftover imports, leftover prop plumbing.
Refactored — src/chat-pane.tsx split
- 2,221 → 1,247 lines. 974-line drop.
- 13 new files under
src/chat-pane/(bubbles/, types.ts, council-config.tsx). - 8 bubble components each have their own file: MessageBubble, CouncilPending, CouncilResponse, CouncilSynthesizing, CouncilVerdict, StreamingAssistant, Serendipity, CouncilSuggestion, Thinking, DistillDraft.
formatMetaBadgeextracted to its own pure helper.- Zero behavioral change. Public API preserved via re-exports —
app.tsx's imports from./chat-pane.tsxcontinue to work unchanged.
Refactored — src/app.tsx split
- 2,978 → 2,860 lines. 118-line drop.
- New
src/app-keyboard.tsxexposesuseAppKeyboard(args)— the global keyboard handler is now its own hook. - Every shortcut preserved verbatim: q quits, n new (context-sensitive), e edit, o open skill folder, R reload boundaries, ↑↓ navigate, Esc exits chat, 1-5 view jump, etc.
- Setters typed as
Dispatch<SetStateAction<...>>to match React'suseStatereturns exactly.
Added — prevail upgrade self-update
- Checks GitHub Releases, compares to current VERSION, prompts y/N for confirmation.
- Streams the platform binary into the binary's own directory (atomic rename via
renameSyncon the same APFS volume), verifies SHA-256 against the published checksum. - Detects brew-installed binaries (
/opt/homebrew/,/usr/local/Cellar/, etc.) and routes tobrew upgrade prevailinstead. - Flags:
--check,--force,--pre. Aliases:update,self-update. - 21 new tests covering platform/arch matrix, semver compare, shasum parsing, mocked GitHub API.
Tests
+53 tests across the cluster (9 chip + 6 error-boundary + 21 upgrade + 17 other). Total: 181 pass / 0 fail / 3 skip across 22 files.
[0.9.2] — 2026-06-04 · Hard security cluster
Phase 3 of the production-readiness audit. The four real security items from the SECURITY.md threat model, shipped as one cluster.
Added — Operating-manual prompt-injection hardening
A new "Treat vault contents as untrusted input" section in vault-demo/AGENTS-operating.md tells the AI explicitly that vault file contents are user-provided input, not authoritative instructions. The model must flag suspected injection attempts as PROMPT-INJECTION SUSPECTED in <path> and refuse to act on embedded commands without re-confirmation from the current user-turn. Cross-referenced from SECURITY.md and docs/threat-model.md.
Added — Tamper-evident _log/ via SHA-256 sidecar
Every writeTurnSummary call now also appends <entry-id> <sha256> to a sibling _log/.shasum file. New prevail vault verify command walks the .shasum files, re-hashes the matching entries, and surfaces mismatches (red !) and missing entries (yellow ?). Best-effort write — never crashes the chat path.
Added — Council cost estimator + maxCallsPerTurn cap
Before firing a /council turn, prevAIl computes the panelist × lens × chair call count and prints a one-line ~$X.XX for N calls (rough estimate) system message. If the count exceeds the councilMaxCallsPerTurn cap (default 16), the turn is refused with a friendly system message — no fanout, no spend, chat input stays usable. Coarse per-CLI heuristics: claude $0.005, codex $0.004, gemini $0.003, ollama $0.
Added — MCP server auth + parent-process check
prevail mcp now:
- Requires a token. Auto-generated 32-byte random hex token on first run, persisted to
~/.prevail/mcp.json(chmod 0600). Every non-initializerequest must include_meta.authorization: prevail-<token>(constant-time compared). Unauthorized requests get JSON-RPC error -32001. - Verifies the parent process. Refuses to start if the parent isn't a TTY or a known IDE/MCP client (vscode, cursor, jetbrains, claude, goose, continue, cline, etc.). Pass
--unsafe-detachto override for legitimate exotic launchers.
Tests
+22 new tests (council-cost 7 + mcp-config 4 + tamper-evident verify 4 + mcp-server smoke 7). Total: 146 pass / 0 fail / 3 skip.
[0.9.1] — 2026-06-04 · Operational hygiene + vault tooling
Phase 2 of the production-readiness audit. Five tasks shipped in one tag — all small, isolated, additive.
Added — prevail vault prune --older-than <duration>
Walks <vault>/<domain>/_log/*.md and <vault>/<domain>/_journal/{decisions,facts}.md older than the threshold. Dry-run by default; --force to actually delete. Reports "would free X / N files" before doing anything. Will NEVER touch state.md, QUICKSTART.md, PROMPTS.md, open-loops.md, or skills/. Duration syntax: 30d, 12h, 7d12h, 1y.
Added — prevail vault backup and prevail vault restore
Tarball the vault + safe parts of ~/.prevail/ (config.json, sessions DB). Explicitly excludes telegram.json, mcp.json, connectors/*/auth/*, and anything matching *.token. Restore prompts for the exact vault basename to confirm before overwriting.
Added — prevail vault verify (placeholder)
No-op stub for now. Will be the tamper-evident-log verifier when #41 lands.
Added — ~/.prevail/debug.log with rotation
New module src/debug-log.ts exposes logDebug(category, message, meta). Synchronous JSON-Lines append. Rotates at 5MB (keeps 3 archives). All files chmod 0600. prevail doctor --debug prints the last 50 entries.
Added — Output caps on lightweight LLM calls
runChatTurn gained an optional maxOutputChars field. When the stream crosses the cap, the child process gets SIGKILL'd via process group and the reply returns sliced with a ... (truncated at N chars) suffix. Wired into:
- Journal distillation — 8000 chars
- Auto-council classifier — 200 chars (it should only say YES or NO)
- Serendipity post-turn — 4000 chars
Hardening — secrets at rest
Audited every ~/.prevail/ writer. All sensitive files (config.json, telegram.json, OAuth refresh tokens) already chmod 0600. No new code needed; verified and documented.
Docs
README Scope section now includes the vault-sync caveat: sync <vault>/ only; never sync ~/.prevail/.
Tests
+27 new tests (14 vault-ops + 6 debug-log + 7 cli-bridge truncation). Total: 131 pass / 0 fail / 3 skip.
[0.9.0] — 2026-06-04 · OSS scaffolding + threat model
Phase 1 of the production-readiness audit. Docs and OSS scaffolding only — no code changes, no behavioral changes. Sets up the foundation the subsequent security hardening work will lean on.
Added — Security posture
- SECURITY.md at repo root — full threat model, in-scope / out-of-scope, known limitations, private reporting flow via GitHub Security Advisories.
- docs/threat-model.md — long-form expansion of SECURITY.md with code cross-references, trust-boundary diagram, worked example of a prompt-injection scenario.
Added — Open-source scaffolding
- CODE_OF_CONDUCT.md — direct, project-tailored expectations for contributors.
- CONTRIBUTING.md refresh — adds Welcome / Before-you-start / Dev setup / Code style / Commit convention / PR flow sections. Existing LifeApp plugin protocol content preserved verbatim.
- .github/ISSUE_TEMPLATE/ — bug report + feature request YAML form templates, plus config.yml that disables blank issues and links security reports to the private advisory channel.
- .github/PULL_REQUEST_TEMPLATE.md — short checklist (typecheck, build, tests, CHANGELOG, backwards-compat).
Added — Architecture docs
- docs/data-flow.md — how a turn moves end-to-end through cockpit → CLI → _log → _journal → benchmark, plus the vault folder layout.
- docs/extending.md — how to add a framework, lens, CLI bridge, ConfigBar chip, slash command. Line-targeted references into the codebase.
- docs/scope-discussion.md — what prevAIl is and isn't, copy-paste-ready for the pinned GitHub Discussion.
Added — Distribution scaffolding
- Formula/prevail.rb — Homebrew formula targeting the prebuilt release binaries (macOS arm/intel + Linux arm/intel). SHA256 placeholders to be filled by release automation.
- Formula/README.md — release-process notes for maintainers.
Added — README updates
- New Platform section documenting bun-only runtime and supported targets (macOS arm64/x64, Linux arm64/x64, Windows via WSL).
- Expanded Docs · changelog · roadmap to link the new docs/ files.
- New Scope section before License making the single-user / not-SaaS framing explicit.
Added — Pinned GitHub issues
- Issue #1 — pinned "Project scope" canonical reference.
- Issue #2, #3, #4 — three "follow-up" items lifted from prior commit messages into real tracked work.
[0.8.2] — 2026-06-04 · Skills tab — edit, open, scaffold
Three new entry points into the Skills tab. Zero impact on existing flows (click-to-select still toggles chat-context selection, all other tabs unchanged).
Added — Edit the highlighted skill
- Keyboard:
eon the Skills tab opens the highlighted skill'sSKILL.mdin the bundledEditorPane. Reuses the existing edit-mode plumbing — sameCtrl+Sto save, sameEscto back out. - Mouse: the highlighted row sprouts a right-aligned
✎ editchip; click to fire the same action. Non-cursor rows render unchanged — no chip clutter.
Added — Open the skill folder
- Keyboard:
oon the Skills tab opens the highlighted skill's folder in Finder / Explorer / xdg-open. Lets you edit any file in the skill bundle, not justSKILL.md. - Mouse: the highlighted row also gets a
▸ openchip alongside✎ edit. Both havestopPropagationso clicking either never accidentally toggles the skill's chat-context selection.
Added — Scaffold a new skill
- Keyboard:
non the Skills tab opens the CommandBar with promptnew skill in <domain> ›. Submit a name → writes<vault>/<domain>/skills/<id>/SKILL.mdwith a minimal frontmatter + section template. Cursor lands on the new row soeimmediately opens it in the editor. - Mouse: a
+ new skillfooter row appears at the bottom of the skills list. - Toolbar: the bottom
[n new]chip is now context-aware — on Skills tab it scaffolds a skill; elsewhere it still scaffolds a domain.
Help text update
The skills tab subheader now reads: [n] new skill · [e] edit highlighted in $EDITOR · [o] open its folder.
[0.8.1] — 2026-06-04 · UI polish + the GIFs the v0.8 release deserved
A polish pass on top of v0.8.0's seven new features.
Fixed — ConfigBar chips
The ConfigBar rendered correctly during typecheck but the chips' values were either clipped, missing, or run-together in the actual terminal. Five distinct bugs, each found by user screenshot:
- Values blank. opentui clips when a single
<text>mixes a literal segment with a JSX interpolation. Pre-building a single template literal didn't help; splitting label + value into TWO<text>cells inside one<box>did. - No space between label and value. opentui strips BOTH leading and trailing whitespace inside text cells. Fix: leading character on every value cell is a non-breaking space (U+00A0), which terminals render as a space but opentui treats as a normal glyph.
- Inconsistent casing. Framework + Lens labels (BLUF, FIRST PRINCIPLES) were uppercase but on/off/suggest were lowercase. All values now uppercase for visual uniformity.
- Labels and values same color. Hard to scan. Now two-tone: label dim (theme.fgDim), value highlighted (aiAccent cyan, or gold for Council when ON).
- Council scope-hint noise. Removed the
· domain/· globalsuffix on framework/lens chips — the user already knows scope by which surface they clicked.
Fixed — _log/ meta line carries the full cockpit state
The > meta: blockquote on every chat-turn entry now includes the FULL config snapshot at send time:
> meta: claude · model=claude-opus-4-7 · framework=BLUF · lens=CONTRARIAN · web=on · serendipity=off · council=on
Date stays implicit in the file path, time in the ## HH:MM section header. Every other dial that could have shaped the turn is now greppable.
Changed — journal/ → _journal/
Parity with _log/. Same naming convention for both AI-managed sidecar folders.
Added — Two new demo GIFs
assets/demo.gif— main hero. Boot, walk the 20+ life domains, return. Shows wordmark + sidebar + active workspace + ConfigBar.assets/deliberation.gif— navigate to wealth, walk the tabs (state / quick start / prompts / skills / back to chat). Shows the per-domain workspace structure.
[0.8.0] — 2026-06-04 · Memory, judgment, calibration
Six features bolted onto v0.7.0's council foundation. Each one stands alone; together they turn the cockpit from "ask multiple models" into a system that remembers, distills, and audits itself.
Added — ⬡ Web toggle on the ConfigBar
The webAccess allow/deny setting was buried in the Tools panel. Promoted to a first-class clickable chip on the WorkspaceConfigBar AND the global defaults block in the banner. Click cycles allow ↔ deny.
Added — ▣ Checkpoint (raw transcript persistence)
Default ON. Every chat turn (single chat + council verdict) appends the full prompt + full reply verbatim to <domain>/_log/YYYY-MM-DD.md. The existing summarized log shape is preserved for raw: false callers; the user's chats are no longer truncated to 220/400 chars. New ▣ Save chip on the ConfigBar with per-domain override.
Added — journal/ folder with AI-distilled decisions + facts
New module src/journal.ts. After every turn, an async lightweight call extracts:
DECISION:— one short sentence naming the decision the user is making, or NONE.FACTS:— up to 5 bullets of concrete numbers / dates / rules surfaced, or NONE.
Appended to <domain>/journal/decisions.md and <domain>/journal/facts.md with timestamped backlinks to the _log/ entry that produced them. Best-effort and silent on failure — the raw _log/ stays the source of truth, journal is the index on top.
Added — ◉ Serendipity (Option B)
Per-domain toggle, OFF by default. When on, every turn fires a SECOND lightweight call after the main reply asking for one non-obvious adjacent angle, fact, or question the user did NOT ask but would benefit from. The result lands as its own dim ◉ serendipity bubble below the main reply. Distinct from the main answer so the visual hierarchy makes it obvious which is which.
Added — ◐ Auto-council detection
Three modes (autoCouncil config, default "suggest"):
"off": skip the classifier; sendMessage runs single chat as today."suggest": fire classifier in PARALLEL with the chat. On YES, append a passive⚖ this looks council-worthybubble. Click re-runs through council. Zero latency penalty on the main reply."auto": BLOCK on the classifier. On YES route to runCouncil; on NO fall through to chat. The user opts into latency for auto-routing.
Classifier prompt explicitly includes the user's stated examples — "Summarize this email" (NO), "Should I leave my job?" (YES). Per-domain override available.
Added — Canonical benchmark dataset
The biggest one. Three new CLI subcommands wrap a new <vault>/benchmark/ area:
prevail bench seed --domain <name>— write a stub canonical question with FILL-IN placeholders.prevail bench seed --from-log <domain>— import the most recent council verdict from that domain's_log/as a draft (the user's ground-truth answers already live there).prevail bench run --canonical [--cli <kind>] [--model <id>] [--council]— fire every canonical question at the target CLI (or council). ReusesrunChatTurn+runCouncilOneShotso behavior matches the cockpit. Writes results + timing to<vault>/benchmark/runs/<date>_<label>/results.{md,json}.prevail bench score [--run <name>] [--no-judge]— two-layer scoring:- Mechanical keyword match (0-100%): hits/misses on
expected_verdict_keywords. Objective floor. - LLM-as-judge (0-10): tight rubric (10 = right decision + matching reasoning, 0 = wrong or fails to commit). Per-question rationale captured.
- Mechanical keyword match (0-100%): hits/misses on
prevail bench leaderboard— cross-run scoreboard sorted by judge_avg then keyword_avg.
Distinct from the bundled bench/questions/ generic suite. This one is for the user's personal Q&A with KNOWN ground truth — the test you can point at a new model the day it ships.
Fixed — Council escalation symmetry
council-suggestion bubble reuses the existing kind: "council" ChatCommand path, so a click re-fires the prompt through runCouncil with no special-case glue.
[0.7.0] — 2026-06-04 · Council, lens, framework — the deliberation release
The biggest jump since v0.6. prevAIl's pitch is now sharply about ONE thing — hard decisions across multiple models — and the cockpit, persistence, and ergonomics all aligned around that.
Added — Cognitive lenses
- 8 lenses that change HOW a panelist attacks a question (orthogonal to frameworks, which change how the answer is STRUCTURED):
FIRST PRINCIPLES,OUTSIDER,CONTRARIAN,EXPANSIONIST,EXECUTOR,ALIEN,MOM,DAD. - Lens = "all" fanout. Every council panelist runs every lens (4 CLIs × 8 lenses = 32 panelist calls per question). The chair synthesizes ACROSS lenses with a different prompt — "divergence is the signal, not noise."
- Per-domain lens overrides stored in
domainLenses. Cycling the chip on a domain workspace mutates that domain only; cycling the global chip in the banner sets the fallback.
Added — Per-domain framework overrides
- Same mechanic,
domainFrameworksmap. Workspace bar chip = domain override; banner chip = global fallback.resolveResponseFramework/resolveResponseLenscentralize resolution so every consumer (council runner, CLI bridge, chat status line) agrees.
Added — Per-bubble metadata + decision log
- Every assistant bubble renders a dim badge underneath:
claude · claude-opus-4-7 · ◆ BLUF · ◇ CONTRARIAN. Captured at SEND TIME, not render time. - Default-model fallback. When a panelist runs on a CLI's default model, the badge shows
gpt-5.4 (default)rather than blank. Every panelist reads uniformly informative. - Per-domain decision log.
writeTurnSummaryemits a> meta: <cli> · framework=… · lens=…blockquote in<domain>/_log/YYYY-MM-DD.md. - SQLite persistence. New
messages_extsidecar table (FTS5 won't ALTER) joins framework/lens onto message rowid.
Changed — UI architecture
- Unified ConfigBar.
⚖ Council · ◆ Framework · ◇ Lens ▸ vault ✎ edit— rendered at the BOTTOM of the content area in BOTH chat and workspace mode. Built once inapp.tsx, passed asbottomBarto ChatPane and DomainDetail. - TabStrip slimmed. Council toggle and configure left the strip — they lived there AND on the workspace bar. Picking one removes the duplication.
- Banner compacted to match the wordmark's 7-row height. Defaults block is two rows:
⚖ Council · ◆ Framework · ◇ Lens/◇ configure · ▸ tools. Each chip gets a distinct glyph.
Changed — Apps collapsed
SHOW_APPS = falseat the top ofsrc/app.tsxhides the LIFE APPS sidebar section, thesswap-focus key, AppDetail, and the new-app flow. The connector architecture stays in the codebase. Flip the constant to re-enable when the connector → council grounding pipeline is wired.
Fixed
- Gemini ESC cancel. The
geminiwrapper script swallows SIGTERM. Spawn withdetached: trueand signal the whole pgroup with SIGKILL. - Overlay keyboard leak. Arrows inside Tools / Council Config were navigating the sidebar underneath. Overlays now own the keyboard; only ctrl+c propagates.
- Skills tab header bleed. Switched to single template literals — multiple JSX expressions inside one
<text>caused opentui to interleave lines. - Branding chip clipping. Split into two rows with distinct glyphs (⚖ ◆ ◇).
- "· domain" scope hint removed. Scope is implicit in where the chip lives.
Skill count accuracy
- Connector stubs (
type: appin frontmatter) no longer counted toward domain skill totals.
[0.6.1] — 2026-06-03 · UX polish + visibility
Iterative fixes to v0.6.0 based on real-use feedback. No new features so much as making the existing ones findable.
Fixed — Navigation
- No more "press Escape to see the app." Removed the auto-open-chat
useEffectthat was silently re-setting mode to "chat" on every navigation, undoing every nav-fix attempt. Clicking a sidebar item now lands you on the workspace immediately. - Tab strip off-by-one. Clicking
statewas selectingquickstart, clickingskillswas selecting nothing. TabStrip array includeschatat index 0 butVIEW_ORDERdoesn't — the click handler was passing TabStrip indices straight tosetViewIdx. Now offset by 1 so each tab actually selects what it says. - Arrow nav + Enter /
ckeys no longer force-open a separate chat pane. The embedded chat in every workspace is enough.
Fixed — Layout
- Wordmark spacing. Five iterations on this. Final shape: 10×7 uniform ANSI-Shadow-style letters in identical bounding boxes, all gaps equal (single cell between every letter, no group breaks). The
Aglyph's body is now centered with its top rows. Banner padding above and below the hero. - Domain pane: nothing below the chat input. No more reference strip — chat fills the entire pane, input at the very bottom.
- App Overview compacted from 8 labeled rows to 4 (
kind/how/scopeplus the title line carrying status + last probe). Issue + fix rows only appear when the connection is broken.
Added — Visibility
- Workspace config bar at the top of every domain and connector pane:
📂 open vault(spawns Finder/Explorer/xdg-open) ·⚖ Council ON/OFF·◆ Framework <name>. All clickable. Three things users were doing via slash commands before. - Global toggles in the banner top-right:
defaults ⚖ ON/off ⚙ configure │ ◆ <framework>. The⚙ configurelink opens the full CouncilConfigPanel (pick engines, pick models, pin the chair). The⚖toggle writes acouncilDefaultOnto config — per-chat overrides still win when set. - Panel health row in the banner:
panel ✓ Claude ✓ Codex ⚠ Gemini ⠋ Ollama. You see BEFORE firing council which panelists are healthy. - Per-domain prompt suggestions. DomainChat empty-state reads
PROMPTS.mdfrom the domain folder and extracts question-shaped lines; falls back to generic prompts when the file is sparse. - Multi-select skills in the domain Skills tab. Click a skill to select it (☑); click again to unselect. Selected skills are injected as
<selected_skills>context into the next chat turn.
Added — Defaults
- Domains default to the chat tab. Clicking a domain in the sidebar opens its chat with suggested prompts —
state/quickstart/prompts/skillsare still one click away. - Single version source of truth at
src/version.ts. Imported by--version, the banner, and the MCPserverInfo.
Skills shipped per connector (carried from v0.6.0)
- github:
pr-queue,repo-stars-trend - plaid:
list-institutions,recent-transactions - youtube-analytics:
channel-metrics - linkedin:
profile-views(stub — browser runner phase 6) - google-calendar:
today-events(stub — MCP runner phase 5)
[0.6.0] — 2026-06-03 · connectors are workspaces, not manifests
Apps stop being passive manifest viewers and become live workspaces. Clicking any connector now lands you on a Overview + Chat page: connection status at the top, a working chat with the connector's data right below. No more "click Skills to find anything."
Added — Tabbed connector workspace
Every app now has its own 5-tab workspace:
Overview + Chat— compact connection card on top, live chat scoped to this connector below. Per-app starter prompts ("what was my biggest spend last month?" for Plaid, "which PR has been open longest?" for GitHub). Streaming responses.Auth— env vars / files with ☑/☐ live check marks + a plain-language explanation of what each integration type (api/oauth/browser/mcp/a2a/manual) actually means.Sync— scheduled skills with their cron expressions.Skills— runnable skills with▶ Runbuttons + inline streaming results.Data— file tree under<connector>/data/with sizes + mtimes.
Connector workspace replaces the domain-style state | loops | quickstart | prompts | skills tabs when viewing an app — apps are connectors, not life domains, so the navigation now matches the mental model.
Added — Skill execution layer (src/connector-skills.ts)
Each skill is one markdown file under <connector>/skills/<id>.md with YAML frontmatter declaring runner type, trigger, auth requirements, inputs, and outputs.
LLM runner (the leveraged 80% case): spawns a panelist CLI with the skill description + scoped env + inputs. Most flexible runner type — covers everything from "list institutions" to "monthly receipts" without writing imperative HTTP/browser/MCP code.
Security guards:
- Output paths confined to
<connector>/data/(no../escape) buildSkillEnv()starts fromscrubbedEnv(), adds back only the auth keys the skill explicitly declared${input.x}/${env.X}substitution is strict — unknown vars throw- chmod 0600 on every written output
- Per-skill log under
<connector>/_log/
Added — Skills shipped per connector
| Connector | Skills | Runner |
|---|---|---|
| github | pr-queue, repo-stars-trend | ✅ llm (runnable today) |
| plaid | list-institutions, recent-transactions | ✅ llm (runnable today) |
| youtube-analytics | channel-metrics | ✅ llm (runnable after OAuth) |
profile-views | ⚠ stub — browser runner ships in v0.6.x | |
| google-calendar | today-events | ⚠ stub — MCP runner ships in v0.6.x |
Added — Connector CLI commands
prevail connectors skills <id> # list runnable skills
prevail connectors run <id> <skill> --input k=v # execute a skill
Added — Manifest scaffolding for vault apps
Vault apps (the ones authored before this redesign, no manifest.json) now show a yellow ⚠ No manifest yet panel with a ⊕ Scaffold button. One click writes a starter manifest.json + empty skills/ dir into the app folder, idempotently.
Added — Architecture doc
docs/connector-architecture.md lays out the full 7-phase plan: skill schema + LLM runner (this release), API runner, tabbed workspace (this release), connector-scoped chat (this release), sync orchestration, browser runner, A2A as MCP-over-network.
Fixed
- Wordmark spacing — every row of PREV / AI / L is now padded to the widest row in its group, leading space added inside AI to compensate for V's tapered right edge, GROUP_GAP bumped to 3 spaces. AI no longer looks clumped against PREV.
- Connector workspace was nested inside the Skills tab — apps now own the entire pane; domain-style tabs (
state | loops | quickstart) are no longer shown for apps.
[0.5.0] — 2026-06-03 · calibration + distribution
Five-feature release. The product becomes smarter about you (calibration loop), reaches further (MCP), and reads faster (streaming) — without breaking the "everything is markdown" promise.
Added — Streaming responses
Each panelist's text streams into its bubble as the model produces it. runChatTurn gains an onChunk callback; council runner exposes onPanelistChunk(idx, delta) so the UI updates the right bubble. Wired through claude/codex/gemini stdout + ollama SSE. Perceived latency drops 80% — same end-to-end time, but you see tokens land in real time instead of staring at a 30-second spinner.
Added — MCP server mode
prevail mcp speaks JSON-RPC 2.0 over stdio — the standard MCP transport every host (Claude Desktop, Cursor, Continue, Goose, ChatGPT Desktop) speaks. No SDK dependency. Five tools exposed: council (full panel + verdict), chat (single-CLI), list_domains, read_state, read_log. Calls invoked via MCP write to the same vault log as TUI/Telegram. Every other agent ecosystem becomes a distribution channel.
Added — Council vs. yourself (calibration loop)
/gut <your take>before/councilcaptures your gut answer in one line.- Both gut + verdict + a 90-day
retro_duedate are written as an HTML-comment metadata line at the top of the log entry. Invisible in rendered markdown, greppable on disk. /calibration pendinglists log entries past their retro_due where you haven't recorded an outcome./calibration outcome <id> <text>records what actually happened./calibration statusshows the running scoreboard: "right when you agreed with council X%, right when you disagreed Y%". Stored as<domain>/_calibration.md, regenerable from the log entries.
Added — Markdown-native vault memory
Embeddings live as a single inline comment line right under each log entry's time header — no DB, no separate index file, the vault stays portable. After every writeTurnSummary, the runner asks the Ollama-detected embedder (default nomic-embed-text) for a 384-dim vector and splices it in. At query time, linear scan + dot product across the vault → top-k most semantically similar prior decisions are prepended as <context> to every council fanout. Sub-100ms at personal-vault scale. Silent fallback when no embedder is available.
Added — Public council benchmarks
bench/ships with three example questions acrosswealth/,career/, andhealth/.prevail bench listshows the suite.prevail bench run [--domain X] [--question Y]fires/councilon each question, writes per-question results + a top-level markdown summary table to~/.prevail/bench-results/<date>/.- Methodology + scoring rubric pattern documented in
bench/README.md. PR-able question contributions.
[0.4.0] — 2026-06-02 · auth, locks, paths
Hardening + the OAuth runner that makes the YouTube-Analytics example actually work end-to-end. Two days after v0.3.0, all three deferred items from the audit close-out are now shipped:
Added — OAuth 2.0 + PKCE runner
prevail connectors oauth <id>(and/connectors oauth <id>in the TUI) walks the full authorization-code-with-PKCE flow:- Spins up a loopback HTTP server bound to
127.0.0.1only (never0.0.0.0) - Opens the user's browser to the provider's consent screen
- Catches the redirect with
stateparameter verification (CSRF protection) - Exchanges code for tokens with PKCE verifier
- Saves the refresh token at
~/.prevail/connectors/<id>/auth/refresh.token(chmod 0600) - 5-minute hard timeout so a stuck browser never wedges the daemon
- Spins up a loopback HTTP server bound to
refreshAccessToken(connectorId)— exchange the saved refresh token for a fresh access token. Used by the probe layer + any connector skill that needs Bearer auth.- Generic over provider: works for Google services (YouTube Analytics, Calendar, Drive, Gmail), GitHub Apps, Notion, Linear — anything that speaks OAuth 2.0 + PKCE. Provider specifics (auth/token URLs, scopes, extra query params like Google's
access_type=offline) live in the manifest'soauthblock. - YouTube Analytics example manifest now has a full
oauthblock so the flow works the moment the user setsPREVAIL_GOOGLE_CLIENT_ID+PREVAIL_GOOGLE_CLIENT_SECRET.
Added — Cross-process file lock for schedule + briefing ticks
- New
src/file-lock.tsprovidestryAcquireLock()/withLock()based on atomicO_EXCLfile creation. Used byschedule.tickAndRunDueandbriefings.tickBriefings. Without this, runningprevail daemon --telegramalongside the TUI could double-fire any cron entry that hit during the same minute on both processes — closing audit finding #10. - Stale-lock recovery via PID liveness check (
kill -0) + a 5-minute mtime floor, so a crashed daemon recovers cleanly on restart.
Added — Vault path validation
- New
src/path-safety.tsenforces "we never read or write outside the vault" as an invariant, not an emergent property:validateVaultPath()rejects empty/relative/system paths (/,/etc,/var,/System,/dev, ...) and null-byte pathsisSafeEntryName()rejects domain/app entry names with control chars, null bytes,.., leading dots, or excessive lengthresolveSafeChild()rejects symlinks that escape the vault root afterrealpathresolution
scanVault()now applies all three at scan time so a misconfigured vault (or a symlink farm dropped into one) can't trick prevAIl into walking system directories.
Added — Connectors CLI subcommand
prevail connectors list— every detected connector with auth type + idprevail connectors test <id>— run the manifest'sauth_checkprobe (same probe the UI runs on Test Connection)prevail connectors oauth <id>— kick off the OAuth flow
[Unreleased]
Security — adversarial sweep
External review surfaced 10 findings. This batch ships the 8 actionable ones:
- Vault-shell gate —
prevail schedulenow refusessh -cunlessPREVAIL_ALLOW_VAULT_SHELL=1. Without the gate, a malicious entry in a synced vault could RCE the operator. - Council injection chain blocked — panelist replies have their
## sectionheaders blockquoted before embedding in the chair synthesis; the verdict parser now matches the LAST## Verdictsection so a panelist-quoted fake can't override the chair's real verdict. - Telegram
from.idvalidated — both sender AND chat now must be on the allowlist, and non-private chats (groups/supergroups/channels) are refused outright. - Subprocess env scrub —
PREVAIL_TELEGRAM_*,ANTHROPIC_API_*,OPENAI_API_*,GOOGLE_API_*,AWS_*,GITHUB_TOKEN,OP_SERVICE_ACCOUNT_TOKEN, and*_SECRET/*_PRIVATE_KEY/*_PASSWORDare stripped before spawning Claude/Codex/Gemini. A prompt-injectedenv | grep TOKENcan no longer exfiltrate them. - Daemon abort cancellation — Ctrl-C now SIGTERMs every in-flight panelist + briefing instead of leaving subprocesses burning API budget for up to 120s.
- Session DB / config chmod 0600 — sessions.db, prompt logs, JSONL session files, and config.json are no longer world-readable under default umask.
- Defensive manifest coercion — a malformed
manifest.jsonno longer crashesscanCommunityAppsor hides itself by neutering legit entries; every field is type-checked, length-capped, and char-class-restricted. - Auto-summary quote-shields LLM output — verdicts written to
_log/YYYY-MM-DD.mduse markdown blockquotes so a prompt-injected verdict can't persist as instructions for tomorrow's Paperclip/OpenClaw morning brief.
Added — TUI surfacing for every feature
Every shipping feature now has a visible UI surface, not just a CLI subcommand:
/telegramin chat — status, setup, allow/remove chat IDs, launch hint. Same~/.prevail/telegram.jsonstorage asprevail telegram./briefingin chat — list, add (with quoted-string parser for cron + prompt), remove. Same.briefings.jsonstorage asprevail briefing./connectorsin chat — quick auth-status snapshot for every app at a glance./ollama— switch the chat's engine to your local Ollama instance.- Test Connection button in every app detail view — runs the manifest's declared
auth_checkand shows the live result (status, what's missing, fix hint).
Added — Per-app auth validation
Each connector manifest can now declare an auth_check block describing how to verify it works. The probe runner handles five auth kinds:
env-keys— required env vars must be present + non-empty (e.g. Plaid:PLAID_CLIENT_ID,PLAID_SECRET)file-exists— required files must exist (e.g. YouTube Analytics OAuth refresh token at~/.prevail/connectors/youtube-analytics/auth/refresh.token)command— spawn a binary, check exit code + optional stdout match (e.g. LinkedIn:playwright --version)http— GET a URL with optionalauth_header_envfor API-key auth; 401/403 surfaces asexpired(e.g. GitHub:https://api.github.com/userwithGH_TOKEN)mcp— verify a stdio MCP binary is on PATH OR ping an HTTP MCP server (e.g. Google Calendar viamcp-server-gcal)manual— manual-step list + optional freshness check on a watched file The app detail view auto-probes on open and shows live status/detail/fix-hint plus a clickable⟳ Test Connectionbutton. SSRF-guarded against metadata endpoints (169.254.169.254,metadata.google.internal).
Example connectors shipping with auth_check blocks: Plaid (api), LinkedIn (browser), YouTube Analytics (oauth), GitHub (http+api), Google Calendar (mcp).
Fixed
- Model picker shows version numbers first.
claude-opus-4-7/claude-sonnet-4-7/ etc. now lead the council model picker; naked aliases (opus,sonnet,haiku) fall to the end. Previously the picker visually read as "opus / sonnet / haiku ..." and users couldn't tell which version each alias resolved to.
[0.3.0] — 2026-06-02 · cockpit reaches out
This release pulls prevAIl out of the terminal. Local models, Telegram bridge, scheduled briefings, a self-curating vault, and a council that finally shows you the disagreement.
The competitive-research thesis for this release: the closest comparables in the personal-AI space (Hermes, Khoj, Goose, Agent Zero) all have at least one of these features. prevAIl now has all four, plus the things they don't — single binary, domain-folder UX, council across the CLIs you already pay for.
Added — Engines
- Ollama / OpenAI-compatible 4th engine. Any endpoint that speaks
/v1/chat/completions(Ollama, LM Studio, llama.cpp server, vLLM) is now a first-class panelist alongside Claude / Codex / Gemini. Detected automatically by probingGET /api/tags(falls back to/v1/models); shows up in the CLI bar and in the council picker. Privacy-sensitive domains (health, wealth) can run a local-model-only council.- Default endpoint:
http://localhost:11434(override withPREVAIL_OLLAMA_URL) - Default model:
llama3.1(override withPREVAIL_OLLAMA_MODEL) - Friendly probe error when the configured model isn't pulled (
ollama pull <name>) - Council bubble color: electric cyan (matches the AI in prevAIl)
- Default endpoint:
Added — Council surfaces disagreement
- The chair's four-section synthesis output (
What each panelist said/Consensus/Divergence/Verdict) is now parsed and rendered as distinct visual blocks, not one wall of text. The whole point of running a council is the disagreement; burying it inside paragraph three defeated the purpose. Three changes ship together:- TUI verdict bubble: when divergence is substantive, it renders in its own electric-cyan accent panel under a
🔀 Where panelists disagreedheader. The Verdict line gets its own gold-edged hero block. Title bar shows· 🔀 disagreementwhen the panel split. - Telegram delivery ships Consensus / Divergence / Verdict as separate messages so each section arrives with its own header on the user's phone — disagreement no longer drowns mid-paragraph.
- Vault writeback flags days with disagreements via a
🔀 disagreementtag in the daily log header, and uses the structured Verdict line as the assistant snippet (not the chair's full breakdown). Scrolling the log, the user can spot which calls were contentious without re-reading the whole council session.
- TUI verdict bubble: when divergence is substantive, it renders in its own electric-cyan accent panel under a
- Falls back gracefully to plain rendering when the chair model ignores the format request — never silently drops content.
Added — Scheduled domain briefings
prevail briefing add --cron "<cron>" --domain <name> --prompt "<text>" [--mode council] [--deliver log|telegram|both]— typed, domain-aware, council-aware scheduled prompts. Sits on top of the existing 5-field cron scheduler but adds structure (which domain, which mode, where to deliver) that ad-hoc shell schedules don't carry.- The daemon now runs a 60-second tick loop: any due briefing fires, the verdict lands in
<domain>/_log/YYYY-MM-DD.md(via the same auto-summary hook), and ifdeliver=telegram|boththe chair's verdict is pushed to every allow-listed chat. So at 7am your wealth panel runs, at 7:01am your phone has the verdict. prevail briefing run <id>— manual fire for testing without waiting for cron. Log delivery still happens; telegram delivery is daemon-only.- Storage:
<vault>/.briefings.json(separate from.schedule.jsonso the two systems can evolve independently).
Added — Self-curating vault
- Per-domain auto-summarization writeback. Every chat turn (and every council verdict) appends a one-paragraph snapshot to
<domain>/_log/YYYY-MM-DD.md— the user prompt + the reply, timestamped, tagged with the CLI/chair that answered. Over time each domain becomes its own decision log without the user having to take notes. Hooks the TUI'ssendMessage+ council-verdict path AND the Telegram daemon's chat/council reply path through one sharedwriteTurnSummary()helper. Pure heuristic (no extra LLM call), silent failure mode (never blocks the user).
Added — Telegram bridge
prevail daemon --telegram— headless mode that exposes the cockpit over Telegram. Same engines, same council, same/frameworksetting; just from your phone instead of the terminal.prevail telegram setup <bot-token>— bootstrap (token from @BotFather)prevail telegram add-user <chat_id>— mandatory chat-ID allowlist (no open access)- Per-chat state: domain, CLI, model, council on/off — set via
/domain,/use,/council on - Council fanout from Telegram: each panelist arrives as its own message, verdict gets a
⚖header with the chair label - Long-poll mode (no webhook / tunnel needed) — works behind NAT and on any laptop
- Token stored at
~/.prevail/telegram.json(chmod 600), orPREVAIL_TELEGRAM_TOKENenv var
- Extracted
runCouncilOneShot()from app.tsx intosrc/council-runner.tsso the TUI and the daemon share the same fanout + synthesis pipeline (single source of truth for what "the panel" and "the verdict" mean).
[0.2.0] — 2026-06-02 · rebrand + council mode
The launch of prevAIl (formerly aireadyu). Repo, binary, and brand all moved. Headline feature is council mode — ask one question, get three AIs in parallel, and a synthesized verdict.
Added — Council
/council <prompt>(or toggle▣ Council ONin the tab strip) fans the question out to Claude, Codex, and Gemini in parallel; a chair model then synthesizes a single⚖ council verdictfrom the panel.- Multi-model panels: run Opus 4.7 + 4.6 in the same council to compare versions head to head.
- Configurable chair: pin who synthesizes the verdict, or leave it on
auto. - Conversation context preserved across follow-up turns (each panelist sees prior verdicts + prior questions).
- Escape cancels the whole batch — SIGTERMs every panelist + the chair, drops a
(cancelled)bubble, returns the chat to idle. - Council config UI gained: verdict-synthesizer picker, codex auth-tier annotation (
*on pinned models with a footer explainingcodex login --api-key), bigger configure button (replaced the tiny ⚙ glyph).
Added — Quality of life
- ↑/↓ in the chat input recalls prior prompts — terminal-style. Walks the current session AND prior chat sessions for the same domain (persisted in the SQLite log). Adjacent duplicates collapsed. Per-chat, so wealth's stack and content's stack don't bleed.
- Session usage meter in the status line:
4 calls · 12k↑ 8k↓ tokens · ~\$0.16. Counts every CLI invocation (council = N+1 per turn), tokens from a 4:1 char rule, cost from a blended ~$3/$15 per-1M rate. Rendered with~so nobody confuses it for an invoice. /distillis now council-aware — when distilling a chat that includes council exchanges, the transcript preserves panelist + verdict attribution and the prompt asks the model to capture the decision framework the council used, not flatten to one voice.
Added — Docs + distribution
landing/static site scaffold ready forprevail.aideploy (Vercel / Netlify / Cloudflare Pages).- Council demo ASCII frame at the top of the README — real example, three panelists, synthesized verdict, with the new usage badge visible.
- "Why council beats a single model" section in the README explaining the triangulation pitch.
- GitHub repo description + topic tags (
ai-council,multi-model,claude-code,codex,gemini-cli,terminal-ui,opentui,bun,personal-cockpit) for discoverability.
Changed — Codex behavior
- Stops refusing non-coding questions in council with "I'm a software engineer, I only do coding tasks" — a short framing prefix on the prompt nudges Codex to engage directly.
- Envelope (
workdir / model / provider / approval / sandbox / session idlines + the exit-code prefix) stripped from responses so the chat bubble shows only the model's answer. - Operating manual no longer prepended for Codex (no system-prompt channel, used to get echoed back as noise).
Changed — Gemini behavior
- 30-line Node.js stack traces collapsed to the actual error line on API failures (
TerminalQuotaError: You have exhausted your capacity...instead of 30 lines ofat classifyGoogleError (file:///opt/homebrew/Cellar/gemini-cli/...)). - Operating manual no longer prepended for Gemini (same reasoning as Codex).
Changed — Rebrand
- Repo:
fru-dev3/aireadyu→fru-dev3/prevail(old URL redirects, stars/issues preserved). - Binary name:
aireadyu→prevail. - Env vars:
AIREADYU_*→PREVAIL_*(PREVAIL_DATA_DIR,PREVAIL_VAULT,PREVAIL_REPO,PREVAIL_BIN_DIR,PREVAIL_VERSION,PREVAIL_SCHEDULE_ID). - Local config dir:
~/.aireadyu/→~/.prevail/. Installer auto-migrates if it finds the old dir and the new one doesn't exist. - Install path:
~/.local/bin/aireadyu→~/.local/bin/prevail. - Domain:
aireadyu.life→prevail.ai. - New
prevAIlwordmark —AIin electric cyan (#3CD8FF) against gold (#C4A35A)prevandl. High contrast pairing so the AI is visually unmistakable as the heart of the brand. - New
icon.svgmatching the wordmark treatment. - First-run welcome wizard logo updated to match.
Fixed
- Empty LIFE APPS sidebar when the first-run wizard happened to pick an incomplete
dist/vault-demo(e.g. from a partial build).bundledDemoVaultPath()now requires the candidate to contain anapps/subdir before accepting it. - Codex hang at probe time —
stdio: 'ignore'for stdin so codex doesn't wait for input that will never come (carry-forward from v0.1.x diagnosis). - 88 orphan
.bun-buildtemp files (60 MB each) cleaned from the source tree.
Added — Tests
- Regression coverage for
extractCodexReply(success envelope, multi-line body, exit-prefix error path, no-reply fallback, empty input). - Regression coverage for
extractGeminiReply(stack-trace collapse on quota error, plain reply pass-through, empty input). - Total suite: 19 tests pass.
[0.1.2] — 2026-06-01 · last release before rebrand
Final tagged release under the aireadyu name. See https://github.com/fru-dev3/prevail/releases/tag/v0.1.2.