Skip to content
Agentmart
Submit a tool

import-claude-context

Bring the owner's Claude Code history into Sutando: index, extract and haiku-summarise the stock ~/.claude/projects transcripts, stage the result, show the…

Skill by sonichi

IndexedMIT★ 400
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/sonichi-import-claude-context

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Bring the owner's Claude Code history into Sutando: index, extract and haiku-summarise the stock ~/.claude/projects transcripts, stage the result, show the owner a digest and — only on their 'bring it in' — land it in core memory, notes/claude-import/ and People payloads. Triggers: 'import my Claude history', 'read my Claude Code sessions', 'bring my Claude context along', 'what did I work on in Claude Code', and the review replies 'bring it in' / 'bring in <slug>' / 'forget <slug>' / 'include <date>' / 'include personal' / 'hold <date>' / 'forget <date>'. Personal sessions are held back and never quoted; people the store already has are updated, never overwritten. Read-only on ~/.claude; conversation text only; transcripts are processed by Anthropic's Claude API, the provider the Sutando already runs on, which does not train on them. Nothing is uploaded to AG2 Space except the people you approve in the digest, which are saved to your People store.

Use cases: backend, productivity, finance, agent-building

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found
Runs as
Python package

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/import-claude-context

What's inside

Skill name
import-claude-context
Description
Bring the owner's Claude Code history into Sutando: index, extract and haiku-summarise the stock ~/.claude/projects transcripts, stage the result, show the owner a digest and — only on their 'bring it in' — land it in core memory, notes/claude-import/ and People payloads. Triggers: 'import my Claude history', 'read my Claude Code sessions', 'bring my Claude context along', 'what did I work on in Claude Code', and the review replies 'bring it in' / 'bring in <slug>' / 'forget <slug>' / 'include <date>' / 'include personal' / 'hold <date>' / 'forget <date>'. Personal sessions are held back and never quoted; people the store already has are updated, never overwritten. Read-only on ~/.claude; conversation text only; transcripts are processed by Anthropic's Claude API, the provider the Sutando already runs on, which does not train on them. Nothing is uploaded to AG2 Space except the people you approve in the digest, which are saved to your People store.
License
MIT
Instructions
140 lines in SKILL.md · view on GitHub

Before installing

  • Ships executable scripts

Files (9)

  • SKILL.md
  • entities.md · prompts/
  • project.md · prompts/
  • session-summary.md · prompts/
  • _common.py · scripts/
  • extract.py · scripts/
  • finalize.py · scripts/
  • index.py · scripts/
  • progress.py · scripts/

SKILL.md (first lines)

# Import Claude context

Sutando starts out knowing the owner's projects, decisions, open threads and people by reading the conversations they already had with Claude Code. The transcripts under the **stock** `~/.claude/projects/` (`claude_home_path("projects", vanilla=True)` — never Sutando's relocated `.claude-sutando`) are indexed without a model, the dialog is cleaned and redacted on disk, cheap-model subagents summarise it, the result is **staged** and shown to the owner as a digest, and only their "bring it in" moves it into the Sutando-owned sinks.

**Usage**: `/import-claude-context [--projects a,b] [--session uuid] [--since 30d] [--dry-run] [--counts-only] [--json] [--new] [--cloud] [--stage [--known-people file]] [--commit] [--discard] [--forget slug] [--include date|uuid] [--include-personal] [--hold date|uuid] [--forget-session date|uuid] [--purge-dumps]`

## When it runs

