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.
- omega/__init__.py +0 -0
- omega/__main__.py +589 -0
- omega/artifacts.py +151 -0
- omega/checkpoint.py +246 -0
- omega/compact.py +106 -0
- omega/config.py +285 -0
- omega/eval/__init__.py +3 -0
- omega/eval/cli.py +127 -0
- omega/eval/examples/plan-version-flag.yaml +11 -0
- omega/eval/examples/relative-age-negative-delta.yaml +14 -0
- omega/eval/examples/version-flag.yaml +10 -0
- omega/eval/manifest.py +129 -0
- omega/eval/prices.py +29 -0
- omega/eval/report.py +135 -0
- omega/eval/runner.py +199 -0
- omega/eval/tasks.py +97 -0
- omega/events.py +145 -0
- omega/export.py +80 -0
- omega/gitlog.py +229 -0
- omega/hooks.py +63 -0
- omega/instructions.py +103 -0
- omega/integrations.py +284 -0
- omega/keys.py +173 -0
- omega/llm.py +442 -0
- omega/loop.py +510 -0
- omega/mcp.py +490 -0
- omega/memory/__init__.py +5 -0
- omega/memory/consolidate.py +103 -0
- omega/memory/curate.py +69 -0
- omega/memory/store.py +321 -0
- omega/memory/tools.py +175 -0
- omega/migrate.py +40 -0
- omega/onboarding.py +242 -0
- omega/permissions.py +137 -0
- omega/secrets.py +173 -0
- omega/server/__init__.py +7 -0
- omega/server/__main__.py +18 -0
- omega/server/app.py +71 -0
- omega/server/auth.py +73 -0
- omega/server/manager.py +287 -0
- omega/server/models.py +123 -0
- omega/server/tasks_api.py +311 -0
- omega/server/terminals.py +245 -0
- omega/server/worker.py +186 -0
- omega/session.py +209 -0
- omega/setup.html +281 -0
- omega/setup_server.py +452 -0
- omega/skills.py +158 -0
- omega/subagent.py +98 -0
- omega/tasks.py +195 -0
- omega/tools.py +590 -0
- omega/trace.py +156 -0
- omega/trajectory.py +146 -0
- omega/ui/__init__.py +0 -0
- omega/ui/composer.py +140 -0
- omega/ui/format.py +708 -0
- omega/ui/plain.py +141 -0
- omega/ui/tui/__init__.py +9 -0
- omega/ui/tui/app.py +958 -0
- omega/ui/tui/history.py +50 -0
- omega/ui/tui/modals.py +292 -0
- omega/ui/tui/onboarding.py +367 -0
- omega/ui/tui/prefs.py +25 -0
- omega/ui/tui/sidebar.py +510 -0
- omega/ui/tui/status.py +115 -0
- omega/ui/tui/theme.py +91 -0
- omega/ui/tui/transcript.py +783 -0
- omega/verify.py +133 -0
- omega_code-0.4.0.dist-info/METADATA +479 -0
- omega_code-0.4.0.dist-info/RECORD +73 -0
- omega_code-0.4.0.dist-info/WHEEL +4 -0
- omega_code-0.4.0.dist-info/entry_points.txt +2 -0
- 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
|