--- type: types/instruction.md name: session-setup description: Scope the wikitool iteration budget to the task by exporting a stable session id before the first tool call. --- # Scope the session budget Every `wikitool` call is counted against a per-session iteration budget. A "session" is keyed by a fallback chain (`chemenu.session`): `WIKITOOL_SESSION_ID` first, then a harness's own session variable where one is registered (`CLAUDE_CODE_SESSION_ID` today), then the parent process id. Without an explicit id, and on a harness with no registered variable, the budget is scoped to whichever shell happened to run the command, so a task spanning several terminals is counted as several sessions - and one that reuses a shell inherits an unrelated count. ## Steps Run this **once per working session**, before the first `wikitool` call that is not exempt from the budget (see ยง Scope for what that means): ```bash export WIKITOOL_SESSION_ID="wiki-$(date +%s)" tools/wikitool sync ``` **An `export` only carries if the shell carries.** Several agent harnesses run every tool call in a freshly initialised shell: the working directory survives, shell state - environment variables, functions - does not, so the variable is gone by the next call and each call falls back to whatever the chain's next step resolves to. On a harness with a registered variable in that chain (Claude Code, via `CLAUDE_CODE_SESSION_ID`), the fallback already keeps every call in one bucket without this step - but it scopes to the *whole* harness session, not to this one task, so a long-running session can carry unrelated prior work into the same count. Setting `WIKITOOL_SESSION_ID` explicitly still narrows the bucket to the task at hand, and remains the only way to scope it at all on a harness with no registered variable - each call falls back to its own parent pid there, and neither the 60-call ceiling nor the loop-breaker can ever trip (measured directly on a real upgrade run: 33 `wikitool` calls in one task split into 21 telemetry buckets under the pid fallback alone). On such a harness, pass the id **inline on every call** instead of `export`, keeping the same value for the whole task: ```bash WIKITOOL_SESSION_ID="wiki-1234" tools/wikitool sync WIKITOOL_SESSION_ID="wiki-1234" tools/wikitool new entity --name "..." ``` Which of the three applies is answerable in one call: run `tools/wikitool budget status` twice in separate calls, and see whether it names the same id both times, and where that id came from - `budget status` prints both. Check the current state at any time with `tools/wikitool budget status`, which is never counted against the budget itself and prints the id it is counting under, and its origin (`WIKITOOL_SESSION_ID`, a named harness variable, or the parent-pid fallback). **Why `sync` here, not just at publish time.** `publish` already pulls before it pushes, but a session that runs many `wikitool` calls before its first `publish` (an ingest, a multi-page update) would otherwise build all of that work against whatever the local clone happened to hold when the session started - stale by however long the previous sync was, on a repo more than one machine or session writes to. Running `sync` first shrinks that window to the start of the session instead of discovering the drift only at the very end. `sync` fetches the remote and fast-forwards or rebases automatically when that is safe; it never commits and never pushes. **Exit 42 (rebase-review)?** Same as any exit 42 - read the diff it prints, judge whether it conflicts with what you are about to do, summarize that to the user, then `tools/wikitool sync --confirm-rebase ` before continuing. See [gates.md](gates.md). ## Multi-unit runs A task planned as several units - a tree ingest, where each unit produces its own source page and its own `publish` - takes one id per unit, derived from the workshop's run key: ```bash export WIKITOOL_SESSION_ID="ingest-documents-handbook/u3" ``` The run key, the workshop directory name and the session id are then the same string, so the checklist in `work//README.md` and the budget state cannot disagree about where the run stands. A new id may only be taken at a unit boundary recorded in `plan.md` - never after a gate refusal. See [gates.md](gates.md). ## Scope **The exemption is an allowlist, not "read-only" or "does not change the wiki."** A command needs this setup unless it is one of the dozen `tools/CONTRACT.md` marks exempt in its command table (`search`, `doctor`, `links show`, `cite id`, `budget status`, the read-only forms of `eval`, `version`, `migrate` and `upstream verify`) - that table, not a rule of thumb here, is the single list. One entry on it, `version regrade`, is exempt only in its bare listing form and counted when it is given positions to regrade; every other entry is exempt however it is called. `lint` is the case that breaks the "changes the wiki" reading: it only writes to `reports/`, which is gitignored, so it looks side-effect-free - but it is not on the allowlist and is counted like any mutating command. A skill that calls only exempt commands needs no session id; a skill that calls `lint` alone still does. The limits themselves, and what to do when one trips, are in [gates.md](gates.md).