1. **Owner ask** (any install, standalone or desktop) — the owner says "import my Claude history", "read my Claude Code sessions", the Settings button's sentence *"Import my Claude Code history, consented in Settings at <ISO>."*, or invokes `/import-claude-context`. Same procedure with `--run-kind user`.
2. **AG2 Space desktop onboarding** — the owner's first-contact DM message carries the import request when they consented on the onboarding card: *"Hi — I'm all set up, say hello. Then import my Claude Code history: I consented on the onboarding card at <ISO>, found N conversations across M projects."* The trigger is an owner message containing **"import my Claude Code history"** together with **"consented on the onboarding card"**, both **case-insensitively** — the standalone form below opens the sentence, so it capitalises "Import" (`classify.py`'s `IMPORT_TRIGGER_SENTENCES` already matches case-insensitively; this keeps the documented rule and the classifier in step). **Match the phrase, not the `at`:** the consent time follows it as `at <ISO>` only when the client recorded one, and as `(time not recorded)` when it did not — a consent captured before the client stored a timestamp is stated, never invented, so a trigger keyed on `at` stops matching exactly those installs. The counts clause is optional the same way. The request also arrives **standalone, without a greeting**, when the card consent was given but its first-contact message already went out: *"Import my Claude Code history, consented on the onboarding card at <ISO>, found N conversations across M projects."* — same case, same `--run-kind onboarding`. Run the procedure below with `--run-kind onboarding`; the message's counts are the card's file count, the acknowledgement uses the index's `conversations` (step 2). **Legacy / fallback:** an earlier desktop wrote the same request as its own task file — `channel_id: onboarding-wizard`, `user_id: onboarding-wizard`, `access_tier: owner`, `priority: low`, body "Run the import-claude-context skill (onboarding run). Consent given on the onboarding card at <ISO> …" — and installed clients may still do so; treat that task exactly as this case (it is consented; `priority: low` orders it after a pending greeting and defers nothing — see `src/task_priority.py`). Its result is `[no-send]` (the wizard channel has no delivery path). A queued import task is never left in `tasks/` for a later turn: `/task-orphan-check` step 3a keeps a started one out of the recovery DM (a started run whose `status.json` has not moved for an hour is listed there as stalled, and still resumes on the next sweep), but only this procedure starts it.
3. **Review reply** — while `$DATA/staged/review.md` exists, an owner message saying **"bring it in"**, **"bring in <slug>"**, **"forget <slug>"**, **"include <date>"**, **"include personal"**, **"hold <date>"** or **"forget <date>"** answers the pending review: run the matching command from step 10 and nothing else. Any other message is not an answer; the set stays staged. The owner may ask what a held session was about: answer in the owner DM only, on request, from the summary — answering does not include it.

This skill is part of the engine and needs nothing from AG2 Space: a standalone Sutando (an OSS install with no desktop app) has cases 1 and 3, where the owner asks in whatever channel they talk to their Sutando in — "DM" below means that owner channel. Case 2 is only how the AG2 Space desktop's onboarding card and Settings button reach the same procedure with the same consent.

In cases 1 and 2 the import is a **background job**: the triggering message gets its reply (case 2: the hello plus one line that the import has started; case 1: the acknowledgement text of step 2 as the reply itself, no separate proactive file), and everything after that — the digest, a failure note — is a proactive message to the owner DM (`results/proactive-<ts>.txt`). The legacy task file's own result is `[no-send]`, with the acknowledgement as a proactive message. The owner's answer (case 3) arrives as a later task.

## Consent contract

