npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@iterahq/learning-agent

v0.4.4

Published

The Itera Learning local agent: capture hooks, the spool, the installer and the shipper. Captures your coding-agent work and ships it to the Itera Learning engine.

Readme

@iterahq/learning-agent

The Itera Learning local agent. It hooks your coding-agent sessions (Claude Code, Codex), captures redacted turns, and ships version 2 sessions with ordered turn moments to the Itera Learning engine. Version 0.4.0 adds the Codex TOML installer, capture consent rules, and a packaged loopback monitor. It retains protocol 0.3.0 and version 2 session emission. Version 0.4.3 adds native Windows hooks, portable consent checks, and visible verification progress. Version 0.4.4 keeps consent previews responsive when an old open turn retains acknowledged spool history. Use the learner-machine guide to upgrade and verify the machine.

This package is public and client-only. It ships with no secrets. Inspect it before you install: npm view @iterahq/learning-agent, or npm pack @iterahq/learning-agent && tar tzf *.tgz to unpack the tarball without installing anything.

Install

Linux, macOS, and native Windows terminals:

npm i -g @iterahq/learning-agent
itera-learning install claude-code  # Claude Code
itera-learning install codex        # Codex CLI

The Codex installer adds one marked block to ~/.codex/config.toml. It preserves all source bytes outside that block. A DFL login is required to ship (dfl-auth login). Full guide: docs/INSTALL.md in the monorepo.

Upgrade an installed agent

Agent 0.4.1 does not contain the update command. Use this command once:

npx --yes --registry=https://registry.npmjs.org --@iterahq:registry=https://registry.npmjs.org @iterahq/learning-agent@latest update

For all later releases, close Claude Code and Codex. Then run:

itera-learning update

The command installs the current public npm release. It detects existing Claude Code and Codex integrations and refreshes only those hooks. It preserves the spool, journal, shipped ledger, credentials, consent rules, and CLI settings. The one-time registry options bypass an existing GitHub Packages scope.

Per-path capture consent

The agent allows capture by default. A new install works without a prompt or a rules file. The optional append-only ledger is ~/.itera-learning/consent.rules. A valid file with no matching rule also allows capture.

itera-learning consent list
itera-learning consent verify  # private disposable self-check
itera-learning consent allow '/work/public/**'
itera-learning consent deny '/work/private/**'
itera-learning consent ask '/work/review/**'

On Windows, drive and UNC patterns are valid. For example, use itera-learning consent deny 'C:\Users\Learner\Private\**' in PowerShell. The CLI stores one canonical, case-insensitive path form.

Each command appends one scope="capture" rule. The last matching rule for a path wins. A deny on any candidate path withholds the event. An ask holds the event for a later decision. Add an allow rule for the same glob to release a held spool episode. Run itera-learning monitor for the primary UI. Its Consent tab uses the same local ledger and exported consent API.

itera-learning consent verify checks append order and scope in a private disposable ledger. It checks mode 0600 on POSIX systems. On Windows, it reports Windows ACL protection without a false POSIX mode claim. It never changes the learner's real rules.

The capture hook checks consent before it writes the spool. The shipper checks again after authentication and immediately before each POST attempt. Thus, a deny added after capture or during authentication still prevents transmission. An existing malformed or unreadable ledger fails closed. This includes a broken symlink, an inaccessible parent, and an invalid JSON escape. The journal records only the decision, stage, and stable rule ID. It does not record the withheld path or raw payload.

The ledger uses path globs. Rules can use *, **, and ?. The ? glob matches one Unicode code point at both gates. The agent checks cwd, project_root, and every absolute JSON string in the hook event. This includes escaped strings, arrays, nested objects, and new field names. A coding CLI can omit a path from its event. A path-specific rule cannot classify that missing value. With an existing ledger, a control character in an absolute path stops capture because the shell cannot preserve its glob meaning. A missing ledger still allows capture. Use ITERA_LEARNING_DISABLED=1 when you need a machine-wide stop that does not depend on event path metadata.

Local Activity and Consent monitor

