dinostack-codex-wrap · diff
git:20261011.2e7a685 to git:20261012.5102812
5 added, 5 removed. Audit A to A.
---
name: dinostack-codex-wrap
description: Codex only. Close a DinoStack work session by safely consolidating context, memory, learnings, deferred work, and handoff state.
---
<!-- Generated by scripts/codex-skills.py. Do not edit directly. -->
Use this skill only in Codex. In another harness, do not execute its workflow or dispatch
instructions; use that harness’s DinoStack integration.
## Codex resource resolution
Before executing this skill, resolve the physical directory containing this loaded `SKILL.md`
(follow the installed skill-directory symlink) and bind it as `AE_SKILL_ROOT`. Set
`AE_CORE_SKILL_ROOT` to `resources` beneath that physical root and validate its
`.dinostack-skill.json` marker has `magic=DINOSTACK_CODEX_SKILL`, `adapter=codex`,
`name=dinostack-codex`, and `schema_version=1`. Resolve every logical resource through
the adjacent `RESOURCE-MAP.json`; reject missing, escaping, symlink-loop, or wrong-type targets.
Derive `AE_REPO_DIR` from the validated core marker plus its mapped `bin` resource and require the
repository signature (`content/SKILL.md`, `.codex`, and the dispatch helper); never fall back to
the process working directory. Bind `AE_PROJECT_DIR` to the absolute invoked project root before
changing directories (`git rev-parse --show-toplevel` when inside a repository, otherwise the
verified invocation directory). Project `.claude/**`, `.agentic/**`, `.gitignore`, QA, settings,
compression, and migration state resolve only beneath `AE_PROJECT_DIR`, never beneath
`AE_REPO_DIR`. Evaluate
`$AE_REPO_DIR/bin/ds-codex-dispatch runtime-bindings "<absolute-invocation-directory>"`
before any operational step. Require its `AE_REPO_DIR` and `AE_PROJECT_DIR` values to match the
independently validated paths above, then consume the same JSON object to bind
`AE_CODEX_CONFIG_DIR`, `AE_SHARED_CONFIG_DIR`, and `AE_ACTIVATION_CONFIG`; fail closed on any
mismatch. Map canonical filesystem tools to Codex filesystem reads, `rg --files`, `rg`, shell, and
`apply_patch`; ask one bounded direct question only after default derivation.
Derive `AE_SESSION_ID` by passing hook JSON to
`$AE_REPO_DIR/bin/ds-codex-session-id`. Native workflows are invoked with `$` syntax.
Other DinoStack workflows remain manual command resources loaded with
`$AE_REPO_DIR/bin/ds-codex-dispatch command <name>`; do not claim bare slash registration.
Codex `spawn_agent` accepts only `task_name`, `message`, and `fork_turns`. Put Tier and model intent
in the task brief or resolve it through role routing before the spawn; never pass Claude-only spawn
fields. When isolation is required, the conductor creates the worktree manually before spawning.
**Codex spawn contract.** Delegate with `spawn_agent` only. Before any spawn that needs an
isolated checkout, run the following from the invoked project root (`$AE_PROJECT_DIR`):
1. `git fetch origin`.
2. Resolve `BASE_BRANCH` with
`$AE_REPO_DIR/bin/ds-codex-dispatch base-branch "$AE_PROJECT_DIR"`. This applies the
canonical precedence: exactly one dedicated unfenced whole-line `BASE_BRANCH:` declaration in
project `AGENTS.md` (with an optional Markdown list prefix and optional `Declaration:` prefix),
then local `develop`, then local `development`. Multiple matching declarations are rejected as
ambiguous. If none exists, the helper fails closed; ask the operator whether to use `main`
(recommended, falling back to `master`) or establish a develop-based workflow, exactly as
required by the base-branch resolution protocol.
3. Choose a unique branch and absolute worktree path beneath `$AE_PROJECT_DIR/.agentic/worktrees/`.
4. Run `git worktree add "$AE_PROJECT_DIR/.agentic/worktrees/<branch>" -b "<branch>" "origin/$BASE_BRANCH"`.
5. Load the named role instructions with
`$AE_REPO_DIR/bin/ds-codex-dispatch agent <role>`.
6. Call `spawn_agent` with supported inputs (`task_name`, `message`, and `fork_turns`). Begin the
message with `Work only in the pre-created worktree <absolute-path>` and include the loaded role
instructions plus the execution contract. The spawned agent must use shell commands in that
worktree and must not edit the conductor checkout.
Codex spawns are asynchronous. The conductor remains responsive, uses the collaboration status and
wait operations to collect completion, and applies the existing review gates to the returned diff.
**Retire or hold each checkout at completion.** A carrier's work is complete when the review gates
on its output have returned, or the work is abandoned, and its agent will be sent no further work.
Then run `$AE_REPO_DIR/bin/ds-retire-carrier <absolute-path>`; it removes only the checkout, never
the branch, or records a hold whose revisit event you act on. Never wait for ticket completion or PR
merge.
Claude hook payload fields and Claude Task behavior do not apply on Codex.
# $dinostack-codex-wrap — On-Demand Session Context Enrichment
> Run the Activation preflight from `$AE_CORE_SKILL_ROOT/METHODOLOGY.md` before proceeding. If inactive, no-op and exit.
Use when you want a richer project-local handoff than the current Codex Stop continuity file provides - for example, before handing off complex in-progress work to a future session.
The current Codex Stop hook writes raw continuity only to `~/.codex/projects/[hash]/context.md` after a Stop event. It does not write project-local `$AE_PROJECT_DIR/.agentic/context.d/<session_id>.md`, does not write `$AE_PROJECT_DIR/.agentic/context.md`, and does not recompose a project-local rollup; migration is deferred to `context-writer-migration`. `$dinostack-codex-wrap` continues to write the richer project-local `$AE_PROJECT_DIR/.agentic/_wrap.md` handoff and populate AGENTS.md with durable decisions, conventions, stack details, and gotchas.
**Relationship to `wrap-ticket`.** `$dinostack-codex-wrap` is the on-demand richer session-summarization tool that targets AGENTS.md and `$AE_PROJECT_DIR/.agentic/_wrap.md` across an entire session and uses Skeptic review, plus root MEMORY.md in exactly one narrow bootstrap circumstance (the dangling-import stub seed during CLAUDE.md-to-AGENTS.md migration - see the pre-flight scaffold-accuracy check, item 1). The per-ticket Phase 11b `wrap-ticket` agent (see `$AE_REPO_DIR/content/agents/wrap-ticket.md`) is a constrained automated subset that fires on every PR opened by `$dinostack-codex-implement-ticket` — it appends to MEMORY.md, decisions.md, and `$AE_PROJECT_DIR/.agentic/_wrap.md` only, never touches AGENTS.md, and runs without Skeptic. They write to one overlapping file, `_wrap.md`; `wrap-ticket` is the ongoing content writer of `MEMORY.md`, not `$dinostack-codex-wrap`. `wrap-ticket` and `$dinostack-codex-wrap` MUST NOT hold `$AE_PROJECT_DIR/.agentic/wrap/lock` concurrently - `$dinostack-codex-wrap` acquires it directly for its own pre-flight; the CONDUCTOR acquires it on `wrap-ticket`'s behalf before every Phase 11b spawn (wrap-ticket itself has no Bash tool and never acquires the lock). If `$dinostack-codex-wrap` is invoked while the conductor holds the lock for a Phase 11b `wrap-ticket` spawn, `$dinostack-codex-wrap` waits per the standard lock-wait protocol below; if the conductor's Phase 11b bounded-wait acquisition attempt (`$AE_REPO_DIR/content/commands/ds-implement-ticket.md` Phase 11b) cannot acquire the lock because `$dinostack-codex-wrap` holds it, the conductor skips Phase 11b with `skipped_reason: "wrap-lock-contention"` WITHOUT ever spawning `wrap-ticket`, and proceeds without learnings capture (Phase 11b is non-blocking).
## Deferred background enrichment (daemon)
Manual `$dinostack-codex-wrap` is synchronous: there is no in-session auto-enrichment protocol. Background completion of forgotten wraps is performed by the deferred-wrap daemon (Claude-only, opt-in via the `deferred_wrap_daemon` toggle in `$AE_PROJECT_DIR/.agentic/config.json`), which headlessly resumes each cleanly-ended session and runs the non-interactive manual workflow 'ds-wrap-deferred' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-wrap-deferred` command. The daemon is the sole consumer of the per-session marker staged in Step 0a; see `$AE_REPO_DIR/content/references/conductor-operating-rules.md` for the daemon drain protocol.
## Your job (main agent)
**Pre-flight scaffold-accuracy check** (runs BEFORE Step 0). manual workflow 'ds-init-project' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-init-project` is the canonical scaffolding spec; $dinostack-codex-wrap uses it as the reference for "what this project should look like." Check for drift and auto-migrate the critical items inline:
**Sentinel short-circuit (evaluate before item 1).** On a steady-state project this pre-flight repeats the same ~15 filesystem probes on every `$dinostack-codex-wrap` invocation for no reason. Cache the result:
- **Watched-paths signature.** The signature covers exactly the paths this pre-flight inspects: the CLAUDE.md/AGENTS.md set (root + every track directory), the `$AE_PROJECT_DIR/.claude/context.md` / `$AE_PROJECT_DIR/.claude/memory.md` / `$AE_PROJECT_DIR/.claude/memory/` session-state paths and their `$AE_PROJECT_DIR/.agentic/` counterparts, the legacy config paths `$AE_PROJECT_DIR/.claude/{qa,deploy,findings,tracking,learnings}.md` and their `$AE_PROJECT_DIR/.agentic/` counterparts, the stub targets (`$AE_PROJECT_DIR/.agentic/tracking.md`, `$AE_PROJECT_DIR/.agentic/deploy.md`, `$AE_PROJECT_DIR/.agentic/learnings.md`), the docs dirs (`docs/overview/`, `docs/technical/`, `docs/planning/`, `docs/research/`), `$AE_PROJECT_DIR/.claude/settings.json`, `$AE_PROJECT_DIR/.claude/settings.local.json`, and `$AE_PROJECT_DIR/.gitignore`. For each watched path, record `path:exists:mtime` (mtime as epoch seconds, or the literal string `absent` when the path does not exist); sort the resulting lines lexicographically by path, join with newlines, and take the sha256 of the result — this is the **signature**.
- **Sentinel path:** `$AE_PROJECT_DIR/.agentic/wrap/.scaffold-verified` (atomic tmp + rename; `mkdir -p $AE_PROJECT_DIR/.agentic/wrap` immediately before the write - the lock-acquisition step's own `mkdir -p` has not run yet at this point in the flow, so a first-ever clean pass needs its own directory guarantee). This is machine-local runtime state, not committed - it is covered by manual workflow 'ds-init-project' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-init-project` Step 9's `$AE_PROJECT_DIR/.agentic/*` umbrella ignore (not individually enumerated - see `content/project-scaffolding.yml`).
- **At the start of this pre-flight:** if the sentinel exists and its stored signature is byte-equal to the freshly-recomputed signature above, SKIP items 1-3 and 5-6 below (the filesystem/migration probes) and proceed straight to Step 0a. Otherwise (sentinel absent, unreadable, or signature mismatch) run items 1-3 and 5-6 in full as described below.
- **Scope exclusion: item 4's release-signal-gated `$AE_PROJECT_DIR/.agentic/deploy.md` stub check is never covered by the sentinel.** That check is gated on release signals detected from session content, not on any filesystem path in the watched-paths signature above — a first-release session that touches only non-watched paths could byte-match the signature and, if this check were also skipped, never get the stub created. So run item 4's release-signal check (and only that check) on EVERY `$dinostack-codex-wrap` invocation regardless of whether the sentinel short-circuit fires for items 1-3/5-6. This is cheap — it is judged from session context already being surveyed, not a filesystem probe — so it does not reintroduce the cost this sentinel exists to avoid.
- **At the end of this pre-flight** (only reached when items 1-3/5-6 actually ran in full, i.e. the sentinel did not short-circuit them): write the sentinel with the freshly-recomputed signature ONLY when the pass reached a fully clean steady state — no CLAUDE.md → AGENTS.md migration performed (item 1), no `$AE_PROJECT_DIR/.claude/` → `$AE_PROJECT_DIR/.agentic/` migration performed (item 2), no legacy config migration performed (item 3), no stub created by item 4's non-release-signal checks, no silent auto-fix applied (item 5), AND no "drift that cannot be auto-fixed" recorded (item 6). If the pass did ANY work or recorded ANY unfixable drift, do NOT write the sentinel — the next `$dinostack-codex-wrap` run must re-run the full pre-flight and re-log the drift so it keeps surfacing under "Watch Out For" until resolved. This conservative rule guarantees the short-circuit only ever fires on a genuinely clean, unchanged scaffold and never suppresses a real drift bullet.
1. **CLAUDE.md → AGENTS.md migration** (per-file, recursive through tracks). For each `CLAUDE.md` in the project (root + every track directory) where a sibling `AGENTS.md` does not already exist:
- `cp <dir>/CLAUDE.md <dir>/AGENTS.md` to preserve content.
- **Root directory:** overwrite `<dir>/CLAUDE.md` with `@AGENTS.md` followed by these two lines byte-exact:
<!-- dinostack:memory-main-only -->
- MEMORY.md (durable facts, operator rulings) is not auto-loaded. Main session: a DinoStack SessionStart hook delivers it; if it is absent or truncated, Read all of it before any other tool call, whatever the task. Subagents: grep on demand (`grep -n -i '<topic>' MEMORY.md`); if your worktree lacks it, grep the primary checkout's copy, never Read it (Read is denied there).
+ MEMORY.md (durable facts, operator rulings) is not auto-loaded. Main session: a DinoStack SessionStart hook delivers it whole, or its head plus a directive for the rest - follow that directive; if neither is in context, grep on demand and Read only bounded windows around hits, never the whole file at once. Subagents: grep on demand (`grep -n -i '<topic>' MEMORY.md`); if your worktree lacks it, grep the primary checkout's copy, never Read it (Read is denied there).
so Claude Code loads the migrated file while `MEMORY.md` reaches only the main session via DinoStack's SessionStart hook. Exception: when `CLAUDECODE=1` and `session-start-memory.py` is absent from `${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json`, write `@AGENTS.md` then `@MEMORY.md` instead and add the Watch Out For line `Legacy @MEMORY.md import written: run the DinoStack installer, then manual workflow 'ds-init-project' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-init-project`, to make MEMORY.md main-session-only.` Either way, per Atomic write discipline <!-- aw-site: claude-md-root --> (unconditional publish). Apply the dangling-import guard: if root `MEMORY.md` does not exist, seed it with the manual workflow 'ds-init-project' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-init-project` Step 8 stub before writing the import (consistent with this preflight's existing silent-stub-creation pattern in item 4), per Atomic write discipline <!-- aw-site: memory-md-seed --> (conditional publish - never overwrite an existing `MEMORY.md`).
- **Track directories:** overwrite `<dir>/CLAUDE.md` with the single line `@AGENTS.md` only - tracks do not have their own `MEMORY.md`, so no `@MEMORY.md` import is added, per Atomic write discipline <!-- aw-site: claude-md-track --> (unconditional publish).
- - Skip directories where `AGENTS.md` already exists (leave `CLAUDE.md` untouched).
+ - Skip directories where `AGENTS.md` already exists (leave `CLAUDE.md` untouched), with one root exception: if root `CLAUDE.md` has the marker line above and the line after it starts with `MEMORY.md (durable facts, operator rulings) is not auto-loaded.` but differs from the pointer line above (a stale pointer from an earlier template), replace that one line with the pointer line above.
2. **`$AE_PROJECT_DIR/.claude/` → `$AE_PROJECT_DIR/.agentic/` session state migration.** If `$AE_PROJECT_DIR/.claude/context.md` exists and `$AE_PROJECT_DIR/.agentic/context.md` does not:
- `mkdir -p $AE_PROJECT_DIR/.agentic`
- `mv $AE_PROJECT_DIR/.claude/context.md $AE_PROJECT_DIR/.agentic/context.md`
- Same for `$AE_PROJECT_DIR/.claude/memory.md` and `$AE_PROJECT_DIR/.claude/memory/` (the auto-memory dir).
- Redo symlinks in `$AE_SHARED_CONFIG_DIR/projects/[hash]/` to point at the new `$AE_PROJECT_DIR/.agentic/` paths.
3. **Legacy config migration (`$AE_PROJECT_DIR/.claude/<name>.md` → `$AE_PROJECT_DIR/.agentic/<name>.md`)** — for each of `qa.md`, `deploy.md`, `findings.md`, `tracking.md`, `learnings.md`:
- **Both paths exist on disk**: do NOT migrate. Log a drift warning in the wrap run output (e.g. "Drift (both $AE_PROJECT_DIR/.claude/findings.md and $AE_PROJECT_DIR/.agentic/findings.md exist - skipping auto-migration; resolve manually via manual workflow 'ds-init-project' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-init-project`)"), and add a bullet under the `_wrap.md` "Watch Out For" section naming the conflicting files. Skip to the next name.
- **Only legacy `$AE_PROJECT_DIR/.claude/<name>.md` exists**: first, run `git status --porcelain` to check working-tree cleanliness. If there are staged or unstaged changes, do NOT migrate - log a drift note ("Skipped migration of legacy $AE_PROJECT_DIR/.claude/<name>.md: working tree dirty. Commit or stash, then re-run $dinostack-codex-wrap or manual workflow 'ds-init-project' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-init-project`.") and add a Watch Out For bullet. If the working tree is clean, migrate: `git mv $AE_PROJECT_DIR/.claude/<name>.md $AE_PROJECT_DIR/.agentic/<name>.md`. Log the move to the wrap run output only.
- **Only `$AE_PROJECT_DIR/.agentic/<name>.md` exists**: no action.
- **Neither exists**: no action at this step - the missing-stub creation below handles creation.
4. **Missing-stub creation.** If any of `$AE_PROJECT_DIR/.agentic/tracking.md`, `$AE_PROJECT_DIR/.agentic/deploy.md` (only when release signals detected), or `$AE_PROJECT_DIR/.agentic/learnings.md` is missing (checked via resolver: `$AE_PROJECT_DIR/.agentic/<name>.md` preferred, legacy `$AE_PROJECT_DIR/.claude/<name>.md` fallback), create a stub at `$AE_PROJECT_DIR/.agentic/<name>.md` per the template in manual workflow 'ds-init-project' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-init-project` Steps 6a-6d, per Atomic write discipline <!-- aw-site: stub-creation --> (conditional publish - skip if the stub already exists). For `$AE_PROJECT_DIR/.agentic/learnings.md`, use the template from manual workflow 'ds-init-project' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-init-project` Step 8 (checked on every run, not gated on release signals). **The `$AE_PROJECT_DIR/.agentic/deploy.md` release-signal check runs every `$dinostack-codex-wrap`, even when the sentinel short-circuit skips the rest of this item and items 1-3/5-6** — see the "Scope exclusion" note above.
5. **Silent auto-fix for remaining drift.** $dinostack-codex-wrap is silent and hands-off. For any drift $dinostack-codex-wrap can fix without user input, fix it inline:
- Create `docs/overview/`, `docs/technical/`, `docs/planning/`, `docs/research/` (with `.gitkeep`) if missing.
- Create `$AE_PROJECT_DIR/.claude/settings.json` (`{}`) if missing, per Atomic write discipline <!-- aw-site: settings-json --> (conditional publish).
- Create `$AE_PROJECT_DIR/.claude/settings.local.json` with `autoMemoryDirectory` set to `$AE_PROJECT_DIR/.agentic/memory` if missing or if the key is not yet present (merge rule: never overwrite an existing value). **Scope note:** `autoMemoryDirectory: $AE_PROJECT_DIR/.agentic/memory` is intentional - it routes Claude Code's native auto-memory writes to a local gitignored scratch area. The canonical conductor-managed, human-reviewed durable-facts store remains `<cwd>/MEMORY.md` (see the **Memory path (MEMORY.md)** note below).
- Create `$AE_PROJECT_DIR/.gitignore` entries for `$AE_PROJECT_DIR/.claude/settings.local.json` and the `$AE_PROJECT_DIR/.agentic/` runtime-artifact block (per manual workflow 'ds-init-project' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-init-project` Step 9) if missing, per Atomic write discipline <!-- aw-site: gitignore --> (unconditional publish - the file itself normally exists; only the entries are conditional, so read current content, add any missing entries, and always write the result back).
- **Pre-AGENTS.md layout detection (DO NOT auto-split inline).** If root `AGENTS.md` is absent AND root `CLAUDE.md` exists with more than the `@AGENTS.md`/`@MEMORY.md` import lines and the memory marker and pointer lines, do NOT attempt the Worker+Skeptic three-way split inline - that migration requires user confirmation of the proposed split, and $dinostack-codex-wrap's silent contract cannot provide one. Instead, add a "Watch Out For" entry in `_wrap.md`: `Pre-AGENTS.md layout detected (CLAUDE.md has real content, no root AGENTS.md). Run manual workflow 'ds-init-project' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-init-project` to run the Worker+Skeptic split and migrate.`
6. **Drift that cannot be auto-fixed.** If any drift requires user input (e.g. Linear workspace slug, Jira base URL, confirmation of release commands, selection among multiple detected web UIs), do NOT prompt during $dinostack-codex-wrap. Instead, record a bullet under "Watch Out For" in the `_wrap.md` output noting which scaffolding items are still incomplete. The user can address these later by running manual workflow 'ds-init-project' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-init-project` interactively. Specific drift kinds that always require user input and must be listed here:
- **CLAUDE.md split** — the pre-AGENTS.md migration requires the user to review and accept the three-way split (AGENTS.md / residual CLAUDE.md / MEMORY.md). $dinostack-codex-wrap cannot perform this silently; it points at manual workflow 'ds-init-project' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-init-project`.
- Linear workspace slug or QA assignee UUID not yet set when `## Linear` is present.
- Jira `JIRA_BASE_URL`, `TICKET_PREFIX`, or transition name not yet set when `## Tracker` is present.
- Release command / rollback procedure confirmation when `$AE_PROJECT_DIR/.agentic/deploy.md` has TODO placeholders.
- Choice among multiple detected web UIs for `$AE_PROJECT_DIR/.agentic/qa.md` in a multi-track project.
All steps are silent on success. Log each migration action taken (e.g. "Migrated admin/CLAUDE.md to admin/AGENTS.md + pointer") to the wrap run output only, not as user prompts. After completing items 1-6, apply the sentinel write rule from the "Sentinel short-circuit" note above (write `$AE_PROJECT_DIR/.agentic/wrap/.scaffold-verified` with the fresh signature only on a fully clean pass; otherwise leave it unwritten). After preflight completes (whether via the sentinel short-circuit or the full run), proceed to Step 0a.
**Pre-flight check — no active Workers.** Before doing anything else, check whether any background Workers or subagents are currently running. If any are, stop and tell the user: "Cannot run $dinostack-codex-wrap while background tasks are active. Please wait for them to finish (or stop them) first." Do not proceed until confirmed.
**Pre-flight lock acquisition.** `$dinostack-codex-wrap` writes several shared project-local files (`_wrap.md`, memory.md, AGENTS.md, compression-state.json, rolling snapshots). It does not write `$AE_PROJECT_DIR/.agentic/context.md`. The current Codex Stop hook writes only `~/.codex/projects/[hash]/context.md`; automatic project-local rollup integration is deferred to `context-writer-migration`. Concurrent `$dinostack-codex-wrap` runs in the same project would clobber the files `$dinostack-codex-wrap` does own. Acquire a project-local lock before proceeding:
1. Ensure `$AE_PROJECT_DIR/.agentic/wrap/` exists (`mkdir -p $AE_PROJECT_DIR/.agentic/wrap`).
2. Attempt acquisition in the FOREGROUND via the acquire helper, single attempt, no wait: `ds-wrap-acquire-lock "$cwd" --role=agent --no-wait --session-id="$AE_SESSION_ID"`. This replaces the old hand-rolled `mkdir $AE_PROJECT_DIR/.agentic/wrap/lock` plus a separate owner-file write: the helper publishes the lock directory and BOTH owner artifacts (the legacy `owner` 2-line body and the schema-validated `owner.json` descriptor) atomically and fail-closed, inside one process - so the window in which the lock directory exists without an owner is eliminated, not merely narrowed - and the descriptor comes from one code path shared with the daemon rather than shell string-formatting. Run it synchronously (foreground): it is a single `mkdir` plus two `rename` calls with no poll loop and no `sleep`, so this is not the long-foreground-poll failure mode, and it must be synchronous because the conductor needs the result before deciding what to do next. Branch on exit code:
- **0** - lock acquired. Proceed.
- **5** - busy (lock already held by someone else). Go to step 3 below.
- **1** - fatal (lib failed to load, or an invalid `--role`). Surface the WARNING line verbatim, then abort.
- **any other exit code** (including 127, the shell's command-not-found code) - surface it verbatim and abort. Never fall through to "proceed" on an unrecognised code.
If the command is not found on PATH at all, abort with a message naming the install step - do NOT fall back to a manual `mkdir`, which would produce a lock directory with no `owner.json` and forfeit the daemon-side live-lock protection this whole design depends on. Word the message harness-neutrally, since this file is shared across adapters: "`ds-wrap-acquire-lock` not found on PATH - re-run your harness's DinoStack install script (`<repo>/.claude/install.sh` for Claude Code, the equivalent script under your adapter directory otherwise) to wire `bin/` onto PATH."
3. **On busy: do not read the owner file, do not classify it, and do not abort.** There is no separate unreadable-owner or stale-owner branch to evaluate here - both are progress conditions the helper itself handles, not give-up conditions for the conductor to decide. Start the helper with Codex `exec_command`; if `exec_command` yields a session ID, keep the conductor responsive and poll that session with `write_stdin` until completion; if it returns a terminal result, branch on that result immediately, letting it wait: `ds-wrap-acquire-lock "$cwd" --role=agent --session-id="$AE_SESSION_ID"`. It polls in-process (jittered ~5s interval) and exits once the lock is acquired or after 20 minutes; non-blocking, so the conductor stays available until notified of completion. On the completion notification, branch on exactly three outcomes:
- **0** - acquired; the lock is now held by $dinostack-codex-wrap. Proceed normally.
- **2** - timeout. Report the helper's final `timeout ...` line verbatim (it already carries role, pid, start time, and an `rm -rf` recovery command). Then abort.
- **1** - unexpected failure. Surface the WARNING line from stdout, then abort.
- **any other exit code** - surface it verbatim, then abort.
Exit codes 3 (unreadable owner) and 4 (stale-needs-manual) no longer exist, deliberately: an unreadable owner and an old-looking timestamp are progress conditions, not give-up conditions. A lock whose owner looks old may be held by a $dinostack-codex-wrap that is legitimately still working - the timestamp alone cannot distinguish that from an abandoned lock, so the waiter keeps waiting and only tells the operator how to unwedge it manually (the HINT's `rm -rf` command) if the operator judges it stuck.
4. Liveness is the authoritative signal, not the timestamp. The helper determines whether the lock's owner is a live process wherever the owner record carries a genuine, checkable PID; the timestamp is used only where the owner record is PID-blind (e.g. a legacy 2-line body from an interactive $dinostack-codex-wrap, whose recorded PID is a shell that has already exited by the time the file lands), and even there it is advisory, not decisive. The conductor does not run its own `ps -p` check - it relies entirely on the helper's exit code.
5. **`--session-id` is the liveness signal for an interactive hold, and it is why an abandoned lock is no longer immortal.** A `--role=agent` descriptor carries `pid: null` by construction, so there is no process to liveness-check; without a session id the helper's verdict for such a lock is `live` FOREVER. Pass `--session-id="$AE_SESSION_ID"` at BOTH acquisition sites above, keeping the flag on the SAME LINE as the invocation. Use `AE_SESSION_ID` only. It is the Codex session binding derived by passing hook JSON to `$AE_REPO_DIR/bin/ds-codex-session-id`. The flag is soft: an unset variable expands to an empty value, the helper publishes `session_id: null`, acquisition still succeeds, and the lock falls back to a pid-blind age rule - degraded, never stealing a live hold. If session-ID derivation yields no identifier, the documented empty-value fallback applies.
**Self-heal on acquire.** Before each attempt the helper clears a lock it can PROVE abandoned - a `role:agent` hold whose session stopped heartbeating past 30 minutes, or any PID-blind hold older than 4 hours - and prints `cleared-abandoned-lock <path>` when it does. This is the only case in which the helper removes a lock, and it is announced, never silent. An old-but-live hold is still waited on exactly as before. The default config (`deferred_wrap_daemon: false`) never launches the daemon that owns the other clear path, so without this the operator was the only recovery mechanism.
The 30-minute mark is advisory only: it changes the *message* the waiter prints (adding the `rm -rf` HINT to the "waiting"/"timeout" line past that age), never the *decision* to keep waiting.
**Lock release is mandatory on every exit path.** The lock dir MUST be removed (run `ds-wrap-release-lock` — the PATH-wired helper that releases `$AE_PROJECT_DIR/.agentic/wrap/lock`) before $dinostack-codex-wrap returns control to the user, on ALL of:
- successful completion at Step 6;
- escalation to the user at Step 3 (format re-invocation limit or contested finding);
- compression failure or escalation at Part E;
- any user-abort path (e.g. drift requiring input, Skeptic scope bail).
If $dinostack-codex-wrap aborts before the lock is acquired (e.g. at the active-Workers check above, or because a live or stale lock was detected and the command aborted without acquiring), no lock was acquired and no release is needed.
**Pre-flight path check:** Confirm `$AE_PROJECT_DIR/.agentic/` exists or can be created. The $dinostack-codex-wrap skill now writes project-local under `$AE_PROJECT_DIR/.agentic/` instead of the legacy `$AE_SHARED_CONFIG_DIR/projects/[hash]/` hashed directories. No disambiguation needed - one canonical location per project.
## Deferred-enrichment data model
This section is the source of truth for the Claude-only deferred-wrap marker schemas. The Claude Stop hook `$AE_REPO_DIR/hooks/stop-context.js`, the OpenCode plugin, and the deferred-wrap daemon consume those schemas. The current Codex Stop hook writes only `~/.codex/projects/[hash]/context.md` and does not stage these project-local markers; migration is deferred to `context-writer-migration`.
**1. `$AE_PROJECT_DIR/.agentic/wrap/pending-<session_id>.json` (the per-session enrichment marker).** One marker per session, keyed by `session_id` in the filename so concurrent sessions never collide. Staged when a session has substantive un-wrapped work, so the daemon (or the next session in that project) completes enrichment idempotently. Schema:
{
"schema_version": 3,
"session_id": "<uuid of the session that staged the marker>",
"staged_at": "<ISO8601 UTC, immutable, FIFO key>",
"status": "pending | ready | in_progress | done | gave_up",
"claimed_by": "<pid/uuid of the claimant currently running enrichment, or null>",
"claimed_kind": "session | daemon | null",
"claimed_at": "<ISO8601 UTC of last claim, or null>",
"attempts": "<int, 0..3>",
"project_root": "<absolute cwd>",
"last_error": "<short string or null>"
}
- `status` lifecycle: `pending` (staged on a Stop turn, not yet finalized) -> `ready` (finalized by a genuine terminal SessionEnd; the SOLE `pending -> ready` transition - there is NO stale-sweep) -> `in_progress` (claimed, enrichment running) -> `done` (completed; marker RETAINED as a `wrapped_at`-stamped tombstone that suppresses same-session re-staging and is reaped by the janitor after ttl) | `gave_up` (`attempts >= 3`; marker retained with a manual-`$dinostack-codex-wrap` notice). Only a `ready` marker is daemon-claimable; an open/idle session leaves its marker `pending` and is never auto-resumed.
- Dropped vs schema_version 1/2: `branch` and `head_sha` (the daemon enriches in the main project dir, so git-state reflects the live tree; enrichment is conversation-driven, not snapshot-driven).
- `claimed_kind` records who holds the claim: `session` (a manual `$dinostack-codex-wrap` Step 0a) or `daemon` (the background wrap daemon). Daemon-startup reclaim acts ONLY on `claimed_kind: "daemon"` markers (MAJOR-C).
- `staged_at` is immutable and is the FIFO ordering key the daemon uses to drain `ready` markers oldest-first. `claimed_at` plus a staleness window are a wastefulness reducer, not a correctness invariant - they make a double-claim rare, never impossible; idempotency is what makes a double-run safe.
- `attempts` increments at claim time, before enrichment begins, so a crash mid-enrichment still counts toward the give-up budget.
**2. `$AE_PROJECT_DIR/.agentic/wrap/last-wrap` (the wrap-recency sentinel).** This project-local sentinel is written only after a successful `$dinostack-codex-wrap` Part A write. Claude and OpenCode marker consumers use it for staging suppression. The current Codex Stop hook writes only `~/.codex/projects/[hash]/context.md` and does not consume this sentinel; migration is deferred to `context-writer-migration`.
**3. `$AE_PROJECT_DIR/.agentic/wrap/deferred-activity.jsonl` (the spillover log).** **No longer produced.** The historical record schema and the drain procedure are NORMATIVE in `$AE_REPO_DIR/content/references/wrap-context-format.md` §"Spillover-drain procedure" - not restated here.
**Pinned header prefix (NORMATIVE).** NORMATIVE in `$AE_REPO_DIR/content/references/wrap-context-format.md` §"Pinned header prefix (NORMATIVE)" - the byte-exact prefix, the date-format caveat, and the matcher contract are not restated here.
**Step 0a - Stage the deferred-wrap safety-net** (runs BEFORE Step 0).
`$dinostack-codex-wrap` is synchronous: it runs the body inline and returns control only after Step 6 completes. Step 0a stages a per-session marker that is consumed by the deferred-wrap DAEMON, not by any in-session pipeline - so that if THIS session is later force-killed or ends without finishing a manual `$dinostack-codex-wrap`, the daemon can complete enrichment headlessly. Staging is GATED: it runs ONLY on the Claude host with the daemon enabled and not inside a daemon run.
**Claude-host + opt-in + non-daemon guard (MAJOR-1).** Wrap both the toggle read and the marker staging in this guard. Off-Claude (no `$AE_PROJECT_DIR/.agentic/wrap/claude-host` sentinel - the sentinel is written only by the Claude SessionStart hook and `$AE_PROJECT_DIR/.claude/install.sh`), toggle off, or inside a daemon run (`AGENTIC_WRAP_DAEMON=1`) -> stage NOTHING, and `$dinostack-codex-wrap` runs byte-identical to the classic synchronous wrap (no marker, no daemon involvement, exactly today's pre-feature behavior):
# Claude-host + opt-in + non-daemon guard. Off-Claude (no $AE_PROJECT_DIR/.agentic/wrap/claude-host sentinel),
# toggle off, or inside a daemon run -> stage nothing, $dinostack-codex-wrap runs byte-identical to today.
if [ -f "$cwd/.agentic/wrap/claude-host" ] && [ "$AGENTIC_WRAP_DAEMON" != "1" ] && <deferred_wrap_daemon toggle is true in $AE_PROJECT_DIR/.agentic/config.json>; then
<stage the per-session pending-<session_id>.json marker (per the schema below)>
fi
When the guard passes, stage `$AE_PROJECT_DIR/.agentic/wrap/pending-<session_id>.json` (atomic tmp + rename) per the per-session schema_version 3 marker in the Deferred-enrichment data model section above, with:
- `schema_version: 3`, `session_id: <this session_id>`, `staged_at: <now, ISO8601 UTC>`, `status: "pending"`, `claimed_by: null`, `claimed_kind: null`, `claimed_at: null`, `attempts: 0`, `project_root: <absolute cwd>`, `last_error: null`.
The marker is keyed by `session_id` in its filename, so per-session markers never collide. If this session's own marker already exists with status `pending`, `ready`, or `in_progress`, do NOT overwrite it (MAJOR-3 `ready`-non-stageable). A marker with `status: gave_up`, or `done` WITHOUT a `wrapped_at` stamp, is not a completion tombstone; staging may proceed. A `done` marker WITH `wrapped_at` is a completion tombstone for THIS session and suppresses re-staging (the `last-wrap`-rollover guard). With the guard false (off-Claude, toggle-off, or under the daemon guard), `$dinostack-codex-wrap` stages no marker at all and behaves exactly as the classic synchronous wrap.
**Step 0a does NOT write `$AE_PROJECT_DIR/.agentic/wrap/last-wrap`.** `last-wrap` is written only after a successful Part A `_wrap.md` write (see Part A). Writing it here would suppress this very session's own recovery marker on the next Stop-hook fire.
Tell the user: "Writing enriched session context — I'll let you know when it's done."
**Step 0-pre - Fast zero-substance short-circuit** (runs AFTER Step 0a, BEFORE Step 0).
Step 0 (below) unconditionally reads the root + track AGENTS.md files in full, reads `$AE_PROJECT_DIR/.agentic/compression-state.json` in full, and runs a `gh pr list` network call - all before Step 0.5 ever gets to decide the session was zero-substance. Step 0-pre detects the unambiguous zero-substance case earlier so those expensive reads and the network call can be skipped entirely. This is purely a hoist of a determination Step 0.5 already makes - it does NOT change the zero-substance criteria, only detects the unambiguous case earlier. (`$AE_PROJECT_DIR/.agentic/learnings.md`'s full content is no longer read unconditionally in Step 0 at all - see the note at Step 0's learnings.md bullet below.)
1. **Run the work-loss safety net now, unconditionally:** `git status --porcelain` and `git stash list`. This check NEVER short-circuits away, on any path - it is the safety net against lost uncommitted work (same rationale Step 0 states below: "critical for preventing work loss"). Capture the raw output of both commands. If Step 0-pre falls through to the full Step 0 (below), Step 0 REUSES this captured output - do not re-run either command there. **In the same batch, also capture** (once, for reuse by every later step that would otherwise re-probe): whether `$AE_PROJECT_DIR/.agentic/memory.md` exists and is non-empty (`test -s`), its entry count if non-empty, the byte size (`wc -c`) of each Part E target (root `<cwd>/MEMORY.md`, `$AE_PROJECT_DIR/.agentic/memory.md` if present, `<cwd>/CLAUDE.md`), and the content of `$AE_PROJECT_DIR/.agentic/compression-state.json` if present. The later `$AE_PROJECT_DIR/.agentic/compression-state.json` read under Step 0, and the "Reuse `git status --porcelain` and `git stash list`" note under Step 0, both consume these captured values instead of re-probing.
2. From the conversation/tool-call history, determine whether the session performed ANY file-mutating tool calls (Edit, Write, NotebookEdit) or git commits.
3. Check whether any specialist agent (`perf-analyst`, `release-orchestrator`, `dependency-auditor`) ran this session with session-scoped issues to capture - the same check Step 0.5's zero-substance criteria make, evaluated here from context already in hand (no new mechanism).
4. **Short-circuit directly to the Step 0.5 "Zero-substance procedure"** - skipping the remainder of Step 0 (the AGENTS.md/compression-state.json reads) and the `gh pr list` call entirely - ONLY when ALL of the following hold unambiguously:
- No file-mutating tool calls and no git commits this session (item 2).
- The `git status --porcelain` output from item 1 shows no tracked changes (no M/A/D entries; untracked-only `??` output does not disqualify).
- The `git stash list` output from item 1 shows no new stashes.
- No specialist agent ran with session-scoped issues to capture (item 3).
- **No stable fact, architectural decision, or convention was established this session that Output 2 (memory entries) would capture** - mirroring the zero-substance path's own Output-2 criterion under Step 0.5, evaluated here from conversation context already in hand rather than from Step 0's stable-facts survey. A discussion-only session that reached an architectural decision with zero file edits does NOT qualify for the short-circuit even though it trips every other gate above - this bullet exists specifically to catch that case.
- **Output 3 (AGENTS.md updates) would be "None" for every file AND no new AGENTS.md candidates exist** - evaluated here from conversation context already in hand, the same way the Output-2 bullet above is.
- No meaningful next steps to record - the conductor judges this the same way Step 0.5 already does: if the only meaningful session output is "answered a question," the case qualifies.
- `$AE_PROJECT_DIR/.agentic/memory.md` (the DS-90 staging area) is absent or empty, per the Step 0-pre item 1 capture above - a non-empty staging area disqualifies the short-circuit, because Part B step 0's staging drain must run.
- No Part E compression target is over its size gate, per the Step 0-pre item 1 capture above - a target already over-gate disqualifies the short-circuit, because Part E must run to compress it.
5. **On ANY uncertainty, fall through to the full Step 0** below rather than short-circuiting - this includes any doubt about whether a stable fact or decision surfaced that Output 2 would capture. This is the same escape-hatch spirit as the "Escape hatch for zero-substance path" note under Step 0.5: when in doubt, do NOT take the fast path.
If the short-circuit fires: go straight to the **Zero-substance procedure** under Step 0.5 below (which still runs Step 5 worktree cleanup and the Step 6 confirmation as normal); do not run Step 0 or the rest of Step 0.5's routing checks.
If the short-circuit does not fire: proceed to Step 0 below, which reuses the `git status --porcelain` / `git stash list` output captured in item 1 above.
**Step 0 — Compile session data** (inline, no subagent needed).
Survey the current conversation and note down:
- The main task and its current state (done? blocked? in progress?)
- All files touched or created this session (from tool call history — be specific: full paths)
- Any errors, gotchas, or near-misses that surfaced
- Specific remaining next steps (file paths, branch names, commands, open PRs — concrete enough to act on without re-reading the chat)
- Tools used during the session
- Stable project facts worth preserving: setup commands that don't change, persistent project-wide gotchas or quirks, architectural decisions made, recurring patterns or conventions established. Distinguish these from temporary state (current task, files touched this session) - conductor-behavioral stable facts go into MEMORY.md, setup commands and task gotchas a subagent needs go into the AGENTS.md output, temporary state into `_wrap.md` only.
- Identify the project root (absolute cwd).
- Note which tracks (subdirectories) had files touched this session — these are candidates for AGENTS.md updates, and their AGENTS.md paths feed the batch below.
**Batch the independent shell/existence/network checks into one Bash invocation.** Rather than issuing separate round-trips, gather everything below in a SINGLE heredoc script:
- **Existence probes** (no content reads yet) for: `$AE_PROJECT_DIR/.claude/compression-state.json`, `$AE_PROJECT_DIR/.agentic/compression-state.json`, `$AE_PROJECT_DIR/.agentic/learnings.md`, the root `AGENTS.md`, and each `[track]/AGENTS.md` for tracks that had files touched this session.
- **Open-PR overlap query** via `gh pr list` (exact command below) — on `gh` unavailable or an error, log "open-PR overlap check skipped (gh unavailable)" and treat the result as an empty set.
```bash
REPO_CWD="<absolute cwd>"
CURRENT_BRANCH=$(git -C "$REPO_CWD" branch --show-current)
PROBE_PATHS=(
"$REPO_CWD/.claude/compression-state.json"
"$REPO_CWD/.agentic/compression-state.json"
"$REPO_CWD/.agentic/learnings.md"
"$REPO_CWD/AGENTS.md"
# add one "$REPO_CWD/<track>/AGENTS.md" line per track touched this session
)
for p in "${PROBE_PATHS[@]}"; do
if [ -e "$p" ]; then echo "EXISTS $p"; else echo "MISSING $p"; fi
done
if command -v gh >/dev/null 2>&1; then
gh pr list --state open --base "$CURRENT_BRANCH" --json number,headRefName,files \
--jq '.[] | {n: .number, branch: .headRefName, files: [.files[].path]}' \
2>/dev/null || echo "GH_PR_LIST_FAILED"
else
echo "GH_UNAVAILABLE"
fi
```
A `#`-comment left unmodified inside the `PROBE_PATHS=( ... )` array literal above is harmless (bash array literals tolerate comment lines between elements) - unlike a `#`-comment inside a backslash-continued `for p in ... \` list, which would orphan the following `; do` into a syntax error. Populate `PROBE_PATHS` with one array element per touched-track `AGENTS.md` path before running.
**Fallback on batch failure.** If the batch script itself errors (non-zero exit from the script as a whole, or an environment where heredoc execution is unavailable), fall back to issuing the existence probes and the `gh pr list` query as individual calls - the pre-optimization behavior. A batch failure must never take down Step 0's data gathering; it only costs the round-trips this batching was meant to save.
Do NOT re-run `git status --porcelain` or `git stash list` here — this batch excludes them by design; reuse the output already captured in Step 0-pre (Step 0 only runs at all when Step 0-pre fell through to the full path).
Using the batch results:
- **Check for and read: the root `AGENTS.md`** (if the existence probe found it), **and any `[track]/AGENTS.md`** files in subdirectories that had files touched this session (if their existence probes found them). Record their full current content — this will be passed to the Worker as a dedicated field so it can avoid duplicating what is already captured.
- **Migrate `$AE_PROJECT_DIR/.claude/compression-state.json` → `$AE_PROJECT_DIR/.agentic/compression-state.json`** if the batch probe found `$AE_PROJECT_DIR/.claude/compression-state.json` AND did NOT find `$AE_PROJECT_DIR/.agentic/compression-state.json`: `mv $AE_PROJECT_DIR/.claude/compression-state.json $AE_PROJECT_DIR/.agentic/compression-state.json`. Log the move to the wrap run output only.
- **Read `$AE_PROJECT_DIR/.agentic/compression-state.json`** if the batch probe (or the migration above) confirms it exists - **reuse the content already captured in Step 0-pre item 1 rather than re-reading it here.** Record its full current content — this will be passed to Part E later to determine whether compression is needed for each target.
- **Do NOT read `$AE_PROJECT_DIR/.agentic/learnings.md` here** - only its existence was probed above (see the batch script). Only the standard path, and the zero-substance path's staging-drain exception (Step 0.5), ever spawn a draft Worker that needs this content; the light path and the ordinary zero-substance path never do. Reading it unconditionally in Step 0 would pay a large read (the file is a growing, unbounded knowledge log) on every route including the two that discard it unused. The full content is read later, immediately before spawning a draft Worker, per the note at Step 1 below - conditioned on the route already determined by Step 0.5, not on a re-evaluation of Step 0.5's criteria. (Tripwire for a future editor: "the light path never spawns a draft Worker" holds only because the light-path trigger below disqualifies non-empty `$AE_PROJECT_DIR/.agentic/memory.md` staging from ever reaching route `R_LIGHT`, not because of the route partition alone - `$AE_REPO_DIR/bin/tests/reach_model.py`'s `executes_part_b_step0` returns `True` for `R_LIGHT` with non-empty staging (`s=1`), a state that trigger disqualifier makes unreachable today. If that disqualifier is ever loosened, re-verify this read gate against the model before trusting this bullet.)
- **Check for missing AGENTS.md files:** For each directory that had files touched this session, use the batch's existence probe above rather than a separate check. Skip generated/artifact directories (`node_modules`, `.next`, `dist`, `out`, `build`, `.expo`, `.turbo`, `coverage`, `.cache`, `__pycache__`, `.git`). For each non-generated directory the probe found missing an AGENTS.md, note it as a **new AGENTS.md candidate** and include it explicitly in the raw data passed to the draft Worker. The Worker will propose content for these new files; the conductor will create them automatically without asking the user.
- **Reuse `git status --porcelain` and `git stash list` from Step 0-pre** (do not re-run) to capture uncommitted changes and stashes. If there are uncommitted tracked files (M, A, D - not ??), list them explicitly. This is critical for preventing work loss across sessions - if the user asked to commit and files were missed, this is the safety net.
- **Note specialist agent outputs** - if `perf-analyst`, `release-orchestrator`, or `dependency-auditor` ran this session, capture their key findings from the pointer return already in context (no new mechanism, do not open the report file): stable facts belong in memory.md entries - `perf-analyst`'s `hotspot` field (confirmed hotspot with its measured cost), `release-orchestrator`'s release version and tag, and `dependency-auditor`'s `critical_findings` array (known CVEs + affected package, present when `critical_count > 0`); session-scoped issues (a partial deploy, a perf regression under investigation, an unresolved dependency conflict) belong in Watch Out For.
- **Note Trivial commits** — if any commits this session were classified Trivial, include them in "files touched" and "next steps" as normal. Trivial commits produce no Skeptic artifact and no adversarial brief - do not flag their absence as a gap. Only note the commit SHA and what changed.
- **Note task-state summary** - if `$AE_PROJECT_DIR/.agentic/tasks.jsonl` exists, apply the **task-state fold** (`$AE_REPO_DIR/content/references/task-state-file.md`) and filter the **folded records** whose `session_id` is the current session - not a raw-line filter, which double-counts a task that was taken over: a dispossessed task's own records still carry this session's `session_id` even though the fold has moved ownership elsewhere. Include in the session wrap summary: final task status counts (N done, N blocked, N failed, N abandoned) over that filtered folded set. Do NOT copy task entries into MEMORY.md - they are already durable in the file.
- **Note loop-state summary** — loop state is keyed per ticket, so enumerate every `$AE_PROJECT_DIR/.agentic/loop-state-*.json` **plus** the legacy `$AE_PROJECT_DIR/.agentic/loop-state.json`, and consider only those whose `session_id == $AE_SESSION_ID`. **The `session_id` filter is required, not optional:** it needed no gate in a one-file world, but a keyed checkout routinely holds another session's healthy in-flight loop, and reporting that as this session's incomplete loop is a false alarm on every wrap. For each surviving candidate: if `status=active`, note in the wrap summary that an incomplete loop was active when `$dinostack-codex-wrap` ran (the conductor should investigate before ending the session); if `status=interrupted`, note a pending resume is available (the next `$dinostack-codex-implement-ticket` invocation on that ticket will offer to resume). Name the candidate by its `loop_key` (falling back to `ticket_id`, then to the filename) so the note identifies which ticket. The wrap command does NOT delete or modify any loop-state file, keyed or legacy - that is the user's choice (resume vs fresh-start). Do NOT copy loop state details into MEMORY.md or `_wrap.md` beyond the one-line status note.
- **Enumerate open PRs targeting the conductor's current branch** - from the batch's `gh pr list` output above (do not re-run the query). $dinostack-codex-wrap writes its doc additions onto the conductor's current branch (typically `main`) - this deferral pass concerns the Part B (`$AE_PROJECT_DIR/.agentic/memory.md`) and Part C (AGENTS.md) targets specifically; for the write mechanism itself see the Consumers list under the Atomic write discipline (NORMATIVE) section in $AE_REPO_DIR/content/references/wrap-context-format.md: the 17 sites - 3 in the merge algorithm, 14 in ds-wrap.md. If those additions cite file paths or feature keys that live on branches with open PRs not yet merged, the doc additions will land on the target branch describing files/keys that do not yet exist there.
Record the resulting `{pr_number, head_branch, modified_files[]}` set as the **open-PR overlap set**. If the batch logged `GH_UNAVAILABLE` or `GH_PR_LIST_FAILED`, log "open-PR overlap check skipped (gh unavailable)" to the wrap run output and pass an empty set forward — the deferral logic becomes a no-op rather than blocking the run. The set is supplied to the draft Worker as a dedicated field (see Step 1) so it can flag deferral candidates; the conductor enforces deferral at write time in Step 4.
This raw data is what the draft Worker will format. The Worker is a fresh agent with no session memory, so if you don't supply the details here, they won't appear in the output.
**Step 0.5 - Route to light, zero-substance, or standard path.**
Inspect what Outputs 2 and 3 would contain based on the raw data already compiled in Step 0. Do not spawn anything yet.
**Reached two ways.** Normally this step runs after full Step 0 compiles its raw data. It can also be reached directly from Step 0-pre's fast short-circuit, which evaluates the same criteria below from conversation context already in hand, before Step 0's expensive reads ever run. The criteria themselves are defined here and only here - Step 0-pre does not redefine them or replace them with a weaker mechanical proxy; it falls through to full Step 0 on any doubt rather than guessing. Do not read Step 0-pre's earlier evaluation as license to skip re-checking these criteria in spirit - if anything about the session's substance is unclear, the standard or light path is the safe default.
**Zero-substance path** - triggers when ALL of the following hold:
- Output 2 (memory entries) would be "None"
- Output 3 (AGENTS.md updates) would be "None" for every file AND no new AGENTS.md candidates exist
- No specialist agent (`perf-analyst`, `release-orchestrator`, `dependency-auditor`) ran with session-scoped issues to capture
- The session had effectively no file activity worth preserving in `_wrap.md`: no uncommitted tracked changes, no new stashes, no files touched beyond reads, no meaningful next steps to record. The conductor should judge - if the only meaningful session output is "answered a question", it is zero-substance.
Zero-substance procedure:
- Do NOT write `_wrap.md`. The current Codex Stop hook already wrote the session's raw continuity to `~/.codex/projects/[hash]/context.md`. A zero-substance `$dinostack-codex-wrap` would only create an empty project-local handoff; automatic project-local rollup integration remains deferred to `context-writer-migration`.
- Skip Steps 1-3 entirely (no Worker, no Skeptic) - **UNLESS `$AE_PROJECT_DIR/.agentic/memory.md` (the DS-90 staging area) is non-empty**, in which case still spawn the draft Worker and Skeptic, but scoped to Output 2 only (Outputs 1 and 3 stay "None" for a genuinely zero-substance session). This is a gate-conditional exception, not a widening of the zero-substance criteria: the ROUTE stays zero-substance (per `$AE_REPO_DIR/bin/tests/reach_model.py`'s `ROUTE_PREDICATES`, which never consult staging state), but a pending drain still needs adjudication before it can be presented and merged.
- Skip Step 4 Parts A, C entirely. **Do NOT skip Part B unconditionally** - Part B's own skip test (see Part B above) already governs whether it runs; a non-empty staging area means Part B runs even on an otherwise zero-substance session, citing `$AE_REPO_DIR/bin/tests/reach_model.py` invariant R1 (Part B step 0's reachability is a downstream disqualifier independent of which route a session takes).
- Skip Part D and Part D.5 (no session activity to extract skill-candidate or feedback signals from)
- Skip Part E **only if no target is over its size gate** (see Part E's own Gate below) - a target that crossed its gate from prior-session drift, with no session change here, still needs to compress; citing `$AE_REPO_DIR/bin/tests/reach_model.py` invariant R2.
- Skip Part F (no session activity means no ticket-referencing commits to detect)
- Skip Part G (no session activity means no knowledge-file changes to commit) **unless Part B wrote this run** - see Part G's own skip condition below, which turns on this-run write activity rather than which route was taken; a staging drain writes root `MEMORY.md` even on the zero-substance route, and that write still needs a commit.
- Still run Step 5 (worktree cleanup) - that is always useful
- **Step 6 confirmation.** If Part B did not run (staging area was absent or empty, the common case): "zero-substance path - nothing new to capture this session; ran worktree cleanup only". If Part B DID run and write (staging drain, per the exception above): the mandated literal above does not apply - instead report per the general Step 6/Part G confirmation requirements (all paths written, which Skeptic round(s) ran, and the Part G outcome), same as the standard path.
**Light path** - triggers when the zero-substance conditions do NOT all hold BUT ALL of the following hold:
- Output 2 (memory entries) would be "None" - STRICT: even a single memory entry routes to standard path
- Output 3 (AGENTS.md updates) would be "None" for every file AND no new AGENTS.md candidates exist
- No specialist agent ran with session-scoped issues to capture
- `$AE_PROJECT_DIR/.agentic/memory.md` (the DS-90 staging area) is absent or empty. **This is a trigger disqualifier, not a widening of the "Skip Part B" list below** - the light path skips Steps 1-3 (no draft Worker, no Skeptic), so a non-empty staging area cannot be safely drained here (the drain needs an adjudicator to judge each entry). A session that would otherwise qualify for light path but has non-empty staging falls through to the standard path instead, where Part B step 0 runs with full adjudication.
Light path procedure (replaces Steps 1-3; preserves parts of Step 4):
1. Main agent drafts `_wrap.md` inline from the Step 0 raw data, following the Output 1 structure exactly. No Worker, no Skeptic.
2. Skip Step 1 (draft Worker) and Steps 2-3 (Skeptic + sign-off validation).
3. Proceed to Step 4 Part A with the inline draft.
4. Skip Part B (MEMORY.md - input is None and staging area is empty, per the light-path trigger disqualifier above), Part C (AGENTS.md - input is None).
5. Run Part D (skill-candidate wrap-time signal) and Part D.5 (session-feedback capture signal) - the light path still ran a session worth extracting from.
6. Skip Part E **only if no target is over its size gate** (see Part E's own Gate below) - citing `$AE_REPO_DIR/bin/tests/reach_model.py` invariant R2, the same gate-conditional exception the zero-substance path applies.
7. Run Step 5 (worktree cleanup) as normal.
8. Run Step 6 as normal, including Part F (tracker status reconciliation) - a light-path session can still have committed ticket-referencing work even with no memory/AGENTS.md updates to review.
9. Step 6 confirmation must say: "light path (no stable facts or AGENTS.md updates to review this session)".
**Escape hatch for light path:** If, while drafting `_wrap.md` inline, the main agent notices something it wants the Skeptic to review - ambiguous next-step wording, uncertainty about whether a fact is stable or temporary, unfamiliar territory in the raw data - it must abandon the light path and fall back to the standard path. The light path is for cases where there is genuinely nothing worth an adversarial pass.
**Escape hatch for zero-substance path:** If the conductor has ANY uncertainty about whether the session is truly zero-substance - for example, the user asked a question whose answer feels architecturally significant, or an implicit decision was made without writing anything down - it must abandon the zero-substance path and use the light or standard path instead. When in doubt, do not use the zero-substance path.
**Standard path** - triggers when neither of the above applies (i.e. at least one of Outputs 2/3 has real content, OR a specialist agent ran with session-scoped issues). Proceed to Step 1 unchanged.
**Step 1 — Spawn a draft Worker** (background, general-purpose):
Every section of this spawn prompt that the conductor composes is a slot per `$AE_REPO_DIR/content/references/subagent-protocol.md` §11 Output Expectations, "**Brief section form**".
**Read `$AE_PROJECT_DIR/.agentic/learnings.md` here, immediately before this spawn, if the Step 0 existence probe found it.** This is the sole point where its full content is read (see the note at Step 0's learnings.md bullet above) - deferred this far specifically because only a draft-Worker spawn consumes it, and this is the only place a draft Worker is spawned from the standard path. The zero-substance path's staging-drain exception (Step 0.5) also spawns a draft Worker from this same template and must perform this same read first when it does.
---
You are a Worker agent. Format the raw session data below into three outputs. Replace all placeholders with real content from the data provided. If a section genuinely has nothing to say, write the word "None" — never leave brackets or template text.
**Raw session data:**
[paste your Step 0 notes here verbatim — this covers the task, files touched, errors, next steps, tools used, and stable facts. Do NOT embed existing AGENTS.md file contents here; those go in the dedicated field below.]
**Existing learnings:**
[Paste the full current content of `$AE_PROJECT_DIR/.agentic/learnings.md` read just above, clearly labeled. If the file was not found, write "None." The Worker must check whether a proposed memory entry is already captured here as a structured learning before proposing it.]
**Existing AGENTS.md file contents:**
[For each AGENTS.md file read in Step 0, paste its full current content here, clearly labeled with its absolute path, e.g.:
File: /Users/alice/myapp/AGENTS.md
Content:
<full file content>
File: /Users/alice/myapp/backend/AGENTS.md
Content:
<full file content>
If no AGENTS.md files were found, write "None."]
**Open-PR overlap set:**
[paste the `{pr_number, head_branch, modified_files[]}` entries captured in Step 0, or "None" if no open PRs target the conductor's current branch / the check was skipped. The conductor will use this to defer doc additions whose cited paths overlap an open PR — but you should still flag candidates so the conductor's deferral pass has hints.]
**Output 1 — `_wrap.md` draft (the curated context)**
Produce this exact structure. Include only temporary session state here (current task, files touched, recent errors, next steps). Do not include stable project facts in this file - those belong in Output 2.
# Session Context
*Written by $dinostack-codex-wrap on YYYY-MM-DD. Managed only by `$dinostack-codex-wrap`. Not committed to git.*
*Project: [absolute cwd]*
## Recent Focus
[1–3 sentences: what was being worked on when $dinostack-codex-wrap was invoked]
## Current Task / Next Steps
[Specific next steps: file paths, branch names, open PRs, exact commands. Concrete enough to act on without reading the chat history.]
## Key File Paths
[Files touched or created this session that the next session will care about]
## Uncommitted Changes
[Output of `git status --porcelain` for tracked files only (M/A/D/R, not ??). If working tree is clean, write "(working tree clean)". If there are uncommitted files, list each with its status prefix. This section is a safety net - if the user asked to commit all changes and files appear here, they were missed.]
## Stashes
[Output of `git stash list`, or "(no stashes)" if empty. Stashes may contain work from previous sessions that was never committed.]
## Watch Out For
[Session-specific issues, errors, or near-misses from this session only. Stable/recurring project quirks do not belong here - those go in root MEMORY.md. Or: None.]
## Tools Used
[Comma-separated list of unique tools used this session]
**Output 2 - MEMORY.md entries (max 3)** <!-- drain_model.CAP -->
Review the raw session data for stable project facts: setup commands that don't change, persistent project-wide gotchas or quirks, architectural decisions, recurring patterns, project conventions. For each stable fact, produce one entry in this format:
`- **YYYY-MM-DD:** [what was decided and why this approach was chosen - alternatives considered may be noted as supporting context, in one to two sentences]`
Use today's date for all entries. If there are no stable facts to record, write "None."
**Report, never silently drop.** The conductor's Step 4 write MUST report, in the same completion message: (a) the cap-deferred count with a one-line title per deferred entry (candidates that would have been presented this run but did not fit under the 3-entry cap), and (b) every rejected-on-the-merits entry with its reason (a candidate the adjudicator actually looked at and declined to keep). Neither class is silently swallowed.
**`sid` disposition table.** Before merging staged and fresh candidates (see Part B step 0 below), the draft Worker's output is not enough on its own - the conductor assigns each candidate a `sid` and records a disposition for every `sid` that was presented this run. An outcome-3 (`SKIPPED_DUPLICATE_OF_DESTINATION_OR_ALREADY_PRESENTED`) or outcome-4 (`SKIPPED_ALREADY_A_STRUCTURED_LEARNING`) row MUST carry a quoted destination text - the exact existing line the new candidate duplicates - not just a claim that a duplicate exists. `$AE_REPO_DIR/bin/tests/drain_model.py` is the normative source for the `sid` scheme, the nine-outcome enum, and this quote requirement; it is not restated here.
Before proposing a memory entry, check the **Existing learnings** field above. If the same fact is already captured as a structured learning entry (same pattern, same gotcha, same architectural decision), skip it. Do not duplicate content between `$AE_PROJECT_DIR/.agentic/learnings.md` and `$AE_PROJECT_DIR/.agentic/memory.md` — they serve different purposes (structured fix-patterns vs. session-synthesized stable facts), but the underlying fact should only be recorded once.
Stable = true every session, not just this one. Temporary = only relevant right now (current task, files touched this session).
For architectural and technology decisions especially: the entry must clearly state why the chosen approach was selected on its own merits. Alternatives considered and their rejection reasons are useful supporting context but are secondary - the positive reasoning for the choice is the primary requirement. A future session asking "should we reconsider X?" should find the answer in the entry without re-researching it.
**Deferral hint:** If an entry's substance depends on file paths, feature keys, or symbols that appear in the Open-PR overlap set above (i.e. the fact only becomes true once an unmerged PR lands), append the marker `[defer-pr: <pr_number>]` to the end of the entry text. The conductor uses this hint plus its own path cross-reference to route the entry to `$AE_PROJECT_DIR/.agentic/memory-pending.md` instead of `$AE_PROJECT_DIR/.agentic/memory.md`.
**Output 3 — AGENTS.md updates**
For each AGENTS.md file whose current content was provided in the "Existing AGENTS.md file contents" field above, produce proposed additions only - not a full rewrite. Use that existing content as your baseline: do not propose content already present there.
Format each proposed update as:
File: [full path to AGENTS.md]
Section: [section name, e.g. "## Decisions", "## Conventions", "## Stack", "## Key Conventions"]
Add:
- [bullet point to add]
- [another bullet if needed]
If a section doesn't exist in the target file yet but should be added, indicate:
File: [full path to AGENTS.md]
New section: [section name]
Content:
[section content]
If content in an existing entry should be corrected or superseded, indicate:
File: [full path to AGENTS.md]
Section: [section name]
Update: [existing text] → [replacement text]
Rules:
- Only propose content that was actually established or learned in this session. Do not hallucinate or infer.
- Do not duplicate content already present in the existing AGENTS.md (check against the "Existing AGENTS.md file contents" field provided above).
- Do not contradict existing content without flagging it as an Update.
- For root AGENTS.md: focus on `## Decisions` (resolved architecture decisions as brief bullets) and `## Conventions` (patterns and rules the project follows).
- For subdir AGENTS.md: focus on `## Stack`, `## Key Conventions`, and any new relevant categories (Commands, Schema, Flows, Gotchas) that emerged this session.
- Quality directive: lean and curated. No verbose rationale paragraphs, no outdated entries, no conflicting information. Brief, actionable bullets only.
- If nothing new for a particular file, write "None" for that file.
- If no AGENTS.md files were found in the project, write "None."
- **Deferral hint:** if a proposed addition cites a file path, directory, or feature key that appears in the Open-PR overlap set above (i.e. the addition describes something that only exists on an unmerged branch), append the marker `[defer-pr: <pr_number>]` to the end of each affected bullet or section content. The conductor uses this hint plus its own path cross-reference to route the addition to `$AE_PROJECT_DIR/.agentic/agents-md-pending.md` instead of applying it now.
**New AGENTS.md files:** For any touched directory explicitly noted as a "new AGENTS.md candidate" in the raw session data (i.e. the directory had files touched but has no existing AGENTS.md), propose creating a new file. Use this format:
File: [full path to new AGENTS.md]
New file: true
Content:
# [Directory name]
[One sentence description of what this directory contains, based on the session data.]
## Stack
[Key technologies from package.json or inferred from file types touched - bullet list]
## Key Conventions
[Conventions observed from the session - bullet list. If none observed, omit this section.]
## Gotchas
[Any gotchas or sharp edges encountered - bullet list. If none, omit this section.]
This is automatic - do not ask the user. Populate sections from session context and any package.json content included in the session data.
Return all three outputs clearly labeled. Do not write to disk.
---
**Step 2 — When the draft Worker returns, spawn a fresh Skeptic** (background, general-purpose, never resumed).
Scope constraint: the Skeptic reviews only the accuracy and completeness of the context file and the AGENTS.md updates. Its findings must only trigger context file or AGENTS.md rewrites - never code changes, bug fixes, or any development work. If the Skeptic notes that the context file describes pending work that is already complete (or vice versa), the fix is to update the wording to reflect reality accurately.
Provide this adversarial brief, the Global-context input set (`## Global-context inputs` block per `$AE_REPO_DIR/content/references/skeptic-protocol.md` Section 4.5 - fields 1-3 are `n/a - internal scaffolding artifact review (no code diff, no architect plan/Brief/qa_criteria applies)`; field 4 (per-consumer impact table) is `n/a - internal scaffolding artifact (not a shared-utility surface, no per-consumer impact table applies)`; field 5 is the existing AGENTS.md file paths from Step 0; field 6 is the draft `_wrap.md`/AGENTS.md-update content; field 7 (conductor spawn brief) is `n/a - internal scaffolding artifact (no conductor claim-bearing brief text distinct from the artifact itself)`), then the draft and the existing AGENTS.md file contents from Step 0. **Omit any section below whose corresponding Output is "None"** - always keep the Output 1 (`_wrap.md` accuracy) review as the baseline pass; drop the memory-review language if Output 2 is "None"; drop the AGENTS.md-review paragraph if Output 3 is "None". The full brief below is the "all outputs present" case:
> "Is this context file accurate and actionable? Check each section: Does Recent Focus correctly describe what was actually happening — or is it vague, generic, or wrong? Are the Next Steps specific enough to act on without reading the chat history (file paths, commands, branch names)? Are Key File Paths complete — is anything relevant omitted? Does Watch Out For capture real gotchas, or is it empty when it shouldn't be? Is any section still template text rather than real content?"
>
> "Also review the proposed AGENTS.md updates (Output 3): Is each proposed addition actually derived from this session's work - or is it generic, hallucinated, or already present in the existing file content provided? Is any content going to the wrong file (project-wide content should go to root; track-specific content should go to the track subdir)? Are updates lean - brief bullets only, no verbose rationale? Does any proposed addition contradict or duplicate existing entries in the same file?"
Require this statement before sign-off: "Active search: I have applied the adversarial brief and actively searched for Critical and Major findings."
**Step 3 — Validate sign-off format.**
A valid sign-off requires the mandatory elements defined in `$AE_REPO_DIR/content/references/skeptic-protocol.md` Section 11 (the seven always-required lines, emitted verdict-first: the sign-off phrase, Findings:, Reviewed:, Active search:, Manifest check:, Test-CI-wiring check:, Neutrality check:; the conditional spec-deviation, PR-SHA-range, and prose-scoped-re-check `Scope:` elements do not apply to this internal review). If any element is missing, spawn a new Skeptic with format instructions (not a new re-route round). Limit: 3 format re-invocations, then escalate to the user.
If Critical or Major findings remain: spawn a new draft Worker with the original draft and findings, get a revised draft, then spawn a fresh Skeptic (Step 2). Repeat until sign-off. If the same finding is contested across 2+ re-routes without resolution, escalate to the user.
**Step 4 — Write to disk** (main agent, inline — do NOT delegate to a subagent).
Background subagents cannot reliably get Write/Edit permissions. The main agent must perform all writes directly. Invoking $dinostack-codex-wrap implies permission to write these files.
**Mandatory Skeptic on hand-authored output.** If the conductor authored any of the final outputs inline — for example, after a draft Worker hallucination, after a re-route loop hit its limit, after a light-path escape hatch fell back to the standard path mid-flight, or any other case where the conductor bypassed the Worker → Skeptic chain in Steps 1–3 — the conductor MUST spawn a fresh Skeptic on the on-disk files BEFORE releasing the lock in Step 6. The conductor's escape hatch from Worker iteration does NOT exempt the outputs from Skeptic review; that loophole is closed. The Skeptic in this case reads the on-disk files directly (`$AE_PROJECT_DIR/.agentic/_wrap.md`, `$AE_PROJECT_DIR/.agentic/memory.md`, any AGENTS.md files updated, plus any deferred-write files at `$AE_PROJECT_DIR/.agentic/memory-pending.md` and `$AE_PROJECT_DIR/.agentic/agents-md-pending.md`) and applies the same adversarial brief from Step 2 (with the same scope constraint — the Skeptic's findings only trigger doc rewrites, never code changes). If the Skeptic raises Critical or Major findings, the conductor revises the on-disk files inline and re-spawns a fresh Skeptic until sign-off, subject to the same 3-re-route limit; on cap exhaustion, escalate to the user with the open findings.
**Project directory:** [absolute cwd]
**Output path (curated context):** `$AE_PROJECT_DIR/.agentic/_wrap.md`. Project-local, gate-free, created if absent. **This is a retarget, not a rewrite** - `$dinostack-codex-wrap` executes the identical Part A algorithm; only the file it reads and writes changed from `context.md` to `_wrap.md`.
The project-local derived-rollup contract belongs to harnesses that implement the project-local context writer. The current Codex Stop hook instead writes only `~/.codex/projects/[hash]/context.md` and does not recompose `$AE_PROJECT_DIR/.agentic/context.md`. `$dinostack-codex-wrap` writes `$AE_PROJECT_DIR/.agentic/_wrap.md`; integrating that handoff into an automatic Codex project-local rollup is deferred to `context-writer-migration`.
Why: the current Codex Stop hook writes continuity only to `~/.codex/projects/[hash]/context.md` and does not read or recompose project-local `$AE_PROJECT_DIR/.agentic/context.md`. `$dinostack-codex-wrap` owns its richer project-local `$AE_PROJECT_DIR/.agentic/_wrap.md` handoff under the wrap lock. Connecting that handoff to an automatic Codex project-local context writer remains deferred to `context-writer-migration`. Create `$AE_PROJECT_DIR/.agentic/` if it does not exist.
**Memory path (MEMORY.md):** `<cwd>/MEMORY.md`. The canonical durable-facts store, delivered to the main session only, by a SessionStart hook (never `@`-imported). Part B below writes directly to this file, capped at 3 entries per run <!-- drain_model.CAP --> (the normative cap is `$AE_REPO_DIR/bin/tests/drain_model.py`'s `CAP = 3`). `$AE_PROJECT_DIR/.agentic/memory.md` is now **deferred-wrap daemon staging**: written exclusively by the deferred-wrap daemon (manual workflow 'ds-wrap-deferred' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-wrap-deferred` Step 3, which has no Skeptic and no Open-PR deferral pass), and drained into root `MEMORY.md` by this synchronous `$dinostack-codex-wrap`'s Part B on a later run. It is gitignored and is never read at session start.
**Migration note:** Earlier versions of this skill wrote to `$AE_SHARED_CONFIG_DIR/projects/[hash]/{context,memory}.md`. If those files exist for the current project but the project-local files do not, copy them once into `$AE_PROJECT_DIR/.agentic/` before merging. Symlinks at the old hashed location pointing at the new project paths are acceptable - they preserve any platform mechanism that auto-loads from the legacy path while keeping writes gate-free.
**Part A — Write `_wrap.md` (the curated context)**
The pinned header prefix, the spillover-drain procedure, the `$AE_PROJECT_DIR/.agentic/wrap/last-wrap` write contract, and the `_wrap.md` rolling-session-label merge algorithm are defined in `$AE_REPO_DIR/content/references/wrap-context-format.md` (the shared normative home cited by both `$dinostack-codex-wrap` and manual workflow 'ds-wrap-deferred' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-wrap-deferred`). This Part A is the `$dinostack-codex-wrap`-specific wrapper around that shared algorithm; the algorithm itself is NOT restated here.
Inside the Part A `_wrap.md` write window (the whole-flow `wrap/lock` acquired at pre-flight is held throughout - see "Pre-flight lock acquisition" and the Step 6 release; Part A introduces no new lock window), run, in this exact order:
1. **Atomic spillover drain** - the 3-step rename-first procedure in `$AE_REPO_DIR/content/references/wrap-context-format.md` §"Spillover-drain procedure": rename `$AE_PROJECT_DIR/.agentic/wrap/deferred-activity.jsonl` -> `$AE_PROJECT_DIR/.agentic/wrap/deferred-activity.jsonl.draining.<pid>`, fold its records into `_wrap.md`'s curated `## Recent Focus` region (each record carries its own `session_id`, preserving cross-session provenance), then unlink the renamed copy. Apply the Recent-Focus dedup rule from that reference (key the folded draft by `session_id`+`staged_at`; skip a re-folded duplicate) so a duplicate enrichment of the same marker is idempotent.
2. **Rolling-session-label merge write** of `$AE_PROJECT_DIR/.agentic/_wrap.md` - the algorithm in `$AE_REPO_DIR/content/references/wrap-context-format.md` §"`_wrap.md` rolling-session-label merge algorithm" (file-absent / non-/ds-wrap / merge branches, the duplicate-claim dedup, the 1-to-10 label rolling window, and the per-section merge rules). The merged write always begins with the pinned header prefix `# Session Context\n*Written by $dinostack-codex-wrap` (the matcher contract); no site parses the header date.
3. **Write `$AE_PROJECT_DIR/.agentic/wrap/last-wrap`** = this session's `session_id` (atomic) - per `$AE_REPO_DIR/content/references/wrap-context-format.md` §"`$AE_PROJECT_DIR/.agentic/wrap/last-wrap` write contract".
The net behavior of Part A is unchanged by this extraction: the cited reference is semantically identical to the algorithm `$dinostack-codex-wrap` formerly inlined here (identical aside from the Result/Return label), pinned by the golden-file parity test (`$AE_REPO_DIR/hooks/tests/test-wrap-context-format-golden.js`).
**Part B — Write MEMORY.md**
**Step 0 - drain the staging area (runs BEFORE the skip test below).** If `$AE_PROJECT_DIR/.agentic/memory.md` exists and is non-empty, read it and assign each entry a stable `sid`. Its entries were written without Skeptic review and without compression, so they are **additional candidate input to Output 2**, merged with this run's fresh candidates and passing through the same review, dedup, and cap <!-- drain_model.CAP -->.
**`sid` rendering and namespace.** Staged entries take `sid` = `"s"` + decimal arrival index (`s0`, `s1`, ...). Fresh entries take `sid` = `"f"` + decimal arrival index (`f0`, `f1`, ...). Both are non-empty strings, disjoint by prefix. **Two live failure modes this prevents:** an integer `0` is falsy and raises a misleading "entry missing required 'sid'"; and staged and fresh entries are validated for uniqueness **against each other**, so two independently 0-based numberings collide, abort the run, leave staging untouched, and make every later run abort identically. **`sid` values MUST be echoed back verbatim; do not re-number.**
The drain algorithm is defined normatively and executably in `$AE_REPO_DIR/bin/tests/drain_model.py`. It is NOT restated here. The contract is one sentence: **an entry is removed from staging if and only if it was PRESENTED to the adjudicator this run - whatever verdict it received, including rejection on the merits.**
**The routing contract - that this step is reached at all - is defined in `$AE_REPO_DIR/bin/tests/reach_model.py` (invariant R1).** Any edit to the Step 0-pre short-circuit criteria, the zero-substance criteria, the light-path trigger, or the skip test below changes the routing surface that invariant covers.
After the root write succeeds - and only after, inside the held lock - rewrite `$AE_PROJECT_DIR/.agentic/memory.md` **wholesale** from the retained set, in the order the model returns them, via temp file + `rename()`. **Never delete lines; never delete the file.** An empty retained set writes an empty file. On failure, lost lock, or a failed `sid` table: leave staging untouched and take the bounded abort path.
**Skip test.** Skip Part B entirely only if the memory entries input is "None" **AND** `$AE_PROJECT_DIR/.agentic/memory.md` is absent or empty. **A run with no fresh entries but a non-empty staging area MUST still run Part B.**
**Open-PR deferral pass (run BEFORE the read/merge steps below).** For each proposed memory entry (fresh or drained-from-staging), cross-reference the file paths, directory paths, and feature keys cited in the entry against the Open-PR overlap set captured in Step 0. An entry is **post-merge-deferred** if any cited path or key appears in the `modified_files[]` list of any open PR, OR the Worker tagged the entry with `[defer-pr: <pr_number>]`. Strip the marker from the entry text and route the entry to `$AE_PROJECT_DIR/.agentic/memory-pending.md` (append-only; create the file if missing) under a heading `## Pending PR #<pr_number> (<head_branch>)`, per Atomic write discipline <!-- aw-site: memory-pending --> (unconditional publish - read current content if present, append, write back). Non-deferred entries continue to the steps below. The pending file is plain markdown - a follow-up doc PR after the source PRs merge can move entries from `$AE_PROJECT_DIR/.agentic/memory-pending.md` into root `MEMORY.md` (in compiled mode, below, into `$AE_PROJECT_DIR/.agentic/memory-capture-inbox.md` instead). Rationale: docs land on the conductor's branch (typically `main`) before source PRs merge; without deferral, `MEMORY.md` describes paths or keys that do not yet exist on the target branch.
- 1. Use the Read tool to attempt to read the file at the root `MEMORY.md` path.
+ 1. Check whether the root `MEMORY.md` exists (`test -f`). Do not Read it whole; step 3 works from grep hits.
2. **If the file does not exist**: write all non-deferred entries directly as a markdown list, per Atomic write discipline <!-- aw-site: memory-md-fresh --> (unconditional publish). Return: "Wrote fresh MEMORY.md to [path] (N entries written, M deferred to memory-pending.md)."
- 3. **If the file exists**: read its content. For each non-deferred entry, check whether the same fact is already captured - not just as an exact string match, but semantically (same architectural decision, same gotcha, same command). Also check `$AE_PROJECT_DIR/.agentic/learnings.md` (read at Step 1, per that step's note): if the same fact is captured as a structured learning entry, skip the new memory entry. If an existing entry covers the same fact, skip the new entry. If the new entry supersedes an existing one (same topic but updated or corrected), replace the existing entry in place with the new one - **name every supersession** in the return line by quoting the existing entry's text beside its replacement. Otherwise append the new entry. Write the merged result, per Atomic write discipline <!-- aw-site: memory-md-merge --> (unconditional publish). Return: "Updated MEMORY.md at [path] (N entries added, M entries superseded [superseded-entry-text -> replacement-text, ...], K deferred to memory-pending.md)."
+ 3. **If the file exists**: for each non-deferred entry, grep `MEMORY.md` for its distinctive terms (paths, commands, identifiers: `grep -n -i '<term>' MEMORY.md`) and Read only a window around each hit (offset/limit, at most 100 lines per Read, halving `limit` on a too-large error), then check whether the same fact is already captured - not just as an exact string match, but semantically (same architectural decision, same gotcha, same command). Also check `$AE_PROJECT_DIR/.agentic/learnings.md` (read at Step 1, per that step's note): if the same fact is captured as a structured learning entry, skip the new memory entry. If an existing entry covers the same fact, skip the new entry. If the new entry supersedes an existing one (same topic but updated or corrected), replace the existing entry in place with the new one - **name every supersession** in the return line by quoting the existing entry's text beside its replacement. Otherwise append the new entry. Write the merged result (apply the supersessions and appends to a copy of the file, then rename the copy over `MEMORY.md`), per Atomic write discipline <!-- aw-site: memory-md-merge --> (unconditional publish). Return: "Updated MEMORY.md at [path] (N entries added, M entries superseded [superseded-entry-text -> replacement-text, ...], K deferred to memory-pending.md)."
**Compiled mode** (`ds-memory-capture detect` reports `two-tier` or `two-tier-unresolved`, else the `^index:` grep fallback; see `$AE_REPO_DIR/content/references/memory-shard-convention.md` §Two-tier capture) replaces steps 2-3: dedup as in step 3, but never edit `MEMORY.md` and never replace in place. Append each surviving entry as one line of `$AE_PROJECT_DIR/.agentic/memory-capture-inbox.md`, citing a superseded entry's shard id in its text, then run `ds-memory-capture flush` and carry its `captured:` and `flush:` lines into the return line.
**Part C — Write AGENTS.md updates**
Skip Part C entirely if the AGENTS.md updates input above is "None" or all files within it are marked "None".
**Open-PR deferral pass (run BEFORE iterating files).** For each proposed `Add:`, `New section:`, `New file: true`, and `Update:` block, cross-reference the file paths, directory paths, and feature keys cited in the proposed content against the Open-PR overlap set captured in Step 0. A block is **post-merge-deferred** if any cited path or key appears in the `modified_files[]` list of any open PR, OR the Worker tagged the block with `[defer-pr: <pr_number>]`. Strip the marker from the block content and route the deferred block to `$AE_PROJECT_DIR/.agentic/agents-md-pending.md` (append-only; create the file if missing) under a heading `## Pending PR #<pr_number> (<head_branch>) — <target AGENTS.md path>`, per Atomic write discipline <!-- aw-site: agents-md-pending --> (unconditional publish - read current content if present, append, write back). Non-deferred blocks continue through the per-file write below. A follow-up doc PR after the source PRs merge can move entries from `$AE_PROJECT_DIR/.agentic/agents-md-pending.md` into the actual AGENTS.md files. Rationale: docs land on the conductor's branch (typically `main`) before source PRs merge; without deferral, AGENTS.md describes paths or keys that do not yet exist on the target branch — exactly the failure mode that historically produced Critical findings during $dinostack-codex-wrap Skeptic review.
For each file with non-deferred updates:
1. Use the Read tool to attempt to read the current file content.
2. **If the file does not exist** (Read returns a file-not-found error): create a minimal stub appropriate for the file type, then continue to steps 3-6 to apply the proposed updates into it.
- **Subdirectory AGENTS.md** (any path that is not the project root's AGENTS.md - i.e. the file is not at `[cwd]/AGENTS.md`): create a stub with `# [directory name]` as the H1 (derive from the parent directory of the file path), a `## Stack` section header, and a `## Key Conventions` section header.
- **Root AGENTS.md** (the file is at `[cwd]/AGENTS.md`): create a stub with `# [project name]` as the H1 (derive from the cwd directory name), a `## Decisions` section header, and a `## Conventions` section header.
If the draft Worker proposed a complete `New file: true` block with content, use that content as the starting file instead of the minimal stub.
After creating the stub or new file, proceed with steps 3-6 to apply the proposed updates into it. Return: "Created and updated AGENTS.md at [path] (N additions)."
3. For each `Add:` update: locate the target section. Append the new bullet(s) at the end of that section, before the next `##` heading (or at end of file if it's the last section). Do not duplicate any bullet already present (check semantically, not just string match).
4. For each `New section:` update: insert the new section after the last existing section in the file, maintaining the document's natural flow (decisions and conventions before gotchas; stack and key conventions before less-common categories). Do not blindly append without regard to the existing structure.
5. For each `Update:` update: find the existing text and replace it with the replacement text.
6. Write the updated file to disk, per Atomic write discipline <!-- aw-site: agents-md-write --> (unconditional publish).
Return: "Updated AGENTS.md at [path] (N additions, M updates)" for each file written, or "Skipped [path] (nothing to add)" if all proposed additions were already present.
**Part D — Skill-candidate wrap-time signal**
Skip Part D on the **zero-substance path** (already skipped Steps 1-3; no session activity to extract from). Run Part D on the **light path** and the **standard path**. This step runs INSIDE the `wrap/lock` window already held from pre-flight. Soft-fail: any error in this step is silently swallowed; Part D failure NEVER breaks or delays the wrap.
**Gate:** Read `$AE_PROJECT_DIR/.agentic/config.json`. If `skill_candidate_detection` is explicitly `false`, skip Part D entirely. Default (key absent or config missing) is `true` - proceed.
**Extraction (inline LLM reasoning over the session already reflected on in Step 0):**
Emit a JSON array of 0-5 entries identifying DISTINCT domains where you or the user repeatedly did manual work, or worked around the same friction, this session - the kind of recurring manual workflow that might warrant a reusable skill/command/preset/lint-rule. Exclude one-off actions. Output `[]` if nothing qualifies.
Each entry shape:
- `domain` (required): short lowercase-hyphenated slug naming the recurring workflow (e.g. `adapter-rebuild`, `skeptic-context-block`). Reuse an obvious existing slug if the same workflow has appeared before; exact-match merging is the helper's job, not yours.
- `exampleNote` (required): one sentence describing the concrete instance observed this session.
- `suggestedArtifact` (optional): one of `command|named-agent|preset|lint-rule`.
Do NOT include `count`, `firstSeen`, `lastSeen`, or any tally fields - those are helper-assigned.
**Write and invoke (Bash):**
Write the extracted array to a temp file and call the deep-cluster helper. Use `$AE_SESSION_ID` as the session id; if it is unset or empty, skip the invocation entirely (soft no-op).
```bash
CLUSTER_TMP=$(mktemp /tmp/wrap-clusters-XXXXXX.json)
cat > "$CLUSTER_TMP" << 'EOF'
[...the extracted array...]
EOF
# Skip if no session id
if [ -n "$AE_SESSION_ID" ]; then
node $AE_REPO_DIR/hooks/lib/skill-candidate-deep-cluster.js "$REPO_CWD" "$AE_SESSION_ID" "$CLUSTER_TMP" 2>/dev/null || true
fi
rm -f "$CLUSTER_TMP" 2>/dev/null || true
```
Where `$REPO_CWD` is the absolute cwd of the project (the same value identified in Step 0). Any failure (non-zero exit, missing node, missing helper) is silently swallowed via `|| true`; the wrap continues normally.
**Part D.5 — Session-feedback capture signal**
Skip Part D.5 on the **zero-substance path** (already skipped Steps 1-3; no session activity to extract feedback signals from) — same skip condition as Part D. Run Part D.5 on the **light path** and the **standard path**, immediately after Part D and still INSIDE the `wrap/lock` window already held from pre-flight. Soft-fail: the whole step is wrapped swallow-all — any error anywhere in Part D.5 is silently swallowed; Part D.5 failure NEVER breaks or delays the wrap. Unlike Part D, **Part D.5 has no config gate** — it is always-on regardless of `skill_candidate_detection` or any other toggle.
**Deterministic evidence gathering.** Each of the four signals below is individually guarded: a missing file, a missing or broken `ds-feedback` binary, or a read error on any ONE signal must never break or stall the wrap, and must never prevent the remaining signals from being checked. Gather candidates from whichever signals are available; skip any that error or are absent.
1. **Tool-friction signal.** If `$AE_PROJECT_DIR/.agentic/events.jsonl` exists, read only its last ~500 lines (bounded read — never read the whole file). Filter to lines where `event == "tool_failure_workaround"` AND `data.session_uuid == $AE_SESSION_ID`. Each match is a candidate: `category = tool-friction`, evidence `"tool_failure_workaround: <tool> (<domain_tag>) - <note>"`.
2. **Skeptic-loop-stall signal.** If any `$AE_PROJECT_DIR/.agentic/loop-state-*.json` (or the legacy `$AE_PROJECT_DIR/.agentic/loop-state.json`) exists AND its `session_id == $AE_SESSION_ID` AND `status == "stalled"`: one candidate per such file, `category = process-escalation`, `scope` defaults to `methodology`, evidence citing `loop_state.termination_reason` (`cap_reached` | `convergence_failure` | `blocked`) plus the `last_phase` and `ticket_id` at time of stall.
3. **Guardrail-fire signal.** Two independent sub-sources, evaluated separately (a fire in one is not double-counted against the other — they read different files with different scopes):
- **(a) Abdication-guard counter.** If `$AE_PROJECT_DIR/.agentic/.abdication-guard-fire-count` exists and its `count >= 1` at wrap time: one candidate, `category = guardrail-fire`, `scope` defaults to `methodology`. **Honesty note:** this counter resets to 0 on every genuine new user turn (see `$AE_REPO_DIR/hooks/enforce-no-abdication.py`), so a nonzero count at wrap time reflects only "an abdication-guard loop was present in the session's final turn" — it is NOT a whole-session tally of guardrail fires. State this narrowly in the evidence text; do not describe it as a session-wide count.
- **(b) Enforcement fire-log.** If `$AE_PROJECT_DIR/.agentic/.enforcement-fires.jsonl` exists, read it from the tail and tally occurrences per `hook` value, **counting only rows whose `decision` is not `"allow"`** (schema: `{ts, hook, decision, reason}` plus optional `session_id`/`agent_id`/`tool_use_id`/`detail`, see `$AE_REPO_DIR/hooks/lib/enforcement_log.py`). Rows whose `detail.agent_self_corrects` is `true` are also excluded, because the deny is the whole remedy and the agent applies it itself. **Filter before bounding, and bound on the filtered count, not the raw one:** collect the last ~500 rows that SURVIVE the filter, scanning no more than ~5000 raw tail lines to find them (whichever limit is hit first ends the scan - it is still a bounded read, never the whole file). Bounding on raw lines instead would let `enforce-no-abdication.py`'s roughly one `"allow"` row per conductor turn crowd genuine fires out of the window: at this repo's measured ~88 fire-rows/day a raw 500-line window spans ~5.7 days, and every-verdict rows shrink it in proportion to how busy the session was. If the tally is non-empty: one candidate, `category = guardrail-fire`, `scope` defaults to `methodology`, evidence citing the per-hook counts (e.g. `"enforcement-fires: enforce-tier=3, enforce-shippable-edit=1"`). **Honesty note:** this tally does not filter on the optional `session_id`, so it is a REPO-WIDE cumulative count across every session that has ever run since the file was created (or last rotated/deleted), not a count scoped to the current session. State this narrowly in the evidence text; do not describe it as this-session-only. **The `decision != "allow"` filter is load-bearing, not tidiness:** `enforce-no-abdication.py` DOES write to this file, and it is the one hook that logs every verdict path it reaches rather than only its actions - including a plain `"allow"` row on every clean conductor turn. Tallying raw row counts would report those clean turns as guardrail fires and swamp every genuine fire from every other hook. That hook's blocks (`decision: "deny"`) are genuine fires and DO belong in this tally; it also still keeps the separate counter per (a) above, so a fire from it can legitimately appear in both signals.
4. **Operator-correction signal.** Using the same session reflection Part D already surveys (no new mechanism — inline LLM reasoning over the transcript), identify up to 5 turns where the operator explicitly corrected, rejected, or expressed frustration with a conductor action this session. Each is a candidate: `category = operator-correction`, evidence = the operator's verbatim correction, trimmed to the relevant sentence(s).
If the combined candidate set across all four signals is empty: emit `[]` and STOP here — no temp file write, no `ds-feedback` invocation (mirrors Part D's "output `[]` if nothing qualifies"). This keeps Part D.5 latency-free on light and zero-substance sessions where nothing fired.
**Draft extraction (inline LLM reasoning; may merge closely-related candidates):**
For each surviving candidate, produce a draft object `{scope, severity, category, evidence, suggested_title, suggested_body}`. Do NOT include `id`, `ts`, `status`, `repo`, or `session_uuid` — those five fields are CLI-owned and assigned by `ds-feedback append` itself.
- `category`: copied verbatim from the source signal type above (deterministic, not LLM-judged).
- `scope`: defaults per source type as listed above (`methodology` for skeptic-loop-stall and guardrail-fire). **Repo-identity override:** if the current repo root contains BOTH `$AE_REPO_DIR/content/commands/ds-wrap.md` AND `$AE_CORE_SKILL_ROOT/METHODOLOGY.md` (i.e. this IS the methodology source repo, not a consumer project), force `scope = methodology` for every draft regardless of source type. Otherwise, for `tool-friction` and `operator-correction` candidates, judge `scope` from the evidence content — AE process or tooling friction is `methodology`; friction with the working project's own feature or code is `project`.
- `severity`: LLM judgment — `process-escalation` is `high`; `guardrail-fire` or a clearly-mistaken correction is `medium`; a single minor tool-friction instance is `low`.
- `suggested_title`: one line, ticket-ready.
- `suggested_body`: 2-4 sentences of markdown, ticket-ready.
Emit a JSON array of 0-5 drafts. The extracted JSON must not emit `__WRAP_FEEDBACK_JSON__` on a line by itself (trivially satisfied — the heredoc below uses that token as its closing delimiter, and no legitimate draft field would ever consist solely of that string).
**Write and invoke (Bash):**
Write the extracted drafts array to a temp file and call `ds-feedback append`. Use `$AE_SESSION_ID` as the session id; if it is unset or empty, skip the invocation entirely (soft no-op). The heredoc uses a distinctive delimiter (`__WRAP_FEEDBACK_JSON__`, not `EOF`) so that verbatim operator-correction text captured by Signal 4 cannot prematurely close the heredoc and truncate the JSON payload — quoted (`<< '__WRAP_FEEDBACK_JSON__'`) so there is still no variable expansion or injection.
```bash
FEEDBACK_TMP=$(mktemp /tmp/wrap-feedback-XXXXXX.json)
cat > "$FEEDBACK_TMP" << '__WRAP_FEEDBACK_JSON__'
[...the extracted drafts array...]
__WRAP_FEEDBACK_JSON__
# Skip if no session id
if [ -n "$AE_SESSION_ID" ]; then
ds-feedback append --repo "$REPO_CWD" --session-uuid "$AE_SESSION_ID" --file "$FEEDBACK_TMP" >/dev/null 2>&1 || true
fi
rm -f "$FEEDBACK_TMP" 2>/dev/null || true
```
Where `$REPO_CWD` is the same absolute cwd of the project Part D already uses. Any failure (non-zero exit, missing or broken `ds-feedback` binary, lock contention) is silently swallowed via `|| true`; the wrap continues normally.
**Part E — Curate and compress always-loaded memory files**
Part E does two things in one pass: token-density compression (unchanged from prior behavior) AND entry-level curation - deduplication, stale-claim verification, and pointer-compression of learnings-backed entries. Curation is invisible to the operator: it runs inside the same Worker+Skeptic+backup machinery below, gated by the same size gate, with no separate operator-facing output.
**Cost accepted (Pillar 8).** This mechanism adds a 6-label classification taxonomy, a positive-match citation regime for every deletion/merge, a second (async, Phase 11b-triggered) execution path with its own lock-acquisition step, and 3 new `compression-state.json` fields - real machinery, not free. It is accepted against the failure it catches: always-loaded memory files accumulate redundant and stale entries over time, and every byte of that accumulation is per-session resident cost paid by every future session. Retirement condition: if a future memory architecture removes the always-loaded `MEMORY.md` tier entirely, this curation pass retires with it.
**Second cost accepted, deliberately unbounded (DS-221).** The write-once invariant at step 4(e) means a deterministically-failing stop-short path - most concretely the Ignore guard (below), which fires only after the curation Worker and Skeptic have already run to completion - re-trips on every single run, forever, with no operator-visible signal that it is happening repeatedly. On the async Phase 11b path (`$AE_REPO_DIR/content/commands/ds-implement-ticket.md`, Phase 11b) this is swallowed silently: each affected PR can burn a full curation Worker + Skeptic chain, including up to 3 re-route rounds, discard the result at the Ignore guard or the Staleness guard, and repeat the same cost on the next PR - unbounded, with no counter and no escalation. This is the deliberate trade against round 1's alternative (a streak counter plus an operator escalation at streak>=3), which round 1 built and lost sign-off over for adding structural surface that was not the fix. The cost is accepted as strictly better than the defect it replaces (a silent baseline reset that made the gate's own claim - "this target moved past the size gate" - false), not as a cost that has been made small.
Skip Part E only if Parts B and C both reported no changes (no new memory entries, no AGENTS.md updates) **AND no target below is over its size gate** - citing `$AE_REPO_DIR/bin/tests/reach_model.py` invariant R2: a target that crossed its gate from prior-session drift, with no Part B/C change this session, must still be compressed. Part A always writes `_wrap.md` and is not a signal of session-meaningful change (and `_wrap.md` is not a Part E target - see below).
**Targets:**
- `[cwd]/MEMORY.md` (**primary** - delivered to each main session, so its size is per-session conductor cost). Not a target in compiled mode (see Part B): editing compiled output wedges the project's `ingest`.
- `$AE_PROJECT_DIR/.agentic/memory.md` (the DS-90 staging area) if it exists.
- `[cwd]/CLAUDE.md` if it exists at the project root.
Skip any target that does not exist.
**State file:** `$AE_PROJECT_DIR/.agentic/compression-state.json`. Schema:
{
"targets": {
"<absolute path>": {
"last_compressed_size_bytes": <int>,
"last_compressed_at": "<YYYY-MM-DD>",
"original_backup_path": "<absolute path to FILE.original.md>",
"rolling_snapshots": ["<absolute path to FILE.pre-YYYY-MM-DD-HHMMSS.md>", ...],
"entries_deleted_last_run": <int, optional - absent or 0 when never curated>,
"entries_merged_last_run": <int, optional - absent or 0 when never curated>,
"entries_converted_to_pointer_last_run": <int, optional - absent or 0 when never curated>
}
}
}
If the file does not exist, treat all targets as never-compressed.
**Gate:** For each target, compute current file size in bytes. Compress only if:
- (a) No prior entry exists for this target AND current size > 2000 bytes, or
- (b) A prior entry exists AND current size >= 1.5 * `last_compressed_size_bytes`.
Otherwise skip that target silently.
**For each target that passes the gate:** Steps 1-3 below run identically on both the synchronous `$dinostack-codex-wrap` path and the async Phase 11b path (`$AE_REPO_DIR/content/agents/wrap-ticket.md` "Post-return Part E curation gate check", `$AE_REPO_DIR/content/commands/ds-implement-ticket.md` Phase 11b). Step 4 diverges - see the "Async-path amendment" inside step 4 below.
1. Spawn a dedicated background curation Worker (general-purpose) with this brief verbatim:
> You are a curation Worker. Rewrite the file content below into a token-dense form suitable for an LLM to read on every session start, AND curate its entries per the classification rules below. Hard constraints, no exceptions:
> - Preserve every technical fact, decision, gotcha, and rationale. If you are not certain a phrase is filler, keep it.
> - Never alter: file paths, absolute or relative; shell commands; environment variable names; version numbers; dates; URLs; project names; person names; flag names; function/identifier names; quoted strings; code blocks; markdown links.
> - **Self-policy exception:** when the target file is `$AE_REPO_DIR/content/commands/ds-wrap.md` itself, never alter this command's own routing statements (trigger criteria, skip tests, gate conditions, route/procedure names) - a scoped exception applies only to correcting a numeric figure the Worker can independently verify (e.g. a stale byte count), never to rewording a conditional.
> - Entries may be merged ONLY when classified TRUE_DUPLICATE (a near-identical restatement of the same fact, confirmed by semantic read, not substring match) or SUPERSEDED_MERGE (a later entry's own prose supersedes, extends, or corrects an earlier one). A merged entry MUST retain every distinct date from both originals and MUST NOT drop any fact unique to either original. A merge that would drop a distinguishing fact is forbidden - fall back to AMBIGUOUS (no action) instead.
> - You may: drop articles (a/an/the), drop hedging (just/really/basically), collapse multi-sentence prose into fragments, replace verbose connectors with punctuation, merge bullet sub-points when the meaning is identical AND neither bullet carries a date or timestamp.
> - You must: keep the markdown structure intact (headings, list nesting, code fences). Keep section headings byte-identical so future readers can locate facts.
> - **Output exactly two blocks, nothing else:** (1) the rewritten file content, (2) a structured classification report (label, action, citation - see below) for every entry you acted on. No prose commentary outside those two blocks.
>
> **Curation inputs (in addition to the target file content below):** the content of `$AE_PROJECT_DIR/.agentic/learnings.md` (or targeted grep results against it if the full file is large), plus live-tree Read/Grep/Bash re-verification for any entry carrying a temporal or live-state claim (cues: "currently", "now", "as of", "is broken/fixed/pending", "TODO", or an unqualified present-tense claim about repo state).
>
> **Per-entry classification.** For every entry you act on (merge, rewrite, or delete), classify it as exactly one of:
> - `CONDUCTOR_BEHAVIORAL` - keep, compress-only, no other action.
> - `LEARNINGS_BACKED` - rewrite to a one-line pointer in the form `- **YYYY-MM-DD:** [<id>] <one-line hook>`.
> - `STALE_VERIFIED_FALSE` - the entry's claim is currently false. You MUST cite two things: (1) the exact file/line or command output that PROVES the original claim false (e.g. "read content/foo.md:12, no longer matches" or a command's actual output), and (2) a sole-copy grep against `$AE_PROJECT_DIR/.agentic/learnings.md` plus tracked `AGENTS.md` files. If that grep FINDS a matching counterpart line (cite it - the exact grep command and the matching line), the fact is preserved elsewhere and you may delete the entry. If the grep finds NOTHING (the entry is the SOLE copy of the fact anywhere), do NOT delete it - instead rewrite it in place to a one-line RESOLVED tombstone: `- **YYYY-MM-DD RESOLVED <today>:** <original one-line summary> - <what changed, cite file/PR if found>`.
> - `TRUE_DUPLICATE` - merge into the earliest entry, list all dates, delete the redundant later copy. Cite the exact grep/read command and the matching earlier-entry text establishing the duplication.
> - `SUPERSEDED_MERGE` - a later entry's own prose supersedes, extends, or corrects an earlier one; merge into the later entry, list all dates, delete the redundant earlier copy. This label requires a TWO-PART citation to satisfy the POSITIVE-match rule below: (a) the later entry's own superseding language (quote the exact sentence that supersedes/extends/corrects), AND (b) an explicit fact-by-fact mapping showing every unique fact in the removed earlier entry has a destination in the merged result (e.g. "earlier entry's fact X -> now covered by merged sentence Y"). If any unique fact in the earlier entry has no destination in (b), the merge is FORBIDDEN - do not merge; fall back to `AMBIGUOUS` instead.
> - `AMBIGUOUS` - leave untouched. This is the conservative default: when in doubt, classify AMBIGUOUS and take no action.
>
> **Deletion citation requirement - POSITIVE match only.** No deletion without a cited POSITIVE match: for every entry you delete (only possible under `STALE_VERIFIED_FALSE` with a found counterpart, the redundant copy under `TRUE_DUPLICATE`, or the redundant copy under `SUPERSEDED_MERGE`), your return must cite the POSITIVE match required by that entry's label - for `STALE_VERIFIED_FALSE`/`TRUE_DUPLICATE`, the exact grep/read command AND the matching counterpart line or text it found (in `$AE_PROJECT_DIR/.agentic/learnings.md`, a tracked `AGENTS.md`, or the duplicating entry itself); for `SUPERSEDED_MERGE`, the two-part citation defined under that label above (the superseding language is what satisfies POSITIVE-match here, not a grep - there is no grep for "this entry's own later revision"). An EMPTY or negative grep result is never grounds for deletion under `STALE_VERIFIED_FALSE`/`TRUE_DUPLICATE` - it means sole-copy, and the entry must instead take the `STALE_VERIFIED_FALSE` RESOLVED-tombstone path (rewrite in place, never delete).
>
> **Classification report block.** List, for every entry you acted on: its classification label, the action taken, and the full citation required above (for `STALE_VERIFIED_FALSE`: both the falsity proof and the positive-match grep result or its absence; for `TRUE_DUPLICATE`: the positive-match citation; for `SUPERSEDED_MERGE`: both parts of its two-part citation - the superseding language AND the fact-by-fact mapping). Also report, once, the exact byte size and mtime of the target file you read at spawn time (the "read-time snapshot"): on the async Phase 11b path, the conductor compares this against the file's state at write time before applying your draft; on the synchronous `$dinostack-codex-wrap` path nothing currently consumes it. Report it unconditionally either way - this field makes no path-specific claim.
>
> File content:
- > [paste full file content]
+ > [paste full file content - this rewrite needs the whole file, so Read it in chunks (offset/limit, at most 100 lines per Read, halving `limit` on a too-large error) until EOF, never in one Read]
2. When the curation Worker returns, spawn a fresh Skeptic (background, general-purpose, never resumed) with this adversarial brief verbatim, followed by the Global-context input set (`## Global-context inputs` block per `$AE_REPO_DIR/content/references/skeptic-protocol.md` Section 4.5 - fields 1-3 are `n/a - internal scaffolding artifact review (no code diff, no architect plan/Brief/qa_criteria applies)`; field 4 (per-consumer impact table) is `n/a - internal scaffolding artifact (not a shared-utility surface, no per-consumer impact table applies)`; field 5 is the target file's path; field 6 is the original file content and the compressed/curated draft plus the Worker's per-entry classification report; field 7 (conductor spawn brief) is `n/a - internal scaffolding artifact (no conductor claim-bearing brief text distinct from the artifact itself)`), then the original file content and the compressed/curated draft:
> You are reviewing a memory-file compression and curation pass for fact loss and unsafe deletion. The original file is the source of truth. The compressed file must preserve every technical fact, decision, path, command, date, version, URL, and rationale from the original. Stylistic compression of prose is allowed; semantic loss is not.
>
> Walk the original file section by section. For each fact, locate it in the compressed file. Classify any discrepancy:
> - Critical: a path/command/date/version/URL/identifier was altered, dropped, or invented.
> - Critical: a decision, gotcha, or rationale was dropped or its meaning changed.
> - Critical: an entry was deleted or merged without a cited, independently re-run POSITIVE-match grep result (the exact command AND the matching counterpart line or superseding text it found). An empty/negative grep result never justifies a deletion - a Worker citing one as grounds for deletion is itself a Critical finding.
> - Critical: a `STALE_VERIFIED_FALSE` claim was acted on without you independently re-opening the same file/line or re-running the same command the Worker cited as proof the claim is false.
> - Critical: a `SUPERSEDED_MERGE` was acted on without independently verifying BOTH parts of its required citation: (1) re-read the later entry and confirm its prose genuinely supersedes/extends/corrects the earlier one as claimed, AND (2) re-check the fact-by-fact mapping - walk every unique fact of the removed earlier entry and confirm each has a real destination in the merged result. Any unmapped fact (a fact the mapping claims is covered but is not actually present in the merged text, or a fact the mapping omits entirely) is a Critical finding - overrule the merge back to keep-as-is.
> - Major: structural - a heading was renamed or a section was merged in a way that obscures lookup.
> - Major: an `AMBIGUOUS`-classified entry was acted on (merged, rewritten, or deleted) rather than left untouched.
> - Minor: stylistic regressions only.
>
> You may overrule any DELETE or MERGE action back to keep-as-is or keep-as-pointer. The default on any doubt is keep - never delete.
>
> Require this statement before sign-off: "Active search: I walked the original section by section and verified every fact appears in the compressed output, independently re-ran every cited positive-match grep and falsity-proof check for deleted or merged entries, and for every SUPERSEDED_MERGE independently re-read the superseding prose and re-verified the fact-by-fact mapping."
>
> Sign-off format (verdict first): "No unresolved Critical or Major findings. Sign-off granted. ... Findings: ... Reviewed: ... Active search: ... Manifest check: ... Test-CI-wiring check: ... Neutrality check:"
3. Validate sign-off format the same way Step 3 does (the mandatory elements per `$AE_REPO_DIR/content/references/skeptic-protocol.md` Section 11 - the seven always-required lines; the conditional spec-deviation, PR-SHA-range, and prose-scoped-re-check `Scope:` elements do not apply here). If any element is missing, spawn a new Skeptic with format instructions (not a re-route round). Limit: 3 format re-invocations, then escalate to the user.
If Critical or Major findings remain: spawn a new curation Worker with the original file content, the prior draft, and the findings; get a revised draft; spawn a fresh Skeptic. Repeat until sign-off. Limit: 2 re-routes, then skip compression/curation for that target this session and log the failure in Step 6.
**Re-route snapshot carry-forward (staleness guard integrity).** The FIRST curation Worker's read-time snapshot (byte size + mtime, from its "Classification report block") is the only one that matters - it reflects what the target file actually looked like when curation began. Every re-spawned Worker in a re-route round is handed that ORIGINAL snapshot alongside the original file content, the prior draft, and the findings, and must report it back unchanged in its own "Classification report block" - a re-route Worker never re-stats the target file itself, since re-stating it would silently reset the staleness guard's baseline to whatever the file looks like mid-review, defeating the guard's purpose. The conductor's write-time re-stat at step 4 always compares against this one original snapshot, regardless of how many re-route rounds occurred.
4. On sign-off, the writer of this step depends on which path triggered Part E - see "Async-path amendment" immediately below for the second case. On the **synchronous `$dinostack-codex-wrap` path** (this Part E, invoked directly by a running `$dinostack-codex-wrap`), the main agent (not a subagent - same rationale as the rest of Step 4) writes in this order, already holding `$AE_PROJECT_DIR/.agentic/wrap/lock` from `$dinostack-codex-wrap`'s own pre-flight lock acquisition:
- **Backup location.** A repo-root target (`[cwd]/MEMORY.md`, `[cwd]/CLAUDE.md`) never backs up beside itself - a `MEMORY.original.md` sitting next to a committed `MEMORY.md` is itself a new committed-looking file nobody asked for. Repo-root targets back up to `$AE_PROJECT_DIR/.agentic/wrap/memory-backups/` instead (`mkdir -p` first). Backup filenames are stem-based: `MEMORY.md` -> `MEMORY.original.md`, `MEMORY.md` -> `MEMORY.pre-YYYY-MM-DD-HHMMSS.md` for rolling snapshots, same for `CLAUDE.md`. A non-root target (`$AE_PROJECT_DIR/.agentic/memory.md`) keeps backing up beside itself as before (already gitignored, no committed-file risk).
- **Ignore guard.** Before the FIRST backup write for a repo-root target this run, check `git check-ignore -q $AE_PROJECT_DIR/.agentic/wrap/memory-backups/`. If the directory is NOT gitignored, skip compression for repo-root targets this run and log why (`$AE_PROJECT_DIR/.agentic/wrap/memory-backups/ is not gitignored - skipping compression for repo-root targets to avoid staging backup files`). **Do not modify `$AE_PROJECT_DIR/.gitignore`** - this is a detection guard, not a fix.
- (a) If `FILE.original.md` does not already exist, create it from the current (pre-compression) file content, per Atomic write discipline <!-- aw-site: original-md --> (conditional publish). Never overwrite an existing `.original.md` - it is the canonical first-ever backup.
- (b) Write a rolling snapshot `FILE.pre-YYYY-MM-DD-HHMMSS.md` (using the current UTC timestamp at write time) from the current (pre-compression) file content, per Atomic write discipline <!-- aw-site: rolling-snapshot --> (unconditional publish). Always write; never skip.
- (c) Prune rolling snapshots: keep only the 3 most recent `FILE.pre-*.md` snapshots for this target (by timestamp in filename). Delete older ones.
- (d) Overwrite `FILE.md` with the compressed content, per Atomic write discipline <!-- aw-site: compressed-overwrite --> (unconditional publish).
- (e) **Write `last_compressed_size_bytes` only here, only now.** This sub-step is the sole point in Part E - on either path, sync or async - permitted to create or advance `last_compressed_size_bytes` (or any of the other fields this sub-step writes) for a target. Every other way this chain can stop short of here - re-route exhaustion, the ignore guard, a lock-acquisition failure, a staleness-guard discard, or the session ending mid-chain - must leave the target's `compression-state.json` entry, whether it already exists or does not, byte-for-byte as it found it: never create a new entry, never touch an existing one. Update `$AE_PROJECT_DIR/.agentic/compression-state.json` with `last_compressed_size_bytes` set to the byte count of the compressed output, `last_compressed_at` set to today's date, `original_backup_path` set to the absolute path of the `.original.md` file, `rolling_snapshots` set to the sorted list of absolute paths of the retained rolling snapshots for this target, and `entries_deleted_last_run`, `entries_merged_last_run`, `entries_converted_to_pointer_last_run` set to the counts of `STALE_VERIFIED_FALSE`-with-counterpart deletions, `TRUE_DUPLICATE`+`SUPERSEDED_MERGE` merges, and `LEARNINGS_BACKED` pointer-rewrites respectively from the sign-off round's final classification report (0 when the curation Worker acted on no entries of that kind this run). Create the file if it does not exist (the `$AE_PROJECT_DIR/.agentic/` directory is already created by the lock acquisition step).
**Async-path amendment (deliberate, not a silent exception - the conductor is the writer on BOTH paths, no separate third agent for the write).** On the **async Phase 11b path** - triggered by `$AE_REPO_DIR/content/commands/ds-implement-ticket.md` Phase 11b's "Post-return Part E curation gate check", never by a running `$dinostack-codex-wrap` session - the CONDUCTOR owns the entire chain end to end, preserving the "main agent (not a subagent)" rationale verbatim: on both paths, the main agent performs the write, never a subagent.
Sequence: the conductor spawns the curation Worker (step 1) as a background subagent and proceeds immediately to Phase 12 - still non-blocking, still invisible. On the Worker's completion notification - whenever it arrives in the session, possibly turns later, after other work - the conductor spawns the Skeptic (step 2). On sign-off, or after the existing up-to-3 re-route loop (step 3, which the conductor already owns identically on the sync path), the CONDUCTOR ITSELF performs this step 4 sequence - (a) through (e), including the Backup location and Ignore guard sub-steps above - with two additions specific to this path:
- **Lock.** The conductor acquires `$AE_PROJECT_DIR/.agentic/wrap/lock` (the same lock `$dinostack-codex-wrap` and `wrap-ticket` share, via `ds-wrap-acquire-lock`) immediately before the Backup location sub-step, and releases it on every exit path per the existing "Lock release is mandatory on every exit path" discipline above (no new release semantics - the conductor already owns this discipline). If acquisition fails, skip the entire step 4 write - no partial writes, no backup-only-then-abandon - soft-fail, no operator-facing output; the gate simply re-trips on a later PR.
- **Staleness guard.** Ordering is pinned: AFTER acquiring the lock (the Lock bullet above), and BEFORE the Backup location sub-step, the conductor re-stats the target file (byte size and mtime). The curation Worker's return (step 1's "Classification report block", carried forward unchanged through any re-route round per the "Re-route snapshot carry-forward" note in step 3) carries the read-time snapshot size+mtime it captured at spawn time. If the current stat differs from that snapshot in either field: RELEASE the lock and DISCARD the curated draft entirely - skip the write, no compare-and-retry, no partial merge, discard-and-defer is the whole rule - the gate re-trips on a later PR. This is what makes the batch-ticket-N+1-races-ticket-N case safe: ticket N+1's own `wrap-ticket` run changes `MEMORY.md`'s size/mtime before ticket N's curation chain reaches this step, so ticket N's stale draft is discarded rather than silently overwriting ticket N+1's write.
**If the session ends before the chain completes** (Worker still running, or the chain is awaiting Skeptic sign-off, or awaiting the conductor's own turn to reach step 4), nothing is written: no backup, no overwrite, no `compression-state.json` change. The target file and its state entry are exactly as they were before the gate tripped. This is not a lost update - it is deferred work with zero side effect, and the gate re-trips on whichever later PR next crosses the threshold.
**Step 5 — Worktree cleanup.**
If the project is a git repository, run manual workflow 'ds-cleanup-worktrees' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-cleanup-worktrees` now. This removes stale isolation worktrees and merged feature branches so the repo is clean for the next session. Then run `$AE_REPO_DIR/bin/ds-retire-carrier --list-holds` and re-run `$AE_REPO_DIR/bin/ds-retire-carrier <path>` for each listed path, adding `--release` to an explicit hold only when its revisit event occurred. If either cannot run, print one line naming it and the reason; never skip silently.
A wrap runs unattended at session end, and DS-245 made `--min-age-hours` off-by-default, so this step needs the 24h floor an operator-invoked run does not. **It is applied automatically and this step sets nothing.** manual workflow 'ds-cleanup-worktrees' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-cleanup-worktrees` Step 2 applies the floor whenever `$AE_PROJECT_DIR/.agentic/wrap/lock` is present, and the whole-flow lock acquired in this command's pre-flight is held until Step 6's `ds-wrap-release-lock`, which runs strictly after this step - so the lock is present for the whole of Step 5 by construction.
That mechanism is deliberate and must not be "simplified" back to an exported variable (DS-245 review round 1, CM2): Step 5 reaches Step 2's block as a SEPARATE shell invocation, and shell state does not survive between invocations, so `DS_CLEANUP_MIN_AGE_HOURS=24` set here would be gone by the time that block runs and the floor would be silently absent on exactly the path that needs it. `DS_CLEANUP_MIN_AGE_HOURS` still works for a caller that can set it in the SAME invocation as the block, and still takes precedence; it is simply not the mechanism here. Why the floor matters on this path: a concurrent session's worktree can be unlocked between turns and idle past the activity window, and nothing here surfaces this step's per-entry decisions to the operator in the same turn.
**Step 6 — Terminal marker transition + confirm completion.**
Release the pre-flight lock: run `ds-wrap-release-lock` (the PATH-wired helper that releases `$AE_PROJECT_DIR/.agentic/wrap/lock`). This must run before returning to the user, regardless of whether any prior step reported "skipped" or "nothing to do".
**Terminal marker transition (transitions its OWN marker to a retained `done` tombstone on completion).** When Step 0a staged a per-session `$AE_PROJECT_DIR/.agentic/wrap/pending-<session_id>.json` marker (the daemon guard passed), this synchronous `$dinostack-codex-wrap` transitions its OWN marker to a retained `done` tombstone on completion so the daemon does not later re-wrap a session the user already wrapped manually. When the Step 0a guard was false (off-Claude, toggle-off, or under the daemon guard), no marker was staged and there is nothing to transition - skip this block entirely. Transition the marker ONLY at true completion:
- **Full success** (`_wrap.md` written + Part B/C applied + Part E settled, no escalation outstanding): set this session's marker `status: done` AND stamp `wrapped_at: <now ISO8601 UTC>` and RETAIN the marker (do NOT unlink it). The retained `done` tombstone prevents this same session being re-staged after `$AE_PROJECT_DIR/.agentic/wrap/last-wrap` rolls to a different session; the daemon janitor reaps it after `deferred_wrap_pending_ttl_days`. A partial or escalated run leaves the marker in its pre-Step-6 state so the daemon can complete it later.
**Part F - Tracker status reconciliation.**
Runs OUTSIDE the `wrap/lock` window - strictly AFTER `ds-wrap-release-lock` above has already run, never before. Tracker/gh API calls (ticket queries, `gh pr list`, comment posts) are slow and must not extend how long `$dinostack-codex-wrap` holds `$AE_PROJECT_DIR/.agentic/wrap/lock` - other conductors and `wrap-ticket` invocations queue behind that lock. Purpose: give conductor-led ticket work (a session that touched a ticket outside `$dinostack-codex-implement-ticket` - e.g. a direct fix committed to `BASE_BRANCH`) a tracker footprint, so the ticket's tracker column doesn't silently lag behind shipped work.
Skip Part F entirely on the **zero-substance path** (see Step 0.5) - no session activity means no ticket-referencing commits to detect. Part F runs on the light path and the standard path, same as Part D.
**Gate.** Resolve `TRACKER`, `TICKET_PREFIX`, AND the 6 `TRACKER_STATE_*` values (`TRACKER_STATE_IN_PROGRESS`, `TRACKER_STATE_IN_REVIEW`, `TRACKER_STATE_QA`, `TRACKER_STATE_DEV_COMPLETE`, `TRACKER_STATE_BLOCKED`, `TRACKER_STATE_DONE`) using the SAME resolution chain as `$dinostack-codex-implement-ticket` Setup (AGENTS.md `## Linear` / `## Tracker` sections, plus the `$AE_PROJECT_DIR/.agentic/tracker.yml` local overlay). If `TRACKER == none`, skip Part F silently - no output, no log line. When `_source` is `overlay` or `merged`, print a `Tracker config source:` line before proceeding.
Also resolve `TRACKER_STATE_DIAGNOSTIC` (same field/default as `$dinostack-codex-implement-ticket` Setup).
Also resolve `TRACKER_PIPELINE_ORDER` (same fields and default as `$dinostack-codex-implement-ticket` Setup).
Also resolve `TRACKER_TRANSITIONS_MODE` (same `$AE_PROJECT_DIR/.agentic/tracker.yml` `transitions:` key and default as `$dinostack-codex-implement-ticket` Setup); pass it as `transitions_mode` below.
**Detect ticket keys referenced in this session's work (cheap, bounded).**
1. Ticket-key-shaped tokens (`<TICKET_PREFIX>-<n>`) already visible in the commit messages of any commit the conductor made this session - already known from this session's own tool-call history, no extra call needed.
2. One bounded `git log` call on `BASE_BRANCH` to catch keys in commits whose recorded message differs from what the conductor typed (e.g. a squash-merge commit rewritten by the merge tool): `git log <BASE_BRANCH> -E --grep="<TICKET_PREFIX>-[0-9]+" --oneline -20` - capped at the last 20 commits, one call.
3. If the session worked on a not-yet-merged feature/fix/chore branch, also scan that branch's own commits: `git log <BASE_BRANCH>..<branch> --oneline` - naturally bounded to the session's own branch work.
4. Union and dedupe the resulting keys. If none found, skip the rest of Part F silently.
**Reconcile each detected key.** For each detected ticket key, run the manual workflow 'ds-ticket-status-sync' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-ticket-status-sync` single-ticket "Resolution algorithm (single ticket)" (`$AE_REPO_DIR/content/commands/ds-ticket-status-sync.md`) - do NOT duplicate that algorithm here. On a warranted transition, fire the Tracker Writeback Helper (`$AE_REPO_DIR/content/references/tracker-writeback.md` `## Tracker Writeback Helper`) with `forward_only_guard: true`, `tracker_state_values` (the 6 values resolved in the Gate above), `diagnostic_enabled`, `linear_team_key` (Linear only), `pipeline_order` (`$TRACKER_PIPELINE_ORDER` resolved in the Gate above), and `transitions_mode` (`$TRACKER_TRANSITIONS_MODE` resolved in the Gate above), exactly as manual workflow 'ds-ticket-status-sync' via `$AE_REPO_DIR/bin/ds-codex-dispatch command ds-ticket-status-sync` does. **Gate the comment on the Writeback Helper's return payload having `transitioned: true`.** If the forward-only guard skipped the transition, the transition failed, or `status == "skipped_unconfigured_state"`, do NOT post a comment (a repeatedly non-transitioning attempt would otherwise re-post the same comment on every `$dinostack-codex-wrap` run). Regardless of comment outcome, print one operator-visible line per transition attempt so failures stay visible:
[wrap: Part F] <KEY>: '<current>' -> '<expected>' (evidence: commit [<sha>](<commit-url>)) - transitioned
[wrap: Part F] <KEY>: '<current>' -> '<expected>' (evidence: commit [<sha>](<commit-url>)) - FAILED: <error>
[wrap: Part F] <KEY>: '<current>' -> '<expected>' - SKIPPED: <diagnostic>
`<sha>` is the newest commit that surfaced `<KEY>` in the detection step above; link or print it bare per `$AE_REPO_DIR/content/references/conventions-detail.md` §External Comment Discipline.
The `<diagnostic>` slot renders the Tracker Writeback Helper's own return payload. When `status == "skipped_transitions_manual"`, `diagnostic` is `null` (per `$AE_REPO_DIR/content/references/tracker-writeback.md`'s `skipped_transitions_manual` clause) - rendering it verbatim would print an empty diagnostic. Render `transitions_mode=manual (no comment posted - the comment above is gated on transitioned: true); run \`ds-tracker set transitions auto\` to re-enable` instead. For every other status this line's `<diagnostic>` holds, render the payload's own diagnostic text unchanged.
Accumulate any `unmatched_state_name` returned by the guard across all detected keys this run; if the tally is non-empty, print ONE aggregate line before the final relay-to-user step: `[wrap: Part F] N ticket(s) had a current state that did not match any configured TRACKER_STATE_* value (distinct states seen: <name1>, <name2>, ...) - same-category comparison skipped for these.`
**Soft-fail (absolute).** Any error anywhere in Part F - tracker resolution failure, git call failure, MCP/gh API failure, subagent spawn failure - is swallowed with a one-line stderr log (`[wrap: Part F] <error>`), and Part F moves on to the next key or exits cleanly. Part F NEVER breaks, delays, retries-with-backoff, or blocks `$dinostack-codex-wrap`'s return to the user. It runs once, best-effort, after the lock is already released - a slow or failing tracker call costs the user nothing beyond Part F's own runtime.
**Part G - Knowledge-file commit.**
Runs OUTSIDE the `wrap/lock` window - strictly AFTER `ds-wrap-release-lock` above has already run, never before (same placement rationale as Part F: this step performs git and filesystem operations that must not extend how long `$dinostack-codex-wrap` holds `$AE_PROJECT_DIR/.agentic/wrap/lock`). Part G is independent of Part F and may run in either order relative to it, but both must run after lock release. Purpose: `$dinostack-codex-wrap` is NOT the ongoing content writer of most of the five files below - root `MEMORY.md`, `decisions.md`, and `$AE_PROJECT_DIR/.agentic/learnings.md` are written by `wrap-ticket`, `learnings-agent`, and `learning-extractor` respectively (see `$AE_REPO_DIR/content/references/conductor-operating-rules.md` §"wrap-ticket writer carve-out" for the full writer map; the sole exception is `$dinostack-codex-wrap`'s own narrow bootstrap stub-seed of root `MEMORY.md`, described in the pre-flight scaffold-accuracy check, item 1, above). `AGENTS.md` and `$AE_PROJECT_DIR/.agentic/tracking.md` have no single ongoing-writer agent in that map: `AGENTS.md` is edited ad hoc by whichever agent or conductor step touches it, and `$AE_PROJECT_DIR/.agentic/tracking.md` is written by `$AE_REPO_DIR/bin/ds-tracker`, a CLI, not a subagent. But this repo's own git workflow only ships a commit via a worktree-isolated `engineer` branch (see `AGENTS.md` §Conventions), so any conductor-side write to these five files - whichever agent made it - never reaches a commit unless a human remembers to do it by hand later. Part G closes that gap: it commits verbatim copies of the surviving files to a fresh branch and pushes it, leaving the human to open the PR.
Write-ordering among the five files is not a `$dinostack-codex-wrap`-internal question, because `$dinostack-codex-wrap` is not their ongoing writer - Part G only picks up whatever state they are in when it runs. The one real ordering question - whether `learning-extractor`'s write to `$AE_PROJECT_DIR/.agentic/learnings.md` precedes `wrap-ticket`'s read of it during Phase 11b - has no guarantee: `learning-extractor` is fire-and-forget (`$AE_REPO_DIR/content/commands/ds-implement-ticket.md`, Phase 6 clean exit), and `wrap-ticket` explicitly reads "whatever entries exist ... may be partial or empty. No warning needed" if extraction has not finished by Phase 11b. This is a documented, accepted race with a safe degraded fallback, not an ordering guarantee, and it is entirely outside `$dinostack-codex-wrap`'s scope.
**Skip condition turns on whether THIS RUN wrote anything, not on which route was taken.** On the **zero-substance path** (see Step 0.5), skip Part G only when Part B's own skip test (see Part B's "Skip test" above) also evaluated to skip - i.e. the memory entries input was "None" AND `$AE_PROJECT_DIR/.agentic/memory.md` was absent or empty, so no session activity happened and any dirty knowledge file predates this session and is not this session's to commit; that residual becomes the concern of the session-start knowledge-strand sweep (`$AE_REPO_DIR/content/rules/conventions.md` §Session Context and Memory). **Reuse Part B's skip test as the signal - do not re-derive a separate condition.** When the zero-substance staging-drain exception fired (a non-empty `$AE_PROJECT_DIR/.agentic/memory.md` made Part B run and write root `MEMORY.md` even on an otherwise zero-substance session), Part G MUST also run: Part B's write happened THIS run and sits uncommitted exactly like any standard-path write, and the next run's staging area is now drained empty - no later `$dinostack-codex-wrap` run will ever pick it up. Part G runs unconditionally on the light path and the standard path, same as Part D and Part F.
**Absolute soft-fail contract.** Part G NEVER blocks, NEVER fails the wrap, and NEVER propagates an error to the user beyond a one-line log. It inherits Part F's soft-fail contract verbatim: any error anywhere in Part G - git failure, disk failure, push failure, missing git config - is swallowed with a one-line stderr log (`[wrap: Part G] <error>`), and Part G moves on or exits cleanly.
**Candidate set**, in this fixed order:
1. `<cwd>/MEMORY.md`
2. `<cwd>/decisions.md`
3. `$AE_PROJECT_DIR/.agentic/learnings.md`
4. `<cwd>/AGENTS.md`
5. `$AE_PROJECT_DIR/.agentic/tracking.md`
**Resolve `BASE_BRANCH` and fetch, before gating.** Resolve `BASE_BRANCH` per `$AE_REPO_DIR/content/rules/conventions.md` §Base branch resolution steps 1-3 (declared in `AGENTS.md` - this repo declares `BASE_BRANCH: main` - else a local `develop` branch, else a local `development` branch). Part G's absolute soft-fail contract prohibits any interactive prompt, so it never takes that section's step-4 interactive stop-and-ask when none of the three resolve: it falls straight to the step-5 default (`main`, falling back to `master`) without asking, and Part G never auto-creates a branch regardless of which path resolves. Then run `git fetch origin`. Both must happen here, before the per-file gating below, because that gating compares against `origin/<BASE_BRANCH>` and must not compare against a stale local copy of the remote ref.
**Per-file gating** - each check is a hard skip for THAT FILE ONLY; it never aborts the sweep of the remaining candidates:
- File does not exist -> skip silently (no log line; this is the common case).
- File's entry in the candidate set above (exactly as listed - `$AE_PROJECT_DIR/.agentic/learnings.md`, not `learnings.md`) is present in `knowledge_commit_exclude` (`$AE_PROJECT_DIR/.agentic/config.json`) -> skip THIS FILE ONLY, but print a VISIBLE one-line diagnostic: `[wrap: Part G] <f> is excluded via knowledge_commit_exclude - not committed.` Do not redirect this to `/dev/null` or otherwise suppress it - same audibility rationale as the adjacent gitignored-file bullet.
- `ds-migrate verify-commit-path <f> --project-root <cwd>` exits 1 -> a negation in the live `$AE_PROJECT_DIR/.gitignore` appears to target `<f>`, but git still reports it ignored (a DEFEATED negation - the point-of-use, exact-path counterpart to `$AE_REPO_DIR/bin/ds-migrate check`/`apply`'s manifest-wide, probe-based sweep, which must synthesize candidate probe paths for the 4 directory-form negations and can miss an unguessed spelling; see `_compute_negations_defeated`'s docstring in `$AE_REPO_DIR/bin/ds-migrate` for that residual). Skip THIS FILE ONLY, but print a VISIBLE one-line diagnostic: `[wrap: Part G] ERROR: <f> has a negation in $AE_PROJECT_DIR/.gitignore that appears to target it, but git still reports it ignored (DEFEATED NEGATION) - not committed. Fix $AE_PROJECT_DIR/.gitignore by hand, then re-run \`ds-migrate apply\`.` **Do not redirect this to `/dev/null` or otherwise suppress it.** Exit 0 or exit 2 (git missing, not a worktree, or an untrusted check result - a soft-fail that must never itself block a commit) both fall through unchanged to the ordinary gitignore bullet below.
- `git check-ignore -q -- <f>` succeeds (exit 0, file is gitignored) -> skip, but print a VISIBLE one-line diagnostic quoting the matched rule: run `git check-ignore -v -- <f>` and print `[wrap: Part G] <f> is gitignored (rule: <matched-rule-output>) - not committed.` **Do not redirect this to `/dev/null` or otherwise suppress it** - a gitignored knowledge file is exactly the silent-strand failure mode this unit exists to make audible. This bullet fires only when the DEFEATED-negation bullet above did not - i.e. either no negation targets `<f>` at all (e.g. DinoStack's own repo, which excludes four of the five candidates by gitignore with zero negations attempted - a deliberate, audible no-op, not a defect - and excludes the fifth, `AGENTS.md`, via `knowledge_commit_exclude` instead, since it must stay tracked and un-ignored in every repo including this one) or the negation works and this bullet's own `check-ignore` simply does not trigger.
- File exists and is not gitignored, but is byte-identical to its `origin/<BASE_BRANCH>` version -> skip silently, nothing to ship. Check with `git cat-file -e origin/<BASE_BRANCH>:<f>` first: if the path does not exist at that ref (non-zero exit), the file is entirely new content and is NOT unchanged - it survives gating regardless of what `git diff --quiet` would report, because `git diff --quiet origin/<BASE_BRANCH> -- <f>` exits 0 (falsely "unchanged") for a path absent from the ref, which would otherwise silently defeat this feature for a project's first-ever `decisions.md` or `$AE_PROJECT_DIR/.agentic/learnings.md`. Only when the path exists at that ref does the byte-identity check apply: `git diff --quiet origin/<BASE_BRANCH> -- <f>` exits 0 -> skip.
- File exists, is not gitignored, and DOES differ from `origin/<BASE_BRANCH>`, but was already captured onto a ticket PR branch by `$dinostack-codex-implement-ticket` Phase 11e -> skip, and print a VISIBLE one-line diagnostic naming the file, the branch, and the commit: `[wrap: Part G] <f> already captured on the ticket PR branch <branch> (commit [<sha>](<commit-url>)) - not committed again.` This bullet is deliberately LAST in the gating order, so an unchanged file still reports "unchanged" via the preceding bullet and only a genuinely changed file can ever report "already captured". Determine the answer from `$AE_PROJECT_DIR/.agentic/knowledge-commit-state.json` (written by Phase 11e on its push-success path only). The gate fires ONLY when ALL of the following hold: the file exists and parses as JSON; its `commit` field is non-empty; `git cat-file -e <commit>^{commit}` succeeds (the branch may have been squash-merged and deleted, leaving the commit unreachable); `git cat-file -e <commit>:<f>` succeeds; and `git diff --quiet <commit> -- <f>` exits 0. **Any other condition means the gate does NOT fire and Part G proceeds exactly as it does today** - the gate is deliberately fail-open toward committing, because a duplicate commit is a reviewable diff a human resolves in the PR, whereas failing open toward skipping would silently drop knowledge, which is the exact failure Part G exists to prevent. `deleted_lines` is NOT consulted: this gate is content-based, not risk-based.
No marker cleanup is needed. Once the ticket PR merges, the file matches `origin/<BASE_BRANCH>` and the preceding byte-identity bullet skips it first, so a stale marker is inert rather than harmful. Accepted residual: a file changed AGAIN after the Phase 11e commit correctly does not fire this gate, so that content lands on two branches; Part G has no merge algorithm by design, and the PR diff is where a human catches it.
**Compiled-mode ride-along.** When `<cwd>/MEMORY.md` survives gating in compiled mode (see Part B), every path printed by `ds-memory-capture new-shards --ref origin/<BASE_BRANCH>` survives with it and is copied and staged in steps 2-3 below, so the committed `MEMORY.md` ships with the shards capture added (capture never changes or deletes a shard).
If NO file survives gating, Part G is a no-op: emit the `[phase: wrap-part-g]` breadcrumb - no worktree, no branch, no commit - but it still emits one `ds-emit knowledge_commit` event per step 10 below, with `status: "no-changes"`, so the no-op outcome remains auditable in `events.jsonl` the same as every other outcome.
**Otherwise (at least one file survives):**
1. Create an ephemeral worktree on a new branch: `chore/knowledge-<YYYYMMDD>-<6-hex-random>`, cut from the already-fetched `origin/<BASE_BRANCH>` resolved above. The branch name is derived from the current UTC date plus 6 random hex characters ONLY - no operator identity, no hostname, no absolute path (universality is a project pillar - the branch name must be reproducible-shaped for any teammate on any machine). Worktree path: `$AE_PROJECT_DIR/.agentic/worktrees/<branch-name>`, consistent with the rest of the methodology's worktree convention.
2. Copy each surviving file from the conductor's checkout into the worktree at the same relative path, VERBATIM - a plain byte-for-byte file copy (`cp`), never a re-render or re-format. Create parent directories in the worktree as needed (`$AE_PROJECT_DIR/.agentic/learnings.md` needs `$AE_PROJECT_DIR/.agentic/` to exist in the worktree).
3. `git -C <worktree> add` each copied file. **If step 1 (worktree creation), step 2 (file copy), or step 3 (git add) fails** (a `git worktree add` failure, a disk or permission error on the copy, or any other non-zero exit): print a visible warning (`[wrap: Part G] setup failed at step <N>: <error> - skipping knowledge commit.`), remove the ephemeral worktree per step 9 below if one was created, and stop here - this is a soft-fail, not an escalation, and it records `status: "setup-failed"` at step 10.
4. Resolve commit name/email from `git config user.name` / `git config user.email`. **If either is missing or empty: unstage the files (`git -C <worktree> reset`), print a visible warning (`[wrap: Part G] git user.name/user.email not configured - skipping knowledge commit.`), remove the ephemeral worktree per step 9 below, and stop here** - this is a soft-fail, not an escalation.
5. **Deletion warning.** Inspect `git -C <worktree> diff --cached --numstat` for deleted-line counts per file - the diff is still staged at this point, ahead of the commit in step 6. If NO file shows deleted lines, no warning is printed. If a file shows deleted lines, branch on whether `$AE_PROJECT_DIR/.agentic/compression-state.json` records a `last_compressed_at` of today for that file:
- **Compression-explained branch.** When `last_compressed_at` for `<file>` is today AND `<file>` is `<cwd>/MEMORY.md`: emit `[wrap: Part G] NOTE: <file> has N deleted lines vs origin/<BASE_BRANCH> - Part E compressed this file this session under Skeptic fact-loss review (pre-compression backups at $AE_PROJECT_DIR/.agentic/wrap/memory-backups/). This is expected, not a revert.` Then proceed normally - this is informational, not a warning.
- **Unexplained branch (the original warning; preserve loudly).** Otherwise, print a PROMINENT warning naming each such file and its deleted-line count: `[wrap: Part G] WARNING: <file> has N deleted lines vs origin/<BASE_BRANCH> - this commit may revert content another session already merged. Review the PR diff carefully before merging.` Then still proceed - do not abort, do not attempt to merge or reconcile, and do not build any detection machinery. Rationale (state this in the warning's spirit, not just here): the conductor's local copy may be stale relative to a knowledge commit another session already pushed and merged; a verbatim copy in that case would revert it. The design deliberately has no merge algorithm - the PR diff is where a human catches this, so the correct behavior here is to make the risk loud, not to build reconciliation logic.
6. Commit with `git -C <worktree> commit -s` (this repo enforces DCO - `Signed-off-by` is mandatory). Commit message:
chore(knowledge): capture <comma-separated basenames> from session
Signed-off-by: <name> <email>
`<comma-separated basenames>` is the list of surviving files by basename only (e.g. `MEMORY.md, decisions.md`), in candidate-set order. **If the commit fails** (a pre-commit hook rejection, a DCO/`Signed-off-by` mismatch, or any other non-zero exit): unstage the files (`git -C <worktree> reset`), print a visible warning (`[wrap: Part G] git commit failed: <error> - skipping knowledge commit.`), remove the ephemeral worktree per step 9 below, and stop here - this is a soft-fail, not an escalation, and it records `status: "commit-failed"` at step 10.
7. Push the branch: `git -C <worktree> push -u origin <branch-name>`. **Do NOT run `gh pr create`.** On push failure (network, auth, remote rejected): soft-fail per the absolute contract - log `[wrap: Part G] push failed: <error>` and skip to step 9 (worktree cleanup); do not retry.
8. On push success, print the branch name plus a ready-to-paste PR-open command: `gh pr create --base <BASE_BRANCH> --head <branch-name>`. This is a printed affordance only, not an auto-executed action and not a prompt awaiting a reply - `$dinostack-codex-wrap` summarizes a session, it does not ship on the operator's behalf, so it must never open an outward-facing PR unprompted.
9. **Remove the ephemeral worktree on every exit path from this subsection where one was created** - success, the steps-1-3 setup-failure soft-fail (when the worktree exists), the step-4 missing-git-config soft-fail, the step-6 commit-failure soft-fail, and the step-7 push-failure soft-fail all reach this cleanup (`git worktree remove` the ephemeral worktree; a leaked worktree is not an acceptable outcome of a soft failure).
10. Emit one `ds-emit` event describing the outcome: `ds-emit knowledge_commit - - '<json>'` (see `$AE_REPO_DIR/bin/ds-emit` for the exact 4-arg signature: `<event> <agent|-> <task_id|-> <json_data>`). The JSON `data` payload includes at minimum: `status` (one of `committed`, `no-changes`, `setup-failed`, `commit-failed`, `push-failed`, `failed` - `failed` denotes the step-4 missing-git-config soft-fail specifically, and `setup-failed` denotes a steps-1-3 worktree-creation/copy/stage soft-fail), `files_committed` (array of the candidate-set entries actually committed, exactly as listed above - e.g. `$AE_PROJECT_DIR/.agentic/learnings.md`, not `learnings.md` - not basenames), `files_skipped_ignored` (array of candidate-set entries, same non-basename form, skipped by the gitignore gate or the `knowledge_commit_exclude` config toggle - the field does not distinguish which of the two fired for a given file; the printed diagnostic line is the only place that distinction is visible), and `branch` (the branch name, or `null` when no branch was created). Emit this event on every path that reaches a determinable outcome - the no-op path (`status: "no-changes"`), the setup-failure soft-fail (`status: "setup-failed"`), the missing-git-config soft-fail (`status: "failed"`), the commit-failure soft-fail (`status: "commit-failed"`), and the push-failure soft-fail (`status: "push-failed"`) - not only on success. There is no `skipped-ignored` status: a file skipped by the gitignore gate is recorded in `files_skipped_ignored` regardless of the overall outcome, and the overall `status` is driven only by whether the run produced a commit, not by which files were gitignored along the way. Today the `[phase: ...]` breadcrumb is `echo`-only and produces no durable record; this event is what makes Part G's outcome auditable after the fact. The `knowledge_commit` event is now emitted from two sites, so the payload also carries `site` - one of `wrap-part-g` or `phase-11e` - plus `files_staged`, the list of files staged before the commit attempt; `files_committed` keeps the same meaning at both sites (files ACTUALLY committed), which means it is empty on every non-success status; and Phase 11e's `status` enum omits `setup-failed` (worktree-specific, with no analogue in a checkout-free commit) while adding `revert-risk-skipped`, `disabled`, and `no-branch` (the PR branch ref did not resolve - distinct from `no-changes`, because the ref-absence warning is printed to stdout and would otherwise leave no durable record of which of the two occurred).
**Residual coverage.** `$dinostack-codex-wrap` is manual and synchronous (see line 13, "Manual `$dinostack-codex-wrap` is synchronous"), and the deferred-wrap daemon that can complete a forgotten wrap headlessly is Claude-only and opt-in, defaulting to `deferred_wrap_daemon: false` (see the "Claude-host + opt-in + non-daemon guard" note under Step 0a). So a session that ends without ever invoking `$dinostack-codex-wrap` still strands its knowledge-file writes until a LATER session's start-up sweep fires the read-only notice (`$AE_REPO_DIR/content/rules/conventions.md` §Session Context and Memory, the knowledge-strand sweep) - and permanently, if no later session ever runs. Part G narrows this gap; it does not close it.
Relay confirmation to the user. Include all paths written (`_wrap.md`, root `MEMORY.md`, any AGENTS.md files updated or skipped, and any deferred-write paths at `$AE_PROJECT_DIR/.agentic/memory-pending.md` and `$AE_PROJECT_DIR/.agentic/agents-md-pending.md`), the marker transition outcome (`done` tombstone retained, or "no marker staged" when the Step 0a guard was false), the Part F outcome (ticket keys detected and any transitions fired, or "no tracker configured" / "no ticket keys detected this session" / "skipped - zero-substance path"), and the Part G outcome (files committed and the pushed branch name plus ready-to-paste `gh pr create` command, or the no-op/soft-fail reason: "no knowledge-file changes this session" / "<file> is gitignored" / "<file> has a defeated negation - fix $AE_PROJECT_DIR/.gitignore by hand" / "setup failed" / "git user.name/user.email not configured" / "git commit failed" / "push failed" / "skipped - zero-substance path" / "<file> already captured on the ticket PR branch"). Also include the cleanup summary if Step 5 ran. When Part B's `flush` captured a shard and its `flush:` line reads `batch_due=true`, add: "Verbatim memory shards exceed the 40000 B batch trigger - run this project's index batch (see its memory-shard convention doc)."
**Commit-ownership notice.** When Part B or Part E modified root `MEMORY.md` this session, the confirmation MUST also state that file's Part G commit status - read from Part G's actual status enum below (`committed`, `no-changes`, `setup-failed`, `commit-failed`, `push-failed`, `failed`; never invent a value outside that enum). If the status is anything other than `committed`, append: "Commit it before ending the session - the next session's git preflight may stash or reset it."
**The confirmation message MUST explicitly state which Skeptic rounds ran.** State the Skeptic round count for Steps 2–3 (draft Worker review) and the on-disk Skeptic round count from the Step 4 preamble (mandatory Skeptic on hand-authored output, if it ran). If any draft Worker → Skeptic round was skipped — for example, the conductor authored outputs inline because the Worker hallucinated, the light path was taken, or the zero-substance path was taken — say so explicitly and explain why. A confirmation that omits the Skeptic-round summary is non-conforming.
Include compression results from Part E: for each file compressed, list the file path with before and after byte counts (e.g. "memory.md compressed: 4821 -> 2103 bytes"). If Part E was skipped (no changes this session) write "No compression needed (no session changes)." If no targets crossed the gate write "No compression needed (targets below threshold)." If a target failed after 2 re-routes, write "Compression failed for [path] after 2 re-routes - skipped this session."
**Final reminder:** After `$dinostack-codex-wrap` completes, close the session cleanly so the current Codex Stop hook can finish writing `~/.codex/projects/[hash]/context.md`. The project-local migration remains deferred to `context-writer-migration`.