omega-code 0.4.0__py3-none-any.whl

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 (73) hide show
  1. omega/__init__.py +0 -0
  2. omega/__main__.py +589 -0
  3. omega/artifacts.py +151 -0
  4. omega/checkpoint.py +246 -0
  5. omega/compact.py +106 -0
  6. omega/config.py +285 -0
  7. omega/eval/__init__.py +3 -0
  8. omega/eval/cli.py +127 -0
  9. omega/eval/examples/plan-version-flag.yaml +11 -0
  10. omega/eval/examples/relative-age-negative-delta.yaml +14 -0
  11. omega/eval/examples/version-flag.yaml +10 -0
  12. omega/eval/manifest.py +129 -0
  13. omega/eval/prices.py +29 -0
  14. omega/eval/report.py +135 -0
  15. omega/eval/runner.py +199 -0
  16. omega/eval/tasks.py +97 -0
  17. omega/events.py +145 -0
  18. omega/export.py +80 -0
  19. omega/gitlog.py +229 -0
  20. omega/hooks.py +63 -0
  21. omega/instructions.py +103 -0
  22. omega/integrations.py +284 -0
  23. omega/keys.py +173 -0
  24. omega/llm.py +442 -0
  25. omega/loop.py +510 -0
  26. omega/mcp.py +490 -0
  27. omega/memory/__init__.py +5 -0
  28. omega/memory/consolidate.py +103 -0
  29. omega/memory/curate.py +69 -0
  30. omega/memory/store.py +321 -0
  31. omega/memory/tools.py +175 -0
  32. omega/migrate.py +40 -0
  33. omega/onboarding.py +242 -0
  34. omega/permissions.py +137 -0
  35. omega/secrets.py +173 -0
  36. omega/server/__init__.py +7 -0
  37. omega/server/__main__.py +18 -0
  38. omega/server/app.py +71 -0
  39. omega/server/auth.py +73 -0
  40. omega/server/manager.py +287 -0
  41. omega/server/models.py +123 -0
  42. omega/server/tasks_api.py +311 -0
  43. omega/server/terminals.py +245 -0
  44. omega/server/worker.py +186 -0
  45. omega/session.py +209 -0
  46. omega/setup.html +281 -0
  47. omega/setup_server.py +452 -0
  48. omega/skills.py +158 -0
  49. omega/subagent.py +98 -0
  50. omega/tasks.py +195 -0
  51. omega/tools.py +590 -0
  52. omega/trace.py +156 -0
  53. omega/trajectory.py +146 -0
  54. omega/ui/__init__.py +0 -0
  55. omega/ui/composer.py +140 -0
  56. omega/ui/format.py +708 -0
  57. omega/ui/plain.py +141 -0
  58. omega/ui/tui/__init__.py +9 -0
  59. omega/ui/tui/app.py +958 -0
  60. omega/ui/tui/history.py +50 -0
  61. omega/ui/tui/modals.py +292 -0
  62. omega/ui/tui/onboarding.py +367 -0
  63. omega/ui/tui/prefs.py +25 -0
  64. omega/ui/tui/sidebar.py +510 -0
  65. omega/ui/tui/status.py +115 -0
  66. omega/ui/tui/theme.py +91 -0
  67. omega/ui/tui/transcript.py +783 -0
  68. omega/verify.py +133 -0
  69. omega_code-0.4.0.dist-info/METADATA +479 -0
  70. omega_code-0.4.0.dist-info/RECORD +73 -0
  71. omega_code-0.4.0.dist-info/WHEEL +4 -0
  72. omega_code-0.4.0.dist-info/entry_points.txt +2 -0
  73. omega_code-0.4.0.dist-info/licenses/LICENSE +21 -0