itera-learning monitor              # http://127.0.0.1:5173/#consent
itera-learning monitor --port 5194  # choose another local port

The installed package includes the server and UI assets. No checkout, Vite process, backend login, or CDN is needed. The Ship journal returns 50 rows per page so the browser keeps one bounded page in memory. The server always binds to 127.0.0.1. The command accepts no public-host override. Stop it with Ctrl+C. Version 0.4.4 skips transcript and diff reconstruction for acknowledged episodes. This keeps Consent responsive when an older open turn retains shipped spool rows.

The Consent tab shows rules, pending paths, counts, source sessions, destination origin, and post-redaction payload previews. Allow, Deny, and Ask append local rules. Missing or unmatched rules allow capture. There is no first-run modal. Previews use the real session planner inside private temporary state. They do not change the live spool, cursor, cache, outbox, configuration, or journal. An unsafe or incomplete preview appears as unavailable, rather than as exact JSON.

Only the local rules endpoint writes consent state. The monitor never posts capture payloads to the engine. Capture-time denials contain safe metadata only; the monitor cannot reconstruct their withheld paths or payloads. Execution consent is outside this release.

Verify a fresh capture

itera-learning verify start claude-code
# Or: itera-learning verify start codex

Paste the printed prompt into a new session. Wait for the response and close the session. Then run:

itera-learning verify finish

The command requires the unique fresh session, SessionEnd, a version 2 episode, nonempty agent output, and complete engine acknowledgement. It reports invalid historical NDJSON lines without printing their content.

Export your server-held learning data

Run itera-learning export --out /private/new-directory with a build that includes this command. The destination must not exist. The command uses your normal caller session in memory. The command requests learning-export/v2. The engine must support format=v2 before this client release. It validates both exact v1 and v2 contracts, including the known schema before or after re-pass receipts. V2 explicitly excludes retired checkpoints and singleton configuration. It includes their current models, artifacts, and all deployed receipts. Shared reference metadata is separate from your records. Every collection has a count and SHA-256 hash. Directories use mode 0700. Files use mode 0600. The completion manifest is written last. An inaccessible table, unstable second read, invalid hash, unknown inventory, or foreign-owned row stops the export. The client validates the complete collection inventory before it creates the destination. HTTP errors do not trigger a retry or a downgrade. This command does not change server data. It does not export local spool files, provider copies, or backups. See the data boundary for the exact scope and current stop-control limitations.

What it does, and what leaves your machine

  • Capture hooks write one spool line per prompt, turn end, and SessionEnd. The hook is POSIX sh — no node, no python, no jq on the critical path.
  • On Stop and SessionEnd, one command appends capture before it starts a detached shipper. There is no recurring timer. A manual ship --once also processes the spool.
  • The shipper contacts exactly two hosts: api.iterahq.dev (trade your DFL token for an Itera session) and engine.iterahq.dev (POST the episodes).
  • Every captured line and every shipped transcript slice passes the shared hooks/redact.sh scrub first. Redaction is best-effort defence-in-depth, not a guarantee — the hard control is ITERA_LEARNING_DISABLED=1, which stops all capture at once.

What each episode carries

Each session carries project_root and ordered moments. Each moment retains the original CLI session_ref and a redacted turn envelope. The first capture of that turn freezes these fields from the CLI transcript:

  • agent_turn — the agent's final message plus the turn's tool calls. It grounds actor attribution in what the agent DID, not how the prompt was phrased.
  • diff — the working-tree diff of the turn's cwd at ship time: the git ref, the branch, a size-bounded git diff, and a --stat summary. Attached to the latest turn per cwd. Across consecutive turns it separates the agent's edits from the learner's edits on top.
  • superseded_by + dwell_ms — on an interrupted turn (a new prompt arrived before the turn ended): a reference to the prompt that replaced it, and how long the learner let it run first. The rhythm and rephrase-chain signal.
  • abandoned — the paths the turn refused: denied permissions, rejected plans, stopped or errored tool calls. Read from the same transcript slice; each detail is redacted. Known limit: a turn the learner killed before it reached the Stop hook has no slice, so its abandoned paths are not captured.

