# ADR 0073: discern co-manages Codex's auto-generated environment.toml, and reuses the cwd-based teardown verb for its cleanup

> **Amendments.**
>
> - **Vocabulary ([ADR 0120](0120-launch-verb-canon.md), [ADR 0169](0169-the-launch-glossary-canon.md)):** current spellings are `discern`, the gate, or the bar for the retired product-category wording, and `Shared file` (formerly `Co-managed seed` / co-managed file); the decisions below are unchanged.

**Status**: accepted; extends [ADR 0031](0031-typed-provider-integration.md) (one typed provider registry) with an optional worktree-app seam, and builds on [ADR 0040](0040-worktree-hooks-in-the-binary.md) (the worktree verbs parse their own input) and [ADR 0045](0045-mcp-is-core-infrastructure.md) (integration artifacts are re-established on every refresh, not seeded once)

## Context

Phase B wires Codex's worktree lifecycle. Codex exposes two committable surfaces a development system can drive, and they behave unlike Claude Code's `WorktreeCreate` / `WorktreeRemove` contract:

- **A per-session `SessionStart` hook** in `.codex/hooks.json` — the portable `worktree ensure` analogue, fired in the bare CLI. Handled like any other provider hook (a `HooksIntegration` + a seed template; ADR 0071).
- **`.codex/environments/environment.toml`** with `[setup]`/`[cleanup]` `script` keys — a real create/teardown pair, but with two properties that make it unlike every integration file discern had wired before:
  1. **It is auto-generated by the Codex _app_** (a "DO-NOT-EDIT" file the app rewrites), carrying the app's own keys (`version`, `name`, `[[actions]]`).
  2. **Its scripts run only for worktrees the Codex _app_ creates** (under `$CODEX_HOME/worktrees`), as **bare commands in the worktree cwd with no stdin payload** — not for discern's own sibling worktrees, and not with the `{worktree_path}` JSON that Claude's `WorktreeRemove` hands `worktree remove`.

Two questions fell out: how should discern write into a file the app owns and regenerates, and what runs as `[cleanup]` when there is no stdin to carry the worktree path?

## Decision

**discern co-manages `environment.toml` through a new optional provider seam, and points `[cleanup]` at the pre-existing cwd-based `worktree teardown` verb — no new verb is introduced.**

1. **A `WorktreeAppIntegration` on the provider record** (`src/lib/providers.ts`), optional like `hooks?`/`skillsDir?`:

   ```ts
   interface WorktreeAppIntegration {
     readonly configFile: string;
     register(root: string): Promise<string[]>; // files written, idempotently
   }
   ```

   Only Codex declares one. `registerCodexEnvironment` merges `[setup].script = "discern worktree ensure"` and `[cleanup].script = "discern worktree teardown"` via the comment-preserving `TomlEditor`, rewriting a script key only when it is absent or still set to discern's own default. A user-customized script value is preserved, while the app's `[[actions]]` and comments survive untouched. Codex's schema additionally REQUIRES top-level `version` (number) and `name` (string) — a file missing them is rejected with `expected string, received undefined` at `name` — so discern seeds `version = 1` and `name = "Discern"` **set-if-absent**: a file discern writes from scratch validates (giving immediate Codex-environments access), while a file the app already created keeps its own `version`/`name`. Writing those root-level keys is why `TomlEditor` gained `hasRootKey` / `setRootLiteral` (the `discern.toml` subset has no pre-section keys; a foreign Shared file does). It is safe when the file is absent (created).

