How to run a council, read the results, schedule work — and how to reshape the UI.
New here? Do INSTALL.md first.
- The idea in one paragraph
- Quick Setup
- The Desk — running a debate
- Reading the results
- Parallel Run
- The Office
- Tasks — scheduled prompts
- Prompts and council packs
- Hall of Fame
- Settings
- Building a better UI
- Recipes
One model gives you one confident answer and no way to tell whether it's right. This app sends your question to several models at once, lets them critique each other anonymously, and then shows you the parts they all agreed on separately from the parts they split on. The agreement is usually safe. The split is where you need to think. Over time it records which model wins on which kind of task — from your prompts and your blind picks, not a public benchmark.
Opens automatically on first run; reachable any time from the Quick Setup button.
| Step | What you choose |
|---|---|
| 1. You | Your name, UI mode (Jharvis HUD or Characters), light/dark, seasonal theme. |
| 2. Office | Office type and which agent roles to hire. |
| 3. Models | Check/start Ollama, paste optional cloud keys. |
| 4. Assign | Provider + model per agent. |
Skip is safe — everything is editable later in Settings.
Left rail — Ollama status, active model, ask lanes, agents armed. Right rail — the roster. Click to select or deselect; up to four at once. Bottom bar — debate configuration, then the prompt.
| Control | Options | What it does |
|---|---|---|
| Agents | 2 / 3 / 4 | How many lanes run in parallel. |
| Mode | Critique / Jury / Diff | Critique = peer review round. Jury = independent votes. Diff = straight comparison, no cross-talk. |
| Judge | You pick / Synthesis / Fastest / Success % | Who decides. You pick runs the blind taste test. Success % uses your own historical scores. |
| Rounds | 1 / 2 / 3 | R1 answer · R2 anonymised peer critique · R3 revise. |
| Consensus | Off / Any / Most / Strong | Below the threshold, synthesis is skipped and you get the contested claims instead of a fake merged answer. |
The right of the bar estimates cost and time before you commit — EST. FREE · ~60s
when every lane is local.
Type in the prompt bar and press Run debate. To address one agent only, start
with @Name.
Rounds cost time. Three rounds with four agents is twelve model calls. Start with one round, two agents, and turn depth up when the question deserves it.
The claim-level view. Each claim is extracted and marked:
- Agreed — every lane asserted it. Usually safe.
- Split — lanes disagree. This is the part worth your attention.
- Confident split — lanes disagree and both report high confidence. The most dangerous kind of answer, and the one a single-model chat would hide from you.
When agreement falls below your threshold, synthesis is skipped on purpose. You get the contested claim clusters with each side's position, plus the judge call that was saved. A "no consensus" result is a real answer — it means the question is genuinely unsettled and a merged paragraph would have papered over it.
Claims are verified against keyless sources: the local calculator and unit converter, Frankfurter for currency, then Wikipedia and DuckDuckGo. Verdicts render inline:
| Verdict | Meaning |
|---|---|
| Supported | A source backs it. |
| Unsupported | Nothing found. Not the same as false. |
| Contradicted | A source disagrees. Investigate. |
| Unchecked | Opinion, or offline mode is on. |
Models may end a reply with {confidence, unknowns, abstained}. Missing or malformed
values stay null — the app never invents a confidence score. Abstaining counts as a
correct outcome, so a model that admits it doesn't know isn't punished in the scores.
With judge mode You pick, answers render as anonymous Answer A / B / C. Model, provider, latency and cost stay hidden until you choose. Shuffling is seeded, so a run replays identically.
Use it. Everyone thinks they judge on quality; most people judge on the brand name.
/run — one prompt to every selected agent at once, no debate, no judge. Each card
shows queued → working → done / fail and uses that agent's own provider and model.
Use it to compare raw output, or to get several drafts fast.
/office — your agents as staff. Tower view lists every floor with a live log
line; Floor view is the walkable room. Click any agent for detail and chat.
Each hire has a mission, explicit refusals, and things it must ask for before starting. That's what makes it staff rather than a preset — it persists across runs and shapes how that agent argues in a debate.
Reassign a model at any time from agent detail or Settings → Assign.
/tasks — schedules resolved in a real IANA timezone (defaults to the active office).
| Kind | Behaviour |
|---|---|
| Manual | Runs only when you press it. |
| Interval | Every N minutes/hours. |
| Daily | A wall-clock time each day. |
| Weekly | A wall-clock time on chosen days. |
Tasks only fire while the app is open. Slots missed while it was closed run once
on reopen; further skipped slots increment a missedRuns counter instead of replaying
a backlog at you.
Watch mode stores the last non-identical diff per task — so you see what changed in the answer, not just the newest answer.
/prompts — save and reuse prompt templates.
Council packs are shareable debate recipes (jharvis-council/v1): members
(archetype, title, provider, model) plus the debate settings.
- Export scrubs a forbidden-key list — API keys, secrets, office identity, local paths.
- Import supports validate, dry-run, and a non-destructive apply that only creates agents you're missing.
Two starters ship in docs/packs/: local-free-council.json and
mixed-local-cloud-council.json.
Your personal leaderboard. Prompts are classified into six task types — code, analysis, writing, factual, planning, other — and each model accumulates runs, wins, latency, known cost and abstentions per type.
Sorted by win rate, ties broken by cost then latency. GET /api/model-stats?leaderboard=1.
This is the payoff of blind picking: after a few dozen runs you know that your cheap local model beats the expensive one on your writing, and loses on your code — for your work, not someone's benchmark.
Cost-aware routing uses it: free lanes within 10 win-rate points of the best paid
option rank first. Token estimates use chars/4 and say so.
| Setting | What it does |
|---|---|
| Boss name | What the app calls you. |
| Offline only | Refuses every cloud provider at the provider layer. Requests never leave the process. |
| Show paid models | Off = Ollama, Groq, Gemini free, OpenRouter :free only. |
| Ollama base URL | Default http://127.0.0.1:11434. |
| API keys | Per provider. Stored on this machine; masked once set. Each row has a Get a free key ↗ link straight to that provider's key page, plus its current free-tier limits. |
| UI mode | jharvis (HUD) or characters (plain). |
| Appearance | Light or dark. |
| Theme | Auto-seasonal or pinned. |
| Character set | Pixel · Emoji · Robot · Dino · Cartoon. |
Every key row in Settings links to the provider's own key page and states what the free tier gives. Current limits:
| Provider | Free tier | Key page |
|---|---|---|
| Ollama | Unlimited — it's your machine | ollama.com |
| OpenRouter | 20 req/min · 50 req/day (1,000/day after a one-time $10 top-up) | openrouter.ai/keys |
| Groq | 30 req/min · 1,000 req/day | console.groq.com/keys |
| Google Gemini | Free via AI Studio. Prompts may be used for training outside the EU/UK/EEA | aistudio.google.com/apikey |
| OpenAI | Paid | platform.openai.com/api-keys |
| Anthropic | Paid | console.anthropic.com |
| Kimi (Moonshot) | Paid | platform.kimi.ai |
Provider limits change. The in-app notes are accurate as of August 2026 — check the provider's own page for the current figure.
A note on stealth / preview models. OpenRouter periodically hosts free anonymous preview models. They are genuinely capable, but the provider is undisclosed and may retain your prompts and completions. If that matters for what you're asking, use a local Ollama model or turn on Offline only.
The app is a standard Next.js 15 App Router + React 19 + TypeScript project. No component framework to fight — plain CSS with design tokens.
src/app/ Pages (App Router) and API routes
layout.tsx Fonts, ThemeProvider, AppChrome
page.tsx Home — the Desk
src/components/ React components
jharvis/ The HUD: Desk, Debate, Hall, Apps, Shell
office/ Liquid office shell and Datapad
src/styles/ The CSS bundles, imported in order by app/globals.css
src/app/globals.css imports them in a fixed, cascade-sensitive order:
| File | Owns |
|---|---|
tokens.css |
Design tokens. --ink, --paper, --surface, --accent, --input-bg, plus the [data-appearance="dark"] overrides. |
base.css |
Element defaults. |
layout.css |
Page shell and grid. |
components.css |
Buttons, cards, fields, chips. |
jharvis.css |
The Jharvis HUD (dark ground). |
themes.css |
Seasonal themes. |
setup.css |
Quick Setup and hiring. |
office.css |
Liquid office. |
developer.css |
Developer mode. |
hud-skin.css |
Loaded last — re-skins the HUD, including the light-appearance remap. |
The one rule that will save you hours: style with tokens, never literal colours. A hard-coded
background: #0d0c0cin one bundle survives every theme and appearance change downstream — that is exactly how the HUD once ended up rendering dark text on a black input in light mode. If you must pin a literal surface, add the matching light counterpart inhud-skin.cssin the same commit.
Three independent switches, all on <html>:
| Attribute | Values | Set by |
|---|---|---|
data-ui |
jharvis · characters |
UI mode |
data-appearance |
light · dark |
Appearance |
data-theme |
everyday · christmas · diwali · halloween · newyear · holi · summer · monsoon |
Theme (auto-seasonal by default) |
They multiply: 2 × 2 × 8 = 32 combinations. A change is only done when it holds in both appearances of the mode you touched. Check the light one — it's the default, and it's the one that breaks.
// src/app/insights/page.tsx
export default function InsightsPage() {
return (
<div className="ab-main">
<h1>Insights</h1>
</div>
);
}Add the link in src/components/Nav.tsx. Server component by default; add
"use client" only when you need state or effects.
// src/app/api/insights/route.ts
import { NextResponse } from "next/server";
export const runtime = "nodejs";
export async function GET() {
return NextResponse.json({ ok: true });
}Never return an API key from a route. Follow the pattern in src/lib/settings.ts:
publicSettings() returns configured: boolean plus a masked hint, never the value.
Anything that differs between server and browser — clocks, Math.random(), locale or
timezone formatting — must not be in the first render. Start empty and fill in an
effect:
const [clock, setClock] = useState(""); // same on server and client
useEffect(() => {
const tick = () => setClock(new Date().toLocaleTimeString());
tick();
const t = setInterval(tick, 1000);
return () => clearInterval(t);
}, []);Seven Google fonts load via next/font/google in layout.tsx and expose CSS
variables (--font-display, --font-hud, --font-mono-hud, …). They are fetched at
build time, so a fully offline build fails there. To build offline, switch to
next/font/local with the font files in public/.
npm run lint
npm run build
node --test --experimental-strip-types src/lib/*.test.ts # 94 unit tests
npm run check:secretsThen click every page: / /office /run /tasks /prompts /developer
/settings /wiki /changelog — with the browser console open. A clean console is
part of the definition of done.
Cheapest useful council — three local Ollama models, 1 round, judge = Synthesis, consensus = Any agreement. Free, ~60s, and you still see divergence.
Decide something that matters — 4 agents mixing local and cloud, 3 rounds, consensus = Strong, judge = You pick. Slow and worth it.
Fact-check a draft — paste it as the prompt, mode = Diff, and read the grounding verdicts. Contradicted claims first.
Find out which model suits you — run twenty real questions with judge = You pick, then open Hall of Fame. Ignore your assumptions; read the table.
Daily brief — a daily task at 08:00 in your timezone, watch mode on. You get the answer, and the diff from yesterday.
Architecture and full API reference: WIKI.md.