Optional transcript and diff fields remain absent after a capture miss. A missing or invalid absolute cwd holds the spool for repair. A non-Git directory uses its normalized absolute cwd as its root. A missing legacy CLI reference becomes unavailable, with session_ref_missing: true in the moment payload. This marker does not invent a CLI session identity.

Ship-on-Stop, and the retry spool

Each ship is a full idempotent catch-up over the whole spool. The spool is the durable local buffer: it is not cleared until a ship succeeds. So:

  • A ship that fails (the network is down) leaves the batch in the spool, untouched, and does not advance the ledger.
  • The next Stop, SessionEnd, or manual ship retries the exact frozen outbox. Later transcript, diff, spool, or configuration changes cannot alter that request. A partial acknowledgement retains the whole request. The engine returns existing receipts for sessions it already accepted.
  • A transient blip is smoothed over by a bounded in-process retry before the ship gives up to the next Stop.

The spool is bounded: once a spool file is fully shipped and no longer the newest few, it is pruned. A file that still holds an unshipped or open line is never pruned.

Session boundaries and configuration

The gap is 30 minutes by default. Set session_gap_minutes to a positive number in ~/.itera-learning/config.json (or $ITERA_LEARNING_HOME/config.json):

{ "session_gap_minutes": 45 }

A root change, machine context change, gap greater than the configured value, or matching SessionEnd closes a session. An end marker must match the latest turn’s CLI source/reference and follow every captured turn’s end. A marker from an older overlapping terminal does not close newer work. A CLI reference or source change alone does not split it. Turn timestamps determine order. The session retains all CLI references in its moments. Git supplies the repository root when available.

The trailing session stays open until a later invocation observes a complete gap or a SessionEnd. A prompt without its turn end also keeps the session open. No timer wakes solely to flush that session. An explicit SessionEnd can close an incomplete turn without inventing its missing turn end.

Private durable state

The ledger has two new adjacent files: .capture-cache.json and .outbox.json. Both contain private, redacted capture data, never credentials. Files use mode 0600; directories use 0700. File sync, atomic rename, and directory sync complete before POST. The cache freezes each completed turn before an open session waits for more turns. The outbox freezes the exact request and the next ledger state. Only a full acknowledgement advances the network ship ledger. A local-only library call instead commits its explicit local sink.

An open session holds the byte-offset barrier at its first retained spool fragment. Acknowledged turns after that barrier remain in the shipped-ID set. Late lines already present in the spool pair by timestamp. Lines that arrive after an acknowledged session form a later capture; they do not rewrite an existing receipt. Corrupt durable state fails closed.

A batch holds at most 500 sessions and 7 MiB. Remaining sessions stay pending. A single session above 7 MiB or 10,000 moments stays in the spool and requires inspection; the client does not silently split or discard it.

A PID and nonce lock protects each local outbox. Recovery requires proof that the local owner PID is dead. A live, paused, inaccessible, or unknown PID keeps the lock. Recovery leaves one small, nonempty .reaped-<nonce> directory per crashed owner. These tombstones prevent two recovery attempts from removing a new owner's lock. Keep them while shippers can run. No age-based lease can steal a live lock. A contending detached ship retries normal lock acquisition for up to 60 seconds. Thus a final hook that arrives after the owner's collection can run after that owner releases. Exhaustion fails visibly in the detached log; the spool stays intact for a later hook or manual ship. There is no unbounded retry loop. The client checks ownership before durable writes and every POST.

The v1 spool reader and turn API remain available. Each prompt retains its original spool version. Before v2 emission, the client drains legacy prompts through v1 batches with their original episode IDs. This preserves idempotency when the old client reached the engine but lost its acknowledgement. A new v2 turn-end can close an old v1 prompt; that turn still drains as v1. Genuinely unsent legacy turns are sent, not discarded or repackaged into new session IDs. Ready legacy turns drain first. Unresolved legacy prompts retain their original byte barriers and remain eligible for a later v1 Stop. They do not block independently closed v2-origin sessions. The same invocation continues to v2 after full acknowledgement of ready legacy turns. A partial legacy acknowledgement stops that transition. The boundary is explicit: old prompts remain turn-grain data; only new v2 prompts form session moments. Upgrade preserves spool files, pending turns, ledger offsets, and journals. Uninstall does not remove this history. Package and host release remain a separate operation.

