Skip to content

v0.26.0 — a contract that parses, and a start that says when it doesn't

Latest

Choose a tag to compare

@jmvbambico jmvbambico released this 21 Oct 21:44
0b5df75

og init was writing contracts that do not parse, and nothing said so. This release fixes that, and makes og start tell you when the contract it is about to work without is missing or broken.

Found by running og init in two real repos: both reported stack none detected and produced a .agents/orchestration.yaml that yaml.safe_load refuses.

The silent failure

When the generated YAML fails to parse, _load_contract catches the exception and returns None. Every gate, protected branch and never_write rule is then ignored — silently. You believe you have a contract; you have none.

It fails closed: require_contract=True means a merge in a repo with no loadable contract is ASKed, never auto-allowed. So the cost was lost configuration and false confidence, not an unguarded merge. But nothing surfaced it.

Two bugs produced it, and they compounded. detect_stacks only looked at the repo root, so any monorepo or nested layout detected nothing — and with no stack detected, never_write was empty, which tripped a fallback that emitted - "# - "vendor/**"" and broke the whole file. Nested-project repos, exactly the ones that need orchestration, reliably got a broken contract.

Both are fixed. Stack detection now walks to depth 3, skipping node_modules, target, vendor, dist, build, .venv and hidden directories, and emits each gate with its directory:

- name: unit-console-src-tauri
  run: cd -- console/src-tauri && cargo test
  scope: worker

render() now parses its own output before writing and refuses loudly on failure — one line that would have caught the original bug at authoring time.

Values in the contract stay what you wrote

Directory names now reach the contract from the filesystem, which made them an untrusted input. A directory named console app produced cd console app && cargo build, a command that cannot run inside a contract that still validates. A directory containing a quote broke the YAML outright, and the new guard then blamed og for it.

Separately, bare values were emitted unquoted, so YAML 1.1 coerced them: a branch named off loaded as boolean False, 2026-10-21 as a date, 123 as an int — handed to a merge gate that compares branch names. Values now keep the plain form only when they round-trip as the identical string, checked against PyYAML itself rather than a character class that would drift from it. run: cargo test, integration_base: main and task_prefix: feature/ stay unquoted.

og start says when there is no usable contract

With no contract, og start served fine and the merge policy failed closed, but the orchestrator planned and delegated with no gates, no protected branches and no integration base — which looks like it is working.

It now warns, to stderr, and never blocks. Resolution mirrors _load_contract exactly: walk up from the workspace directory, first .agents/orchestration.yaml wins, a parse failure there is final. A check that disagreed with the policy would teach you to distrust the warning.

Four states are reported separately, because they have different fixes: nothing found; present but unparseable; valid YAML that is not a mapping; and PyYAML missing — which makes the policy load nothing whatever is on disk, and is a machine problem, not a repo one. A valid contract prints nothing.

On a terminal it offers to run og init, naming the file and saying plainly that --force will overwrite it. Ctrl-C at the offer or during the questions abandons the scaffold and lets the start continue. --no-contract-check, or OG_START_CONTRACT_CHECK=0, silences it.

og init --questions

An AI driving og init had to scrape interactive prompts while install.sh --questions handed it a JSON schema. That asymmetry is gone. og init --questions emits the decision schema — what was detected, what is being asked, and not_asked with reasons for what og decided itself. Read-only, writes nothing.

A marked block in an existing AGENTS.md

A project with its own AGENTS.md used to print AGENTS.md exists — left untouched and never receive the orchestration directives at all. It now gets a block between <!-- og:orchestration:begin --> markers, updated in place on rerun. The markers are the entire boundary of what og may rewrite: your line endings, your fenced examples and everything outside them come through byte-identical. If a document somehow carries two og blocks, og picks one deterministically and says which.

Three questions, and only three

On a terminal og init now asks what it cannot measure: your branching model (it sets protected, which the merge gate DENYs on), which gates touch a shared database, a fixed port or the network — asked concretely, because a shared-state gate running per-worktree is how parallel workers corrupt each other — and confirmation of the detected gates. Gates og is unsure about default to removed; cleanly detected ones stay.

merge_policy.enabled, diff thresholds, task_prefix and high-risk paths are decided, not asked. If your roster holds a single vendor, it says so rather than letting you find out when a review is silently degraded.

Input that ends is a decline: EOF or Ctrl-C aborts with exit 2 and writes nothing. A contract built from unanswered questions fails open — the orchestrator quotes it back and runs exactly what it says — where no contract fails closed and warns. --force plus EOF leaves an existing contract byte-identical.

og start waits for a host before pinning

start_host_daemon exits 0 when the daemon is running but has not yet registered, so a start could reach the project pin with no online host. That pin is the host a new session launches its runner on, so a session created any other way got The runner for this session is not available. It now waits, then restarts the daemon once — the only thing that clears a daemon that is alive but wedged.

default_agent means less than it claimed

Verified against omnigent 0.17.0: default_agent is read only by omnigent's cli.py, never by the server. It governs og chat alone; what binds the agent in the web UI is the project pin. Every message on that path overstated its reach, and og status now distinguishes the two. The failed-pin message also explains that the picker cannot stand in for the pin — it lists built-in agents only, so your orchestrator shows as "Agent unavailable" even while the pin works.

Tests

1657 → 2498 passing. tests/test_og_init.py is new (it did not exist), and includes a property sweep over generated AGENTS.md documents asserting that user text survives, exactly one block is live, at most one is rewritten, and the warning agrees with the resolution. bin/og's new paths are covered by pty-driven tests verified against deliberately broken copies of the script.

Four cross-vendor review passes found seven blocking defects across the series; the shipped state returned none.