omega-coding-agent 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omega_coding_agent-0.1.0/.gitignore +48 -0
- omega_coding_agent-0.1.0/LICENSE +21 -0
- omega_coding_agent-0.1.0/PKG-INFO +447 -0
- omega_coding_agent-0.1.0/PRODUCT-BACKLOG.md +327 -0
- omega_coding_agent-0.1.0/READING-ORDER.md +172 -0
- omega_coding_agent-0.1.0/README.md +432 -0
- omega_coding_agent-0.1.0/TIER-1.md +130 -0
- omega_coding_agent-0.1.0/TIER-2.md +302 -0
- omega_coding_agent-0.1.0/TIER-3.md +730 -0
- omega_coding_agent-0.1.0/pyproject.toml +68 -0
- omega_coding_agent-0.1.0/src/omega_agent/__init__.py +19 -0
- omega_coding_agent-0.1.0/src/omega_agent/agent_events.py +166 -0
- omega_coding_agent-0.1.0/src/omega_agent/cancellation.py +52 -0
- omega_coding_agent-0.1.0/src/omega_agent/events.py +157 -0
- omega_coding_agent-0.1.0/src/omega_agent/harness.py +497 -0
- omega_coding_agent-0.1.0/src/omega_agent/hooks.py +112 -0
- omega_coding_agent-0.1.0/src/omega_agent/loop.py +190 -0
- omega_coding_agent-0.1.0/src/omega_agent/provider.py +56 -0
- omega_coding_agent-0.1.0/src/omega_agent/session/__init__.py +34 -0
- omega_coding_agent-0.1.0/src/omega_agent/session/entries.py +48 -0
- omega_coding_agent-0.1.0/src/omega_agent/session/jsonl.py +99 -0
- omega_coding_agent-0.1.0/src/omega_agent/session/store.py +310 -0
- omega_coding_agent-0.1.0/src/omega_agent/session/tree.py +101 -0
- omega_coding_agent-0.1.0/src/omega_agent/tool_runner.py +116 -0
- omega_coding_agent-0.1.0/src/omega_agent/tools.py +97 -0
- omega_coding_agent-0.1.0/src/omega_agent/types.py +222 -0
- omega_coding_agent-0.1.0/src/omega_ai/__init__.py +14 -0
- omega_coding_agent-0.1.0/src/omega_ai/anthropic.py +709 -0
- omega_coding_agent-0.1.0/src/omega_ai/fake.py +184 -0
- omega_coding_agent-0.1.0/src/omega_ai/openai.py +580 -0
- omega_coding_agent-0.1.0/src/omega_ai/openai_codex.py +720 -0
- omega_coding_agent-0.1.0/src/omega_ai/provider.py +22 -0
- omega_coding_agent-0.1.0/src/omega_ai/retry.py +146 -0
- omega_coding_agent-0.1.0/src/omega_coding/__init__.py +18 -0
- omega_coding_agent-0.1.0/src/omega_coding/approval.py +374 -0
- omega_coding_agent-0.1.0/src/omega_coding/auth.py +384 -0
- omega_coding_agent-0.1.0/src/omega_coding/builtin_tools.py +812 -0
- omega_coding_agent-0.1.0/src/omega_coding/cli.py +1001 -0
- omega_coding_agent-0.1.0/src/omega_coding/clipboard.py +224 -0
- omega_coding_agent-0.1.0/src/omega_coding/commands.py +904 -0
- omega_coding_agent-0.1.0/src/omega_coding/compact.py +377 -0
- omega_coding_agent-0.1.0/src/omega_coding/context.py +187 -0
- omega_coding_agent-0.1.0/src/omega_coding/cost.py +123 -0
- omega_coding_agent-0.1.0/src/omega_coding/env.py +86 -0
- omega_coding_agent-0.1.0/src/omega_coding/evals.py +137 -0
- omega_coding_agent-0.1.0/src/omega_coding/eventlog.py +160 -0
- omega_coding_agent-0.1.0/src/omega_coding/file_lock.py +57 -0
- omega_coding_agent-0.1.0/src/omega_coding/headless.py +125 -0
- omega_coding_agent-0.1.0/src/omega_coding/history.py +42 -0
- omega_coding_agent-0.1.0/src/omega_coding/models.py +560 -0
- omega_coding_agent-0.1.0/src/omega_coding/oauth.py +519 -0
- omega_coding_agent-0.1.0/src/omega_coding/paths.py +159 -0
- omega_coding_agent-0.1.0/src/omega_coding/redact.py +194 -0
- omega_coding_agent-0.1.0/src/omega_coding/status.py +316 -0
- omega_coding_agent-0.1.0/src/omega_coding/subagent.py +185 -0
- omega_coding_agent-0.1.0/src/omega_coding/system_prompt.py +125 -0
- omega_coding_agent-0.1.0/src/omega_coding/truncate.py +175 -0
- omega_coding_agent-0.1.0/src/omega_coding/tui/__init__.py +13 -0
- omega_coding_agent-0.1.0/src/omega_coding/tui/adapter.py +130 -0
- omega_coding_agent-0.1.0/src/omega_coding/tui/app.py +1111 -0
- omega_coding_agent-0.1.0/src/omega_coding/tui/approval.py +146 -0
- omega_coding_agent-0.1.0/src/omega_coding/tui/banner.py +272 -0
- omega_coding_agent-0.1.0/src/omega_coding/tui/config.py +81 -0
- omega_coding_agent-0.1.0/src/omega_coding/tui/login.py +161 -0
- omega_coding_agent-0.1.0/src/omega_coding/tui/state.py +299 -0
- omega_coding_agent-0.1.0/src/omega_coding/tui/themes/__init__.py +267 -0
- omega_coding_agent-0.1.0/src/omega_coding/tui/themes/high-contrast.json +33 -0
- omega_coding_agent-0.1.0/src/omega_coding/tui/themes/oxblood-dark.json +33 -0
- omega_coding_agent-0.1.0/src/omega_coding/tui/themes/oxblood-light.json +33 -0
- omega_coding_agent-0.1.0/src/omega_coding/tui/themes/slate.json +33 -0
- omega_coding_agent-0.1.0/src/omega_coding/tui/widgets.py +766 -0
- omega_coding_agent-0.1.0/src/omega_coding/version.py +42 -0
- omega_coding_agent-0.1.0/tests/conftest.py +119 -0
- omega_coding_agent-0.1.0/tests/stub_anthropic.py +159 -0
- omega_coding_agent-0.1.0/tests/stub_openai.py +161 -0
- omega_coding_agent-0.1.0/tests/test_agent_events.py +49 -0
- omega_coding_agent-0.1.0/tests/test_anthropic_translation.py +97 -0
- omega_coding_agent-0.1.0/tests/test_approval.py +580 -0
- omega_coding_agent-0.1.0/tests/test_auth.py +743 -0
- omega_coding_agent-0.1.0/tests/test_caching.py +218 -0
- omega_coding_agent-0.1.0/tests/test_cancellation.py +263 -0
- omega_coding_agent-0.1.0/tests/test_clipboard.py +133 -0
- omega_coding_agent-0.1.0/tests/test_codex.py +503 -0
- omega_coding_agent-0.1.0/tests/test_commands.py +514 -0
- omega_coding_agent-0.1.0/tests/test_compact.py +349 -0
- omega_coding_agent-0.1.0/tests/test_edit.py +222 -0
- omega_coding_agent-0.1.0/tests/test_env.py +157 -0
- omega_coding_agent-0.1.0/tests/test_eventlog.py +192 -0
- omega_coding_agent-0.1.0/tests/test_events.py +61 -0
- omega_coding_agent-0.1.0/tests/test_fake_provider.py +81 -0
- omega_coding_agent-0.1.0/tests/test_file_lock.py +73 -0
- omega_coding_agent-0.1.0/tests/test_harness.py +195 -0
- omega_coding_agent-0.1.0/tests/test_headless.py +206 -0
- omega_coding_agent-0.1.0/tests/test_images.py +178 -0
- omega_coding_agent-0.1.0/tests/test_instruments.py +336 -0
- omega_coding_agent-0.1.0/tests/test_layers.py +144 -0
- omega_coding_agent-0.1.0/tests/test_loop.py +236 -0
- omega_coding_agent-0.1.0/tests/test_models.py +1098 -0
- omega_coding_agent-0.1.0/tests/test_modes.py +164 -0
- omega_coding_agent-0.1.0/tests/test_oauth.py +646 -0
- omega_coding_agent-0.1.0/tests/test_openai_translation.py +237 -0
- omega_coding_agent-0.1.0/tests/test_orphan_repair.py +175 -0
- omega_coding_agent-0.1.0/tests/test_paths.py +207 -0
- omega_coding_agent-0.1.0/tests/test_provider_contract.py +371 -0
- omega_coding_agent-0.1.0/tests/test_record_hook.py +281 -0
- omega_coding_agent-0.1.0/tests/test_redact.py +236 -0
- omega_coding_agent-0.1.0/tests/test_retry.py +357 -0
- omega_coding_agent-0.1.0/tests/test_search_tools.py +228 -0
- omega_coding_agent-0.1.0/tests/test_session.py +471 -0
- omega_coding_agent-0.1.0/tests/test_status.py +325 -0
- omega_coding_agent-0.1.0/tests/test_subagent.py +197 -0
- omega_coding_agent-0.1.0/tests/test_system_prompt.py +134 -0
- omega_coding_agent-0.1.0/tests/test_tools.py +322 -0
- omega_coding_agent-0.1.0/tests/test_tree.py +454 -0
- omega_coding_agent-0.1.0/tests/test_truncate.py +119 -0
- omega_coding_agent-0.1.0/tests/test_tui.py +924 -0
- omega_coding_agent-0.1.0/tests/test_tui_approval.py +106 -0
- omega_coding_agent-0.1.0/tests/test_tui_widgets.py +1456 -0
- omega_coding_agent-0.1.0/tests/test_version.py +84 -0
- omega_coding_agent-0.1.0/uv.lock +721 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
|
|
12
|
+
# Local secrets. .env holds a live ANTHROPIC_API_KEY; .env.sample is the
|
|
13
|
+
# committed placeholder template.
|
|
14
|
+
.env
|
|
15
|
+
!.env.sample
|
|
16
|
+
|
|
17
|
+
# Study material: cloned third-party reference repos (Pi, Tau) and pulled
|
|
18
|
+
# video transcripts. Reference input, never committed to this repo.
|
|
19
|
+
research/
|
|
20
|
+
|
|
21
|
+
# Generated PDF/HTML handbook output
|
|
22
|
+
dev-notes/dist/
|
|
23
|
+
|
|
24
|
+
# Exported chat transcripts. Personal working records, not project docs — and
|
|
25
|
+
# they can quote file contents and command output verbatim, so they stay local.
|
|
26
|
+
dev-notes/session-logs/
|
|
27
|
+
|
|
28
|
+
# Git worktrees created for isolated work. Local scratch, never committed.
|
|
29
|
+
.claude/worktrees/
|
|
30
|
+
|
|
31
|
+
# Root-level exported transcript. Same reasoning as docs/session-logs/ above:
|
|
32
|
+
# a personal working record that quotes file contents and command output.
|
|
33
|
+
SESSION-TRANSCRIPT.md
|
|
34
|
+
|
|
35
|
+
# Saved omega sessions. Local working state, and they quote file contents and
|
|
36
|
+
# command output verbatim - same reasoning as dev-notes/session-logs/.
|
|
37
|
+
.omega/
|
|
38
|
+
|
|
39
|
+
# Commits recorded by the pre-push hook, waiting to be written to the Notion
|
|
40
|
+
# sync log. Local bookkeeping, cleared once synced.
|
|
41
|
+
.omega-sync-pending.jsonl
|
|
42
|
+
|
|
43
|
+
# Claude Code worktrees — local scratch, never committed
|
|
44
|
+
.claude/worktrees/
|
|
45
|
+
|
|
46
|
+
# Playwright MCP screenshots and page snapshots
|
|
47
|
+
.playwright-mcp/
|
|
48
|
+
docs-arrow.png
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rushil Jariwala
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: omega-coding-agent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A terminal coding agent, built from scratch in layers.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.14
|
|
8
|
+
Requires-Dist: anthropic>=0.120.2
|
|
9
|
+
Requires-Dist: httpx>=0.28
|
|
10
|
+
Requires-Dist: openai>=3.3.1
|
|
11
|
+
Requires-Dist: pydantic>=2.11
|
|
12
|
+
Requires-Dist: python-dotenv>=1.2.2
|
|
13
|
+
Requires-Dist: textual>=8.2.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# omega
|
|
17
|
+
|
|
18
|
+
A terminal coding agent, built from scratch in layers.
|
|
19
|
+
|
|
20
|
+
Named after a physics letter, following [Pi](https://github.com/earendil-works/pi) and
|
|
21
|
+
[Tau](https://github.com/huggingface/tau) — the two MIT-licensed agents this is studied from.
|
|
22
|
+
Written independently, not forked.
|
|
23
|
+
|
|
24
|
+
**Currently at Tier 3 — "it survives a long task, and has a face."** 15,179 lines of source,
|
|
25
|
+
770 tests, all offline.
|
|
26
|
+
|
|
27
|
+
* **[`READING-ORDER.md`](READING-ORDER.md) — start here.** All 53 files in the order to read them,
|
|
28
|
+
one line each, plus the questions to hold while reading.
|
|
29
|
+
* [`TIER-1.md`](TIER-1.md) — what the first tier does, and what it deliberately left out
|
|
30
|
+
* [`TIER-2.md`](TIER-2.md) — what this tier adds, and where each remaining gap plugs in at Tier 3
|
|
31
|
+
* [`TIER-3.md`](TIER-3.md) — every row filled: compaction, caching, a Textual TUI, branching,
|
|
32
|
+
search tools, structured logging, subagents, images
|
|
33
|
+
* [`PRODUCT-BACKLOG.md`](PRODUCT-BACKLOG.md) — packaging, OAuth, extensions: what a product needs and a
|
|
34
|
+
study project does not
|
|
35
|
+
|
|
36
|
+
Tier 1 proved the loop terminates. Tier 2 makes it safe to point at a real repository: it can be
|
|
37
|
+
interrupted without corrupting the conversation, it remembers across restarts, it asks before it
|
|
38
|
+
destroys anything, and its provider abstraction is no longer a claim but a measured result.
|
|
39
|
+
|
|
40
|
+
## Install it
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
curl -fsSL https://omega-coding-agent.vercel.app/install.sh | sh
|
|
44
|
+
omega
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The script installs `uv` if it is missing, puts omega in an isolated environment,
|
|
48
|
+
verifies the command it just created, and tells you if the bin directory is not on your `PATH`. It
|
|
49
|
+
**never edits a shell rc file** — uv owns `PATH`, and a tool that appends to `~/.zshrc` is one you
|
|
50
|
+
cannot cleanly uninstall.
|
|
51
|
+
|
|
52
|
+
The distribution is **`omega-coding-agent`** (`omega` is taken on PyPI by an unrelated games library); the
|
|
53
|
+
command stays `omega`. Until that name is published the installer pulls from git, which is one line
|
|
54
|
+
to change and means it can be tested today rather than after a release.
|
|
55
|
+
|
|
56
|
+
`omega --version` reports which one you have, and needs no credentials — that is what the installer
|
|
57
|
+
checks with.
|
|
58
|
+
|
|
59
|
+
## Run it from a clone
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
uv sync
|
|
63
|
+
|
|
64
|
+
uv run omega --fake # scripted responses — no key, no network, no credits
|
|
65
|
+
uv run omega # Anthropic; needs ANTHROPIC_API_KEY (see below)
|
|
66
|
+
uv run omega --provider openai # OpenAI Chat Completions
|
|
67
|
+
uv run omega -c # continue the most recent session here
|
|
68
|
+
uv run omega --sessions # list saved sessions and exit
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**`omega` opens a terminal UI.** It starts on a wordmark and six facts — model, path, git branch,
|
|
72
|
+
approval mode, session and version — which scroll away as soon as you ask something.
|
|
73
|
+
|
|
74
|
+
**Leaving:** `ctrl+c` twice, or `ctrl+d` once. A single `ctrl+c` stops the turn in progress; on an
|
|
75
|
+
idle prompt it arms and says so, because `ctrl+c` is the reflex for "stop that" and a closed session
|
|
76
|
+
has no undo. `ctrl+q` is deliberately not an exit — Textual provides it by default, and a third way
|
|
77
|
+
out with no confirmation would undo the point of the other two.
|
|
78
|
+
|
|
79
|
+
**Copying:** selecting text copies it, and the status line says how much; `/config auto-copy off`
|
|
80
|
+
stops that. With something selected, `ctrl+c` copies it instead of stopping anything. `ctrl+v`
|
|
81
|
+
pastes from the system clipboard — `pbcopy` first, OSC 52 over SSH — and a long paste folds to
|
|
82
|
+
`[paste #1 +N lines]` in the prompt, while the model still gets every line.
|
|
83
|
+
|
|
84
|
+
`/` opens the command list, `↑`/`↓` walk back through what you typed, `ctrl+o` expands every tool
|
|
85
|
+
call at once (and shows the startup facts again when there are none yet), and a finished tool call
|
|
86
|
+
collapses to one line you can click open. While a turn runs, the line above the prompt spins and
|
|
87
|
+
says what is actually happening — `reading loop.py`, `running npm test`, `thinking` — not just
|
|
88
|
+
"working".
|
|
89
|
+
|
|
90
|
+
**A message sent mid-turn can be taken back.** Typing while the model works queues a correction for
|
|
91
|
+
the next request; `↑` on an empty prompt pulls the last one back into the box to edit. The queue was
|
|
92
|
+
write-only before, so a typo was final short of cancelling the whole turn.
|
|
93
|
+
|
|
94
|
+
**The splash shrinks.** Six facts in a bordered box are worth their rows before you have asked
|
|
95
|
+
anything and worth nothing afterwards, so the first question replaces them with a three-line header —
|
|
96
|
+
mascot, version, model, directory. `ctrl+o` brings the rest back.
|
|
97
|
+
|
|
98
|
+
Pasting more than one line collapses to `[pasted #1 +57 lines]` and expands again on send, so a
|
|
99
|
+
traceback keeps its newlines instead of losing everything after the first.
|
|
100
|
+
|
|
101
|
+
**Signing in.** `/login` stores a credential in `~/.omega/auth.json` (mode `0600`, in a `0700`
|
|
102
|
+
directory); `/logout` removes it. The order is **`auth.json` → exported variable → not signed in**,
|
|
103
|
+
following Pi: *"a stored credential owns the provider; ambient/env is consulted only when nothing is
|
|
104
|
+
stored"* (`resolve.ts:44-46`).
|
|
105
|
+
|
|
106
|
+
That order was the other way round until recently, and the reversal fixes a real trap: with a key in
|
|
107
|
+
`.env`, you could sign in with your Claude subscription, be told it worked, and have the token never
|
|
108
|
+
used. Nothing was wrong and nothing said so. "Owns" is the strong form — a stored entry that exists
|
|
109
|
+
but is unreadable resolves to *nothing*, never to the environment, because falling through silently
|
|
110
|
+
is how you debug the wrong key for an afternoon.
|
|
111
|
+
|
|
112
|
+
An install that has never run `/login` is unaffected: `.env` files and exported variables work
|
|
113
|
+
exactly as they always did. `/login` says when it has taken over from an exported variable, and
|
|
114
|
+
`/logout` says when it is handing control back.
|
|
115
|
+
|
|
116
|
+
With no credentials omega still starts: the startup facts say `not signed in`, and the first query
|
|
117
|
+
comes back telling you to run `/login` rather than the program refusing to open. `omega --sessions`
|
|
118
|
+
and `--resume` need no credentials at all. `omega -p` does, and exits non-zero without them.
|
|
119
|
+
|
|
120
|
+
**Signing in with an account.** `/login anthropic` offers two ways in: paste an API key, or open a
|
|
121
|
+
browser and sign in with a Claude Pro/Max subscription. The browser flow is authorization-code +
|
|
122
|
+
PKCE — a loopback server on `localhost:53692` catches the redirect, and the verifier never leaves
|
|
123
|
+
the process.
|
|
124
|
+
|
|
125
|
+
**It signs in as Claude Code, and you should know that before you use it.** Anthropic registers no
|
|
126
|
+
third-party OAuth apps, so the account option works by presenting Claude Code's client id — the
|
|
127
|
+
same one both references ship (`oauth_anthropic.py:32`, `anthropic.ts:29`). The cost is not only at
|
|
128
|
+
the login screen: Anthropic rejects the token unless the request also carries Claude Code's identity
|
|
129
|
+
headers *and* a system block reading `You are Claude Code, Anthropic's official CLI for Claude`, so
|
|
130
|
+
every OAuth turn opens by telling the model it is a different product. `src/omega_coding/oauth.py`
|
|
131
|
+
carries the full reasoning.
|
|
132
|
+
|
|
133
|
+
Holding a client id of your own? Put it in `~/.omega/oauth.json` and it replaces the built-in
|
|
134
|
+
entirely. The API-key path claims nothing and sends none of the above.
|
|
135
|
+
|
|
136
|
+
**OpenAI has no account option**, deliberately. Its subscription OAuth returns a token
|
|
137
|
+
`api.openai.com` rejects — it opens `chatgpt.com/backend-api/codex/responses`, a different wire
|
|
138
|
+
format needing its own adapter (1,054 lines in Tau's `openai_codex.py`). A sign-in that succeeds and
|
|
139
|
+
then cannot serve a request is worse than no sign-in.
|
|
140
|
+
|
|
141
|
+
`/theme` switches between `slate` (the default — greys, so a failed tool is the only saturated
|
|
142
|
+
thing on screen), `oxblood-dark`, `oxblood-light` and `high-contrast`.
|
|
143
|
+
|
|
144
|
+
Two other surfaces, both behind a flag:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
uv run omega -p "what does loop.py do?" # one shot: answer on stdout, exit — pipeable
|
|
148
|
+
echo "fix the failing test" | uv run omega -p
|
|
149
|
+
uv run omega --repl # the plain print/input prompt
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`-p` keeps stdout for the answer and puts tool activity on stderr, so `omega -p "..." > out.txt`
|
|
153
|
+
captures the answer and nothing else. A non-interactive stream falls back to the REPL on its own —
|
|
154
|
+
Textual cannot run on a pipe, so `omega < script.txt` keeps working.
|
|
155
|
+
|
|
156
|
+
`--fake` is not a stub. It drives the entire agent — loop, harness, tools, streaming, approvals —
|
|
157
|
+
through `FakeProvider`, so you can watch the whole thing work without spending anything.
|
|
158
|
+
|
|
159
|
+
Useful flags: `--yes` approves tool calls automatically (it does **not** disable the
|
|
160
|
+
refuse-outright list), `--confine` refuses any path outside the working directory instead of
|
|
161
|
+
asking, `--no-save` skips writing a session, `--resume ID` reopens a specific one,
|
|
162
|
+
`--context-window` and `--compact-threshold` move the point compaction fires, and
|
|
163
|
+
`--base-url` points the OpenAI adapter at a local server:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
uv run omega --provider openai --base-url http://localhost:11434/v1 # Ollama, free
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Drop an `OMEGA.md` in the working directory and its contents are appended to the system prompt,
|
|
170
|
+
so project conventions stop being something you retype.
|
|
171
|
+
|
|
172
|
+
### Installing it, and where the key goes
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
uv tool install --editable /path/to/omega # `omega` now works from any directory
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`--editable` so the installed command always runs the current source — otherwise you are running a
|
|
179
|
+
snapshot and wondering why your changes did nothing.
|
|
180
|
+
|
|
181
|
+
The API key is searched for **outward from wherever you run omega**, nearest first:
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
./.env this project
|
|
185
|
+
../.env …and its parents, up to your home directory
|
|
186
|
+
~/.config/omega/.env set it once, every project sees it
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
An exported variable beats every file, so `ANTHROPIC_API_KEY=… omega` is a one-off override. The
|
|
190
|
+
nearest file wins per variable, and further files fill in the rest — a project `.env` can override
|
|
191
|
+
just the key while inheriting everything else from your global one.
|
|
192
|
+
|
|
193
|
+
If the key is missing, omega prints the exact list of paths it searched rather than only saying it
|
|
194
|
+
is unset.
|
|
195
|
+
|
|
196
|
+
## In the conversation
|
|
197
|
+
|
|
198
|
+
Everything above is chosen before the conversation starts. Inside it, a leading `/` addresses the
|
|
199
|
+
program rather than the model, and a leading `!` addresses the shell:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
/help list these commands
|
|
203
|
+
/login [provider] sign in — a subscription in the browser, or an API key
|
|
204
|
+
/logout [provider] remove a stored credential
|
|
205
|
+
/sessions saved sessions for this project
|
|
206
|
+
/resume <id> switch to another session; the terminal UI shows its conversation
|
|
207
|
+
/clear start a fresh session; the old one is kept on disk
|
|
208
|
+
/rewind [n] go back before your last question; the old branch is kept
|
|
209
|
+
/compact [pct] shrink the conversation now
|
|
210
|
+
/model [name|refresh] switch model, keeping the conversation, or refresh the list
|
|
211
|
+
/cost tokens and spend so far
|
|
212
|
+
/context how full the context window is
|
|
213
|
+
/theme [name] change the colours (terminal UI only)
|
|
214
|
+
/config [setting] settings such as auto-copy (terminal UI only)
|
|
215
|
+
/exit leave omega
|
|
216
|
+
!<command> run a shell command — no model, no tokens
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`/context` shows the total, then the breakdown — and says so when the window is a guess:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
~1,695/1,000,000 tokens (0%)
|
|
223
|
+
tools 1,220 ← widest slice first, because that is what you would trim
|
|
224
|
+
messages 280
|
|
225
|
+
system 195
|
|
226
|
+
free 998,305
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
On a model omega has no entry for, the same command adds a line rather than presenting its
|
|
230
|
+
fallback as a fact:
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
The window is a fallback, not this model's real figure - omega has no entry for it.
|
|
234
|
+
Put the number in ~/.omega/models.json to fix it.
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Tau shows the same three buckets in `/status` (`commands.py:443`) and Pi shows no breakdown at all.
|
|
238
|
+
Claude Code's seven buckets include skills, custom agents and memory files, none of which omega
|
|
239
|
+
has — four permanent zeroes are noise, so omega prints the three it can actually measure.
|
|
240
|
+
|
|
241
|
+
`/model` with no name lists what the active provider offers and marks the current one; with a name
|
|
242
|
+
it switches and the conversation carries straight over — the transcript lives on the harness and the
|
|
243
|
+
model is read at request time, so nothing is rebuilt. The **context window moves with it**, which is
|
|
244
|
+
the half that would otherwise fail on the *next* request rather than on the switch. A model
|
|
245
|
+
belonging to another provider is refused rather than sent.
|
|
246
|
+
|
|
247
|
+
### A model shipped today and omega's list predates it
|
|
248
|
+
|
|
249
|
+
You are not stuck, and you never were: `/model <name>` takes any name and sends it as typed. But
|
|
250
|
+
being *allowed* to type it is only half. omega also has to know how big it is, because that number
|
|
251
|
+
is what compaction budgets against — and a million-token model assumed to be 200k compacts at a
|
|
252
|
+
fifth of its capacity and throws away context nobody needed to lose.
|
|
253
|
+
|
|
254
|
+
So tell it. `~/.omega/models.json`:
|
|
255
|
+
|
|
256
|
+
```json
|
|
257
|
+
{
|
|
258
|
+
"anthropic": [
|
|
259
|
+
{"name": "claude-opus-6", "window": 1000000, "note": "shipped this morning"}
|
|
260
|
+
]
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
A provider key omega has an adapter for (`anthropic`, `openai`, `openai-codex`), a list of models,
|
|
265
|
+
`name` and `window` required, `note` optional. `window` is in **tokens**.
|
|
266
|
+
|
|
267
|
+
**Where the built-in windows come from.** Every figure in `models.py` was read from
|
|
268
|
+
[models.dev](https://models.dev)'s JSON API (`curl https://models.dev/api.json`) and parsed out of
|
|
269
|
+
the raw response, not summarised — the audit table with the exact key for each model is in that
|
|
270
|
+
file's docstring. Two rows were wrong before that audit: `claude-sonnet-5` and `claude-opus-5` both
|
|
271
|
+
shipped at 200,000 and are 1,000,000, so compaction had been firing at a fifth of the real
|
|
272
|
+
capacity.
|
|
273
|
+
|
|
274
|
+
**And you should not have to type it in at all.** `/model refresh` asks models.dev directly:
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
> /model refresh
|
|
278
|
+
Asking models.dev…
|
|
279
|
+
anthropic 14 models
|
|
280
|
+
openai 43 models
|
|
281
|
+
openai-codex not on models.dev — built-ins stand
|
|
282
|
+
|
|
283
|
+
New since omega's built-in list: claude-fable-5, claude-haiku-4-5, claude-opus-4-6 (+43 more)
|
|
284
|
+
Cached in ~/.omega/models-cache.json.
|
|
285
|
+
Anything you wrote in ~/.omega/models.json still wins.
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
So the model list has **three layers**, lowest precedence first:
|
|
289
|
+
|
|
290
|
+
| layer | where | who writes it |
|
|
291
|
+
|---|---|---|
|
|
292
|
+
| built-ins | `models.py` | compiled in — works offline, always |
|
|
293
|
+
| refreshed | `~/.omega/models-cache.json` | `/model refresh` |
|
|
294
|
+
| yours | `~/.omega/models.json` | you, by hand — **wins** |
|
|
295
|
+
|
|
296
|
+
Your hand-written figures beat a refresh, because a later refresh silently overruling a correction
|
|
297
|
+
you typed would undo your work without saying so. A model models.dev has never heard of is still
|
|
298
|
+
yours to describe.
|
|
299
|
+
|
|
300
|
+
The fetch is a command, never automatic. omega does not reach a third-party host during a turn you
|
|
301
|
+
asked for something else, and it never blocks startup — a fresh clone with no network has the full
|
|
302
|
+
built-in list immediately, which is the thing Pi's build-time generator gives up
|
|
303
|
+
(`pi/.gitignore:11` means a fresh Pi clone knows no models at all).
|
|
304
|
+
|
|
305
|
+
Four things worth knowing:
|
|
306
|
+
|
|
307
|
+
- **Your entries go on top of the built-ins, not instead of them.** Adding one Claude keeps the
|
|
308
|
+
other five and puts yours first in the picker. Same name as a built-in means yours wins, once —
|
|
309
|
+
which is how you correct a window omega ships wrongly.
|
|
310
|
+
- **The file is read on every call**, so an edit lands without restarting omega.
|
|
311
|
+
- **It adds models, not providers.** A provider needs an adapter in code; a model needs a name and
|
|
312
|
+
a number. An unrecognised provider key is reported rather than honoured, because models in the
|
|
313
|
+
picker for something nothing can send a request to is worse than the mistake being pointed out.
|
|
314
|
+
- **A malformed file is reported, not fatal and not silent.** One bad entry is skipped and named in
|
|
315
|
+
`/model`'s output; the good entries still load, and the built-ins are never at risk.
|
|
316
|
+
|
|
317
|
+
If you skip the file entirely, `/model` says so plainly rather than printing its fallback as a
|
|
318
|
+
fact:
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
> /model claude-opus-7
|
|
322
|
+
Now using claude-opus-7. The conversation is unchanged.
|
|
323
|
+
Not in omega's list, so it is passed through as typed.
|
|
324
|
+
omega does not know this model's window and is assuming 200,000 tokens,
|
|
325
|
+
which is what compaction budgets against. Put the real figure in
|
|
326
|
+
/Users/you/.omega/models.json to fix it:
|
|
327
|
+
{"anthropic": [{"name": "claude-opus-7", "window": <tokens>}]}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
This is Tau's design (`catalog_loader.py:229` merges the user's list over the built-in one as a
|
|
331
|
+
union, overlay first) rather than Pi's, which generates its catalog from `models.dev` into a
|
|
332
|
+
`.gitignore`d directory (`pi/.gitignore:11`) — a better pipeline for 721 models, and for omega's
|
|
333
|
+
twelve a Node build and a network dependency that leave a fresh clone unable to name a single one.
|
|
334
|
+
|
|
335
|
+
`exit` and `quit` still work without a slash. An unknown `/foo` is an error naming the nearest
|
|
336
|
+
match rather than a prompt forwarded to a paid API, which is where both references differ.
|
|
337
|
+
|
|
338
|
+
**`!cmd` has no private path to the shell.** It runs through the same `run_shell` tool the model
|
|
339
|
+
uses, so it meets the same approval gate, the same refuse-outright list, the same timeout and the
|
|
340
|
+
same output budget. Its output is *not* added to the conversation — Pi and Tau both add it, and
|
|
341
|
+
the argument for diverging is in `src/omega_coding/commands.py`.
|
|
342
|
+
|
|
343
|
+
## Check it
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
uv run pytest -q # 770 tests, ~55s, fully offline
|
|
347
|
+
uv run mypy --strict src
|
|
348
|
+
uv run ruff check .
|
|
349
|
+
uv run python -m omega_coding.evals # smoke eval: does the assembled agent still work?
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
The test suite never touches the network. Every provider call is faked at the interface boundary —
|
|
353
|
+
which is why `omega_ai/fake.py` was written before the real adapter — and the two vendor SDKs are
|
|
354
|
+
faked one layer lower, in `tests/stub_anthropic.py` and `tests/stub_openai.py`, so the adapters'
|
|
355
|
+
own retry and auth behaviour is testable too.
|
|
356
|
+
|
|
357
|
+
The **smoke eval** is deliberately not a test. The tests check units; the eval checks the
|
|
358
|
+
assembled agent against a task. It catches the class of breakage where every unit passes and the
|
|
359
|
+
whole thing still does nothing.
|
|
360
|
+
|
|
361
|
+
## Layout
|
|
362
|
+
|
|
363
|
+
Three packages, following Tau. The rule that decides which one a file belongs to is a single
|
|
364
|
+
question: **what does this thing know about?**
|
|
365
|
+
|
|
366
|
+
```
|
|
367
|
+
omega/src/
|
|
368
|
+
├── omega_ai/ ── L1 · one vendor's wire format, and nothing else ──
|
|
369
|
+
│ ├── provider.py a 5-line re-export. The contract lives one layer down.
|
|
370
|
+
│ ├── fake.py scripted replay. Written before the real adapter.
|
|
371
|
+
│ ├── retry.py backoff — invisible above this layer
|
|
372
|
+
│ ├── anthropic.py one wire format
|
|
373
|
+
│ └── openai.py a different one. Also Groq, Together, Ollama, vLLM.
|
|
374
|
+
│
|
|
375
|
+
├── omega_agent/ ── L2 · messages, events, tools, turns ──
|
|
376
|
+
│ ├── types.py the neutral message model
|
|
377
|
+
│ ├── events.py the 12 stream events
|
|
378
|
+
│ ├── agent_events.py the 10 agent events
|
|
379
|
+
│ ├── provider.py THE CONTRACT. The consumer owns the interface.
|
|
380
|
+
│ ├── tools.py Tool and ToolResult
|
|
381
|
+
│ ├── hooks.py the six seams
|
|
382
|
+
│ ├── loop.py 190 lines, and it should not grow
|
|
383
|
+
│ ├── tool_runner.py one tool call → one tool result
|
|
384
|
+
│ ├── harness.py owns the transcript, the queues, cancellation
|
|
385
|
+
│ ├── cancellation.py a token you can actually set
|
|
386
|
+
│ └── session/ append-only JSONL, parent_id on every entry
|
|
387
|
+
│
|
|
388
|
+
└── omega_coding/ ── L3 + L4 · files, shells, policy, the screen ──
|
|
389
|
+
├── paths.py path resolution. ONE place, not per-tool.
|
|
390
|
+
├── file_lock.py one lock per resolved path
|
|
391
|
+
├── truncate.py 2,000 lines / 50 KB, tail-biased
|
|
392
|
+
├── builtin_tools.py read, write, edit, run
|
|
393
|
+
├── approval.py the gate — fills before_tool_call
|
|
394
|
+
├── redact.py keeps credentials out — fills after_tool_call
|
|
395
|
+
├── history.py what is kept vs what is sent
|
|
396
|
+
├── context.py how full the window is
|
|
397
|
+
├── cost.py tokens; dollars only if you supply a price
|
|
398
|
+
├── system_prompt.py the standing instructions, and OMEGA.md
|
|
399
|
+
├── commands.py /help, /sessions, /clear … and the ! shell escape
|
|
400
|
+
├── status.py the working line, with truthful labels
|
|
401
|
+
├── env.py finds .env by walking outward from where you are
|
|
402
|
+
├── headless.py prompt in, transcript out. No keyboard.
|
|
403
|
+
├── evals.py the smoke eval
|
|
404
|
+
└── cli.py the composition root. Reads last.
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
`omega_agent/session/` is the only subfolder any of them earned — which is also true of Tau, whose
|
|
408
|
+
entire agent core has exactly one, and it is this one. Folders follow subsystems.
|
|
409
|
+
|
|
410
|
+
**Where a file goes, when it is ambiguous:** `hooks.py` is core because the loop *declares* the
|
|
411
|
+
callbacks it will consult. Everything that *fills* one — `approval.py`, `redact.py`, `history.py` —
|
|
412
|
+
is application, because each is a decision, and the loop asks rather than decides.
|
|
413
|
+
|
|
414
|
+
### The check that the layering held
|
|
415
|
+
|
|
416
|
+
It used to be a `grep` in this README. It is now a test:
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
uv run pytest tests/test_layers.py -q
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
which asserts that `omega_agent` imports nothing above it, that `omega_ai` does not know the app
|
|
423
|
+
exists, that only a composition root names a concrete provider, and that exactly two files import a
|
|
424
|
+
vendor SDK. It reads imports with `ast` rather than text, because all three packages *mention* each
|
|
425
|
+
other in their docstrings while importing none of them.
|
|
426
|
+
|
|
427
|
+
The stronger version of the same check is in the git history. Adding the second provider — a
|
|
428
|
+
genuinely different wire format, with the opposite rule about how tool results are sent — required
|
|
429
|
+
changes to the adapter package, its tests, and a few lines of provider selection in `cli.py`.
|
|
430
|
+
Nothing else moved: not the loop, not the interface, not either event vocabulary. `git show ac0e370`
|
|
431
|
+
has the full accounting.
|
|
432
|
+
|
|
433
|
+
## Why it's shaped this way
|
|
434
|
+
|
|
435
|
+
Every design decision traces to a document in [`../docs/`](../docs/) — in particular
|
|
436
|
+
`03-architecture/04-boundaries-and-layout.md` for the layer rules,
|
|
437
|
+
`03-architecture/02-beginner.md` for the nine failures each layer exists to fix, and
|
|
438
|
+
`01-teardown/` for where each pattern came from in Pi and Tau.
|
|
439
|
+
|
|
440
|
+
The nine-failure scorecard is the quickest way to see which tier this is:
|
|
441
|
+
|
|
442
|
+
| Failure | Fixed in |
|
|
443
|
+
|---|---|
|
|
444
|
+
| one big output kills the session · nothing appears until it finishes · no turn limit | Tier 1 |
|
|
445
|
+
| Ctrl-C corrupts the conversation · it deletes something you wanted · one rate limit ends the run · two edits lose data · no persistence | **Tier 2** |
|
|
446
|
+
| switching providers means a rewrite | Tier 1 built the seam · **Tier 2 proved it** |
|
|
447
|
+
| context fills up and dies · it costs more than it should | Tier 3 |
|