omega/verify.py ADDED
@@ -0,0 +1,133 @@
1
+ """Project verification checks -- auto-detected from files on disk, or taken
2
+ from a `verify.checks` config override. See loop.py for how these are run at
3
+ the end of a BUILD-mode turn."""
4
+ import json
5
+ import re
6
+ import subprocess
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Literal
10
+
11
+ TAIL_LINES = 40
12
+ TAIL_CHARS = 4000
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class Check:
17
+ name: str
18
+ command: str
19
+ kind: Literal["test", "lint", "types"]
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class Result:
24
+ check: Check
25
+ ok: bool
26
+ exit_code: int
27
+ tail: str
28
+
29
+
30
+ def _safe_read(path: Path) -> str:
31
+ try:
32
+ return path.read_text()
33
+ except OSError:
34
+ return ""
35
+
36
+
37
+ def _has_mypy_config(root: Path, pyproject_text: str) -> bool:
38
+ if "[tool.mypy]" in pyproject_text:
39
+ return True
40
+ if (root / "mypy.ini").exists():
41
+ return True
42
+ setup_cfg = root / "setup.cfg"
43
+ return setup_cfg.exists() and "[mypy]" in _safe_read(setup_cfg)
44
+
45
+
46
+ def _npm_checks(root: Path, pkg: Path) -> list[Check]:
47
+ try:
48
+ data = json.loads(_safe_read(pkg) or "{}")
49
+ except json.JSONDecodeError:
50
+ return []
51
+ scripts = data.get("scripts") if isinstance(data, dict) else None
52
+ if not isinstance(scripts, dict):
53
+ return []
54
+ runner = ("pnpm run" if (root / "pnpm-lock.yaml").exists()
55
+ else "yarn run" if (root / "yarn.lock").exists()
56
+ else "npm run")
57
+ out: list[Check] = []
58
+ if "test" in scripts:
59
+ out.append(Check("test", f"{runner} test", "test"))
60
+ if "lint" in scripts:
61
+ out.append(Check("lint", f"{runner} lint", "lint"))
62
+ if "typecheck" in scripts:
63
+ out.append(Check("typecheck", f"{runner} typecheck", "types"))
64
+ return out
65
+
66
+
67
+ def detect(cwd: str) -> list[Check]:
68
+ """Project checks inferred from files present in `cwd` -- no config
69
+ override applied here; see `resolve()` for that."""
70
+ root = Path(cwd)
71
+ checks: list[Check] = []
72
+
73
+ pyproject = root / "pyproject.toml"
74
+ pyproject_text = _safe_read(pyproject) if pyproject.exists() else ""
75
+ uv_prefix = "uv run " if (root / "uv.lock").exists() else ""
76
+
77
+ if "pytest" in pyproject_text or (root / "tests").is_dir():
78
+ runner = f"{uv_prefix}pytest -q -x" if uv_prefix else "python -m pytest -q -x"
79
+ checks.append(Check("pytest", runner, "test"))
80
+ if ("[tool.ruff]" in pyproject_text or (root / "ruff.toml").exists()
81
+ or (root / ".ruff.toml").exists()):
82
+ checks.append(Check("ruff", f"{uv_prefix}ruff check", "lint"))
83
+ if _has_mypy_config(root, pyproject_text):
84
+ checks.append(Check("mypy", f"{uv_prefix}mypy", "types"))
85
+
86
+ pkg = root / "package.json"
87
+ if pkg.exists():
88
+ checks.extend(_npm_checks(root, pkg))
89
+
90
+ if (root / "Cargo.toml").exists():
91
+ checks.append(Check("cargo-test", "cargo test", "test"))
92
+ if (root / "go.mod").exists():
93
+ checks.append(Check("go-test", "go test ./...", "test"))
94
+
95
+ makefile = root / "Makefile"
96
+ if makefile.exists() and re.search(r"(?m)^test\s*:", _safe_read(makefile)):
97
+ checks.append(Check("make-test", "make test", "test"))
98
+
99
+ return checks
100
+
101
+
102
+ def resolve(cwd: str, override: list[str] | None) -> list[Check]:
103
+ """`override` (config.Config.verify_checks) replaces auto-detection
104
+ entirely when set -- each entry is run verbatim as a shell command."""
105
+ if override is not None:
106
+ return [Check(name=cmd, command=cmd, kind="test") for cmd in override]
107
+ return detect(cwd)
108
+
109
+
110
+ def run(checks: list[Check], cwd: str, timeout: int = 300) -> list[Result]:
111
+ results: list[Result] = []
112
+ for check in checks:
113
+ try:
114
+ proc = subprocess.run(check.command, shell=True, cwd=cwd, capture_output=True,
115
+ text=True, timeout=timeout)
116
+ code = proc.returncode
117
+ output = (proc.stdout or "") + (proc.stderr or "")
118
+ except subprocess.TimeoutExpired:
119
+ code = -1
120
+ output = f"(timed out after {timeout}s)"
121
+ except (OSError, subprocess.SubprocessError) as e:
122
+ code = -1
123
+ output = f"error running check: {type(e).__name__}: {e}"
124
+ tail = "\n".join(output.strip().splitlines()[-TAIL_LINES:])
125
+ if len(tail) > TAIL_CHARS:
126
+ tail = tail[-TAIL_CHARS:]
127
+ results.append(Result(check=check, ok=code == 0, exit_code=code, tail=tail))
128
+ return results
129
+
130
+
131
+ def summarize(results: list[Result]) -> str:
132
+ return "; ".join(f"{r.check.name} {'ok' if r.ok else f'FAILED(exit {r.exit_code})'}"
133
+ for r in results)
@@ -0,0 +1,479 @@
1
+ Metadata-Version: 2.5
2
+ Name: omega-code
3
+ Version: 0.4.0
4
+ Summary: A fast personal coding agent harness
5
+ Project-URL: Homepage, https://github.com/Timothy102/omega
6
+ Project-URL: Repository, https://github.com/Timothy102/omega
7
+ Project-URL: Issues, https://github.com/Timothy102/omega/issues
8
+ Author: Tim Cvetko
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: agent,anthropic,cli,coding-agent,harness,llm,openai
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: anthropic>=1.2.0
22
+ Requires-Dist: fastapi>=0.141.1
23
+ Requires-Dist: httpx<1,>=0.27
24
+ Requires-Dist: mcp<2,>=1.9
25
+ Requires-Dist: openai<3,>=2
26
+ Requires-Dist: pydantic>=2.13.5
27
+ Requires-Dist: pyyaml>=6.0.3
28
+ Requires-Dist: rich<15,>=13
29
+ Requires-Dist: textual>=8.2.8
30
+ Requires-Dist: uvicorn>=0.52.4
31
+ Requires-Dist: websockets>=17.1
32
+ Description-Content-Type: text/markdown
33
+
34
+ # omega
35
+
36
+ A fast, small coding agent for your terminal. Bring your own models.
37
+
38
+ omega is a harness, not a model. It runs a tool-use loop against any
39
+ OpenAI-compatible endpoint, so you choose what drives it — open-weights models,
40
+ a hosted API, or a mix, with a different model for each job.
41
+
42
+ ```
43
+ $ omega "why is the auth test failing?"
44
+ ⏺ bash pytest tests/test_auth.py -x
45
+ ⏺ read src/auth.py
46
+ The test asserts a 401 but `verify_token` returns 403 for an expired
47
+ token — src/auth.py:88 raises Forbidden instead of Unauthorized.
48
+ ```
49
+
50
+ ## What's in it
51
+
52
+ - **Parallel + streaming tool dispatch.** Tool calls execute *while* the model
53
+ is still generating the next one, not after the response closes.
54
+ - **Planning mode.** `--plan` gives the model read-only tools and asks for a
55
+ plan. The restriction is enforced at dispatch, not just hidden from the schema.
56
+ - **A permissions layer.** Read-only commands run freely; anything that can
57
+ change your machine asks first; a small set of things is refused outright.
58
+ - **Sessions.** Every turn is saved. Resume with `--continue`, list with
59
+ `omega sessions`.
60
+ - **MCP, without the token cost.** Connect Linear, Notion, Sentry and friends.
61
+ Their tools stay out of the prompt until the model searches for them —
62
+ 85 connected tools cost ~700 tokens instead of ~38,000.
63
+ - **Subagents.** Delegate wide searches to a cheaper model and get back a
64
+ summary, so raw output never enters your main context. Their tool activity
65
+ streams into your transcript as it happens; several run in parallel.
66
+ - **Context that doesn't fill up.** Any tool result over 4k chars is written to
67
+ disk and the model sees a preview plus a `fetch_result` handle. Compaction
68
+ exists but rarely triggers.
69
+ - **It can ask you things.** An `ask_user` tool blocks the turn on a real
70
+ question with arrow-key options, instead of guessing.
71
+ - **Persistent memory.** A local knowledge graph (SQLite + FTS5), scoped per
72
+ project and globally, with background consolidation.
73
+ - **A terminal UI.** Bare `omega` opens a full-screen TUI: transcript, live
74
+ activity panel, status bar with token usage. `omega "prompt"` stays plain
75
+ text for scripts and pipes.
76
+
77
+ ## Install
78
+
79
+ Requires Python 3.11+, [ripgrep](https://github.com/BurntSushi/ripgrep), and
80
+ Node (only if you want MCP servers).
81
+
82
+ ```bash
83
+ git clone https://github.com/Timothy102/omega.git && cd omega
84
+ uv tool install omega-code # puts `omega` on your PATH; or `uv sync` to hack on it
85
+ ```
86
+
87
+ ## Setup
88
+
89
+ ```bash
90
+ omega setup
91
+ ```
92
+
93
+ Opens a local page in your browser to pick a provider, paste an API key,
94
+ choose a model for each role, and connect MCP servers. It measures each model's
95
+ latency so you can see what you're choosing.
96
+
97
+ Prefer a file? Write `~/.omega/config.json` yourself:
98
+
99
+ ```json
100
+ {
101
+ "providers": {
102
+ "my-provider": {
103
+ "baseUrl": "https://api.example.com/v1",
104
+ "apiKeyEnv": "MY_API_KEY"
105
+ },
106
+ "anthropic": {
107
+ "type": "anthropic",
108
+ "apiKeyEnv": "ANTHROPIC_API_KEY"
109
+ }
110
+ },
111
+ "models": {
112
+ "opus": { "model": "claude-opus-5", "provider": "anthropic", "context": 1048576, "effort": "high" },
113
+ "small": { "model": "small-model", "provider": "my-provider", "context": 128000 }
114
+ },
115
+ "roles": {
116
+ "main": { "alias": "opus" },
117
+ "plan": { "alias": "opus" },
118
+ "subagent_fast": { "alias": "small" },
119
+ "subagent_mid": { "alias": "opus" },
120
+ "compact": { "alias": "small" },
121
+ "memory": { "alias": "small" }
122
+ }
123
+ }
124
+ ```
125
+
126
+ Use `apiKey` for a literal value or `apiKeyEnv` to read from the environment.
127
+ The file is written `0600`. A provider missing its key still loads fine — it
128
+ only fails, with a pointer to `omega setup` or the env var, when a role that
129
+ uses it actually runs.
130
+
131
+ A role is either an alias into `models` (above) or the older inline form
132
+ (`{ "model", "provider", "context" }`) — both work side by side.
133
+
134
+ ### Roles
135
+
136
+ | role | what it does |
137
+ |---|---|
138
+ | `main` | drives your session — use your best model |
139
+ | `plan` | planning mode |
140
+ | `subagent_fast` | bounded lookups — use your quickest model |
141
+ | `subagent_mid` | reasoning across several files |
142
+ | `compact` | summarises old context when the window fills |
143
+ | `memory` | background consolidation of saved memory notes |
144
+
145
+ ### Models
146
+
147
+ `providers[*].type` is `"openai"` (any OpenAI-compatible `/chat/completions`
148
+ endpoint — the default) or `"anthropic"` (the native Anthropic SDK, with
149
+ adaptive thinking, per-turn effort, prompt caching, and refusal fallbacks
150
+ built in). The built-in catalog:
151
+
152
+ | alias | model | provider | context |
153
+ |---|---|---|---|
154
+ | `fable` | `claude-fable-5-1` | anthropic | 1M |
155
+ | `opus` | `claude-opus-5` | anthropic | 1M |
156
+ | `sonnet` | `claude-sonnet-5` | anthropic | 1M |
157
+ | `haiku` | `claude-haiku-4-5` | anthropic | 200k |
158
+ | `spark` | `meta/muse-spark-1.3` | openrouter-style | 1M |
159
+ | `kimi` | `moonshotai/kimi-k3` | openrouter-style | 1M |
160
+ | `glm` | `z-ai/glm-5.3-flash` | openrouter-style | 128k |
161
+ | `astra` | `gpt-6-astra` | openai (native) | 1M |
162
+ | `sol` | `openai/gpt-5.6-sol` | openrouter-style | 1M |
163
+ | `terra` | `openai/gpt-5.6-terra` | openrouter-style | 1M |
164
+ | `luna` | `openai/gpt-5.6-luna` | openrouter-style | 1M |
165
+ | `codex` | `openai/gpt-5.3-codex` | openrouter-style | 400k |
166
+ | `grok` | `x-ai/grok-4.6` | openrouter-style | 500k |
167
+ | `grok-build` | `x-ai/grok-build-0.1` | openrouter-style | 256k |
168
+
169
+ GPT-6 Astra is in limited rollout and not listed on OpenRouter yet, so `astra`
170
+ only appears once an `openai` provider (`OPENAI_API_KEY`) is configured.
171
+ Cursor's Composer has no public API and cannot be added.
172
+
173
+ `omega models` prints the catalog with each role's current default.
174
+ `omega --model <alias-or-model-id>` overrides `main` and `plan` for the
175
+ session; `/model` in the TUI opens a picker (or takes an alias directly:
176
+ `/model sonnet`), and the status bar always shows the alias in use next to
177
+ the underlying model id.
178
+
179
+ ## Usage
180
+
181
+ ```bash
182
+ omega # interactive TUI
183
+ omega "fix the failing test" # one-shot, plain output
184
+ echo "fix the failing test" | omega
185
+ omega --plan "add rate limiting" # read-only: investigate and plan
186
+ omega --model sonnet "..." # override main/plan for this session
187
+ omega --continue # resume this directory's last session
188
+ omega --resume 20260828-174247 # resume by id (a prefix works)
189
+ omega sessions # list sessions
190
+ omega models # show the model catalog and role defaults
191
+ omega memory gc # consolidate memory now
192
+ omega onboard # short terminal setup (no browser)
193
+ omega connections # manage MCP servers (see ## MCP)
194
+ omega "list my Linear issues" # connects enabled MCP servers lazily
195
+ omega --mcp "..." # or connect everything eagerly at startup
196
+ omega --yolo "..." # skip permission prompts
197
+ omega eval run # headless task-suite scoring (see ## Eval harness)
198
+ omega resume [id] # resume a session (prefix works; no id -- pick from a list)
199
+ omega continue # resume this directory's last session
200
+ omega trace <id> [--tools] [--json] # print a session's event trace (see ## Observability)
201
+ omega update # update omega to the latest release
202
+ omega doctor # check your environment and config
203
+ omega --version # print the version
204
+ omega --help # usage and flags
205
+ ```
206
+
207
+ The first time omega runs with no `~/.omega/config.json`, or with no usable key
208
+ for `main`, it launches a small Textual wizard instead of exiting — pick a
209
+ provider, paste (or auto-detect) a key, pick a model, and it runs one real
210
+ turn live in the wizard to prove it works, then drops you straight into the
211
+ TUI. Piped or non-interactive invocations get the original plain `input()`
212
+ prompts instead. `omega setup` opens the fuller browser flow (multiple roles,
213
+ MCP servers, latency benchmarking) any time after.
214
+
215
+ In the TUI: `/plan` and `/build` switch modes, `/model` picks a model,
216
+ `/memory-gc` consolidates memory, `/quit` or ctrl-d exits, ctrl-c abandons
217
+ the current turn without losing the session, up/down walk input history,
218
+ ctrl-o opens the model picker. Permission prompts and `ask_user` questions
219
+ open as modals — arrow keys and enter, or type a free-text answer.
220
+
221
+ Session and edit-safety commands (TUI only):
222
+
223
+ - `/cost` — this session's tokens (in/out/cache) and USD, by model when more
224
+ than one was used, priced from `omega.eval.prices`.
225
+ - `/export [path]` — writes the transcript as Markdown to `path`, or
226
+ `~/.omega/sessions/<id>/transcript.md` by default, and prints where it went.
227
+ - `/compact` — forces compaction now instead of waiting for the token
228
+ threshold, and shows the resulting note.
229
+ - `/undo [n]` — reverts the working tree to the checkpoint from `n` turns ago
230
+ (default 1), after a y/n/always confirm.
231
+ - `/diff` — shows the working-tree diff since the last checkpoint in a modal.
232
+ - `/theme system|light|dark` — `system` (the default) paints nothing of its
233
+ own, so omega takes the terminal's background, text colour and palette and
234
+ looks light or dark along with it; `light` and `dark` force a painted
235
+ palette instead. Remembered in `~/.omega/ui.json`.
236
+ - `/verify` — runs this project's auto-detected checks (tests/lint/types) and
237
+ reports pass/fail per check.
238
+ - `/sessions` — lists this directory's other sessions in a modal; enter
239
+ resumes the selected one in place, replacing the current history.
240
+
241
+ `/undo`, `/diff`, and `/verify` depend on `checkpoint.py`/`verify.py`; if
242
+ those aren't present in a build they print a dim "not available in this
243
+ build" instead of erroring.
244
+
245
+ ## Context and artifacts
246
+
247
+ Every tool result is checked at dispatch: anything over 4,000 characters is
248
+ written to `~/.omega/sessions/<id>/artifacts/` and replaced in the
249
+ conversation with a head+tail preview and an id. The model calls
250
+ `fetch_result(id, offset, limit)` to page through the rest — so a huge test
251
+ log or `cat` costs a few hundred tokens of context, not thirty thousand.
252
+
253
+ The same store backs `save_artifact` / `update_artifact`, which let the model
254
+ build up a plan or report across a turn without re-emitting it each time, and
255
+ `list_artifacts` to see what's there.
256
+
257
+ ## Permissions
258
+
259
+ Every tool call is classified before it runs:
260
+
261
+ - **allowed** — reads, searches, and writes inside your working directory
262
+ - **ask** — anything else, with `[y]es / [N]o / [a]lways` (`a` is remembered in
263
+ `~/.omega/permissions.json`)
264
+ - **refused** — `sudo`, piping a download into a shell, force-pushes, and
265
+ anything touching `~/.ssh`, `~/.aws`, or omega's own config
266
+
267
+ Content from MCP servers and from files outside your project is wrapped in
268
+ `<untrusted>` markers, and reading any of it downgrades `bash` to *ask* for the
269
+ rest of the turn — so a prompt injection in a ticket description can't quietly
270
+ reach your shell.
271
+
272
+ `--yolo` turns prompting off. Use it for scripts, not for exploring.
273
+
274
+ ## MCP
275
+
276
+ `omega connections` manages MCP servers: a catalog of ~45 well-known ones
277
+ (Linear, Notion, GitHub, Postgres, Stripe, ...), whatever's already found in
278
+ your Claude Code config, and whatever you've configured yourself. Remote
279
+ servers proxy through `mcp-remote`, which owns the OAuth dance.
280
+
281
+ ```bash
282
+ omega connections # table: name, state, tools, auth, source, last used
283
+ omega connections catalog # browse the catalog by category
284
+ omega connections add linear # configure a catalog entry
285
+ omega connections add mytool --cmd "npx -y my-mcp-server" --env API_KEY=...
286
+ omega connections connect linear # connect now (triggers OAuth if needed)
287
+ omega connections test linear # connect, report tool count, disconnect
288
+ omega connections enable|disable linear
289
+ omega connections remove linear
290
+ ```
291
+
292
+ Connecting an OAuth server opens an authorize-me URL; `omega connections
293
+ connect` prints it and waits, so re-run it once you've clicked through.
294
+
295
+ Connected tools are *deferred*: they don't appear in the prompt at all. The
296
+ model calls `find_tools("linear issues")` to discover them and `call_tool` to
297
+ run one. Enabled servers connect **lazily** — the first `find_tools`/`call_tool`
298
+ of a session connects everything not yet connected, in parallel, with
299
+ failures recorded instead of raised. `omega --mcp` is still there for connecting
300
+ everything eagerly at startup instead.
301
+
302
+ Add your own server directly in `~/.omega/config.json` if you'd rather skip the
303
+ CLI:
304
+
305
+ ```json
306
+ "mcp": {
307
+ "linear": { "command": "npx", "args": ["-y", "mcp-remote@0.8.1", "https://mcp.linear.app/mcp"],
308
+ "enabled": true, "catalog": "linear" }
309
+ }
310
+ ```
311
+
312
+ `enabled` defaults to `true`; `catalog` is optional and just links the entry
313
+ back to its catalog metadata (auth type, category) for `omega connections`.
314
+
315
+ ## Memory
316
+
317
+ The agent keeps a small local knowledge graph in SQLite (FTS5 full-text search
318
+ + a graph of typed edges), in two scopes:
319
+
320
+ - **project** — `.omega/memory.db` next to the repo you're in; auto-gitignored
321
+ the first time it's written, never committed
322
+ - **global** — `~/.omega/memory/memory.db`, shared across all projects
323
+
324
+ Nodes have a `type` (`fact`, `preference`, `decision`, `entity`, `file_note`,
325
+ `open_question`), a confidence, a volatility, and an importance, which
326
+ together decide what gets auto-injected into the system prompt each session
327
+ vs. what stays recall-only.
328
+
329
+ Tools: `remember` saves a node; `recall` searches both scopes and expands
330
+ related nodes; `supersede` replaces an outdated node while keeping the old
331
+ one queryable; `link` adds an explicit relation (`contradicts`, `depends_on`,
332
+ `part_of`, ...) between two existing nodes. A regex safety net forces
333
+ `sensitivity="sensitive"` on anything that looks like a secret or PII,
334
+ regardless of what the model passed.
335
+
336
+ A background pass (the `memory` role) periodically merges near-duplicates,
337
+ flags contradictions, and retags stale entries — automatically at session
338
+ close once 5+ new nodes have accumulated, or on demand with `omega memory gc`
339
+ (`/memory-gc` in the REPL).
340
+
341
+ ## Skills and project instructions
342
+
343
+ Two ways to steer the agent beyond a single prompt:
344
+
345
+ **Instructions** — an `OMEGA.md` (or `CLAUDE.md`, read as a fallback where no
346
+ `OMEGA.md` exists) is loaded once at startup and folded into the system
347
+ prompt: `~/.omega/OMEGA.md` (global) first, then every `OMEGA.md` from the
348
+ git root down to your working directory — so a monorepo subdir's file adds
349
+ to the root's instead of replacing it — then `.omega/instructions.md` if
350
+ present. Capped at 12,000 characters total, with a pointer to `read` the
351
+ source file for anything trimmed.
352
+
353
+ **Skills** — a `SKILL.md` (frontmatter `name` + `description`, then a
354
+ markdown checklist) is a sub-workflow the model loads on demand with the
355
+ `skill` tool and follows in the same conversation — not a subagent, so
356
+ nothing about the task is lost switching to it. omega reads the same format
357
+ Claude Code uses, so `~/.claude/skills/*` work here unchanged. Discovery
358
+ order (highest precedence first): `.omega/skills/*/SKILL.md` (project),
359
+ `~/.omega/skills/*/SKILL.md` (global), `~/.claude/skills/*/SKILL.md`. A
360
+ compact index (name + description) sits in the system prompt; `skill(name)`
361
+ fetches the full body, with any relative file links it contains rewritten to
362
+ absolute paths so `read` can follow them.
363
+
364
+ ```bash
365
+ omega skills # table: name, source, description
366
+ omega skills show <name> # print a skill's body as the model sees it
367
+ ```
368
+
369
+ ## Eval harness
370
+
371
+ `omega eval` runs a suite of coding tasks headlessly against one or more
372
+ models and scores the results — the way to answer "did that prompt/config
373
+ change make things better or worse" with numbers instead of a vibe.
374
+
375
+ A task is a YAML file:
376
+
377
+ ```yaml
378
+ name: version-flag
379
+ prompt: Add a --version flag to the CLI...
380
+ repo: . # path to run against (default: ".")
381
+ setup: git checkout -- . && git clean -fd # optional, run before the agent
382
+ check: "uv run omega --version | grep -q 0.3" # shell command, exit 0 = pass
383
+ timeout_s: 600 # default 600
384
+ mode: build # build | plan (default build)
385
+ tags: [cli, smoke]
386
+ ```
387
+
388
+ ```bash
389
+ omega eval init # copy 3 example tasks into .omega/evals/
390
+ omega eval run # run .omega/evals/*.yaml against the `main` role
391
+ omega eval run --models opus,sonnet,spark --repeat 3 --jobs 4
392
+ omega eval run path/to/one-task.yaml --json
393
+ omega eval compare 20260901-101500 20260903-090000 # diff two runs
394
+ ```
395
+
396
+ Each run happens in a throwaway copy of `repo` — a `git worktree` for a git
397
+ repo, a plain directory copy otherwise — never the repo you're actually
398
+ working in. `--yolo` semantics apply (no permission prompts). Per task ×
399
+ model × repeat, omega records pass/fail (from `check`'s exit code), model
400
+ rounds, tool calls by name, tokens in/out, an estimated cost from a
401
+ per-million-token price table (`omega/eval/prices.py`), wall time, and a
402
+ context telemetry manifest (per-round token/tool breakdown, system-prompt
403
+ size by zone, and an estimate-vs-actual token drift). Everything lands in
404
+ `.omega/evals/runs/<timestamp>/report.json`, plus a table on stdout.
405
+
406
+ ## Observability
407
+
408
+ Every event a session's turns emit (`omega/events.py`) — tool calls, model
409
+ switches, compactions, checkpoints, verification, background jobs — is
410
+ appended as one JSON line to `~/.omega/sessions/<id>/trace.jsonl`, regardless
411
+ of what either UI chose to render. It's a second, independent sink: nothing
412
+ about the trace depends on the TUI or plain mode having shown that event.
413
+
414
+ ```bash
415
+ omega trace <id> # readable timeline: time offset, glyph, summary,
416
+ # tool durations, and per-turn token/cost totals
417
+ omega trace <id> --tools # filter to just ToolStart/ToolEnd
418
+ omega trace <id> --json # raw JSONL, one event object per line
419
+ ```
420
+
421
+ Each line has the shape `{"t": <epoch>, "turn": <n>, "type": "ToolStart", ...}`
422
+ — `t` and `turn` plus every field of that event's dataclass, flattened. Costs
423
+ are computed from `omega.eval.prices`, matching the alias announced by the
424
+ most recent `ModelUsed` event in that turn.
425
+
426
+ `omega update` re-installs the current release with `uv tool install --force`
427
+ — from PyPI (`omega-code`) if that's how it was installed, or from
428
+ `git+https://github.com/Timothy102/omega.git@main` if it was installed from
429
+ git — then prints the freshly installed `omega --version`. `omega doctor`
430
+ checks Python ≥3.11, `rg`, `git`, `node`/`npx` (for MCP), `uv`, config
431
+ validity, each configured provider's key presence, and that
432
+ `~/.omega/config.json` is `0600`, as a ✓/✗ table.
433
+
434
+ ## Development
435
+
436
+ Uses [uv](https://docs.astral.sh/uv/), [ruff](https://docs.astral.sh/ruff/),
437
+ and [mypy](https://mypy-lang.org/) in strict mode.
438
+
439
+ ```bash
440
+ uv sync # creates .venv with dev deps
441
+ uv run pytest
442
+ uv run ruff check
443
+ uv run mypy
444
+ ```
445
+
446
+ ## Where things live
447
+
448
+ ```
449
+ ~/.omega/config.json provider, models, MCP servers (0600)
450
+ ~/.omega/permissions.json saved allow/deny rules
451
+ ~/.omega/sessions/ one JSON file per session
452
+ ~/.omega/sessions/<id>/artifacts/ offloaded tool output + saved artifacts
453
+ ~/.omega/sessions/<id>/trace.jsonl per-event trace (see ## Observability)
454
+ ~/.omega/sessions/<id>/transcript.md `/export`'s default output path
455
+ ~/.omega/sessions/<id>/checkpoints.json working-tree checkpoints (`/undo`, `/diff`)
456
+ ~/.omega/memory/memory.db global memory (SQLite + FTS5)
457
+ <project>/.omega/memory.db project memory (gitignored)
458
+ ~/.omega/history REPL input history
459
+ ~/.omega/OMEGA.md global instructions
460
+ <project>/.omega/instructions.md project instructions (local-only)
461
+ <project>/OMEGA.md project instructions (shared, committed)
462
+ ~/.omega/skills/*/SKILL.md global skills
463
+ <project>/.omega/skills/*/SKILL.md project skills
464
+ ```
465
+
466
+ Sessions contain full transcripts, including file contents and command output.
467
+ They're local, but treat them as sensitive.
468
+
469
+ ## Status
470
+
471
+ Early. It works and it's tested, but expect rough edges. Known gaps: sessions
472
+ rewrite the whole file each turn (fine for now, will become append-only);
473
+ compaction, when it does trigger, replaces old messages rather than archiving
474
+ them; artifacts and sessions are never garbage-collected; and the TUI has
475
+ been exercised on macOS terminals only.
476
+
477
+ ## Licence
478
+
479
+ MIT