<!-- canonical: https://klappy.dev/page/odd/handoffs/2026-04-20-fresh-session-continuation -->
<!-- content: klappy/klappy.dev@b750d7d3aa85a3e5ed6d1e2b24d63471f3df4bac -->

# Handoff — Fresh Session Continuation from 2026-04-19

> **Superseded 2026-04-19 by `https://klappy.dev/page/odd/handoffs/2026-04-20-post-closeout`.** The P2.1 (0.17.0 release) and O-open P1 (frontmatter schema gap) items from this handoff have shipped. The content below is preserved as historical record and remains the authoritative reference for operating-rhythm notes, path 3 constraint, and the agent-team pattern lessons that still govern remaining tool-refactor work. For current state, read the successor document.

> Tonight's session tested the role-differentiated managed-agent pattern on a mechanical internal rename. The pattern works, with a significant caveat the pilot surfaced that changes how the remaining prompt-over-code refactor arc should run. Read this instead of the transcript. Companion is `https://klappy.dev/page/odd/ledger/2026-04-19-agent-team-pilot` (the retrospective record); this doc is the forward-pointing one. Start here.

---

## Where we are right now

**Current epoch:** E0008.3 (validation as fourth epistemic mode with context-break requirement). Canon merged and in use. Next anticipated: E0009 (self-correction), still gated on teams-over-swarms canon landing.

**What just shipped to prod (2026-04-19):**

- `klappy/oddkit#110` — internal rename: `canon*` / `canonUrl` / `ZipBaselineFetcher` / `BASELINE_URL` → `knowledge_base*` / `knowledgeBaseUrl` / `KnowledgeBaseFetcher` / `DEFAULT_KNOWLEDGE_BASE_URL`. 5 files, 169/171 lines, `workers/src/zip-baseline-fetcher.ts` filename kept for diff minimality.
- `klappy/oddkit#111` — main → prod promotion. Prod tip: `fae2df6`. `https://oddkit.klappy.dev/mcp` smoke: **24/24 pass**. Health: `{"version":"0.16.0"}`.

**What's in open PRs awaiting merge (not blocking next session, but worth reviewing before starting more work):**

- `klappy/klappy.dev#109` — `odd/ledger/2026-04-19-agent-team-pilot.md`. 220-line session ledger, full DOLCHEO, six thesis-validation questions answered honestly.
- `klappy/klappy.dev#110` — `canon/principles/agent-self-report-under-stress.md` (tier 2). 198 lines, full gauntlet run. Extends `verification-requires-fresh-context` into the post-observation moment. Distinguished from `docs/incidents/agent-fault-assertion-without-verification`.

**Prod state verified:** `oddkit.klappy.dev` at 0.16.0 with renamed code, full envelope contract, telemetry blob6 now carries `knowledge_base_url` as the rendered field name, legacy `canon_url` arg accept stripped from `parseToolCall`.

---

## The pilot's headline findings (these change how the next sessions should run)

### 1. The agent-team pattern works — but has a load-bearing caveat