Ship journal — the local, readable record

Every ship attempt appends one line to an append-only NDJSON journal. It is the data source the opt-in local monitor reads, and the artifact that proves a failed-then-recovered ship. It is written on both Linux and macOS.

  • Location: $ITERA_LEARNING_JOURNAL, default ~/.itera-learning/journal.ndjson (mode 0600).
  • Format: append-only NDJSON, UTF-8, one JSON object per line. The shipper never rewrites or truncates it; a reader tails it. Every line carries v for forward compatibility.

Each line:

{
  "v": 1,
  "ts": "2026-09-01T16:00:00.000Z", // ISO-8601 UTC of the ship attempt
  "event": "ship",
  "outcome": "shipped", // shipped | local | noop | failed | dry-run
  "machine": "my-host",
  "agent_version": "0.1.0",
  "trigger": "stop", // stop | manual | unknown
  "collected": {
    // what collect() found this pass
    "lines_read": 4,
    "episodes": 2,
    "pending": 0, // prompts still waiting for their turn to end
    "orphan_turn_ends": 0,
    "lines_skipped": 0, // unreadable / half-written lines
  },
  "batch": {
    // the batch this pass built, or null on a noop
    "size": 2,
    "episode_ids": ["ep_…", "ep_…"], // lets a monitor diff "on device" vs "shipped"
  },
  "engine": {
    // present only when a POST happened, else null
    "url": "https://engine.iterahq.dev",
    "accepted": 2,
    "duplicate": 0,
    "rejected": 0,
  },
  "sink_path": "/…/shipped/….json", // the local sink file, when one was written
  "spool_pruned": ["20260825.ndjson"], // spool files reclaimed this pass
  "error": null, // the failure message, on a "failed" outcome
  "duration_ms": 37,
}

Outcome semantics

| outcome | meaning | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | shipped | POSTed to the engine, every episode acknowledged, ledger advanced. | | local | no engine URL set; wrote the local sink only, ledger advanced. | | noop | nothing new to ship. | | failed | the POST failed, or the engine did not acknowledge every episode. Ledger did NOT advance; the batch stays in the spool and retries on the next Stop. | | dry-run | a dry run; nothing written. |

How the (future) monitor reads it

  • Liveness: the ts of the last line is when the shipper last ran.
  • Pending vs shipped: collect the episode_ids of every shipped/local line and diff against what the spool holds on device.
  • Retry backlog: a failed line whose ids reappear on a later shipped line is a recovered batch; a failed line whose ids never reappear is a batch still stranded.

Uninstall

itera-learning uninstall claude-code          # removes exactly the two hooks
itera-learning uninstall claude-code --all    # also removes any legacy timer (never touches ~/.itera-learning)
itera-learning uninstall codex                # removes only the managed TOML block

Environment

| Variable | Purpose | | --------------------------------- | ----------------------------------------------------------------------------------------------- | | ITERA_LEARNING_DISABLED | 1 stops all capture at once. | | ITERA_LEARNING_HOME | Data root. Default ~/.itera-learning. | | ITERA_LEARNING_API_URL | Engine origin. Unset ships to https://engine.iterahq.dev; set empty for local-only (no POST). | | ITERA_LEARNING_EMBED_URL | itera-api origin for the token trade. Default https://api.iterahq.dev. | | ITERA_LEARNING_JOURNAL | Ship-journal path. Default <home>/journal.ndjson. | | ITERA_LEARNING_CONSENT_RULES | Capture-consent ledger. Default <home>/consent.rules. | | ITERA_LEARNING_SPOOL_KEEP_FILES | Newest spool files kept when pruning. Default 3. | | ITERA_LEARNING_TOKEN | A fixed Itera token; skips the DFL trade. For tests. | | ITERA_LEARNING_SESSION_FILE | Optional path to cache the Itera session, mode 600. Off by default. |