- Run only after the onboarding card's **Import** button or an **explicit** owner request. A mention of Claude Code in passing is not consent; the health check, proactive loop and crons never start this on their own.
- That consent covers reading, summarising and **staging**. Landing the result is a second, separate yes: nothing reaches core memory, `notes/` or the People store until the owner has read the digest (`staged/review.md`) and replied "bring it in" (or "bring in <slug>") in the same conversation.
- An incremental re-run (`--new`: only sessions changed since the last import) needs no new import consent — the first consent covers keeping the import current — but its result is staged and reviewed like the first. Say what you are doing when you do it.
- `--cloud` (uploading the summaries to cloud-recall, corpus `claude-import`) is a **separate, explicit yes** every time. Never in the onboarding run; never inferred from the first consent.
- Before the first full run, when the owner is present, state the counts (`index.py --counts-only --json` — a file count, so "about N transcripts across M projects"; the exact number the owner is told is the index's `conversations`, step 2) and that a digest will follow for approval, then proceed. The onboarding run has the import consent already; do not ask again in the DM — the acknowledgement (step 2) and then the digest are the next things the owner sees.

## Procedure

Set `DATA=<workspace>/data/claude-import` (per host, not vault-synced) and `S=skills/import-claude-context/scripts`. Every script resolves the workspace through `bash scripts/sutando-config.sh workspace`.

The slow part (summaries, roll-ups, entities, staging) never runs in the core's task loop: the main task indexes, extracts, acknowledges, **spawns ONE background subagent** and returns `[no-send]` — "at background" means spawn a subagent (CLAUDE.md), and the core stays free for other tasks. `status.json` phases `indexed → extracted → summarizing → rolling-up → staged → done` let the desktop show progress; from step 5 on it is kept current by `progress.py`, never by hand. Every status also carries the run's identity — `task_id` (the task that started it) and `run_id` — read from `state.json`'s `run`, which `index.py --task-id` stores at run start; the orphan check uses it to tell this task's run from an older one.

**Main task (seconds):**

0. **Greeting and import in the same turn.** When the import request rides on the owner's first-contact message (case 2), the reply is one message: a short hello first, then one line that the import has started (the step-2 acknowledgement is that line — do not send it twice). When a separate greeting or owner message is waiting alongside a legacy import task, answer it first — a short hello, nothing about the import — then **immediately run steps 1–4 of this procedure before ending the turn**: index, acknowledge, extract, spawn the coordinator, write the task's `[no-send]` result. The import is a background job, but *starting* it is not deferrable: nothing re-delivers a task the core has already been told about — the watcher emits `TASK_FILE:` once, `priority: low` is ordering metadata, not a hold, and the startup ceremony (registration, boot recap, `/startup complete`) is no reason to stop. Never leave the import for a later turn, for the proactive loop, or for a nudge from the owner; a task left in `tasks/` after this turn is a bug (2026-09-11: the core answered the hello, reported startup, went idle, and the import sat for six minutes until the owner typed "run the import").

1. **Index** (no model): `python3 $S/index.py --task-id <id> --json` → `$DATA/index.json` + `$DATA/claude-import-index.md` (per project: recovered `cwd`, session count, date range; one `[ ]` row per session with title + date). Phase `indexed`. **`<id>` is the task this run answers** — the `id:` header of the task file (`task-claude-import-<ms>` for the legacy form) or, for a message-triggered run (cases 1–2), the task id of the owner message that carried the request (the `TASK_FILE:` the core was handed); always pass it. The full pass mints a `run_id` and stores `run: {task_id, run_id, started_at}` in `state.json`; `extract.py`, `progress.py` and `finalize.py` carry both ids into every `status.json` they write. `/task-orphan-check` matches a status to a task only by that `task_id` — a run started without one can never mark its task done (the task is kept and listed in the recovery DM), so omitting the flag costs a spurious recovery line, never the import. `--dry-run` prints the counts and writes nothing; `--counts-only` is readdir+stat only — a **file count** that cannot tell a sidechain or a never-answered session apart, good for a fast estimate only. The full index reports `sessions`, `empty` (sessions with no assistant message: aborted or never answered — nothing gets summarised for them) and `conversations` = `sessions − empty`; **`conversations` is the number the owner is told.** The reported `new` is the number of sessions changed since the last index.
2. **Acknowledge immediately** — with `conversations` and `projects` from the index (`N` and `M` below) and nothing else; append ` (K empty ones skipped)` after "M projects" only when `empty` is > 0. When the request came as an owner message (cases 1 and 2), this text **is the reply** to that message — after the hello in case 2 — written as the task's result in step 4; only the legacy task file (no delivery path) gets it as `results/proactive-<ts>.txt` (owner DM). One acknowledgement, never both:

   > Importing your Claude Code history: N conversations across M projects. I'll message you here when it's ready to review — usually 10–20 minutes. Your transcripts are processed by Anthropic's Claude API, the provider your Sutando already runs on, which does not train on them. Nothing is uploaded to AG2 Space except the people you approve in the digest, which are saved to your People store.

3. **Extract**: `python3 $S/extract.py --json` (add `--new` on re-runs, `--projects`/`--session` to narrow, `--max-chars-total` to cap; default 8,000,000 chars, newest sessions first). Produces `$DATA/dumps/<slug>/<uuid>.<n>.txt` — dialog only, harness noise stripped, secrets redacted, ≤120k chars per chunk, 0600 files in a 0700 dir. A session whose cleaned dialog has no turn or fewer than 400 chars (`--min-chars`) gets no dump: it is `skipped_empty` in the counts and `skipped_empty: true` in `state.json` (a `--new` re-run leaves it alone until the file changes). `extracted` counts only the sessions that got a dump, so `extracted` + `skipped_empty` + `errors` is what was looked at. Phase `extracted`.
4. **Spawn the import coordinator** — ONE Agent-tool subagent, run in the background, briefed with steps 5–9, `$DATA`, `$S`, the run kind and the slugs to cover. It may run on the default model (it only orchestrates and reads counts) but must never paste transcript text; its per-session workers are `model: haiku`. Then end the task: for a message-triggered run the result is the reply (case 2: the hello, then the step-2 acknowledgement; case 1: the acknowledgement), for the legacy task file it is `[no-send]`. Do not wait for the subagent; do not summarise inline; do not hand the work back to a later session or a later turn.

**Import coordinator (background subagent):**

5. **Session summaries** — one Agent-tool subagent per session with **`model: haiku`** (owner rule, session-recap: never spend core-model quota on transcripts), prompt = `prompts/session-summary.md` with the slug/uuid/chunk paths filled in. The subagent Reads the chunk files and Writes `$DATA/summaries/<slug>/<uuid>.json` itself; do not paste dump text through the coordinator. Fan out in parallel batches; skip sessions whose `summaries/<slug>/<uuid>.json` is newer than their `extracted_at` in `state.json` (resumable) and sessions marked `skipped_empty: true` there (they have no chunk files — no worker). After each batch run `python3 $S/progress.py` (no model): it recounts from disk — sessions minus `skipped_empty`, summaries present, roll-ups, entities, staged manifest — and rewrites `status.json` (phase `summarizing` with `summarized`/`sessions`, `rolling-up` once every session is in). Never write `status.json` by hand. A subagent that answers `STOP` marks a missing or unreadable input — fix or skip, never retry blindly. Each summary carries `personal` (true when the session was mainly about the owner's private life; the prompt says when) and `personal_reason` (a generic category, ≤ 6 words): a personal session is **held** — it feeds nothing downstream, the owner sees only its date and reason, and the owner's own `--include` / `--hold` (step 10) outrank the classifier. `python3 $S/finalize.py --held-json` lists the held sessions (project, uuid, date, reason — no titles) so the next two steps can leave them out.
6. **Project roll-ups** — one haiku subagent per project, prompt `prompts/project.md`; reads that project's **non-personal** session JSONs (list only those — never a held one; a project whose sessions are all held gets no roll-up), writes `$DATA/projects/<slug>.json` (roll-up + `note_markdown` + `sessions`, the uuids it was written from). Incremental runs pass the previous roll-up so facts carry forward. Then `python3 $S/progress.py`.
7. **Entities** — one haiku subagent over all **non-personal** summaries, prompt `prompts/entities.md`, writes `$DATA/entities.json` (people, companies, deals, decisions, open threads, every entry cited). Then `python3 $S/progress.py`.
8. **Stage** (nothing lands yet). First, if the station's People tool is connected, run `people__list_people` and write `$DATA/known-people.json` — a JSON array with **only** `id`, `slug`, `name`, `email` and `identifiers.emails` per person (no dossiers). Then `python3 $S/finalize.py --stage --run-kind onboarding|user --known-people $DATA/known-people.json --json` (drop the flag when the tool is not connected: the digest then says "Store not checked" and step 10 upserts nobody). It de-duplicates the entities in memory (people sharing an email are one person; a bare first name folds into its unique full name; companies by name — `people_merged` / `companies_merged` in the JSON; `entities.json` itself is not rewritten), drops every citation of a held session, matches each remaining person against the store (any email first, then the normalised name — diacritics folded), and renders everything under `$DATA/staged/`: `memory/claude_import.md` (≤ 2,000 B), `notes/claude-import/<slug>.md` + `overview.md`, `people.json` (people with ≥ 2 citations: every one the store already has as an UPDATE payload, plus ≤ 25 new ones — the **published** set, computed the way `--commit` will publish it: the selected projects' live citations and fields unioned with every other landed project's approved citations and fields, so a person who clears the two-citation floor only through the union, or whose fields change, is shown before it lands), `known-people.json` (the listing as used — re-stages and the commit reuse it), `review.md` (the digest) and `manifest.json` (`held`, `stale_rollups`, `changed_since_approval`, `people_changed_since_approval`, `known_people: checked|not checked`, `people_ambiguous`); `status.json` phase `staged`. It touches neither `memory_dir()`, nor `<workspace>/notes`, nor `MEMORY.md`. The memory file and `overview.md` are single files over every landed project: they are previewed (and, at the commit, rebuilt) from the **approved snapshots** — `$DATA/approved/<slug>.json`, one per landed project, written by `--commit` — plus the staged set, never from a landed project's current `projects/<slug>.json`. A landed project whose roll-up changed since its approval (a re-run nobody reviewed) is listed under Memory in the digest as "changed since approval — bring in <slug> to refresh" and keeps its approved text until the owner brings it in again: stage it (`--stage --projects <slug>`, shown as an update to its note), re-post the digest, commit on their yes. The approved People export is the same kind of shared file: a landed project whose people changed since its approval (an entities re-run added a person or a citation nobody reviewed — `$DATA/approved/people-inputs.json` keeps a `people_hashes` fingerprint per project) is listed the same way under People and keeps its approved citations in `$DATA/people.json` until it is brought in again. The digest's People section also shows, under "Updates to people already in your export", the concrete field diff for each shared person the union changes (e.g. `role: CTO → …`, with the citation count) — compared on the values the dossier publishes (role/company at 80 chars, relationship at 200), never on a shorter preview; a value over 120 chars is shown as its shared prefix elided (`…`) plus both tails, so a changed suffix is always visible — a person whose published fields equal their approved entry has nothing to review and is not listed. `--stage` is the default: a bare `finalize.py` cannot write into a sink. A project marked `stale_rollups` in the manifest (its roll-up was written with a held session, without an included one, or lists a forgotten one) is staged with a placeholder body and cannot be committed: re-run that project's roll-up (step 6) and stage again before posting the digest. Then `python3 $S/finalize.py --purge-dumps` unless the owner asked to keep the dumps (the review and the commit read the summaries, not the dumps).
9. **Digest** — `python3 $S/finalize.py --digest-files --json` writes `$DATA/staged/review.md` to `results/` as proactive files for the **owner DM only**: one `proactive-<ts>.txt` when it fits the bridge, else ordered parts `proactive-<ts>-01.txt` … `-NN.txt`, each headed `Claude Code import — part i/N` (the bridge dead-letters any single proactive body over 48 KiB without delivering it — never write the digest by hand as one file). The digest ends with the reply instructions ("Reply 'bring it in' to save this to your Sutando, 'bring in <slug>' for one project, or 'forget <slug>' to drop one."). This is the ONE place transcript-derived text is shown to the owner: one paragraph per project, its open threads and decisions, the people it would add — "Already in your People store" (name, matched on email or name, citations), "New" and "Needs your call" (a name two store entries share), and under them any landed project whose people "changed since approval" — the size of the memory summary (and under it any landed project whose roll-up "changed since approval", with the reply that refreshes it), and "Held back as personal (N): <date> · <reason>" with the include/hold instructions; a held session is never quoted, not even its title. If any step 5–8 fails part-way, write a short proactive message instead — what was done, as counts ("summarised K of N sessions"), and "say 'import my Claude history' to continue"; the run is resumable and the owner is never left without a message.

**Later task (the owner's reply, case 3 above):**

10. **Review reply** — the commit happens only here, on the owner's explicit yes, never in the run that produced the digest:
    - **"bring it in"** → `python3 $S/finalize.py --commit --json`: moves the staged set into the sinks with the usual guards — memory file ≤ 2,000 B, the `MEMORY.md` row only when `memory-index-budget.py` allows, notes with a dated `## Update` section on re-runs — snapshots each landed roll-up as approved (`$DATA/approved/<slug>.json`), rebuilds the memory file and `overview.md` from the approved snapshots only, writes the approved People export (`$DATA/people.json`, with its inputs in `$DATA/approved/people-inputs.json`) — a full commit publishes the **staged people set verbatim** (exactly what the digest showed, fingerprint-guarded like the rest of the staged set; a `--projects` subset publishes only people from that same staged set, cut to the landed projects' citations — see "bring in <slug>"), so what lands in `people.json` is exactly what was reviewed and no field is recomputed from a later `entities.json` at commit time —, sets `summarized_at` per session (never for a held one) and phase `done`, prints the counts (`changed_since_approval` and `people_changed_since_approval` count the landed projects still waiting for a "bring in <slug>": a roll-up, or people, that changed since they were brought in). Then `python3 $S/finalize.py --people-json` (the approved copy, matched against the store listing taken at staging) and, when the station's People tool is connected, handle each payload by its `existing` field:
      - `existing: null` — a new person: `people__upsert_person` with the payload as it is (`name`, `doc`, `email`/`identifiers`, `source: claude-import`, `last_interaction_at`).
      - `existing: {id, slug, matched_on}` — the store has them: `people__get_person(id)` → write its `doc` to a file and the payload's `doc_append` to another → `python3 $S/finalize.py --people-doc-merge --existing-doc <file> --append <file>` → `people__upsert_person` with the **same `slug`/`id`**, the merged markdown as `doc`, the payload's `identifiers`, `source` and `last_interaction_at`. Never a fresh doc, never a second record.
      - a name under "Needs your call" (`people_ambiguous` in the manifest) is upserted for nobody: ask the owner in the completion message which store entry it is (or that it is someone new), then fix `known-people.json` accordingly (keep the entry they named, or add the person's email to it) and run `--people-json --projects <slug> --known-people $DATA/known-people.json` for that one.
      When the tool is not connected (or the digest said "Store not checked"), note "People sync pending" and leave it for the next run. People are upserted **only after the commit**, never from the staged list.
    - **"bring in <slug>"** → `python3 $S/finalize.py --commit --projects <slug> --json` (an exact slug or a unique part of one; ambiguous is refused). The rest stays staged and `review.md` is re-rendered for it. It publishes to `people.json` **only what the current digest showed for that project**: the people in `staged/people.json`, each cut to the landed projects' citations (this project's, plus the approved citations of projects landed earlier) and kept only while those still clear the two-citation floor — nobody is added or re-ranked, so a person the 25 cap hid behind other projects' names in the combined digest is not published by "bring in <slug>". When the owner wants that project's own view (the people the cap hid), re-stage it alone — `--stage --projects <slug>` — re-post that digest and commit on its yes. Then `python3 $S/finalize.py --people-json --projects <slug>` (a landed project's payloads come from the approved inputs, restricted to the people `people.json` published; it reuses the staged store listing — pass `--known-people` when nothing is staged any more) and handle those as above — only people cited at least twice inside the approved project.
    - **"forget <slug>"** → `python3 $S/finalize.py --discard --projects <slug>` while it is still staged; `python3 $S/finalize.py --forget <slug>` once it has been committed — an exact known slug or a unique part of one, never a path: `../x`, `/abs`, `a/b`, `..`, `~`, an empty, unknown or ambiguous value is refused and deletes nothing, and a target that is a symlink or resolves outside `notes/claude-import/`, `data/claude-import/` or the memory dir refuses the whole command before anything is touched. It removes the project's note, roll-up, summaries, dumps, state, entity citations and approved snapshot, and drops its citations from the approved People export (`people_revoked` in the JSON: people that fall under the 2-citation floor go with them; tell the owner which names, so they can `people__delete_person` the ones only that project justified). A plain `--discard` drops the whole pending set.
    - **"include <date>"** → `python3 $S/finalize.py --include <date> --json` (a uuid, or its first 8+ characters, when the date is shared — the refusal lists the uuids); **"include personal"** → `--include-personal`. The session re-joins its project and the pending review is re-rendered; when the JSON says `stale_rollups` > 0 the project's roll-up was written without it — re-run that roll-up (step 6, now listing the session), stage again and re-post the digest before any commit.
    - **"hold <date>"** → `python3 $S/finalize.py --hold <date> --json`: holds a session the classifier let through (a landed session is refused — the note is on disk); the approved People export loses its citations (`people_revoked`). Its roll-up is now stale: re-run it without the session, stage again, re-post.
    - **"forget <date>"** → `python3 $S/finalize.py --forget-session <date> --json`: deletes that session's summary, dumps, state entry and entity citations, and drops it from the approved People export (`people_revoked`); the index row (Claude Code's own metadata) stays until the next index run, and a later `--new` run summarises the session again unless the transcript is gone or the owner holds it. A roll-up that listed it is stale: re-run, stage, re-post.
    - Anything else, or no reply, leaves the set staged. Never commit on silence, on a re-worded question, or on the original import consent. `--commit` is refused when nothing is staged, when the summaries changed since staging (stale), or when a chosen project's roll-up is stale: fix, stage again and re-post the digest. `--commit` never lands a held session.
11. **Completion message** (after the commit; counts only, plus the People names so they can be undone), as the reply to the owner's message:

    > Saved your Claude Code history: N sessions across M projects (K new; H held back as personal). Wrote `notes/claude-import/overview.md` + M project notes and a 2 KB memory summary (MEMORY.md row: added | skipped — index full). People added to your store: A, B, C (say "forget <name>" to undo); updated with new interactions: D, E. [Needs your call: F — is that <store entry 1> or <store entry 2>, or someone new?] Your transcripts are processed by Anthropic's Claude API, the provider your Sutando already runs on, which does not train on them. Nothing is uploaded to AG2 Space except the people you approve in the digest, which are saved to your People store. Say "import my Claude history" any time to pick up new sessions; `/import-claude-context --forget <slug>` removes one project.

    Never quote transcript text, titles of private sessions, or paths under `~/.claude` in the acknowledgement, the completion message or a failure note; the digest is the only message that carries derived text, and only in the owner DM. A held session is named by date and reason there and nowhere else.

## Never do

- Commit (`finalize.py --commit`) without the owner's explicit yes — "bring it in" or "bring in <slug>" — in the same conversation as the digest. Silence, the original import consent, a yes from anyone but the owner, or a stale staged set is not a yes. Upsert People only after that commit.
- Overwrite an existing person's dossier or create a second record for someone the store already has — existing people only gain an appended section and merged identifiers (`existing` payloads: fetch, `--people-doc-merge`, upsert with the same slug/id). Never upsert a "Needs your call" name until the owner has said who it is, and never upsert anyone when the store could not be listed.
- Quote, title, summarise or land a held (personal) session anywhere but the owner DM on the owner's direct question: the digest, the notes, the memory file, the People payloads, the manifest and the completion message carry its date and reason at most. Including it is the owner's call ("include <date>"), never the coordinator's.
- Refresh the memory file, `overview.md` or the approved People export (`$DATA/people.json`) from a roll-up or an entities pass the owner has not brought in. `--commit` rebuilds the first two from the approved snapshots (`$DATA/approved/<slug>.json`) and never from a changed `projects/<slug>.json`, and takes the export's people for every project outside the commit from `$DATA/approved/people-inputs.json`, never from a changed `entities.json`: a landed project the digest lists as "changed since approval" (under Memory, or under People) keeps its approved text and citations until "bring in <slug>" — committing another project does not refresh it, and neither do you by hand.
- Publish anything to `people.json` that the digest did not show. `--stage` computes the published set — the union of the selected projects' live people with every landed project's approved people — and a full `--commit` publishes that staged set verbatim; if a person's field change (a role, a company, a merged email) is not rendered in `review.md`, their approved fields stay byte-for-byte after the commit. Never work around the fingerprint guard that refuses a stale staged set, and never recompute the export from live `entities.json` at commit time.
- Pass anything but a known slug (or a unique part of one) to `--forget`, or build a deletion path from the owner's words yourself. A path (`../x`, `/abs`, `a/b`), `..`, `~`, an empty or unknown value is refused; a target that is a symlink or resolves outside `notes/claude-import/`, `data/claude-import/` or the memory dir refuses the whole command before anything is deleted. Nothing the import deletes ever lies outside those three places.
- Index a title, prompt, summary or agent name without the importer's redaction: `index.py` runs `_common.redact_text` (`secret_scanner` plus the Google/Anthropic key shapes — the same object `extract.py` uses on the dumps) on every metadata string **before** it is cut to length; the generic scanner alone let an `sk-ant-…` key into the index, and a cut before the redaction can leave half a key. Do not add a second scanner or a shortcut around it.
- Keep an approved People export (`$DATA/people.json`) that cites a forgotten project or session, or a held one: `--forget`, `--forget-session` and `--hold` shrink it to the citations still approved (people under the 2-citation floor go) starting from the published set — never a re-ranking of `approved/people-inputs.json`, so a person the cap hid from the digest is never surfaced by a removal — and it never grows without a new commit; do not hand-edit it and do not re-upsert from an old copy.
- Block the task loop on summarisation, or defer the start. The main task returns its reply (or `[no-send]` for the legacy task file) right after spawning the background coordinator; summaries, roll-ups, entities and staging run in that subagent, never inline in the core task and never "later". Steps 1–4 run in the turn that received the request — a greeting to answer, the startup ceremony, a boot recap or `priority: low` on the legacy task is not a reason to end the turn with the import unstarted.
- Post the digest (`staged/review.md`) anywhere but the owner DM (`results/proactive-<ts>.txt`) — not in a channel, a task result, a log or a note.
- Write, move, rename or lock anything under `~/.claude` — the tree is opened read-only; `index.py`/`extract.py` refuse an out-dir inside it and `finalize.py` refuses a memory dir under it. Do not run `finalize.py` outside the core without `--memory-dir`.
- Read `subagents/**` or any nested transcript, or sidechain sessions (first message `isSidechain: true`) — index.py skips them; do not go around it.
- Let tool I/O, attachments, images or thinking blocks into a dump, a prompt or a note. The extractor takes user + assistant text only; keep it that way.
- Put transcript text on disk before it has been noise-stripped **and** run through `secret_scanner.scan_and_redact`. Keep `[STORED-IN-KEYCHAIN-…]` placeholders verbatim; never reconstruct a secret.
- Log content. `status.json`, stdout of the scripts (`--json`), the app log and the completion message carry counts only — no titles, prompts, quotes or paths. The digest (`staged/review.md`) is the one exception and goes to the owner DM only.
- Send content anywhere but the summarising haiku subagents (Anthropic's Claude API, the provider this Sutando already runs on, which does not train on them) without `--cloud` and a fresh explicit yes. No cloud-recall upload, no telemetry with content, no sharing of notes, nothing to AG2 Space.
- Summarise with anything but haiku; never paste dumps into the core session.
- Install anything to run this skill — no `pip install`, no `--break-system-packages`, no venv, no brew — and never offer to in the DM. When `secret_scanner` prints `mode: DEGRADED` (the `detect-secrets` package is absent on this Mac), the repo-local rules still run and the run proceeds: keep the acknowledgement exactly as in step 2 and add one line at the end of the digest — "Note: secret redaction used the built-in rules only on this Mac." — nothing more.
- Touch `user_profile.md` (read by literal name in `src/voice-context.ts`), or write the `MEMORY.md` row when `memory-index-budget.py` refuses — the file stays, the row is logged as skipped.
- Re-run on a schedule, auto-import for a non-owner, or import for a different user's home (`$SOURCE_CLAUDE_CONFIG_DIR` is honoured only because migration scripts already use it).

## CLI reference

| flag | script | effect |
|---|---|---|
| `--task-id <id>` | index | the task this run answers (task header `id:`; for a message-triggered run the owner message's task id). Stored with a fresh uuid4 `run_id` under `run` in `state.json`; every `status.json` write carries both. Omitted → `task_id: null`, and the orphan check keeps the task rather than archiving it |
| `--projects a,b` | index, extract, finalize | index/extract/`--stage`: slugs matching exactly or as a case-insensitive substring of the opaque slug (e.g. `gtm`); `--commit`/`--discard`/`--people-json`: an exact slug or a **unique** part of one — no match or an ambiguous one is refused |
| `--session uuid` | extract | one session (prefix allowed) |
| `--since 30d` | index | only transcripts modified in the window (`12h`, `2w`, `YYYY-MM-DD` also) |
| `--dry-run` | index | full pass, writes nothing, prints counts |
| `--counts-only` | index | readdir + stat only; opens no transcript; a **file count** for the pre-run estimate — the acknowledgement uses the full index's `conversations` (= `sessions − empty`) |
| `--json` | all | counts as JSON on stdout (counts only) |
| `--new` | index, extract | index: list only sessions changed since the last index; extract: only sessions changed since their last extraction |
| `--cloud` | (skill) | upload the finished summaries to cloud-recall corpus `claude-import` — separate explicit consent; not implemented in the scripts yet, do it by hand only when asked |
| `--stage` | finalize | **the default**: de-duplicate the entities and render the review set under `$DATA/staged/` (memory file, notes, `people.json`, `review.md`, `manifest.json`); writes no sink, phase `staged` |
| `--commit` | finalize | move the staged set into the sinks — only on the owner's yes; refused (exit 1) when nothing is staged or the staged set is stale; with `--projects` lands that subset and re-stages the rest (phase `done`, or `staged` while something is pending); snapshots each landed roll-up as approved (`$DATA/approved/<slug>.json`) and rebuilds the memory file and `overview.md` from the snapshots only — the JSON's `changed_since_approval` counts the landed projects whose roll-up changed since they were brought in; the approved People export takes the landed projects' people from the reviewed entities and every other landed project's from `$DATA/approved/people-inputs.json` — `people_changed_since_approval` counts the landed projects whose people changed since they were brought in; a full commit publishes `staged/people.json` verbatim, a `--projects` subset only the people that file lists, cut to the landed projects' citations (never a recompute over the narrowed projects: a person the cap hid from the digest is never published unseen) |
| `--discard` | finalize | drop the staged set (or the `--projects` subset) without touching the sinks |
| `--people-json` | finalize | print the People payloads (≥ 2 citations; every existing person + ≤ 25 new) — the staged copy while a review is pending, else the last commit's approved copy (`$DATA/people.json`), else computed; `--projects` restricts to people cited inside those projects (from the approved inputs when every one of them has landed — and then only people `$DATA/people.json` published —, else recomputed; matched against `--known-people`, else the staged listing). Approved only by `--commit` |
| `--known-people FILE` | finalize `--stage`, `--people-json` | the station's `people__list_people` output (JSON array: id, slug, name, email, identifiers.emails); matches by email then normalised name; without it the manifest says `known_people: "not checked"` and every payload is `existing: null` |
| `--people-doc-merge --existing-doc F --append F` | finalize | print the existing dossier with the `doc_append` section merged in — a section under the same heading is replaced, otherwise appended (idempotent) |
| `--held-json` | finalize | the held sessions: project, uuid, date, reason — never a title |
| `--include date\|uuid`, `--include-personal` | finalize | lift the hold on one session (a uuid or its first 8+ characters when a date is shared; ambiguous is refused with the uuids) or on all; recorded in `state.json`, a pending review is re-rendered; `stale_rollups` in the JSON says the roll-up must be re-run |
| `--hold date\|uuid` | finalize | hold a summarised session the classifier missed (refused once it has landed); the review is re-rendered, the roll-up is stale until re-run, the approved People export loses its citations (`people_revoked`) |
| `--forget-session date\|uuid` | finalize | delete that session's summary (and partials), dumps, state entry and entity citations, and drop it from the approved People export (`people_revoked`); a roll-up that lists it is stale until re-run |
| `--forget slug` | finalize | an exact known slug or a **unique** part of one — never a path (`../x`, `/abs`, `a/b`, `..`, `~`, empty, unknown or ambiguous is refused, exit 1, nothing deleted; a symlink or a target outside `notes/claude-import/`, `data/claude-import/` or the memory dir refuses the whole command): removes exactly that project's note, summaries, dumps, roll-up, approved snapshot, memory line, state, entity citations, staged copy and its citations in the approved People export (`people_revoked`; a pending review is re-rendered without it) |
| `--purge-dumps` | finalize | delete `$DATA/dumps/` (alone, or together with `--stage`/`--forget`) |
| `--max-chars-total N` | extract | soft cap for one run (default 8,000,000 chars, newest first) |

From sonichi/sutando, MIT.

More from sonichi/sutando

Compare and alternatives

    Featured in

    Signals

    Stars
    400
    Forks
    98
    Open issues
    1,202
    Last commit (this folder)
    Oct 19, 2026 (8 days ago)
    Last release
    Aug 20, 2026 (2 months ago)

    From GitHub (fetched Oct 24, 2026).

    Details

    License
    MIT
    Website
    sutando.ai
    First seen
    Oct 24, 2026
    Last checked
    Oct 24, 2026

    More from sonichi

    Cite this page

    import-claude-context skill. Agent Mart. https://agentmart.dev/skills/sonichi-import-claude-context (last updated 2026-10-24)