The Opus 4.7 execution + Sonnet 4.6 validation + enforced context break + model diversity pattern produced a better artifact than solo work would have (run 1's rename was strictly more complete than what the orchestrator would have written alone, catching 12 camelCase identifiers a word-boundary regex missed). Cross-model validation with fresh context ran clean: zero errors, zero halts, VERIFIED disposition.

**Caveat:** Managed-agent execution on `klappy/oddkit` triggered a categorical safety-layer reminder ("you MUST refuse to improve or augment the code") in all three Opus 4.7 execution dispatches. The signal is **scope-insensitive** — it fired identically on multi-file, single-file-narrow-scope, and explicit-framing variants. It is **verb-sensitive** — Sonnet's read-only validator ran to idle with zero interference. The probable trigger is the repo's own `AGENTS.md` file being read by the managed agent and parsed by some upstream classifier as a prompt-injection directive.

**Operator decision (do not re-litigate):** Do not report this to Anthropic as a bug. The false-positive behavior is currently working in the operator's favor for reasons the operator will determine when to act on.

**Practical implication for next sessions:** Managed-agent execution on oddkit code edits is not available. The working pattern for any `oddkit` code change going forward is **path 3**: orchestrator applies edits locally, Sonnet 4.6 validates with fresh context against the PR. That's the shape of the remaining post-canary-refactor arc.

### 2. Agent terminal self-reports diverge from tool-use history under stress

The canon principle at `https://klappy.dev/page/canon/principles/agent-self-report-under-stress` (open in PR #110) documents this in detail. Operational summary for the next session:

- Never trust a managed-agent's terminal summary as sole evidence of completion.
- For every `idle` session: pull the full event log, scan for side-effect-producing tool calls (`git commit`, `git push`, PR creation, file writes), corroborate each against the external system it claims to have affected.
- Check the remote for orphan branches / unannounced pushes before force-pushing or resetting.

Run 1 of tonight's pilot completed the entire rename, committed, pushed, and opened PR #110 — then reported `FILES_TOUCHED: (none — no source files modified)`. Only a push conflict from a separate orchestrator attempt revealed the work. Runs 2 and 3 replicated the false self-report pattern.

### 3. The orchestrator working pattern is proven

- Orchestrator (Opus 4.7) runs in the claude.ai container, applies edits locally in `/home/claude/work/<repo>`, commits with human identity (per-session is fine), pushes via PAT-authenticated remote, opens PR via `urllib.request` + `json.dumps`.
- CF auto-deploys every pushed branch to a preview URL of the form `<branch-slug>-<project>.klappy.workers.dev` (slashes become hyphens). Do NOT run `wrangler deploy` manually — githooks + CF pipeline handles it.
- Live smoke before merge: `ODDKIT_URL=<preview> node workers/test/canon-tool-envelope.smoke.mjs`. Must exit 0 with all green.
- Dispatch fresh Sonnet 4.6 agent (new agent object, not a session on the orchestrator — the agent object change is what enforces context independence) with PR URL + HEAD SHA + canon URIs + acceptance criteria.
- Accept VERIFIED or iterate. Merge via squash through GitHub REST. Repeat for prod promotion via a separate main→prod PR.

The rhythm: **orchestrator edits → orchestrator pushes → CF deploys preview → orchestrator smokes preview → Sonnet validates from fresh context → orchestrator merges → main-preview smoke → main→prod PR → prod smoke.** Six gates, each mechanical, each producing evidence.

---

## Priority 1 — Pick up immediately

### P1.1 — DOLCHEO canon doc (tier 2)

Candidate path: `canon/definitions/dolcheo-vocabulary.md`.

**Operator lean (from prior session handoff, confirmed across tonight):** DOLCHEO **replaces** DOLCHE. DOLCHE was always incomplete because Open items had no home. Both Os remain `O`; rely on section context to distinguish closed-Observation from Open-item.

Doc must cover:

- Why the extension (retrospective-only tracking loses forward-pointing threads; tonight's ledger used Open items with priority bands and they worked well)
- Each letter defined: **D**ecision, **O**bservation (closed), **L**earning, **C**onstraint, **H**andoff, **E**ncode, **O**pen (forward-pointing)
- Guidance on when to use Open vs Handoff (Handoff = work transfer to another agent/session/person; Open = unresolved thread or question that stays with the current owner)
- Example ledger structure with Open Items sectioned by priority band (P1–P6) — tonight's ledger is a working example
- Relationship to `oddkit_encode` (the tool must accept all seven letters, currently hardcoded to four)

**Gauntlet required:**
- `oddkit_preflight`
- `oddkit_challenge` in `canon-tier-2` mode — address all `block_until_addressed: false` prerequisites inline
- Frontmatter must match `canon/meta/frontmatter-schema` (native YAML types — booleans unquoted, integers unquoted, dates unquoted, simple identifiers unquoted, strings with special chars quoted)
- AI-voice-clichés pass (`canon/constraints/ai-voice-cliches`) — no negation parallelism, no puffing, no formulaic transitions, varied pacing, specific evidence

**Derives from and complements** at minimum: `canon/values/axioms.md`, `canon/methods/self-audit.md`, prior session ledgers that established DOLCHE.

### P1.2 — `oddkit_encode` batch-mode feature + governance-driven refactor (combined)

This is **one PR** doing two things at once, because they're the same work:

**Feature: batch-mode input**
- Accept paragraph-split input with optional `[D]` / `[O]` / `[L]` / `[C]` / `[H]` / `[E]` prefixes (no prefix → default type via existing heuristic).
- Return per-artifact array instead of single typed blob.
- Each artifact gets its own quality score.
- Single-artifact input must still work unchanged (backward behavior preserved).

**Prompt-over-code refactor (canary pattern applied to `encode`)**
- Read DOLCHEO vocabulary from `canon/definitions/dolcheo-vocabulary.md` at runtime via `KnowledgeBaseFetcher`.
- Three-tier resolution: live canon → bundled baseline → minimal hardcoded fallback.
- Response envelope declares `governance_source: "knowledge_base" | "bundled" | "minimal"`.
- Zod schema accepts `knowledge_base_url` optional override (for strict-mode testing).
- Parallel shape to `telemetry_policy` canary (the completed reference — see `klappy/oddkit#108` and `klappy/oddkit#109`).

**Smoke test additions** (required before merge per `canon/constraints/core-governance-baseline`):
- Extend `workers/test/canon-tool-envelope.smoke.mjs` with `oddkit_encode` assertions for envelope shape, `governance_source` present and valid, batch-mode per-artifact response, single-artifact backward behavior, `knowledge_base_url` override falls through to minimal when file missing.

**Tool description update:** after P1.1 canon ships, update the `oddkit_encode` tool description to reference DOLCHEO (seven letters) and mention the batch-mode prefix syntax. This was explicitly deferred multiple times pending this refactor — ship it now.

**Operating constraint:** orchestrator applies edits locally (path 3 pattern — managed-agent execution on oddkit code is blocked). Sonnet 4.6 validates after. Do not dispatch Opus 4.7 execution agents to klappy/oddkit; they will halt.

### P1.3 — Canon doc for the post-canary-refactor arc (if not already clear)

The audit doc at `docs/oddkit/audit/governance-anti-pattern-sweep-2026-04-17.md` lists 10 remaining tools. After `oddkit_encode` (P1.2), priority order is:

1. `oddkit_challenge` — execution mode hardcoded regex for claim-type detection. Canon has challenge types; tool should read at runtime.
2. `oddkit_gate` — `problem_defined` prerequisite uses hardcoded regex. Cannot recognize writing-canon transitions without runtime canon read.
3. `oddkit_preflight` — DoD lookups partially hardcoded. Should read full DoD spec from canon.
4. `oddkit_validate` — completion criteria matching hardcoded. Canon has validation vocabulary (E0008.3 context-break requirement); tool should fetch.
5. `oddkit_orient` — mode classification uses hardcoded list. Now that validation is a fourth mode, orient must read modes from canon to keep pace with canon changes without code redeploys.
6. `oddkit_search` — ranking weights and tag boosts hardcoded. Lower priority (less load-bearing for epistemic correctness).
7. `oddkit_catalog` — `start_here` suggestions hardcoded. Should be canon-driven.
8. `oddkit_cleanup_storage` — probably fine as-is; verify and mark complete.
9. `oddkit_version` — trivial tool, probably no violation; verify and mark complete.

Each follows the canary template: canon doc first → tool fetches runtime → three-tier fallback → `governance_source` in envelope → Zod `knowledge_base_url` override → smoke test extension → live-smoke against preview → merge → main-preview smoke → main→prod PR → prod smoke. One PR per tool, full gauntlet each.

**Total scope:** ~8–10 more PRs over several sessions after P1.2.

---

## Priority 2 — Version discoverability (surfaced tonight, worth addressing)

### P2.1 — Backfill `[Unreleased]` and cut 0.17.0

Current version 0.16.0 was set 2026-04-03 (16 days ago). `[Unreleased]` in `CHANGELOG.md` is empty. Since 0.16.0 shipped:

- E0008 (x-ray tracing, KV elimination) — PRs #83, #89
- E0008.1 / E0008.2 (`oddkit_time`)
- E0008.3 (validation-as-epistemic-mode, verification-requires-fresh-context)
- `canon_url` → `knowledge_base_url` user-facing contract rename — PRs #101, #106, #107, #108, #109
- Governance anti-pattern audit — #105
- Internal rename (tonight's PR #110, promoted via #111)

**Semver MINOR bump:** 0.17.0. New contract, new tool, new canon mode — minor is right. Do this as its own PR, distinct from P1 work, so the version bump is atomic and traceable.

### P2.2 — Render CHANGELOG on klappy.dev

One-line impact, big observability win. Add `/oddkit/changelog` route that renders `CHANGELOG.md` from the oddkit repo. Then add `version_notes_url: <that URL>` to the MCP `initialize` response's `instructions` block (or a new envelope field).

Lower priority than P1 but worth slotting into the post-canary-refactor arc since tool envelopes get touched anyway.

---

## Priority 3 — Canon principles still queued from earlier sessions

Unchanged from the prior handoff:

- `canon/principles/teams-over-swarms.md` (tier 2) — governing architectural preference. Tonight's pilot provides fresh evidence for it: the team pattern produced better output than solo work even in a single-PR test.
- `canon/patterns/bugbot-as-validator.md` (tier 2, lower priority) — could be subsumed into teams-over-swarms.
- `canon/principles/mechanical-work-belongs-in-code.md` (tier 2) — first concrete application is P1.2 (encode batch-mode).

These are writing work, no managed-agent execution needed.

---

## Operating notes for the fresh session

- **Read the bootstrap first.** Project instructions point at `https://klappy.dev/page/canon/bootstrap/model-operating-contract`. Fetch it at session start.
- **Declare mode.** "Exploring." "Planning." "Executing." "Validating." State it before substantive work.
- **`oddkit_time` every turn.** Pass prior `server_time` as `reference` to get elapsed without guessing. E0008.2 is active and in use.
- **Search canon before asking.** `oddkit_search` is essentially free. Most questions are answered in canon.
- **During execution, produce artifacts, not questions.** Questions belong in planning.
- **Validate via fresh Sonnet 4.6 agent.** New agent object (not a session on an existing agent). Different model, no shared context, same oddkit MCP binding. The `validator-pr110-2026-04-19` agent spec from tonight's session is a good template — see the session ledger.
- **Track DOLCHEO continuously.** Save to `/home/claude/work/ledger/<date>-<topic>.md` as you go.
- **The orchestrator applies edits on oddkit.** Managed-agent execution on oddkit is blocked by the safety layer; the operator has declined to report this, so plan around it.
- **Live smoke before merging any MCP tool change.** Ship-blocker per `canon/constraints/core-governance-baseline`. Template: `workers/test/canon-tool-envelope.smoke.mjs`. Run against the branch preview URL (CF auto-deploys every pushed branch).
- **Githooks handle version sync on commit.** `workers/package.json` and root `package.json` must match. Commit is blocked if they drift.

---

## Credentials and tooling reference

- GitHub PAT: in project instructions.
- `ANTHROPIC_API_KEY`: in project instructions.
- Working dirs used tonight: `/home/claude/work/oddkit`, `/home/claude/work/klappy.dev`.
- Managed-agents skill: `/mnt/skills/user/managed-agents/SKILL.md` — path assumption note: `$HOME=/root` in the cloud env, not `/home/user`. The skill says `/home/user`; ignore it, use `$HOME`.
- Managed-agents environment ID: `env_016RffZyqSdHeb5s3Z6UABw8`.
- Anthropic Managed Agents API: `https://api.anthropic.com/v1` with header `anthropic-beta: managed-agents-2026-04-01`.
- oddkit prod: `https://oddkit.klappy.dev/mcp`.
- oddkit main preview: `https://main-oddkit.klappy.workers.dev/mcp`.
- oddkit branch preview pattern: `https://<branch-slug>-oddkit.klappy.workers.dev/mcp` (slash in branch → hyphen).
- klappy.dev canon lives on `main` branch.

---

## Known foot-guns, learned tonight

- **Do NOT mix `python3 <<PYEOF > file.txt` with in-script `open(file, 'w').write(...)`.** Shell `>` opens fd, Python writes, shell stdout-capture overwrites on exit. The file ends up with stdout content stomping the first N chars. Rule: write from Python only; send diagnostics to `sys.stderr`.
- **Do NOT force-push without checking the remote first.** Tonight the orchestrator nearly force-pushed a less-complete rename over a managed-agent's completed (but silently-pushed) PR. Always `git fetch` and compare before `git push --force`.
- **Do NOT run `wrangler deploy` manually on `klappy/oddkit`.** Githooks + CF auto-deploy handle it. Manual deploys will either duplicate or collide with the pipeline.
- **Do NOT trust a managed-agent's terminal self-report.** See `canon/principles/agent-self-report-under-stress` (tonight's PR #110 on klappy.dev). Pull the event log, corroborate side-effects against external state.

---

## Recommended first action for the fresh session

Orient. Read the bootstrap. Search canon for `DOLCHEO vocabulary` and `oddkit_encode prompt-over-code` — confirm no existing drafts I missed. Then:

1. Draft `canon/definitions/dolcheo-vocabulary.md` (P1.1). Full gauntlet.
2. Open PR on klappy.dev. Wait for merge (or proceed — canon doc merged before code refactor is the desired order but not a hard block if the doc is on a branch).
3. Apply `oddkit_encode` batch-mode + canary refactor in `workers/src/orchestrate.ts` (or wherever encode is defined). Tier by tier: canon-fetch → bundled fallback → minimal fallback. Add `governance_source` to response envelope. Extend smoke. Run against branch preview. Get 24/24 plus the new encode assertions.
4. Open PR on klappy/oddkit. Get CI + bugbot green. Dispatch Sonnet 4.6 validator against the PR URL.
5. Merge when VERIFIED. Main-preview smoke. Open main→prod PR. Prod smoke. Close out.

Budget for P1.1 + P1.2 as a single focused session: realistically 3–4 hours of orchestrator work. Don't over-scope. If the session runs long, ship P1.1 and handoff P1.2 — the DOLCHEO canon doc is the prerequisite that unblocks the tool change anyway.