2. **It re-emits on every refresh, with ownership-aware script writes.** `wireProviderWorktreeApp` runs inside `compileGuidelines` right after the MCP wiring — the same timing as `wireProviderMcp` (ADR 0045). Because the app may regenerate the file at any time, a seed (write-once, then the user's) would be silently clobbered and never reapplied; re-emitting **self-heals** the two `script` keys whenever the app rewrites the file and drops them. The self-heal does not imply permanent ownership over the user's commands: once a script value differs from discern's default, refresh treats it as user-owned and leaves it alone. A shared idempotent-TOML core writes back only when the bytes change, so a refresh that finds both scripts present or customized is a clean no-op.

3. **`[cleanup]` reuses the existing cwd-based `worktree teardown` verb.** That verb already resolves the worktree from the process cwd (it walks up to `discern.toml` and runs `worktreeTeardown` against that root) and needs no stdin — exactly the bare-command-in-cwd shape the Codex app invokes `[cleanup]` with. It has existed since the engine's first dispatcher; Phase B adds no teardown verb, only the `environment.toml` line that names it. (`worktree remove`, the Claude `WorktreeRemove` entry point, stays distinct: it reads a `{worktree_path}` stdin payload, which `[cleanup]` does not provide.)

The explicit *no*s:

- **discern does not build its own sibling worktrees around `environment.toml`.** That file serves only the Codex-_app_-managed-worktree workflow; discern's own worktrees stay driven by its CLI/MCP verbs + the `SessionStart` hook. discern writes the file so the app-managed flow works, and stops there.
- **No new teardown verb, and no second teardown core.** `[cleanup]` and Claude's `worktree remove` converge on the one `worktreeTeardown(lifecycleContext(...))` core; only the worktree-resolution differs (cwd vs. payload).
- **The seam is not forced on every provider.** Unlike `mcp`/`trust` (required), `worktreeApp?` is optional: an agent whose app owns no such file declares none and is skipped, never guessed — Cursor/Copilot/Antigravity (Plans C/D) have no `environment.toml`, so a required field would be speculative generality.

## Consequences

- **A developer on Codex-app-managed worktrees gets discern setup/teardown** via `environment.toml`, kept in step with the app's regenerations, while the app's own config and user-customized lifecycle scripts are preserved.
- **The engine stays agent-agnostic.** Every `.codex` path lives in `src/lib/providers.ts` and is reached through the registry; `compileGuidelines` calls `wireProviderWorktreeApp(root, agents)` and constructs no Codex path itself, so the `tests/agent_agnostic_test.ts` guard holds.
- **The contract is regression-proofed.** A test runs the literal `[cleanup].script` discern writes as a bare command in the worktree cwd and asserts it tears the worktree down — so renaming the verb (or the written script) without updating both red-lights the gate rather than silently breaking Codex teardown.
- **One more registry-driven knob.** Adding a future agent with an app-managed lifecycle file is a `worktreeApp` declaration + a `register` function, no plumbing edit — the ADR 0031 payoff, extended to this seam.

## Alternatives considered

- **Seed `environment.toml` once (write-once, like `.codex/hooks.json`).** Rejected: the app regenerates the file, so a one-shot seed is clobbered and never reapplied — the two `script` keys would silently vanish. Re-emitting on refresh is the only shape that survives the app owning the file.
- **Unconditionally overwrite the two `script` keys on every refresh.** Rejected after the launch review: it self-heals app regeneration, but it also destroys a user's deliberate environment setup/cleanup commands. Ownership-aware writes keep the self-heal for absent or discern-default values without treating user customization as drift.
- **A new `worktree cleanup` (or `worktree remove`-style) verb for `[cleanup]`.** Rejected: `worktree teardown` already does cwd-based, no-stdin teardown through the shared core; a second verb would duplicate it and split the teardown logic the gate must keep coherent. (The Phase B brief assumed a new verb was needed; the live code already had one — trust the code over the brief.)
- **Overwrite the whole `environment.toml` (treat it as discern-owned).** Rejected: it is the app's file with the app's keys; a wholesale rewrite would drop `[[actions]]` and fight the app on every regeneration. Surgical, comment-preserving edits of only the two `script` keys co-exist with the app.
- **A required `worktreeApp` field on every provider.** Rejected: only Codex has such a file; a required field would force `none`-like declarations on agents that will never have one — the optional-capability shape (`hooks?`, `skillsDir?`) is the fit.
