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.
Files changed (120) hide show
  1. omega_coding_agent-0.1.0/.gitignore +48 -0
  2. omega_coding_agent-0.1.0/LICENSE +21 -0
  3. omega_coding_agent-0.1.0/PKG-INFO +447 -0
  4. omega_coding_agent-0.1.0/PRODUCT-BACKLOG.md +327 -0
  5. omega_coding_agent-0.1.0/READING-ORDER.md +172 -0
  6. omega_coding_agent-0.1.0/README.md +432 -0
  7. omega_coding_agent-0.1.0/TIER-1.md +130 -0
  8. omega_coding_agent-0.1.0/TIER-2.md +302 -0
  9. omega_coding_agent-0.1.0/TIER-3.md +730 -0
  10. omega_coding_agent-0.1.0/pyproject.toml +68 -0
  11. omega_coding_agent-0.1.0/src/omega_agent/__init__.py +19 -0
  12. omega_coding_agent-0.1.0/src/omega_agent/agent_events.py +166 -0
  13. omega_coding_agent-0.1.0/src/omega_agent/cancellation.py +52 -0
  14. omega_coding_agent-0.1.0/src/omega_agent/events.py +157 -0
  15. omega_coding_agent-0.1.0/src/omega_agent/harness.py +497 -0
  16. omega_coding_agent-0.1.0/src/omega_agent/hooks.py +112 -0
  17. omega_coding_agent-0.1.0/src/omega_agent/loop.py +190 -0
  18. omega_coding_agent-0.1.0/src/omega_agent/provider.py +56 -0
  19. omega_coding_agent-0.1.0/src/omega_agent/session/__init__.py +34 -0
  20. omega_coding_agent-0.1.0/src/omega_agent/session/entries.py +48 -0
  21. omega_coding_agent-0.1.0/src/omega_agent/session/jsonl.py +99 -0
  22. omega_coding_agent-0.1.0/src/omega_agent/session/store.py +310 -0
  23. omega_coding_agent-0.1.0/src/omega_agent/session/tree.py +101 -0
  24. omega_coding_agent-0.1.0/src/omega_agent/tool_runner.py +116 -0
  25. omega_coding_agent-0.1.0/src/omega_agent/tools.py +97 -0
  26. omega_coding_agent-0.1.0/src/omega_agent/types.py +222 -0
  27. omega_coding_agent-0.1.0/src/omega_ai/__init__.py +14 -0
  28. omega_coding_agent-0.1.0/src/omega_ai/anthropic.py +709 -0
  29. omega_coding_agent-0.1.0/src/omega_ai/fake.py +184 -0
  30. omega_coding_agent-0.1.0/src/omega_ai/openai.py +580 -0
  31. omega_coding_agent-0.1.0/src/omega_ai/openai_codex.py +720 -0
  32. omega_coding_agent-0.1.0/src/omega_ai/provider.py +22 -0
  33. omega_coding_agent-0.1.0/src/omega_ai/retry.py +146 -0
  34. omega_coding_agent-0.1.0/src/omega_coding/__init__.py +18 -0
  35. omega_coding_agent-0.1.0/src/omega_coding/approval.py +374 -0
  36. omega_coding_agent-0.1.0/src/omega_coding/auth.py +384 -0
  37. omega_coding_agent-0.1.0/src/omega_coding/builtin_tools.py +812 -0
  38. omega_coding_agent-0.1.0/src/omega_coding/cli.py +1001 -0
  39. omega_coding_agent-0.1.0/src/omega_coding/clipboard.py +224 -0
  40. omega_coding_agent-0.1.0/src/omega_coding/commands.py +904 -0
  41. omega_coding_agent-0.1.0/src/omega_coding/compact.py +377 -0
  42. omega_coding_agent-0.1.0/src/omega_coding/context.py +187 -0
  43. omega_coding_agent-0.1.0/src/omega_coding/cost.py +123 -0
  44. omega_coding_agent-0.1.0/src/omega_coding/env.py +86 -0
  45. omega_coding_agent-0.1.0/src/omega_coding/evals.py +137 -0
  46. omega_coding_agent-0.1.0/src/omega_coding/eventlog.py +160 -0
  47. omega_coding_agent-0.1.0/src/omega_coding/file_lock.py +57 -0
  48. omega_coding_agent-0.1.0/src/omega_coding/headless.py +125 -0
  49. omega_coding_agent-0.1.0/src/omega_coding/history.py +42 -0
  50. omega_coding_agent-0.1.0/src/omega_coding/models.py +560 -0
  51. omega_coding_agent-0.1.0/src/omega_coding/oauth.py +519 -0
  52. omega_coding_agent-0.1.0/src/omega_coding/paths.py +159 -0
  53. omega_coding_agent-0.1.0/src/omega_coding/redact.py +194 -0
  54. omega_coding_agent-0.1.0/src/omega_coding/status.py +316 -0
  55. omega_coding_agent-0.1.0/src/omega_coding/subagent.py +185 -0
  56. omega_coding_agent-0.1.0/src/omega_coding/system_prompt.py +125 -0
  57. omega_coding_agent-0.1.0/src/omega_coding/truncate.py +175 -0
  58. omega_coding_agent-0.1.0/src/omega_coding/tui/__init__.py +13 -0
  59. omega_coding_agent-0.1.0/src/omega_coding/tui/adapter.py +130 -0
  60. omega_coding_agent-0.1.0/src/omega_coding/tui/app.py +1111 -0
  61. omega_coding_agent-0.1.0/src/omega_coding/tui/approval.py +146 -0
  62. omega_coding_agent-0.1.0/src/omega_coding/tui/banner.py +272 -0
  63. omega_coding_agent-0.1.0/src/omega_coding/tui/config.py +81 -0
  64. omega_coding_agent-0.1.0/src/omega_coding/tui/login.py +161 -0
  65. omega_coding_agent-0.1.0/src/omega_coding/tui/state.py +299 -0
  66. omega_coding_agent-0.1.0/src/omega_coding/tui/themes/__init__.py +267 -0
  67. omega_coding_agent-0.1.0/src/omega_coding/tui/themes/high-contrast.json +33 -0
  68. omega_coding_agent-0.1.0/src/omega_coding/tui/themes/oxblood-dark.json +33 -0
  69. omega_coding_agent-0.1.0/src/omega_coding/tui/themes/oxblood-light.json +33 -0
  70. omega_coding_agent-0.1.0/src/omega_coding/tui/themes/slate.json +33 -0
  71. omega_coding_agent-0.1.0/src/omega_coding/tui/widgets.py +766 -0
  72. omega_coding_agent-0.1.0/src/omega_coding/version.py +42 -0
  73. omega_coding_agent-0.1.0/tests/conftest.py +119 -0
  74. omega_coding_agent-0.1.0/tests/stub_anthropic.py +159 -0
  75. omega_coding_agent-0.1.0/tests/stub_openai.py +161 -0
  76. omega_coding_agent-0.1.0/tests/test_agent_events.py +49 -0
  77. omega_coding_agent-0.1.0/tests/test_anthropic_translation.py +97 -0
  78. omega_coding_agent-0.1.0/tests/test_approval.py +580 -0
  79. omega_coding_agent-0.1.0/tests/test_auth.py +743 -0
  80. omega_coding_agent-0.1.0/tests/test_caching.py +218 -0
  81. omega_coding_agent-0.1.0/tests/test_cancellation.py +263 -0
  82. omega_coding_agent-0.1.0/tests/test_clipboard.py +133 -0
  83. omega_coding_agent-0.1.0/tests/test_codex.py +503 -0
  84. omega_coding_agent-0.1.0/tests/test_commands.py +514 -0
  85. omega_coding_agent-0.1.0/tests/test_compact.py +349 -0
  86. omega_coding_agent-0.1.0/tests/test_edit.py +222 -0
  87. omega_coding_agent-0.1.0/tests/test_env.py +157 -0
  88. omega_coding_agent-0.1.0/tests/test_eventlog.py +192 -0
  89. omega_coding_agent-0.1.0/tests/test_events.py +61 -0
  90. omega_coding_agent-0.1.0/tests/test_fake_provider.py +81 -0
  91. omega_coding_agent-0.1.0/tests/test_file_lock.py +73 -0
  92. omega_coding_agent-0.1.0/tests/test_harness.py +195 -0
  93. omega_coding_agent-0.1.0/tests/test_headless.py +206 -0
  94. omega_coding_agent-0.1.0/tests/test_images.py +178 -0
  95. omega_coding_agent-0.1.0/tests/test_instruments.py +336 -0
  96. omega_coding_agent-0.1.0/tests/test_layers.py +144 -0
  97. omega_coding_agent-0.1.0/tests/test_loop.py +236 -0
  98. omega_coding_agent-0.1.0/tests/test_models.py +1098 -0
  99. omega_coding_agent-0.1.0/tests/test_modes.py +164 -0
  100. omega_coding_agent-0.1.0/tests/test_oauth.py +646 -0
  101. omega_coding_agent-0.1.0/tests/test_openai_translation.py +237 -0
  102. omega_coding_agent-0.1.0/tests/test_orphan_repair.py +175 -0
  103. omega_coding_agent-0.1.0/tests/test_paths.py +207 -0
  104. omega_coding_agent-0.1.0/tests/test_provider_contract.py +371 -0
  105. omega_coding_agent-0.1.0/tests/test_record_hook.py +281 -0
  106. omega_coding_agent-0.1.0/tests/test_redact.py +236 -0
  107. omega_coding_agent-0.1.0/tests/test_retry.py +357 -0
  108. omega_coding_agent-0.1.0/tests/test_search_tools.py +228 -0
  109. omega_coding_agent-0.1.0/tests/test_session.py +471 -0
  110. omega_coding_agent-0.1.0/tests/test_status.py +325 -0
  111. omega_coding_agent-0.1.0/tests/test_subagent.py +197 -0
  112. omega_coding_agent-0.1.0/tests/test_system_prompt.py +134 -0
  113. omega_coding_agent-0.1.0/tests/test_tools.py +322 -0
  114. omega_coding_agent-0.1.0/tests/test_tree.py +454 -0
  115. omega_coding_agent-0.1.0/tests/test_truncate.py +119 -0
  116. omega_coding_agent-0.1.0/tests/test_tui.py +924 -0
  117. omega_coding_agent-0.1.0/tests/test_tui_approval.py +106 -0
  118. omega_coding_agent-0.1.0/tests/test_tui_widgets.py +1456 -0
  119. omega_coding_agent-0.1.0/tests/test_version.py +84 -0
  120. 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 |