Every line is a real event type. None of it needed you awake.
II. The premise
Three things you keep.
I
The model
Local with Ollama, cloud via your own OpenRouter key, or both — mixed freely. A fast model scouts and plans. A capable one executes. A lighter one handles background work. Workers pick whichever model fits their task. The loop doesn't care which brain is inside: swap freely, your keys, your routing, your call.
II
The memory
Markdown files in data/memories/. Plain text, full-text indexed, tagged with source and type — yours forever. Stays put when you switch models. Stays put when a provider changes its terms. Not locked to any subscription, not sludge accumulating in a chat thread. Read, edit, or delete with any text editor — and the agent can correct or retire its own entries as it learns better.
III
The machine
No accounts. No cloud subscription. No usage telemetry. A Python server that binds to localhost until you say otherwise — then network mode with HTTPS and bearer-token auth. The harness is yours to inspect, fork, and rebuild.
III. The anatomy
What you're getting.
Pernix is one Python codebase that fits in your head. A FastAPI server, a state machine, a streaming agent loop, a memory store, a workspace.
On top of it: a built-in PWA, a REST API, and a Swagger UI you can poke from any browser.
Run it on a dedicated VM, container, or spare Linux box. Open http://localhost:8090. Talk to it. Watch it think. Read the code that made it think that way.
storageSQLite for sessions · Markdown for memory · filesystem workspace
licenseMIT
localhost:8090
A real 3.1 session, one take: asked to compare two cities' forecasts and save the result, the agent fetched both from Open-Meteo, wrote the file, and reported what it found — every tool call still open for inspection. Local Qwen3.8-27B.
The same session on a 390‑point phone: the sidebar becomes a drawer, the status bar collapses to a strip, and the tool rows and the answer stack full-width above the composer.
IV. The toolbelt
A toolbelt. Tools the agent reaches for.
Memory, visible
markdown on your disk
pernix.decisions.md6d
user.profile.md2h
pernix.lessons.md1d
pernix.tools.md4d
14 entries→9· 5 merged overnight
Facts, decisions and lessons as plain Markdown in data/memories/ — full-text indexed, tagged with source and type, editable in any text editor. Recall hands the agent each entry with its file and its age, so an old belief is visibly old. The idle sweeps merge duplicates and retire what went stale.
Inside a turn the agent fans work out to sub-agents, each in its own session on whichever model fits — a vision specialist beside a code model beside a researcher. Flat by design: workers don't spawn workers. The parent parks in awaiting_workers and resumes itself when they land.
spawn_worker · await_workers · message_worker
Autonomy
a finish line, not a step graph
goalbudgeted
gatepytest -qexit 0
beatround boundary
A goal is intent that outlives a turn — budgeted in tokens, time and continuations, auto-resumed when a turn hits its ceiling. A gate is a shell check the model cannot talk its way past: it passes on the exit code or not at all. Heartbeats steer running work without interrupting it.
goal_create · add_gate · set_heartbeat · evaluate
Cron
a standing order
0 7 * * 1-5
M
T
W
T
F
S
S
each run opens its own session — Cron: morning-brief
Put a standing prompt on a schedule and the brief is built before you wake, the digest writes itself, the watchdog never sleeps. When a run needs you, it pings your phone through the PWA or a webhook.
schedule_job · test_job · list_scheduled_jobs
MCP
other people's tools
mcp_<server>_<tool>
External Model Context Protocol servers — on the box or across the network — register as ordinary Pernix tools. The same scout curation, the same dangerous-tool gate, the same per-tool health metrics as the built-ins.
One Python process on a box you own. The model can be local with Ollama, the memory is files on your disk, the workspace is a directory you can cd into. No accounts, no telemetry, no hosted control plane — it binds to localhost until you say otherwise. The two dashed lines are the only ones that leave, and only if you put a key in .env.
python run.py · localhost:8090 · .env
also in the box
search_web
browse_web
http_get
repl
job_start
view_image
spaces
skills
reflect & retry
self-written tools
a model per role
images · audio · pdf
PWA + web push
voice input
light theme
screen-reader floor
session search
tool safety gate
RLM
reliability ledger
dreams
canary suite
adaptive policy
archive & storage
In the app
session › transcript — fan out, await, merge
Explorer › Knowledge — six files, twenty entries
Explorer › Files — a file the agent wrote, rendered
V. The unfolding
How a turn thinks itself out.
Every message you send rolls through five phases. Each one runs on a model suited to its job — fast for planning, capable for acting, light for verifying.
01
Session
Your message lands on a persistent thread — text, images, audio, PDFs. Append-only. Resumable. Restart-proof.
queue
02
Scout
A small fast model in a fresh context plans the approach — recalls what you've told it before, picks tools, loads only the relevant skills.
fast model
03
Agent Loop
The main model executes. Streams tokens, calls tools, reads results, calls more tools — until done. If the cloud rate-limits, it falls back to your local model mid-loop.
main model
04
Reflect
A quality gate verifies intent was met. Returns pass, retry, or escalate. Up to two retries before surfacing.
verify
05
Post‑hooks
Auto-titling, memory distillation, worker cleanup. The cleanup runs in the background after you've already seen the answer.
background
event loghover a phase to sample its events
Compaction trims old turns when context fills past 75%. The originals stay in the database — only the prompt view changes.
Snooze runs between turns. While the agent is idle, it works a ladder of maintenance to completion — dedupes memory, distills your profile, mines finished sessions for lessons, archives post-mortems, and (when enabled) dreams. The moment you send a new message, Snooze cancels mid-stride and resumes next idle — your work always wins.
Recovery assumes the worst. Kill the server mid-turn and restart it: interrupted sessions are swept to safety at boot, parents parked on workers are recovered, and clients replay any events they missed by sequence number. Nothing pretends it didn't happen.
VI. The state machine
Ten states. One session at a time.
Every session is in exactly one state, and every edge is a (state, reason) pair in an exhaustive table — anything not in the table is rejected. Transitions are logged, replayed to the UI in real time, and recovered after a crash. Hover or tap a state to see its real exits; left alone, the diagram walks an actual turn.
Edges drawn from the table in sessions/state_v2.py. Two housekeeping reasons are omitted for legibility. reaper-unstick returns six states to idle_ready — scouting, processing, pause_requested, paused, awaiting_user, awaiting_workers; cancel-timeout covers those six plus compacting, finalizing and cancelling. Both are explicit rows in the table, not a generic force.
localhost:8090 · State timeline
The same machine inside the app: five turns as phase bars, the fifth opened to its plan.
VII. The interface
An open API. Tinker freely.
The web UI is one client. There are many. Pernix is built on FastAPI, which means every endpoint the UI calls is also yours to call — from a script, a cron job, another service, your terminal.
Open localhost:8090/docs while the server runs and you get a live Swagger UI: every endpoint, every schema, every response model — try-it-able right from the browser. /redoc if you prefer ReDoc. The fastest way to learn the system is to poke it.
streamingServer-Sent Events on /api/sessions/{id}/events for tokens, tool calls, state transitions.
resumableSequence-numbered replay on reconnect — clients never miss an event.
scriptableThe same API the PWA uses. Build CLIs, integrations, custom UIs.
discoverableOpenAPI 3 schema at /openapi.json. Generate clients in any language.
GET/api/sessions/{id}/eventsSSE stream · tokens, tools, state
+ 157 more endpoints · openapi.json
VIII. The soul
Three markdown files, no black boxes.
Pernix's behavior beyond raw model output is shaped by plain text on disk.
SOUL.md defines who it is. RULES.md defines how it acts. SESSIONS.md injects deployment-specific context — the user's timezone and key facts, the domains this installation is allowed to act in, per-domain permission levels, and active long-running intents the agent is tracking. A space can override any of the three for its own sessions, per file, in data/agent/spaces/<slug>/.
It's the opposite of a black box. The personality, the operational guardrails, the project conventions — all editable, all auditable, all yours. Open them in any text editor. The agent picks up the change on its next turn.
Want it more terse? Edit a paragraph in SOUL.md.
Need a project guardrail? Add a line to RULES.md.
Want to constrain what domains the agent acts in? Set permission levels in SESSIONS.md.
Switching context entirely? Swap the whole file out.
# Identity
You are Pernix — a capable, focused AI assistant.
You help with complex tasks, think carefully before acting,
and communicate clearly.
## Core Traits
- Pragmatic: Prefer working solutions over perfect ones.
Ship, then iterate.
- Direct: Minimal preamble. Get to the point.
No filler phrases.
- Curious: Enjoy understanding systems deeply
before changing them.
- Careful: Confirm intent before irreversible
actions. Measure twice, cut once.
## Communication Style
- Concise by default — expand when the topic demands it.
- No sycophancy. No "great question!"
- When referencing code, include file paths and line
numbers so the user can navigate directly.
# Operational Rules## Capability Discovery
- When a task requires capabilities your current model
lacks, discover what is available rather than
giving up.
- Use list_available_models and discover_tools
to find models and tools that can fill the gap.
## Delegation
- Delegate specialized work to workers via spawn_worker.
Use the model parameter to run a worker on a model
suited to the task.
- Do not switch the global model for a one-off
specialized task — delegate instead.
## Persistence
- When an approach fails, diagnose why and try a different
approach before giving up.
- Exhaust your options before telling the user something
cannot be done.
# Session Context## User Context
- Timezone: America/Los_Angeles
- Key facts: goes by Cal, prefers bullet
summaries over prose
## Enabled Domains
- research & writing
- code review
- weekly digest
## Permission Levels# 1 Read · 2 Suggest · 3 Draft · 4 Confirm · 5 Auto
- research & writing: level 5
- code review: level 3
- weekly digest: level 5## Active Intents
- Track open PRs on the auth branch and
surface blockers each morning.
---name: meeting-notes-to-actions
description: Turn meeting notes or a transcript into
a clean action-item list with owners and dates.
when: user pastes meeting notes or asks to
"extract action items" or "what did we agree".
---# Meeting notes → action items
1. Read top to bottom. Don't skim.
2. Pull concrete commitments only, not summaries.
"Cal will review the deck by Friday" — action.
"We talked about the deck" — not an action.
3. Group by owner. Each entry: owner, action,
due date (mark unknown as ??).
4. Surface decisions separately under "Decisions".
5. End with open questions — anything unresolved.
Install Ollama, pull a recent model, clone the repo, run the server. Pernix is well-tested with the latest Qwen 3 series on Ollama, and with current frontier models on OpenRouter. Use whatever's current — agentic workloads benefit from newer models with stronger tool-calling and reasoning.
01
Install the prerequisites
Python 3.11+. Ollama if you want local models — pull a current Qwen 3 release. An OpenRouter key or any OpenAI-compatible server (vLLM, LM Studio) works too; Ollama is optional.
02
Clone & install
Standard Python: clone, venv, pip install -r requirements.txt. Optional: copy .env.example for OpenRouter or Tavily keys.
03
Run it
python run.py. Open localhost:8090. Pick a model in Settings. Say hello — and open /docs in another tab to watch the API.
04
Make it yours
Edit SOUL.md. Write a skill. Set a goal behind a gate. Schedule it on cron. Read the code in core/ — it fits in your head.
~/pernix
$ git clone https://github.com/calvincs/Pernix.git$ cd pernix$ python3 -m venv .venv && source .venv/bin/activate$ pip install -r requirements.txt$ playwright install chromium # browser for browse_web$ cp .env.example .env # add API keys if you have any$ ollama pull <your-current-qwen3># or any modern frontier model$ python run.py Pernix → http://127.0.0.1:8090# the UI lives at /, swagger at /docs, redoc at /redoc
X. The release
New in v3.1.0.
Everything since v3.0.0 under one tag — one week of field campaigns on the reference box, one commit per finding. The full story is in the changelog; upgrade notes in the upgrade guide.
Spaces. Named, coloured groups of long-lived sessions that share directives, memory, a workspace home and one REPL kernel.
An MCP client. External Model Context Protocol servers, local or remote, register as first-class Pernix tools — same scout curation, same dangerous-tool gate.
A front end for someone who didn’t write it. A light theme, accessibility as a floor, a real phone and tablet tier, Explorer and Settings regrouped by what you came to do.
Archive, not delete. A chat idle for thirty days leaves the sidebar and keeps every message, still searchable. Migrations v30–v35 run on first boot.
XI. Honest about what this is
A tool for integrations and recurring work.
Pernix is under active development. Knowing what it's for, and what it isn't, saves you the wrong expectation.
It is for…
Vertical work loops. Build a loop tied to one job and keep running it. Email triage, incident response, research digests, meeting-to-action pipelines, voice-memo-to-notes, weekly operations summaries. The product isn't an agent — it's the loop you build around a recurring job.
A headless agent substrate. A FastAPI server with full REST coverage. Wire it into other systems, drive it from scripts, build a custom client, run it as the brain behind a calendar agent or research bot. The web UI is one front-end among many possible.
Recurring work. Skills + cron + memory. The morning brief, the weekly digest, the watchdog, the recurring research crawl — running reliably whether you're watching or not, with a push notification to your phone when something needs you.
An agent in your pocket. The web UI installs as a PWA. Send it a task from your phone over the LAN, drop a photo, an audio file, or a PDF on it, and check back when the agent pings you. Network mode is HTTPS with token auth — your house, your rules.
Model-independent pipelines. Build once with a local model, swap to a frontier API when the task demands it, fall back automatically when it rate-limits. The loop keeps running — you decide the routing.
Tinkering & learning. One Python codebase, every layer auditable. A working harness to read, fork, and rebuild your own ideas on top of.
It isn't…
A coding harness. Use Claude Code, Opencode, Codex, Cursor, or any IDE-integrated agent for serious software work. Pernix is built for a different job.
A commercial product. An independent open-source project, built for daily use by its author and shipped as-is under MIT.
Production software. It executes shell commands and writes files on the host machine — that is what makes it useful, and what makes it dangerous on the wrong box. Run it in a dedicated VM, container, or spare machine, and never expose network mode to the public internet.
Finished. Each release brings new ideas; read the upgrade notes when you pull.
⚠
Alpha software. Here be dragons. Sharp edges. Occasional tears. Bugs included at no extra charge — fire extinguisher sold separately.
✦ ⊹ ✦
Fork it. Read it. Make it yours.
Pernix is a working tool, built for daily use and shared openly — use it, learn from it, build on it.