Release

Where this client runs today, which version each host holds, and how a new version reaches every one of them. A release to an unknown fleet loses the episodes of the host nobody remembered, so the fleet is written down here.

Surveyed 2026-09-07. Every DFL VPS in dfl-infra:vps/_inventory.yaml was scanned read-only. The scan looked for the global npm package, the ~/.itera-learning data root, the hook lines in the CLI settings file, a systemd unit or a cron entry, and a running shipper process.

Hosts

| Host | Present | Path | Version | Invoked by | Updates by | | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | openclaw-tainan (claude-main), hostname openclaw-tainan-debian-4gb-hel1-2 | yes | /usr/lib/node_modules/@iterahq/learning-agent, bin symlinked at /usr/bin/itera-learning | 0.2.0 | Claude Code hooks in /root/.claude/settings.json: UserPromptSubmit → capture.sh user-prompt claude-code; Stop → capture.sh turn-end claude-code then ship-on-stop.sh claude-code. No timer, no cron, no systemd unit. | manual: npm i -g @iterahq/learning-agent@<version> (the hook lines point at the global path, so they survive the upgrade; re-run itera-learning install claude-code only if the hook shape changed) | | Tainan's Mac, hostname Tainans-MacBook-Pro-2.local | yes (per Tainan) | UNKNOWN — Tainan to confirm | UNKNOWN — Tainan to confirm | Claude Code hooks (same two events) | manual: npm i -g @iterahq/learning-agent@<version> | | sandbox-compute (sandbox-manager-containers-debian-16gb-hel1-1) | no | — | — | — | — | | sandbox-db (sandbox-manager-db-debian-8gb-hel1-1) | no | — | — | — | — | | dokploy-vps (dokploy-debian-4gb-fsn1-1) | no | — | — | — | — | | dfl-runner (runner-ubuntu-4gb-fsn1-1) | no | — | — | — | — | | dfl-runner-02 (runner-02-8gb-nbg1-2) | no | — | — | — | — | | amb-hq (amb-hq-4gb-hel1-2) | no | — | — | — | — | | crm-debian-4gb-hel1-1 | UNVERIFIED — claude-main holds no SSH key for this host, so the scan could not run. No episode in the database carries its hostname, so it is presumed absent. | — | — | — | — |

Two matches on the scan are NOT this client. dfl-runner and dfl-runner-02 carry itera-runner-recycle.service / .timer, which recycle the GitHub Actions runners of the iterahq org. amb-hq carries iterahq-code.service and dfl-auth-gate-iterahq.service. All five units are unrelated to the capture client. Do not read a hostname match on the string itera as an installation.

Database reconciliation

The machines that actually shipped episodes are the ground truth for the fleet. Read from learning.work_episodes on the Itera Supabase project (kjefmwlopzeaqgqtlbdn) on 2026-09-07:

| machine | machine_class | Episodes | First | Last | Location | | ----------------------------------- | ---------------------------- | -------- | ---------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | openclaw-tainan-debian-4gb-hel1-2 | agent (343) + human (76) | 419 | 2026-08-28 | 2026-09-07 | openclaw-tainan — matched, version 0.2.0 | | Tainans-MacBook-Pro-2.local | human | 386 | 2026-08-28 | 2026-09-04 | Tainan's Mac — matched, version UNKNOWN | | openclaw-tainan | human | 6 | 2026-08-28 | 2026-08-28 | synthetic, not a host. Hand-POSTed demo and M4 smoke rows from claude-main (project_ref /root and /root/repos/*-demo, prompts prefixed [DEMO] or M4 … smoke). | | smoke-laptop | human | 2 | 2026-09-01 | 2026-09-01 | synthetic, not a host. A fabricated fixture hostname (project_ref /home/user/proj). |

Zero machines are location: UNKNOWN. Every hostname in the database is either a host in the table above or a synthetic fixture.

