Exact-session APU diagnosis and preparation through the existing portable CLI.

APU is a local-first command-line tool for auditing, simplifying, installing, and evaluating durable coding-agent instructions.
The deterministic core inventories Codex and Claude instruction surfaces, produces reviewable JSON plans, applies approved changes transactionally, and rolls them back from local receipts. Optional behavioral fixtures can run through an independently installed and authenticated Codex or Claude CLI.
After installing APU's CLI, load this checkout with claude --plugin-dir /absolute/path/to/apu. On Claude Code 2.1.287+, /apu-diagnose passes the current provider, session ID and working directory to apu-wtf --fresh. It saves an ordinary APU incident and diagnosis and prints their IDs and the existing status. It does not infer a session from nearby files or replace journal evidence with mod events.
/apu-prepare uses that saved diagnosis with apu-intervene --dry-run. APU owns attribution, barrier checks, prompt integrity and policy-mutation checks. A refusal stays a refusal. A successful preparation prints the saved continuation argument vector and leaves its outcome unverified. Repeating the command returns the same preparation in the current mod instance. Neither command submits a prompt, resumes an agent, enables a watcher or changes policy.
Clear, resume, branch, reload and directory changes discard the adapter's cached diagnosis. Diagnose the current session again before preparing. Failed or timed-out calls are not retried automatically; inspect APU's saved artifacts. Commands are explicit and immediate, with a 15-second subprocess deadline and no background polling. The ordinary CLI remains available without the mod for both Codex and Claude. apu-watch controls the watcher and is separate from these commands.
The pilot targets native Windows first. Run claude plugin validate . and claude plugin test .; the generated types and runtime were checked with Claude Code 2.1.296. macOS/Linux CLI launch and live Desktop remain separate qualification checks. Native prompt delivery and voice transport are outside this pilot; no UI event implies successful recovery.
python -m pip install .
apu audit /path/to/repository --json inventory.json
apu propose --inventory inventory.json --output plan.json
apu review plan.json
apu apply plan.json
apu validate
apu init /path/to/repository creates a draft preview. Add --apply for the explicit interactive review and transactional installation flow.
M5 adds deterministic machine-level inventory and campaign-grade restore points. Declare the roots APU may inspect in ~/.config/apu/profile.toml (or %APPDATA%\apu\profile.toml on Windows):
schema_version = 1
global_surfaces = ["~/.claude", "~/.codex", "~/.agents"]
[[roots]]
path = "~/Desktop/MyDocs"
excludes = ["node_modules", "archive/**"]
Root exclusions are enforced before APU descends into matching directories or reads matching instruction surfaces. They constrain both nested repository discovery and each selected repository's child inventory.
apu system audit --json system-inventory.json
apu snapshot create --label before-policy-update
apu snapshot list
apu snapshot diff SNAPSHOT_ID
apu snapshot restore SNAPSHOT_ID
apu system status
Snapshot manifests preserve object types, links, junctions, empty directories, content hashes, and meaningful POSIX modes. Restore is journaled and reverses completed swaps on failure; interrupted recovery uses apu snapshot restore --resume JOURNAL_ID [--unwind].
M6 turns a system inventory into an immutable campaign with deterministic operations and privacy-safe work orders:
apu system propose --inventory system-inventory.json --output campaign.json
apu system apply campaign.json --auto-only
apu system status
To explicitly ask APU to consolidate the effective AGENTS.md and CLAUDE.md policy for one audited repository, add the repository path:
apu system propose --inventory system-inventory.json \
--consolidate-instructions /path/to/repository \
--output instruction-campaign.json
This task is explicit rather than an automatic semantic heuristic. It creates review-required, target-scoped work orders even when the deterministic audit has no findings. It combines audited machine-level instructions with only the repository surfaces present in an effective provider stack, excluding inactive nested files. Effective ancestor instructions are eligible for this explicit review task; ordinary deterministic operations remain profile-bound. Each work order includes the stack as untrusted context and asks the runner to state shared behavior once, keep project facts near the project, preserve provider-specific syntax, and prefer supported imports when appropriate. APU does not edit live files during proposal or dispatch; every accepted candidate still requires review and apply.
Add --emit-prompts DIR to export hand-run work orders; APU warns that these copies are outside private state protection. Credential-exposure findings are always manual-only and render file, line, and content hash without the value. Other findings on sensitive surfaces use private sanitized staging and structurally verified placeholders. Before the first deterministic mutation, APU locks the campaign, records a campaign-bound snapshot covering every planned target, and stamps the receipt with campaign, snapshot, and idempotency data.
M7 makes guidance and model identity explicit, versioned inputs. Refresh is explicit network activity:
apu refresh guidance --profile profile.toml --output guidance-refresh.json
apu refresh models --profile profile.toml --output model-refresh.json
apu system audit --profile profile.toml --json system-inventory.json
Guidance refresh stores bounded exact-byte HTTPS snapshots privately and emits a distillation work order. It never adopts model-written guidance automatically. After reviewing a returned candidate and approval artifact:
apu refresh guidance --profile profile.toml \
--adopt baseline-candidate.json --approval approval.json
apu guidance diff BEFORE_BASELINE AFTER_BASELINE
Model refresh observes installed CLI versions and configured selectors offline, then resolves them only against fixed official provider listing endpoints. Provider credentials are read at request time from the process environment and are never persisted. Exact published model IDs can resolve directly; aliases and omitted defaults remain visibly unverified unless the authoritative listing supplies their mapping. Failed refreshes preserve the matching last-known identity as stale rather than presenting it as current.
System inventories now carry a typed baseline/model evaluation context. New campaigns verify its immutable private artifacts and freeze it in the campaign manifest; older M6 campaigns remain readable, while v1 inventories must be regenerated before creating a new campaign.
APU accepts a content-addressed policy-delta proposal produced from a pinned Git source by AETA. Intake validates the complete proposal, its content-derived identity, and its declared source-hash binding; rejects credential-shaped material; and stores one immutable candidate plus a pending_review receipt in private APU state:
apu intake policy-delta proposal.json
This command does not apply, adopt, or promote the proposal. A later APU-owned review and approval flow must make any policy change.
M8 observes tracked Claude packages from authoritative installed metadata, resolves stable upstream tags during an explicit networked research command, and stores candidate trees privately by content hash. Add package coordinates to the system profile:
packages = ["superpowers@claude-plugins-official"]
Then compare one installed package with its latest stable candidate:
apu research packages superpowers --profile profile.toml
apu research packages superpowers --profile profile.toml --upgrade-capability
Installed and candidate trees run through the same frozen classifier policy. Reports contain only relative finding metadata, hashes, provenance, and explicitly unclassified dynamic surfaces—never instruction bodies. Package caches and provider-managed installation metadata remain read-only. Archive links are never created or followed on disk: a versioned manifest records validated package-internal links, and analysis reads the bound target blob through that virtual view. Until a provider exposes a documented exact-version pin or APU gains a journaled provider-update protocol, research emits hold or work-order and reports upgrade mutation as unavailable instead of claiming an unverified install.
Dispatch accepts only a work order inside APU's private campaign state. On Windows, the Codex adapter first proves that its confined context can write the disposable stage but cannot open the live target for writing. If that probe fails, dispatch is unavailable and the work order stays in the hand-run queue. Claude dispatch currently fails closed because an equivalent enforced isolation adapter is not implemented.
apu dispatch %LOCALAPPDATA%\apu\campaigns\CAMPAIGN_ID\work-orders\WORK_ORDER_ID.md
apu review %LOCALAPPDATA%\apu\campaigns\CAMPAIGN_ID\plans\PLAN_ID.json \
--output reviewed-plan.json
apu apply reviewed-plan.json --yes
The runner sees only an exact staged copy, or a sanitized copy with structural placeholders on sensitive surfaces. Returned content is secret-scanned and quarantined on any file-set, placeholder, relocation, or secret violation. Accepted output becomes a pending full-file plan; it cannot mutate live files until reviewed and applied. The resulting receipt remains bound to the campaign snapshot.
Campaign-bound outcomes derive installed categories from the immutable campaign rather than trusting user input. --activate records an explicit user attestation; empty activation evidence remains the default.
apu outcome record --receipt RECEIPT.json --task-id TASK_ID \
--activate guidance-conflict --validation passed
apu outcome promotion --receipt RECEIPT.json --category guidance-conflict \
--profile profile.toml --deterministic-remediation
apu system status --profile profile.toml
Promotion only emits a review-required profile-edit proposal after ten distinct material activations, a closed monitoring window, no implicating defects, and green close-window fixtures. One implicating escaped defect writes a visible APU-private demotion override without editing the profile.
The live watcher detects likely primary-agent-autonomy-loss in existing Codex and Claude Code JSONL sessions. Automatic attribution requires exactly one provider to have exactly one incomplete session whose normalized working directory matches the current directory and whose last event is no more than ten minutes old:
apu-event "asked me to approve a reversible filename choice"
apu wtf
# Run the exact apu-intervene --diagnosis ... command printed by the diagnosis.
Use --provider codex|claude-code and --session-id to resolve an ambiguity explicitly. If the supplied session belongs to another working directory, if multiple providers qualify, or if selection finds zero or multiple fresh incomplete matches, APU returns no_attribution with a bounded reason code and exits nonzero. It never falls back to a recent session from another project or provider.
apu wtf (also apu-wtf) reuses the latest marked incident only when it matches the current directory, or the directory supplied with --cwd, and every explicit provider/session selector. Otherwise it selects one fresh, incomplete run in that directory. --fresh forces a new snapshot of the run; --trace-root also forces fresh selection. Use --incident ID to inspect a specific saved incident from any directory. It cannot be combined with --fresh, --cwd, --session-id, or --trace-root.
Both commands explain the result and show the session, directory, evidence location, possible causes, and saved diagnosis. Matching instruction rules are clues rather than proof of causation. A continuation command includes --diagnosis ID, so another diagnosis cannot redirect it to a different incident. Insufficient evidence prompts you to describe the stop; possible barriers prompt review instead of continuation. Selection failures explain how to resolve the mismatch or ambiguity. --json retains the existing diagnosis and attribution-error formats.
apu ezpz (also apu-ezpz) prepares recovery when the requested outcome is still unresolved: the agent stopped early, repeated an ineffective approach, or handed routine work and approval requests back to you. It marks a fresh incident with operator-requested-recovery and reports recovery-requested, not a proven diagnosis. The optional description identifies what still needs fixing and travels with the continuation.
The primary-agent-outcome-recovery-v1 instruction tells the agent to verify the actual behavior, test its suspected cause, change an ineffective approach, and act within existing authorization. Tests passing or a process being healthy are supporting evidence, not proof that the user's task works. Necessary human actions remain explicit and unverified until their results are known.
Normal output includes the recovery instruction and the exact incident-bound continuation command. --prompt prints only that instruction for use in the selected chat; it cannot be combined with --json. Neither form launches an agent. Selection remains exact to the working directory and provider. If the agent already ended its turn, use --session-id to select that fresh session explicitly. Automatic selection does not guess between idle chats.
Barrier evidence still takes precedence: possible-legitimate-barrier blocks both prompt-only output and apu-intervene. Old easy-decision-gate incidents retain their original template. Saved diagnosis hashes bind the specific instruction and operator report used by the continuation.
apu ezpz
apu ezpz "tests pass but the voice command still does not work"
apu ezpz "keeps trying the same restart without new evidence" --prompt
apu ezpz "asked me to approve a reversible filename choice" --session-id SESSION_ID
# Run the exact apu-intervene --diagnosis ... command printed by the diagnosis.
apu-intervene resumes a non-interactive Codex session directly; for Codex Desktop and Claude Code sessions it records and prints the exact provider continuation because APU cannot inject into the desktop process. Claude Code transcripts do not reliably distinguish interactive CLI runs from --print, so their continuations always require --execute. Add --dry-run to only record the proposed action. An executed continuation is recorded as resumed, with turn_completed tracking an observed Codex turn-end marker (null when not observed through that transport) and outcome_verification: unverified. A model turn ending does not mark the task completed. After checking the actual result, record the outcome as an operator attestation:
apu-intervene --result completed
Before it constructs or launches a continuation, apu-intervene re-reads the exact trace and proves that its provider, session ID, and working directory still match the incident. It also rejects any diagnosis whose durable_policy_mutation value is not exactly false. The mutating apu apply command keeps its strict session gate and is provider-neutral: it binds to one fresh, incomplete Codex or Claude Code session in the exact working directory, fails closed on ambiguity, and accepts --provider, --session-id, --trace-root, and --cwd like the incident commands.
Watcher state is explicit and never starts a background service. Its health output names both supported providers and includes per-provider attribution time, ambiguity count, and heartbeat plus package version and build hash; it contains no session ID, trace path, cwd, or transcript content:
apu-watch
apu-watch autonomy-loss --disable
apu-watch autonomy-loss --enable
Incident artifacts keep the provider, operator's short description, session identity, nearby record hashes/types, runtime-setting labels, and active surface hashes. They do not persist nearby messages, reasoning, tool inputs or outputs, base instructions, or environment content. request-substitution identifies a different task being substituted for the requested one. A denial produced by provider permissions or a user-authored hook is an operator-designed-gate barrier and suppresses automatic intervention. Interventions are temporary session instructions and never rewrite durable policy.
APU normalizes provider execution records into three content-minimized evidence classes: invocation, result, and repository-state observation. Provider message bodies, reasoning, tool inputs, tool outputs, and command text are inspected only in memory and never enter APU state. Stored records contain safe labels, hashes, exit status, duration, correlation hashes, and optional Git state.
Evidence schema v2 adds exact selector provenance: selector mode, candidate count, last-event age, and confidence. The v2 reader also accepts legacy v1 records, but v2 records are written under a separate behavior/evidence/v2 route so a 0.8.0 reader never encounters a v2 object in its v1 file. During a staged rollout or rollback, ingestion commands can write a complete v1 object with --schema-version 1; apu-event exposes the equivalent --evidence-schema-version 1 option, as does apu behavior audit when it ingests a Codex trace. No version is silently coerced.
Ingest the most recent Codex session for the current repository, then inspect its request/result reconciliation and verify that its source records still match the captured transcript prefix:
apu evidence ingest-codex --json
apu evidence show --provider codex --session-id SESSION_ID --verify --json
Provider lifecycle hooks can stream one JSON object on standard input. The hook adapter projects only recognized metadata and discards unrecognized or sensitive fields before the append-only record is written:
apu evidence ingest-hook --provider claude-code --event PreToolUse
apu evidence ingest-hook --provider claude-code --event TaskCompleted --observe-state
--observe-state adds an independent Git observation after the hook event. It stores the current commit/tree IDs, dirty state, and hashes of changed paths—not the path names. Hooks remain lightweight; APU does not archive transcripts or run a background worker. Incident attribution and the evidence contract both support Codex and Claude Code.
APU can audit recent session evidence for workflow failures without turning the provider log directory into permanent memory:
apu behavior audit
apu behavior audit --provider codex --since 12h --sessions 10 --max-bytes 64MiB
apu behavior audit --session-id SESSION_ID --json
apu behavior gate-cost --since 7d
apu behavior gate-cost reads the start-of-turn gate hook's decision log (one content-free line per gate state write, plus the hook's prompt, tool, and stop lines) and computes the I3 measure from the gate case record: per objective, the completed turns that used only read-only tools while the gate held a plan pending. Prompt excerpts in that log are dropped at parse time and never stored. The audit joins the same log by default (--gate-log PATH to point elsewhere, --no-gate-log to skip) and raises a read-only-turns-while-pending finding for an audited session whose I3 exceeds the target of one.
The default scope is the current project, the last seven days, at most twenty sessions, and at most 256 MiB of source records. Operator-marked incident sessions are considered before ordinary recent sessions and may bypass the age window, but never the session or byte caps. An exact --session-id may also bypass age; there is intentionally no unbounded all-history option. At most 1,000 files per provider are opened for metadata inspection, with exact and incident-marked sources considered first.
The audit checks correlated tool requests/results, repeated identical failures, permission denials, activity after completion, repository state at completion, and whether a mutation made the last passing test stale. Replayable Codex records are verified against their captured transcript prefix. Hook records are labeled observed, not promoted to verified merely because APU stored them. Recorded credential, permission, destructive-action, external-side-effect, and material-information barriers suppress behavioral incident findings rather than counting them as autonomy failures.
Reports live under APU_HOME/behavior/audits. They store detector codes, safe labels, hashes, evidence references, counts, and explicit incident-time versus current-at-audit surface bindings. They do not store messages, reasoning, commands, tool input/output bodies, environment content, changed path names, or raw provider records. APU does not delete or archive provider logs. Detailed APU events are marked as candidates for expiry after thirty days or when their linked monitoring window closes; automated pruning remains a separate future operation.
APU can consume a version-1 lugos.autowork.behavior-delegation-receipt as redacted evaluation evidence. The importer verifies the receipt's canonical hash, accepts only the implemented adaptation tiers 0–2, and retains identity, registry revision, applied-artifact hashes, safe telemetry, and structured evaluation results. It rejects raw prompts, responses, reasoning, environment content, credentials, unknown fields, and credential-shaped strings.
APU can then construct a content-addressed lugos.apu.lugos-orca.behavior-registry-candidate-patch. Every proposal names an exact Lugos Orca commit and behavior-tree hash, cites validated evidence from that same revision, and has requires_review: true and apply_authorized: false. This module has no provider launcher and no patch application function: Autowork owns execution, and a human-reviewed Orca change owns registry mutation.
The strict version-1 JSON schemas are installed under share/apu/schemas and are also checked in at schemas/behavior-evaluation-evidence.schema.json and schemas/behavior-registry-candidate-patch.schema.json.
Selected real-world cases where an audit finding should change agent behavior live under docs/cases/. Each record names the measured repository, the intervention surface, the before state as content-free counts, and the definition of improvement the next step must prove against. The first case is docs/cases/2026-09-09-start-of-turn-gate.md.
A proven intervention becomes durable through the ordinary plan path rather than a hand-run patch. The case's script renders the changed surface and a reviewed plan whose precondition is the live file's hash:
python incidents/scripts/gate_intervention_2026_09_09.py --plan build/gate-intervention
apu review build/gate-intervention/gate-intervention-plan.json
apu apply build/gate-intervention/gate-intervention-plan.json --provider claude-code
apu rollback RECEIPT
python -m pip install -e ".[dev]"
pytest
apu --help
The product rationale is in concept.md, the behavior contract is in spec.md, and implementation sequencing is in [impleme
hooks/register.js 79 lines1let diagnosis = null;
2let generation = 0;
3let busy = false;
4
5function reset() {
6 generation += 1;
7 diagnosis = null;
8 busy = false;
9}
10
11async function act($, prepare) {
12 if (busy) return { text: 'APU: the previous request is still running.' };
13 busy = true;
14 const epoch = generation;
15 try {
16 const session = await $.session.id();
17 const cwd = await $.session.cwd();
18 if (!session || !cwd) return { text: 'APU: exact session identity is unavailable.' };
19 if (diagnosis && (diagnosis.session !== session || diagnosis.cwd !== cwd)) diagnosis = null;
20 if (prepare && !diagnosis) return { text: 'Run /apu-diagnose in this session before preparing a continuation.' };
21 if (prepare && diagnosis.prepared) return { text: diagnosis.prepared };
22 if (!prepare) diagnosis = null;
23 const argv = prepare
24 ? ['apu-intervene', '--provider', 'claude-code', '--diagnosis', diagnosis.id, '--dry-run', '--json']
25 : ['apu-wtf', '--provider', 'claude-code', '--session-id', session, '--cwd', cwd, '--fresh', '--json'];
26 const result = await $.process.run(argv, { timeoutMs: 15000 });
27 if (epoch !== generation) return { text: 'APU: session changed; discard this view and diagnose the current session.' };
28 if (result.exitCode !== 0) {
29 diagnosis = null;
30 let reason = `exit ${result.exitCode}`;
31 try {
32 const failure = JSON.parse(result.stdout);
33 if (failure.kind === 'no_attribution' && /^[a-z_]+$/.test(failure.reason_code)) reason = failure.reason_code;
34 } catch (_) {}
35 return { text: `APU refused or could not complete the request (${reason}). No turn was submitted.\nSession: ${session}\nDirectory: ${cwd}\nRun apu-wtf with this exact provider, session ID and cwd for details.` };
36 }
37 const value = JSON.parse(result.stdout);
38 if (value.provider !== 'claude-code' || typeof value.status !== 'string') throw new Error('invalid result');
39 if (!prepare) {
40 if (typeof value.diagnosis_id !== 'string' || typeof value.incident_id !== 'string') throw new Error('invalid diagnosis');
41 // The CLI owns attribution, evidence, barrier classification and saved artifacts.
42 diagnosis = { id: value.diagnosis_id, session, cwd, prepared: null };
43 const allowed = ['likely-autonomy-loss', 'recovery-requested'].includes(value.status)
44 && value.recommended_intervention?.durable_policy_mutation === false;
45 return { text: `APU: ${value.status}\nSession: ${session}\nDiagnosis: ${value.diagnosis_id}\nIncident: ${value.incident_id}\n`
46 + (allowed ? 'Use /apu-prepare to revalidate and prepare a continuation.' : 'The evidence does not authorize continuation. Resolve any barrier or gather more evidence.') };
47 }
48 if (value.session_id !== session || value.diagnosis_id !== diagnosis.id
49 || value.executed !== false || value.durable_policy_mutation !== false
50 || value.status !== 'planned' || !Array.isArray(value.command)
51 || !value.command.every(part => typeof part === 'string')) throw new Error('invalid preparation');
52 diagnosis.prepared = `APU: ${value.status}\nIntervention: ${value.intervention_id}\nNo turn was submitted; outcome remains unverified.\nContinuation argv:\n${JSON.stringify(value.command)}`;
53 return { text: diagnosis.prepared };
54 } catch (_) {
55 if (epoch === generation) diagnosis = null;
56 return { text: 'APU result unavailable. No automatic retry or continuation was attempted; inspect saved APU artifacts before retrying.' };
57 } finally {
58 if (epoch === generation) busy = false;
59 }
60}
61
62export function register(on) {
63 on('session.start', async ($, e, next) => {
64 reset();
65 await $.command.register({ name: 'apu-diagnose', immediate: true,
66 description: 'Save an APU diagnosis for this exact Claude session' });
67 await $.command.register({ name: 'apu-prepare', immediate: true,
68 description: 'Revalidate the saved diagnosis and print a continuation without executing it' });
69 return next(e);
70 });
71 on('classic.SessionStart', async ($, e, next) => { reset(); return next(e); });
72 on('classic.CwdChanged', async ($, e, next) => { reset(); return next(e); });
73 on('session.end', async ($, e, next) => { reset(); return next(e); });
74 on('command.run', { command: 'apu-diagnose' }, async ($, e) =>
75 e.args.trim() ? { text: 'Usage: /apu-diagnose (targets the current session only)' } : act($, false));
76 on('command.run', { command: 'apu-prepare' }, async ($, e) =>
77 e.args.trim() ? { text: 'Usage: /apu-prepare (uses this session’s saved diagnosis)' } : act($, true));
78}
79