# Safety model The tool assumes the model may request anything the schema allows, including hostile values. Four layers stand between a tool call and the repository. ## 1. Closed schema Parameters are a TypeBox union with `additionalProperties: false` on every variant. The host's own validation is treated as a hint: the tool re-parses the raw request from `unknown` and rejects unknown operations, unknown fields, missing fields, control characters, bidirectional marks, relative paths, and an `open` that names both a path and a branch. The field names each operation accepts are typed against the same schema, so the parser and the schema cannot drift apart. Branch names are length-bounded, must not start with `-`, and go through `git check-ref-format --branch`. The name Git prints back is the one that is used, because `--branch` expands shorthand such as `@{-1}`; validating one name and passing another would defeat every later check. A `base` must resolve to a commit, and the commit id it resolved to travels with it. A workspace id must match Herdr's shape: `w` and up to 16 letters or digits, such as `wG`. ## 2. Fixed argument arrays `git` and `herdr` are invoked with argument arrays. No shell command string is ever built, so there is nothing to quote or escape. Herdr's CLI does not accept `--flag=value`, so a value cannot be fused to its flag the way Graphite allows. Every value is therefore its own argument, and the parser rejects any value that starts with `-` before it is ever placed next to a flag. Each parser returns a distinct branded type, and a command is described as an action plus option-and-value pairs. Each pair names the option and the one branded type it accepts, so a label cannot travel under `--branch` and a plain `string` does not fit anywhere. Unparsed text cannot reach a spawned command. The repository root is parsed the same way when Git reports it. The builder that flattens a command into an argument list is the only way to reach `herdr`. It checks that the action is one of the four, that every option and switch is allowed for that action, that none appears twice, that `--focus` and `--no-focus` never travel together, and that every value still passes the syntax check its parser applied. The types make most of this redundant, so it stays as a second line of defence. ## 3. Preconditions and postconditions Herdr reports what it did. Git is asked independently whether the repository really changed that way. - `create` refuses a branch that is already checked out in a worktree and a path that is already a worktree, sits inside any worktree of the repository or its Git directory, or holds files. The tool may run from a linked worktree, where the main checkout is not the root Git reports, so the check covers every registered checkout. Afterwards the new checkout must appear in the Git worktree list, on the requested branch, marked by Herdr as a linked worktree, and at the commit `base` pointed at when it was parsed. - `open` must return the worktree that was asked for, and Git must list that path. - `remove` refuses a workspace that does not hold a linked worktree of this repository, refuses the main checkout, and requires user confirmation. Afterwards the path must be gone from the Git worktree list. - A result type that does not match the request, such as a `worktree_created` answer to an `open`, is treated as a failure. So is a non-zero exit that carries no error, because nothing in it explains the failure. The Git worktree list is read with `git worktree list --porcelain -z`, so every record ends with a NUL. A checkout path that contains a newline cannot pretend to be another record. If a check fails, the tool reports an uncertain result. It never retries a mutation. Mutations are serialized per repository inside the extension process, so two calls cannot race on the same worktree list. ## 4. Host approval and focus Oh My Pi resolves approval and concurrency from the operation: `list` is a shared read, everything else is an exclusive execute, and `remove` forces a prompt. A `create` that carries its own `path` prompts as well, because the caller, not Herdr, chose the directory to write to. Pi has no such mapping and uses its conservative sequential fallback. Focus is off by default. Every `create` and `open` passes `--no-focus` unless the caller sets `focus: true`, so background work does not move the user's view. The approval prompt is built before the request is parsed, so it shows raw values. Each value is put on one line and truncated first, so a value cannot forge the lines around it. ## Session detection The first tool call verifies that: 1. `HERDR_ENV` is `1`, which Herdr sets in every managed pane. Without it there is no session to talk to, and the tool must not reach into a session it is not part of. 2. The working directory belongs to a Git work tree with absolute repository paths. 3. `herdr --version` reports 0.8.2 or newer. The result is held in memory for ten minutes and never written to disk. A Herdr session is a property of the running process, not of the repository, so caching it in the repository would outlive the fact it records. Nothing in the repository changes. ## Abort handling Every child process receives the host's abort signal, so a cancelled tool call terminates `git` and `herdr` instead of leaving them running. Output is capped at 2 MB per command, and passing the cap is reported as a size limit rather than a missing executable.