The survey found two gaps. The first is now closed by T5; the second remains:

  • T5 now records the client version. At the survey time, the batch parsed agent_version and the engine dropped it. The deployed T5 ingest writes it to work_episodes.agent_version. A rollout can now compare the protocol version, client version, grain, and moments in the database. The per-host journal remains the local shipping receipt.
  • ingest_batch_id is always NULL. The column exists on work_episodes, the route passes null for it on every insert, and no ingest_batches table exists. It cannot group a batch either.

At the survey time, schema_version read 1 for every row on every machine. The row count moves — claude-main ships on every turn — so read the counts above as the shape of the fleet, not as a fixed total.

Protocol version

This 0.4.4 client emits version 2 for new capture. The legacy schemas remain fixed at version 1. Legacy prompts drain through v1 receipts before v2 emission. The engine accepts both versions.

| Contract | File | Version | Scope | | -------------------------------------- | ---------------------------------- | ------- | ------------------------------------------- | | SPOOL_LINE_VERSION | packages/protocol/src/spool.ts | 1 | frozen legacy reader | | captureSpoolLineSchema | packages/protocol/src/spool.ts | 2 | current capture hooks, including SessionEnd | | EPISODE_VERSION | packages/protocol/src/episode.ts | 2 | current session and batch emission | | episodeSchema / episodeBatchSchema | packages/protocol/src/episode.ts | 1 | frozen legacy drain API |

The engine stores the actual envelope version as work_episodes.schema_version. The release candidate and its provenance constant are 0.4.4. The protocol package remains 0.3.0. The D5 release gate is complete.

Published release and host verification

Version 0.4.4 skips transcript and diff reconstruction for acknowledged episodes behind an open-turn barrier. Version 0.4.3 added native Windows hooks, portable consent checks, and visible verification progress. It retains the deployed v2 ingest/export routes and migration 0089. It preserves the verified 0.4.0 monitor, consent, and capture behavior. The normal authenticated API accepted a marked learner-class v2 test. Recent human v1 receipts confirm backward compatibility with agent 0.2.0. See the learner-machine guide.

For an approved host, use the safe package replacement sequence above. Reinstall its existing CLI integrations. Preserve its spool, ledger, capture cache, outbox, journal, consent ledger, and CLI settings. Do not add a new integration as an incidental part of a package upgrade. Verify the installed CLI version, local hook paths, and a new journal receipt. Require agent 0.4.4 and version 2 session/moment data for the fresh capture test. A packed fixture or VPS result does not prove a learner-Mac upgrade.

Maintainers must run npm run test:agent-package before each future release. That test builds and installs local tarballs in an isolated prefix. It exercises the installed CLI and monitor. The test does not publish a package. Keep the public agent and protocol artifacts immutable after publication.

Repair invalid historical spool records

Agent 0.4.1 can write invalid NDJSON when generic secret redaction meets an escaped quote. The shipper skips those records and reports lines_skipped. Install a fixed release before repair. Close Claude Code and Codex, wait for any shipper to finish, then run:

itera-learning repair-spool

The command archives each exact original and invalid physical line under the private ~/.itera-learning/quarantine/ directory. Its manifest records file, line, byte count, and SHA-256 evidence. The active spool keeps every valid line. The command preserves shipped episode IDs and resets only affected file cursors, so the engine can deduplicate any replayed valid episode.

Residual gap (v1)

Shipping still requires a DFL account (dfl-auth login). A true outsider with no DFL account cannot ship in v1. This is a deliberate scope cut; the itera-native login CLI is the later multi-user path.

Export catalog compatibility in 0.4.3

The client requests learning-export/v2 and accepts exactly four source catalogs: 31/32 tables before D, or 29/30 after D. The receipt table accounts for the one-table difference. D removes only checkpoints and generation_config from these catalogs. Unknown, duplicate, partially retired, or missing required tables fail before any export file is created. V1 compatibility accepts only the original 31/32 catalogs; it never silently changes a v1 response. Complete collection, ownership, count, and hash checks remain required.

Use itera-learning update. Preserve the spool and ledger. Agent 0.4.4 keeps protocol 0.3.0 and the version 2 transport contract.