Structure

Moving a company to the new names

A company's own files were renamed: overbot.yml is structure.yml, and .overbot/ is .structure/. The old names are still read for one release, structure check says "rename it", and structure migrate does the move. The names and the one rule that reads them are packages/engine/src/paths.ts; the verb is packages/cli/src/migrate.ts.

What changed

whatwasisread as a fallback until
the policy fileoverbot.ymlstructure.ymlthe release after this one
the record directory.overbot/.structure/the release after this one
the product's environment variablesOVERBOT_<NAME>STRUCTURE_<NAME>the release after this one
the release's asset manifestoverbot-assets.jsonstructure-assets.jsonread under the new name first; still written under the old one (see below)

A company that is written new — new, genesis, a starter — gets the new names. A company written earlier keeps working exactly as it did: every reader asks for the new name first and the old one second, and every writer writes where the company's files already are. Nothing is moved for you.

When both names exist, the new one is read and the old one is ignored; the two are never merged. check says which is which.

What check says

One warning, on a company still on the old names:

rename overbot.yml to structure.yml and .overbot/ to .structure/ — run: structure migrate.
The old names are read until the release after this one

It is a warning, not an error: the check stays green, hooks and work finish are not blocked.

One case is an error: a checkout whose hooks are not running. check asks git which folder it runs hooks from in this checkout (whatever core.hooksPath says, however it is spelled, from whichever config it comes) and says so when that folder is not there — the move reached this clone by a pull or a merge and the setting still names the old folder — or when a hook in it is not executable, or when a hook this checkout keeps in its record directory has no link in the clone's shared hooks folder. Run migrate in that checkout (or the chmod +x the finding names) and they run again. A clone that never set core.hooksPath is not this finding; doctor says so.

migrate

Run it in the company folder. It makes one commit:

  • git mv overbot.yml structure.yml — the same bytes under the new name, so git log --follow still reaches the file's history;
  • everything under .overbot/ moves to the same place under .structure/ — tracked files with git mv; the device-local files git ignores (the attention canary, set-aside work, the company index) with a plain rename;
  • what was ignored stays ignored, and git is the one asked: every line in .git/info/exclude and in your own root .gitignore that names .overbot/ as a path segment gets a twin for .structure/ (/.overbot/x, .overbot/, !.overbot/keep, **/.overbot/private/); then git lists what it ignores under .overbot/ today and says, file by file, whether it would still be ignored under .structure/ — a file that would not gets a line in the file whose pattern ignored it (a pattern from a global excludes file gets its twin in .git/info/exclude, said on the terminal), and a file that cannot be kept ignored is a refusal before anything moves. The .gitignore lines ride in the same commit;
  • lines in your root .gitattributes that name .overbot/ get their twin the same way, and git is asked afterwards whether every moved file's attributes are what they were — a difference is said;
  • hooks in .githooks/ are left alone. If your hooks live under the record directory — asked of git, which folder it runs hooks from in this checkout, not read off the setting's spelling (./.overbot/hooks, .overbot/hooks/, ~/…, an absolute path, a symlink on the way, any scope or include) — core.hooksPath is pointed at one folder inside the clone's git directory that runs each checkout's own hooks, whichever names that checkout is on, so they keep running in every worktree of the clone, moved or not; an absolute path into this checkout's record directory simply follows the directory. The setting is written where this checkout's value lives — and nowhere else: a checkout of the clone whose hooks git ran from somewhere outside the moved directory (its own .githooks, a folder of its own, in its own worktree config) is left as it was, setting, folder and hooks. Then git is asked, in every checkout, where it runs hooks from: the same place as before, unless that place was inside the moved directory. Any other answer, or a value git takes from somewhere this command does not write (its command line, a config outside the clone that wins), is a refusal before anything moves. The clone's shared hooks folder links exactly the hooks some checkout has — to git an absent hook and a silent one are different things (push-to-checkout) — and a hook added later is a check finding until the next migrate links it;
  • the compiled files the company already has (AGENTS.md, the hooks) are rewritten so they name the new files. It never creates a compiled file the company did not have.

