omega is a personal second brain: someone to delegate to and think alongside, with identity, continuity, initiative, opinions, and a growing understanding of how its user works.
omega/
├── src/ # Rust: the append-only episode log ("what exists")
├── python/omega/ # Python: the memory seam, the queue, the turn loop ("what comes back")
├── tests/ # The M0 suite — every case in agent/M0_SPEC.md
├── apps/mac-tray/ # Native macOS desk surface
├── agent/ # The milestone specs (M0_SPEC.md, M1_SPEC.md)
├── docs/ # Offline references: harness practices, systems we've read
├── AGENT.md # Private, gitignored design north star
└── CLAUDE.md # Project working rules
Rust and Python are one artifact, not two: the Rust crate is a PyO3 extension module
imported in-process as omega._log. That is why there is one CI job and not two — a green
Python run against a stale .so is worse than no run at all.
M0 — the spine — is built. An append-only log with a durable frame format, crash
recovery that will not brick itself on a half-landed append, checkpoints that survive
their own corruption, and a memory seam (python/omega/memory/) that is the only module
in the tree allowed to know frames and offsets exist. Its contract is
agent/M0_SPEC.md and every case in it has a test.
M1 — one turn, end to end — runs. The episode codec, the provider seam, the queue, the
turn loop, the outward projection, the socket channel and the resident process are all in.
The build order and what each step owes is in agent/M1_SPEC.md. The
Swift tray (step 8) is the part still outstanding.
.venv/bin/python -m omegaThat opens the store, says what the last stop left behind, and gives you a prompt. Every line you type becomes an episode in the log; the loop reads it back off the log and answers. Ctrl-D or Ctrl-C leaves — between turns, never inside one, so the next start has nothing to report.
Three answers are possible and they read differently on purpose: a reply, (omega chose not to speak), and a logged error. Staying silent is a success, not a failure to
answer, and a UI that showed them the same way would erase the distinction the whole
milestone is built to measure.
| flag | what it does |
|---|---|
--store PATH |
the store directory. Default ~/.omega — a dotdir in $HOME, so two checkouts are not two omegas |
--env PATH |
the .env holding the key. Default: beside the store, then the repo root; never the working directory |
--no-listen |
do not open the localhost socket; this terminal is the only client |
--port PORT |
where the tray connects |
--learned |
print what omega has been taught, then exit. Reads the log, starts nothing, needs no key |
One process holds the log, because the log is opened exclusively by design — so the terminal, the socket and the loop are threads inside that one process rather than several of them.
That exclusivity is why --learned is the audit you sit down to do rather than a question
you ask in passing: it cannot read the log while omega is up, and says so plainly instead
of failing with a lock error. It prints what omega currently believes, what it will wake up
and do, and — the part visible nowhere else — any schedule that was written down but whose
expression will not parse, so it has been silently never firing.
The build is pinned to a virtualenv at .venv (see .cargo/config.toml), so that comes
first — cargo test links libpython and maturin develop refuses to run outside a venv.
python3.11 -m venv .venv # 3.11 specifically: the pin names python3.11
.venv/bin/python -m pip install --upgrade pip
.venv/bin/python -m pip install "maturin>=1.7,<2.0" pytest
cargo test # the Rust half
.venv/bin/maturin develop --release # build the extension into the venv
.venv/bin/pytest # the Python half, against what you just built--release is not optional in practice: the 10,000-append and 10 MiB cases take long
enough in a debug build to look like a hang.
One secret:
cp .env.example .env # then put your key in it.env is gitignored and .env.example documents every variable omega reads. A value
already exported in your shell wins over the file. The test suite never reads the key and
never touches the network — if a test ever needs one, that is a bug in the test.
python -m omega looks for that file beside the store first (~/.omega/.env), then at the
repo root — and deliberately not in whatever directory you happened to run it from, because
"it works from the repo and nowhere else" is a rule nobody can see. Without a key it prints
one sentence naming the exact file to put it in, and exits.
OMEGA_IMAP_USER and OMEGA_IMAP_PASSWORD (a Gmail app password) let omega read your
inbox: never marking anything read, never sending. More mailboxes, such as a work Google
Workspace inbox, sit beside it, each with its own login:
OMEGA_INBOXES=work # short lowercase names, comma separated
OMEGA_IMAP_WORK_USER=user@example.com # required
OMEGA_IMAP_WORK_PASSWORD=... # required; that account's app password
OMEGA_IMAP_WORK_HOST=imap.gmail.com # optional; Gmail's by defaultThe first mailbox is then called personal. Every mail line says which mailbox it is from,
the inbox tools take an inbox argument (a name, or all), and each mailbox keeps its own
hourly cursor and its own sign-in: a refused work login pauses only what reads the work
mailbox. On Workspace an admin may have to allow IMAP and app passwords. With
OMEGA_INBOXES unset, omega behaves exactly as it did with one inbox.
omega can call tools on remote MCP servers (Streamable HTTP, https:// only). Nothing is
on by default and no server is built in: with OMEGA_MCP_SERVERS unset, omega offers
exactly the tools it always did. Each server you name is configured from the environment:
OMEGA_MCP_SERVERS=github,exa # short lowercase names, comma separated
OMEGA_MCP_GITHUB_URL=https://... # required, https only
OMEGA_MCP_GITHUB_TOOLS=a,b # the allowlist: only these are offered
OMEGA_MCP_GITHUB_TOKEN=... # optional; sent as "Authorization: Bearer ..."
OMEGA_MCP_GITHUB_HEADER=... # optional; send the token raw in this header instead
OMEGA_MCP_GITHUB_WRITE_TOOLS=c # tools that change things (see below)The model sees them as <server>__<tool>. Tools are read-only: a tool listed under
_WRITE_TOOLS, or one the server marks as not read-only, is refused until the approval
gate exists. Money never goes through omega. A tool whose name pays, checks out,
orders, books, buys, sells or trades is refused even when you allowlist it, and no setting
lifts that. What a server returns is capped and handed to the model as outside data,
never as instructions.
Some servers it has been shaped around (check each one's own docs for its tool names):
- GitHub remote MCP:
https://api.githubcopilot.com/mcp/with a fine-grained PAT as_TOKEN. Allowlist read tools only (listing and reading issues, PRs and files). - Firecrawl:
https://mcp.firecrawl.dev/v2/mcp. Keyless, and rate-limited. - Exa:
https://mcp.exa.ai/mcp. Keyless. - Google Maps Grounding Lite:
https://mapstools.googleapis.com/mcp, with the API key as_TOKENand_HEADER=X-Goog-Api-Key. Set a daily quota cap on the key in the Cloud console, so a looping model cannot run up a bill. - Expedia:
https://www.expedia.com/mcp. No login. Search only: booking is refused by the money rule, so omega hands you the link.
Tell omega where you are ("I'm at the office", "I'm home") and it notes it; name places with "save this as home". On your own messages it then sees one line, their location as of a time and how it knows, so a maps tool can answer "near me" and route questions. After three hours that line becomes your last known location and is never presented as current. Location never appears in briefs, watches or tells, is never learned as a fact about you, and omega sends it nowhere except as an argument the model passes a tool for the question you asked. History is kept in the log; ask "where was I yesterday at 6".
Your phone can keep it current. Set a token and omega listens for POST /location on
127.0.0.1 only; unset, nothing listens:
OMEGA_LOCATION_TOKEN=<long random string> # e.g. openssl rand -hex 32
OMEGA_LOCATION_PORT=7718 # optional; the defaultOn the host, publish that port to your tailnet only (MagicDNS and HTTPS certificates
enabled), never with tailscale funnel:
sudo tailscale serve --bg --https=7718 http://127.0.0.1:7718Then in the Shortcuts app on the phone (with Tailscale connected), make a shortcut:
- Get Current Location.
- Get Contents of URL:
https://<vm>.<tailnet>.ts.net:7718/location, Method POST, HeaderAuthorization=Bearer <token>, Request Body JSON withlat(Number: the location's Latitude) andlon(Number: its Longitude). Optional fields:accuracy(metres),label,city. Acityalone is accepted as a rough location. - Optionally, an Automation to run it (when you arrive or leave somewhere, or at set times).
A wrong token is a 401 and a bad body a 400; nothing is written for either, and requests are rate-limited. The listener logs nothing, the token included.
apps/mac-tray is a channel, not the agent. It may render conversation, collect context,
and display durable task state, but it must not own memory, personality, initiative
judgement, or agent orchestration. Those belong to the Python/Rust core.
The same rule cuts the other way inside the core: nothing outside python/omega/memory/
touches the store, and the seam speaks episodes — never frames, offsets or CRCs. That one
is enforced by a test, not by discipline.
omega is licensed under the GNU Affero General Public License v3.0 or later. See LICENSE.