migrate --dry-run says what would move and moves nothing.

It refuses, before anything moves, when:

  • the tree has uncommitted changes — it names them. The move is one commit you can read.
  • a serve is running on the company — it names the process and the port. Stop it (serve --stop), migrate, start it again.
  • overbot.yml and structure.yml both exist — which one is kept is yours to settle.
  • the same file exists under .overbot/ and .structure/ — it names them.
  • .overbot/ is a link, not a folder, a submodule lives under it, or git tracks files under it that this checkout does not have (a sparse checkout) — each named.
  • the hooks could not be kept running in every checkout, or a file git ignores would show as untracked after the move — each named, with where the setting or the pattern comes from.

Run it twice and the second run says there is nothing to do.

If the company's own commit gate refuses the commit, everything is put back and the gate's words are shown. A gate compiled by this release reads the rename for what it is — the same bytes under a new name is not a change to canon — so bring an older gate up to date first (compile --write, commit that), then migrate.

The policy file moves byte for byte, so a line in it that names the file or the record directory (overbot.yml: owner, a line over .overbot/) still spells the old name. It means what it meant: the two names are read as one path wherever a policy line, a link, an answer's source or a routine's declared path is matched. Change the spelling when you next edit the policy.

The approved-by scan reads the migration commit for what it is. The same bytes leaving the old name for the new one is not an unsigned change; a later unsigned edit to structure.yml is.

Before you run it: everyone on the company upgrades first

A build from before the rename cannot read structure.yml. Migrate once every device that opens the company runs a build that can.

Work that is in flight

in flightwhat happens
a work entered in its own worktreeIts branch keeps overbot.yml and .overbot/ — its own copy of the files — and keeps working. When it lands, git carries its edits to overbot.yml into structure.yml. Its work record, added under .overbot/work/, lands there: check then says .overbot/ was left behind, and migrate (run again, or run in the work's worktree before it lands) moves it.
a proposal waiting for approvalThe same: read under either name, approved as usual. Its discussion is taken out of the tree at approval, as always, wherever it sat.
a batch run mid-flightThe record keeps the destinations it declared (.overbot/runs/… is the artifact's identity); batch close reads each file where it is now. A process whose canon declares .overbot/runs/<run-id>… still binds.
a thread's record branch (overbot/runs/*)Unchanged; read under either name.

What does not change

The branch names (overbot/proposals/*, overbot/attention, overbot/runs/*, overbot/machines, work/*), the record ref namespace (refs/overbot/*), the worktree folder beside a company (.overbot-worktrees/), and the device's config directory (~/.config/overbot). These are identity, not file names. The files inside the shared record branches (overbot/machines, overbot/attention) and the claim refs also keep their paths this release: every device of a company writes them, whatever build it runs.

Environment variables

Every variable the product reads is read as STRUCTURE_<NAME> first and OVERBOT_<NAME> second — the new name when it is set, even to nothing (a person who set it meant it), else the old. The old names stop being read in the release after this one. When the product starts a child of its own (the CLI, or the desktop host starting a serve) it sets both names where it sets either, and removes both where it removes either. If both names are set to different values, the new one is the one read, and a serve says so when it starts — on the terminal, and in its instance record.

new nameold name (fallback)what it is
STRUCTURE_CONFIG_DIROVERBOT_CONFIG_DIRwhere this device's state lives (config, instance records)
STRUCTURE_ACTOROVERBOT_ACTORwho this process signs as (human/<name>, agent/<name>)
STRUCTURE_MACHINEOVERBOT_MACHINEthis device's name in the fleet
STRUCTURE_MACHINE_ADDRESSOVERBOT_MACHINE_ADDRESSthe address this device is reached at
STRUCTURE_MASTER_KEYOVERBOT_MASTER_KEYthe company key for the encrypted store (CI)
STRUCTURE_KEYCHAINOVERBOT_KEYCHAINwhich macOS keychain holds mail and sign-in tokens
STRUCTURE_ASSET_ROOTOVERBOT_ASSET_ROOTthe folder a serve reads its pages from
STRUCTURE_INSTANCE_OWNEROVERBOT_INSTANCE_OWNERwho owns a serve (cli, app:<host>) — set by a host for its child
STRUCTURE_REPOOVERBOT_REPOthe company a host opens
STRUCTURE_SERVE_REQUIRE_TOKENOVERBOT_SERVE_REQUIRE_TOKEN1 asks every caller for the serve token
STRUCTURE_TRUST_PROXYOVERBOT_TRUST_PROXY1 trusts a reverse proxy's forwarded address
STRUCTURE_TAILSCALE_BINOVERBOT_TAILSCALE_BINwhere the tailscale binary is
STRUCTURE_GENESIS_SCRIPTOVERBOT_GENESIS_SCRIPTa scripted genesis conversation
STRUCTURE_GENESIS_THINKINGOVERBOT_GENESIS_THINKINGthe model's thinking setting for genesis
STRUCTURE_CLAIM_LEASE, STRUCTURE_CLAIM_GRACEOVERBOT_CLAIM_LEASE, OVERBOT_CLAIM_GRACEclaim timing, in seconds (tuning)
STRUCTURE_PLACEMENT_POLL_MS, STRUCTURE_BOUNDARY_POLL_MSOVERBOT_PLACEMENT_POLL_MS, OVERBOT_BOUNDARY_POLL_MSserve polling intervals (tuning)
STRUCTURE_CREATION_TTL_MS, STRUCTURE_CREATION_SWEEP_MSOVERBOT_CREATION_TTL_MS, OVERBOT_CREATION_SWEEP_MShow long an unfinished creation is kept (tuning)
STRUCTURE_TURN_STREAMS_MAXOVERBOT_TURN_STREAMS_MAXhow many turn streams a serve keeps open (tuning)
STRUCTURE_TUI_SNAPOVERBOT_TUI_SNAPa folder the terminal UI writes frame snapshots to (testing)
STRUCTURE_MENU_BAR_LIFETIME, STRUCTURE_GATE_OPEN_COCKPITOVERBOT_MENU_BAR_LIFETIME, OVERBOT_GATE_OPEN_COCKPITa host's gate switches (set by the release gates)

Not renamed, and why:

namewhy it stays
OVERBOT_APIFY_TOKEN, OVERBOT_X_BEARER_TOKEN, OVERBOT_X_MONTHLY_CAP_USD, OVERBOT_ELEVENLABS_API_KEYnot environment variables: the names of lines in a company's encrypted store. Renaming them would orphan what a company has stored.
OVERBOT_REPO_TOKEN, OVERBOT_READ_TOKEN, OVERBOT_ROUTINE_TOKEN, OVERBOT_MAINTAINER_ENABLEDGitHub secrets and variables a company set in its own repository, named in the workflow the compiler writes.
OVERBOT_SIGN_IDENTITY, OVERBOT_NOTARY_PROFILE, OVERBOT_COSIGN*, OVERBOT_RELEASE_REPO, OVERBOT_TAP_REPO, OVERBOT_SKIP_GATES, OVERBOT_BUILD_STAMPthe release tooling's own (bin/), read by a shell script and by TypeScript in the same cut: they move together, with the release's names.
OVERBOT_TEST_*, OVERBOT_GATE_*, OVERBOT_ACCEPTANCE_SCRATCH, OVERBOT_SCREENS_DIR, OVERBOT_GENESIS_GOLDEN, and the build's three flags (named only in bin/, never in a shipped guide — the static gate refuses them in the binary's bytes)the suite's and the build's own: no person sets them. The gate removes every STRUCTURE_* variable from the environment it hands a suite unit, so a variable in your shell cannot outrank the suite's isolation.

A scheduled routine's crontab line carries the settings folder under both names (STRUCTURE_CONFIG_DIR and OVERBOT_CONFIG_DIR), so the build cron fires reads it whichever it knows.

The asset manifest

A serve reads structure-assets.json from an asset root first and overbot-assets.json second. The release pipeline still writes overbot-assets.json; renaming the written name is the release's to do with the rest of its names.