hansard
v0.5.1
Published
Local-first archive, search, and recall for AI-agent conversations.
Maintainers
Readme
hansard
Hansard is a local-first archive, search, and recall tool for conversations with AI coding agents. It preserves source history for later reprocessing and builds portable, provider-independent records for local use.
Highlights:
- imports from Codex, ChatGPT exports, Claude, Gemini, Antigravity, Devin, Windsurf, Cursor, Cline, OpenCode, Aider, and other supported agent tools
- substantially verbatim source backup plus redacted Markdown transcripts and canonical event JSONL
- terminal search, full-session Deep Recall, a local web viewer, and an MCP recall server
- repository-aware organization, activity statistics, memory snapshots, and attachment preservation
- optional S3-compatible device sync and signed, repository-scoped Team sharing
Install
Install the CLI globally so agent-facing recall and continue-from commands can call hansard
from PATH:
npm install -g hansard
hansard init
hansard appHansard does not run install-time lifecycle scripts. After a direct install, run
hansard init explicitly, then use hansard doctor to check optional tools and
configured integrations.
hansard init --dry-run prints the ordered setup stages and possible writes
without creating config, importing history, installing integrations, starting
processes, or contacting remote storage.
The npm package supplies the CLI, not the native desktop bundle. hansard app
activates a separately installed Hansard app on macOS; when no registered app
is available, it opens the same local viewer in the default browser. Use
hansard web to request the browser viewer directly.
For a project-local install:
npm install hansard
npx hansard initOr run the published package without keeping a local install:
npx hansard initRequirements:
- Node.js 20 or newer
sqlite3for Codex, Cursor, and Devin local database importsrg/ripgrep for faster history searchunzipfor ZIP web exportszstdorunzstdfor compressed Codex session files
Run hansard doctor after install to check optional tools and configured
sources.
Recall
hansard integrations add-to codex
hansard integrations add-to claude
hansard integrations add-to gemini
hansard integrations add-to antigravity
hansard integrations add-to devin
hansard integrations add-to cursor
hansard integrations add-to cline
hansard integrations add-to opencode
hansard integrations add-to aiderintegrations add-to codex installs the MCP server plus Codex recall, Deep
Recall, and continue-from skills. integrations add-to claude installs the MCP
server plus Claude /recall, /deep-recall, and /continue-from commands plus
matching Claude skills.
integrations add-to gemini installs the MCP server plus Gemini /recall and
/continue-from commands under ~/.gemini/commands/.
Additional native surfaces are installed where the client exposes one:
| Agent | Recall surface |
| --- | --- |
| Antigravity | MCP config in ~/.gemini/antigravity/mcp_config.json plus Agent Skills at ~/.gemini/antigravity/skills/{recall,continue-from}/SKILL.md |
| Devin | MCP config in ~/.config/devin/config.json plus /recall and /continue-from skills under ~/.config/devin/skills/ |
| Cursor | MCP config in ~/.cursor/mcp.json plus project files .cursor/commands/{recall,continue-from}.md and matching .cursor/rules/hansard-*.mdc rules |
| Cline | MCP config in ~/.cline/data/settings/cline_mcp_settings.json plus /recall.md and /continue-from.md workflows under ~/Documents/Cline/Workflows/ |
| OpenCode | MCP config in ~/.config/opencode/opencode.json plus /recall and /continue-from commands under ~/.config/opencode/commands/ |
| Aider | Loadable recall and continue-from instructions under ~/.hansard/aider/; Aider does not expose a documented custom slash-command registry, so load them with /load ~/.hansard/aider/load-recall.aider or configure the generated markdown as read: in .aider.conf.yml |
The generated recall files use a skill-style layout with command tables,
workflow steps, query-selection guidance, archive layout notes, and
troubleshooting. They ask the agent to infer a focused search query, then call
hansard history and read a bounded latest transcript window with
hansard show <session_id> --json --msg-limit 20, so long session Markdown
cannot hide the tail behind an agent harness output limit. The windowed JSON
omits the full Markdown and canonical-event copies, bounds large tool-result
text, and retains collaboration tool arguments. Agents can page to other
message offsets when needed without direct filesystem access to ~/.hansard.
Users can ask for context naturally, such as /recall the migration bug and
update the test.
The generated continue-from files are stricter: after selecting a session they
instruct the agent to read both the first window
(hansard show <session_id> --json --msg-offset 0 --msg-limit 8) and latest
window (hansard show <session_id> --json --msg-limit 20). The first window
recovers the original goal and constraints; the latest window recovers the
current state, files touched, test status, blockers, and next action before the
agent resumes work.
Deep Recall is the archive-wide research path. It decomposes a question into
keyword, repo, provider, tool, command, and time-window variants; merges ranked
results; reads selected full sessions in isolated model contexts; then
synthesizes an answer with [session:<session-id>] citations. Use it from the
Deep Recall tab or directly:
hansard deep-recall "Compare the migration approaches and explain why each was abandoned"
hansard deep-recall "Build a decision log for the authentication rewrite" --repo github.com/acme/widgets --backend claude --model opusSkill/command files can also be installed directly:
hansard recall install-skill codex
hansard recall install-skill claude
hansard recall install-skill gemini
hansard recall install-skill antigravity
hansard recall install-skill devin
hansard recall install-skill cursor
hansard recall install-skill cline
hansard recall install-skill opencode
hansard recall install-skill aiderThe standalone MCP binary remains available for clients that only need the tool server:
hansard-recallDaily CLI
hansard status, hansard history, hansard show, hansard import,
hansard index, hansard rebuild, hansard classify, and hansard watcher login
use styled human-readable output by default. Use --json
on status, history, and import status/results for script-friendly
structured output. hansard status includes the most recently archived
conversation threads and the sources currently monitored by the supervisor;
hansard history is for search and recall.
Useful history commands:
hansard history
hansard history "codebase explanation" --provider codex-cli --limit 10
hansard history --repo github.com/acme/widgets --since 90d
hansard show <session-id>
hansard show <session-id> --path
hansard delete <session-id> --yes
hansard export <session-id> --out ./hansard-session-export
hansard rebuild --since 30d
hansard app
hansard web
hansard web --no-openhansard app is the normal post-setup entry point. It activates the native
macOS app when installed and otherwise falls back to the browser viewer.
hansard web starts the local conversation viewer and opens the system default
browser. Pass --no-open to print only the local URL and keep the
server running. Deep Recall is the seventh primary tab. It uses the authenticated
Codex or Claude command-line client already installed on the machine, so no
separate API key is required; the model selector switches between available
CLI models. Every submitted question runs the archive-wide planner, search,
full-session readers, and cited synthesis pipeline. Real research stages and
generated text appear inline in the transcript instead of in a detached
progress bar. Research threads are stored under data/hansard/chat/ for
compatibility, outside the normal session index, so they never appear in
Conversations, search, stats, projects, or notifications. CLI calls are
ephemeral/non-persistent where supported, read-only, and stamped with an
internal sentinel as a second guard against re-import. New Research is only a
local composer reset: the first submitted question creates the durable thread,
and legacy zero-message records are removed. Stored General Chat threads remain
readable, but new turns always run Deep Recall and never receive folder context.
The left rail is a
repo tree sorted by the latest updated session, each folder pages through
sessions with a load-more control, search results reuse the same session
reader, and the transcript pane can switch between readable chat bubbles and
the raw markdown archive. Readable chat bubbles load from pre-baked
*.view.json payloads; large windowed transcripts use derived
*.view.pages/ shards so the server reads only the requested messages. Tool
cards initially carry only their name and summary; opening one fetches its
arguments, diff, and output from the same bounded page shard and caches that
detail for later opens. Raw markdown is fetched only when source view is
opened. Search updates while text is entered
with a short debounce. The web endpoint uses a
warm compatible index when available, but it will not synchronously parse or
rebuild an obsolete index during an interactive query or scan every rendered
conversation as a fallback; rebuilds are left to hansard index rebuild or
the supervisor. Search tries an all-term FTS match first; when a longer natural
recall query under-fills, one bounded relaxed pass favors selective terms and
reranks by coverage across the whole session. This tolerates an extra remembered
word or terms split across messages without making every query a broad scan.
Canonical titles and title aliases have their own exact-match table in the FTS
sidecar, so preserving exact-title precedence does not walk session metadata on
every keystroke.
Session lists and stats are backed by derived indexes under
hansard/indexes/, so repeat page loads avoid walking every metadata file.
Settings → Lists controls the initial item count and load-more increment for
Conversations and Memories independently. Each value defaults to 20, accepts
1–50, and applies separately inside every folder. Opening a direct memory link
still reveals the selected item when it falls beyond the configured initial
count.
The static viewer uses shadcn/ui-style design tokens
and compact button/input/select/sidebar patterns without requiring a frontend
build step. Archives still keep stable path:<hash> keys for folders without
git identity, but the UI displays the local path. In Conversations, configured
local roots stay flat in the recency-sorted rail: each actual project is a
direct device/configured-root/relative-path row rather than a synthetic parent
that fans out. Use the pencil on a local project to give its relative component
a shorter display name. The alias is stored
under web.projectDisplayNames in config.json; it never renames the folder,
changes the canonical project key, or requires a reimport. Settings → Folders
provides the same cosmetic naming control for a detected path and all of its
local subfolders at once. Memories use the same direct cosmetic paths while
retaining their raw source path as provenance. Remote Git repos and web-chat
accounts keep their provider-owned names. Projects with linked
subagent transcripts show a disclosure chevron; the child rows are fetched only
when expanded. Opening one uses a full transcript subpage with Original,
previous, and next navigation instead of a modal.
Hosted viewer
By default hansard web binds only to 127.0.0.1 and uses a per-launch
?token= handoff to set an HttpOnly cookie. To serve it on another interface,
pass --host <addr>; bare --host binds 0.0.0.0. Any non-loopback bind
requires a password:
HANSARD_WEB_PASSWORD='choose-a-long-password' hansard web --host 0.0.0.0
# or
hansard web --host 0.0.0.0 --password 'choose-a-long-password'The desktop app runs this loopback viewer with --parent-pipe. Its cookie is a
browser-session cookie with no clock expiry, so an app left open continues to
refresh for the lifetime of its child viewer process. Standalone and hosted web
sessions retain the bounded web-cookie policy.
Use --read-only when sharing the viewer. Remote browsers are always blocked
from mutating endpoints; --read-only also blocks those writes from loopback.
The hosted mode is intended for trusted networks such as Tailscale, a VPN, or
a LAN, or behind a TLS reverse proxy. hansard does not set a Secure cookie
over plain HTTP.
Provider filters use one stable order: OpenAI (codex-cli, codex-desktop,
codex-sdk, chatgpt), Anthropic (claude, claude-code-desktop, claude-cowork,
claude-web, claude-sdk), Google (gemini-cli, antigravity), Cognition
(devin-cli, devin-desktop), then other local tools (cursor, cline, opencode,
aider).
The supervisor is hansard's local background watcher. When it is running, it
watches each source's on-disk history roots with filesystem events (FSEvents
on macOS): writes mark the source dirty and an import runs a few seconds
later, so new conversation turns usually land in the archive within seconds.
SQLite-backed stores such as Cursor's state.vscdb use a wider ~20-second
coalesce window because their databases churn while the app is open. Events
are a latency optimization, never the source of truth — watched sources are
still re-polled on a 15-minute heartbeat to catch dropped events, sources
whose roots can't be watched (Aider project folders, JetBrains Cline) keep
the original 30-second poll with a 5-minute idle cadence, and failing sources
retry with exponential backoff, so an idle machine isn't spawning import
workers all day. Imports use the configured import window, defaulting to the
last 30 days. When login startup is disabled, hansard does not install a login
item. During setup, uncheck Start watcher at login or run
hansard init --no-autostart. A one-time catch-up remains available through
hansard import --source all, along with
hansard watcher start to watch for the current session, or
hansard watcher login enable later. The default
watcher choices are Codex CLI, Codex Desktop, Claude Code CLI, Claude Code
Desktop, Claude Cowork, Gemini CLI, Antigravity CLI, Antigravity 2.0, Antigravity IDE, Devin CLI, Devin Desktop, Windsurf,
GitHub Copilot CLI, Factory Droid, Grok Build, pi, Cursor,
Cline, OpenCode CLI, OpenCode Desktop, OpenCode Web, and Aider. New configs
still support imports.autoDiscoverSources=true, but init records the chosen
watcher list exactly by setting imports.autoDiscoverSources=false.
Cursor raw SQLite recovery is intentionally left to explicit imports such as
hansard import --source cursor --since all; the supervisor handles
incremental Cursor logs going forward and prunes duplicate transcript snapshots.
The detailed watcher/import contract, including sourcePath replacement rules for
multi-session stores such as Cursor SQLite and Devin sessions.db, lives in
docs/history-source-handling.md.
Claude Code can retain an abandoned prompt draft in its JSONL when a revised prompt is submitted before the assistant responds. Current imports collapse those same-parent pending branches to the revised human turn while preserving the original records in the raw archive. Existing Claude CLI archives need a clean reimport to apply that normalization:
hansard import --source claude --since allCodex rollouts can likewise mirror one submitted prompt as both a durable
response_item and a live event_msg, or retain an unanswered prompt after an
explicit abort/rollback. Current imports keep one normalized human turn while
leaving every source record in the raw archive. Codex goal-mode threads, whose
only human intent arrives via thread_goal_updated events, now import a
/goal <objective> user message (deduped across heartbeat re-emissions) that
also titles the session. Claude Cowork now ignores
same-UUID replay copies and uses historical audit timestamps, and legacy Cursor
imports assign each global generation breadcrumb to one Composer before
collapsing only short, unanswered prompt revisions. Existing derived archives
need provider reimports for these parser changes:
hansard import --sources codex-cli,codex-desktop,codex-sdk --since all
hansard import --source claude-cowork --since all
hansard import --source cursor --since allChatGPT and Claude.ai export messages with distinct provider IDs are retained, even when their text repeats, because those can be real retries rather than duplicate transport records.
Slack notifications are an optional, off-by-default notify layer. Run
hansard notify slack setup for the guided path: it opens Slack's app
creation page pre-filled from a manifest (one personal app, chat:write plus
chat:write.customize for per-speaker identities), verifies the pasted bot
token, connects a channel, and sends a test post. Two surfaces toggle independently, each with its own channel: summaries
(one recap per session after it goes quiet for --quiet-minutes) and the
firehose (live turns streamed into one thread per session, batched every
--batch-seconds). Adjust with
hansard notify slack enable --summaries on|off --firehose on|off
--summary-channel C… --firehose-channel C…. Only redacted archive output is
posted, the token can also come from SLACK_BOT_TOKEN, and --repos
restricts posting to an allowlist. Posting is at-least-once on a local queue:
going offline pauses posting but never archiving, and everything drains on
reconnect.
- Desktop notifications are configured per browser/app under Settings → Notifications. New assistant replies can produce OS-level alerts and open the matching session when clicked; preferences stay in browser storage. In a browser they use the web Notification API (and show under the browser's name in OS notification settings); in the Hansard desktop app they post as native app notifications, so Hansard appears in macOS System Settings → Notifications.
For finer control after setup, routes decide which sessions post where: a
session fans out to every matching route, so a #all route plus a
#acme-org route puts Acme sessions in both. Mark a route exclusive to stop
it falling through. Each route can match by owner/provider/repo/path/folder and
carry its own bot token, so multiple bots can post to different channels.
Manage them with hansard notify slack route <list|add|rm|reorder> (e.g.
route add --match-owners acme --token ref:acme-bot --summary-channel C…
--exclusive) and store named tokens with hansard notify slack token
<list|add|rm>. All of this — the default credential, routes, channels,
filters, live tool inclusion, and pasting or replacing bot tokens — is also
editable from the web viewer's dedicated Settings → Slack tab
(loopback-guarded; stored credentials are write-only and shown masked). Slack
owns the app profile, so the same panel links to Slack's app dashboard for
name and icon changes. No reimport is needed.
Folders group projects in the Projects view above the repo level — a Codex
folder, a Claude Code folder, or an org — the same way GitHub owners and
Claude.ai accounts already act as containers. Settings → Folders
automatically lists the same groups detected from the archive; choose
Configure to rename the detected path cosmetically, rename any local
subfolder, or spin a subfolder (including its detected descendants) into its
own top-level display folder, then save the layout explicitly. These controls
never rename directories or change canonical archive keys, and discovery never
changes config.json on its own. Folders can also be defined with
hansard config set containers '<json>'
(an array of { id, label, icon, pathRoot, match }, where match selects sessions by
provider/owner/repo/path). Folders are evaluated at render time, so edits apply
with no reimport. Projects keeps the parent-and-children hierarchy. Conversations
and Memories instead show every actual local project as a direct
device/folder/relative-path row in normal recency order; folder rank never pins
those rows above newer activity.
Remote sync can be configured during init or from the CLI. Local storage remains
canonical. Three sync modes exist: upload (default) pushes changed archive
objects to the remote target, receive mirrors other devices' namespaces into
this machine's archive, and two-way does both in one run. No mode ever deletes
objects — not from the bucket when files are missing locally, and not locally
when objects are missing remotely.
hansard sync configure
hansard sync status
hansard synchansard sync configure opens a picker in a terminal. Choose an existing
configured/env remote, or choose the last option to configure a new R2, S3,
S3-compatible, or local-folder target. After a target is selected, the CLI asks
what to do next: sync now, create a snapshot, change autosync cadence, or exit.
Non-interactive scripts can still pass flags such as --endpoint, --bucket,
and --access-key-id.
The same values can be supplied with HANSARD_REMOTE_ENDPOINT,
HANSARD_REMOTE_BUCKET, HANSARD_REMOTE_ACCESS_KEY_ID, and
HANSARD_REMOTE_SECRET_ACCESS_KEY. R2 also accepts R2_ENDPOINT,
R2_BUCKET, R2_ACCESS_KEY_ID, and R2_SECRET_ACCESS_KEY.
The bucket should already exist before running sync. For Cloudflare R2, copy the
S3 API URL from the bucket's Settings > General page. Cloudflare may include the
bucket in that URL, such as
https://<account-id>.r2.cloudflarestorage.com/<bucket>; hansard accepts that
and derives the bucket automatically. Create an R2 API token from the R2 Object
Storage overview with Manage API Tokens, scoped to the hansard bucket with
read, write, and list permissions. For AWS S3, use an IAM access key scoped to
the bucket or hansard/ prefix with ListBucket and object read/write
permissions.
During hansard init, a configured cloud destination is uploaded immediately
after the initial backfill. If the watcher is allowed to start at login, init
also asks for a cloud autosync cadence; the supervisor then uploads new archive
changes on that interval whenever it is running.
Init always records a device name and a person name in the main hansard
config (person defaults to git user.name, then the OS username). New session
metadata includes both identities; remote sync uses the device slug so multiple
machines stay distinguishable in one archive bucket, and the person identity
keeps sessions attributable when several people share a bucket in team setups.
Existing archived sessions are not rewritten by init; run
hansard import --source all --since all to reimport old local history with the
current device metadata.
Remote objects are namespaced by device so multiple machines can upload into one bucket without overwriting each other's archive roots:
s3://<bucket>/hansard/
devices/
work-laptop/
sessions/...
raw-sources/...
memory/...
telemetry/...
snapshots/
20260504T173000Z/
work-laptop/
sessions/...Sync uploads canonical archive objects, so memory items and their snapshot
history (the Memories tab data) ride along automatically next to sessions.
Derived local indexes under hansard/indexes/ are excluded, and research threads
under hansard/chat/ stay device-local by default because a Deep Recall answer
can combine private material from several projects. Each device rebuilds its
indexes from the canonical archive objects.
Personal Sync
Personal Sync is the private, first-class way to keep one person's complete
Hansard archive available across their own devices. It is intentionally
separate from Teams: it has one random profile identity, no sharing selector,
and always includes sessions, memories, unredacted provider backups under
session .raw/ and shared raw-sources/, attachments, and artifacts.
hansard personal-sync create --endpoint file:///absolute/private/folder
hansard personal-sync create --target r2 --endpoint https://<acct>.r2.cloudflarestorage.com --bucket private-hansard
hansard personal-sync connect --endpoint file:///absolute/private/folder
hansard personal-sync statusCreate writes <prefix>/personal.json with a random profile ID. Other devices
connect to that identity and upload below
<prefix>/personal/<profile-id>/devices/<device-id>/...; renaming a person or
device does not change either stable ID. One Personal Sync profile can be
connected per local Hansard installation, while any number of devices tied to
the same profile can connect. Endpoint, bucket, region, prefix, direction, cadence, and
credentials stay device-local. Use a private destination and narrowly scoped
credentials. Team targets have a narrower contract and never upload raw
provider backups or memories.
In a terminal, hansard sync prompts for the remote target, previews
the upload-only plan, and requires a confirmation phrase before writing. This is
intentional even when only one remote is configured, because multiple remotes
can exist in config/env and a normal sync can overwrite same-key objects for the
selected device namespace. Use --dry-run to preview in scripts, or --yes to
skip the guided confirmation.
Use hansard sync snapshot to upload a redundant point-in-time copy under
hansard/snapshots/<timestamp>/<device>/.... In a terminal it prompts for the
remote, lists existing snapshots, requests a snapshot name, previews
the write, and then confirms before uploading:
hansard sync snapshotIf the remote copy has stale objects after a local reset, cleanup, or full
reimport, use the explicit replace path. In a terminal it previews the selected
remote, shows how many remote objects will be deleted, and requires typed
confirmation before it deletes this device namespace under
hansard/devices/<device>/ and uploads the current local archive.
hansard sync replace
hansard sync wipehansard sync wipe is delete-only. It asks which remote to use, asks for the
scope (device, one snapshot, all snapshots, prefix, or bucket),
previews the exact target and prefix, then requires a two-step typed
confirmation. device leaves snapshots untouched; snapshots leaves normal
device uploads untouched. Run hansard sync afterward to upload this device
again. Scripts can use --dry-run or --yes.
Multi-device sync
hansard sync receive (or sync.mode: receive / two-way in config) mirrors
every other device's namespace from the same bucket into this machine's archive
under received/devices/<device>/…. Received sessions then appear alongside
local ones everywhere — recent lists, search, stats, and the web viewer, which
labels their origin device and offers a device filter. The same session id on
two devices is shown once, preferring the local copy.
hansard sync receive --dry-run # preview what would be mirrored
hansard sync receive # mirror other devices now
hansard config set sync.mode two-way # supervisor autosync uploads + receivesSafety rules, in both directions: receive never deletes local files that are
gone remotely, never writes outside received/, and never touches this
device's own live archive; upload never publishes the received/ mirror back
under this device's namespace. Received sessions are read-only. Remote indexes
(indexes/) are skipped as derived, and large raw-sources/ are skipped unless
--include-raw is passed (or sync.receive.includeRaw is set). Limit which devices
are mirrored with sync.receive.devices (a list of device slugs; empty means
all). After a receive downloads new sessions, the supervisor's next reindex pass
makes them searchable.
Teams
To share only a project's sessions with collaborators, point a team target at a shared bucket (an R2 bucket per project works well):
# creator: generates a manager key, signs team.json, and prints a verified invite
hansard team create --name "Example Team" --endpoint https://<acct>.r2.cloudflarestorage.com \
--target r2 --bucket example-team-hansard \
--repos github.com/acme/widgets --owners another-org \
--upload-interval 5 --receive-interval 5
# print a fresh invite after later policy revisions
hansard team invite example-team
# each collaborator pastes that invite and supplies storage credentials separately
hansard team join --invite 'hansard-team-invite-v1.…'
hansard team status # scope, cadence, member namespaces seen in the bucketteam.json is a signed schema-3 envelope carrying the Team identity, manager
keys, revision chain, scope, and cadence. Create keeps the Ed25519 manager key
in this installation's private key store. A verified invite pins the Team ID,
policy revision and hash, manager fingerprint, and storage locator, but contains
no credentials. Bucket access alone cannot join a Team or authorize a policy
change. Unsigned or tampered manifests, unknown signers, rollback, forks, and
revision gaps fail closed. Shared Team selectors use canonical repositories and
repository owners only; local path-prefix, container, provider, or other device-specific
selector fields are rejected. Repositories and owners can be mixed, and a
session matching any configured value uploads. The selector chooses whole
sessions, not individual messages: a matched transcript can still mention or
contain files from outside the selected repo. Team sync excludes the memory
archive and every raw-provider backup, including session .raw/ folders,
shared provider databases under raw-sources/, and byte-preserved web exports
under raw/. It removes structural local filesystem paths from shared session
metadata, manifests, artifact records, transcript metadata, viewer summaries,
and conversation frontmatter. Full message text and tool calls/results are not
rewritten, however, and attachments are shared as bytes; all of them can still
contain local paths or out-of-scope material.
Team targets live in sync.targets[] alongside the personal remote, each
with its own upload/receive intervals, so a project bucket can run at
minutes-level freshness while a personal backup stays at 30.
The viewer's Settings → Personal Sync page creates or connects the complete
private backup. Settings → Teams creates a signed Team or joins one with a
manager-issued verified invite. Pick Shared Folder, Cloudflare R2, Amazon S3,
or another S3-compatible store and Hansard shows only the relevant path,
endpoint, bucket, signing region, and credential fields, including temporary
session tokens. Upload scope is a repeatable dropdown list that can mix folders
of detected repositories, specific repositories, and repository owners; folder
choices expand to portable canonical repository keys. Create refuses a
bucket/prefix that already contains team.json.
On an existing Team, only a device holding a current manager key sees Edit shared policy and Copy invite. A signed shared-policy edit changes the name, upload scope, and cadence for every member. Every member can use Edit this device for only this machine's endpoint, bucket, prefix, sync direction, and write-only credentials. A local target cannot replace the shared Team selector with path, container, provider, or other scope overrides. Saved credentials are retained only while the storage destination is unchanged, and portable selectors with AND logic or exclusions that the row editor cannot represent are shown as advanced and preserved unchanged. Disconnect removes this device's connection only — it does not delete the bucket or anything already uploaded. Settings refreshes the shared policy and observed device namespaces directly from file, R2, or other S3-compatible storage before presenting them.
Two separate checks protect a Team connection. Cryptographic trust pins the
Team ID, manager key, policy hash, and accepted revision floor. Upload consent
is also fingerprinted locally and bound to the exact destination, prefix, Team
identity, namespace version, selector, and non-secret account/credential
identifier. If a valid signed team.json revision changes the selector
—including a change from selected repositories to all conversations—or the
destination/account changes, scheduled uploads stop before enumerating or
uploading the new set. Preview the matched-session count and destination, then
explicitly confirm that exact preview before uploads resume.
Current Team namespaces are collision-resistant: uploads land under
members/<person>--<id16>/devices/<device>--<id16>/, where each id16 is the
first 16 hexadecimal characters of SHA-256 over the complete stable identity.
Two members who both named their laptop "MacBook Pro" therefore do not merge.
Received teammate sessions appear in the merged viewer with person/device
attribution, read-only.
Sharing disclaimer (shown at create/join and before the first upload): matching sessions upload their normalized archive, including full transcripts, tool output, metadata, attachments, and captured artifacts—even content from outside the matched repos. New Teams uploads exclude raw provider backups, shared raw databases, and byte-preserved web exports. Structural path metadata is removed, but message text, tool calls/results, attachments, and artifacts can still reveal local paths or unrelated material. Redaction is pattern-based rather than semantic, and binary attachments or artifacts are not content-redacted. Anyone with bucket access can read the uploaded data. That storage access is a confidentiality concern, but it does not grant trusted membership or permission to sign a Team policy.
Normal sync never deletes remote objects. To audit and remove current raw keys left by older Teams builds, use the explicit migration:
hansard team purge-legacy-raw <team-name> --dry-run
hansard team purge-legacy-raw <team-name> --yesThe cleanup removes only raw keys whose current or state-recorded legacy
member/device namespace can be identified as this device's. Ambiguous legacy
keys are reported and left untouched. It also removes legacy raw files from
all local read-only Teams mirrors on that machine, because the historical
mirror layout did not record which team target created each file. Cloud cleanup
needs ListBucket and DeleteObject access to both <prefix>/members/ and
<prefix>/snapshots/; ordinary sync does not need those broader cleanup
permissions.
purge-legacy-raw is deliberately not a complete migration or erasure tool.
An existing Team destination may retain normalized personal or out-of-scope
sessions uploaded under an older policy, prior 8-character or renamed
member/device namespaces, snapshots, unidentified raw keys, object versions,
retention copies, replication, and provider backups. Start the hardened Teams
setup with a fresh bucket or prefix; handle retention/history in the storage
provider, and retire the old destination separately.
Unsigned schema-1/2 Team manifests are not accepted by the signed v1 contract;
create the Team again on that fresh destination and distribute verified invites.
Local mirrors are garbage-collected the same explicit way: hansard sync
receive --prune deletes received-mirror files whose remote object is gone
(after the normal receive pass), never anything outside received/devices/ or
received/members/. It follows the same wipe-style confirmation — --dry-run previews, non-TTY
requires --yes, and a TTY asks for a typed PRUNE RECEIVED <scope> code.
Plain receive, two-way, and supervisor autosync never prune ordinary
archive data or legacy team raw mirrors.
On first-class Personal Sync targets, received devices' memories and raw backups are mirrored too and appear read-only in the web viewer's Memories tab, labeled with their origin device; the same memory id on two devices is listed once, preferring the local copy. Team targets never upload or receive memories.
With teammates syncing into one bucket, a daily team digest can post to Slack:
set notify.slack.digest (enabled, channel, token as ref:<name> or
inline, atHour, lookbackHours) and the supervisor posts one summary per day
grouping the window's sessions by person (device when no person is stamped) —
session counts, active repos, models, token totals. Preview any time with
hansard notify slack digest run --dry-run; empty windows post nothing.
To test the full loop without a bucket, point two HANSARD_HOMEs at one local
folder target (file:///some/dir): sync from the first home, then
hansard sync receive in the second.
Archived sessions are stored as readable markdown first, with metadata, raw transcript JSONL, normalized canonical event JSONL, a pre-baked viewer payload, and source/attachment sidecars alongside them:
~/.hansard/data/hansard/
sessions/
repo=<encoded_repo>/
provider=<provider>/
year=2026/
month=04/
day=27/
session=<id>.conversation.md
session=<id>.metadata.json
session=<id>.transcript.jsonl
session=<id>.events.jsonl
session=<id>.view.json
session=<id>.view.pages/ # derived viewer cache
session=<id>.attachments/
attachments.json
<sha256>.<ext>
session=<id>.raw/
manifest.json
001-<original-file-name>hansard memory backup mirrors the same repo/global partitioning for provider
memory. Codex's individual rollout_summaries/*.md files inherit the project
of their archived thread (falling back to the summary's cwd), while
cross-project aggregates such as MEMORY.md and global instructions remain in
the provider's global folder. Rollout summaries preserve thread_id as their
origin session, use the first Markdown H1 as the display title, and use the
Rollout context: paragraph as their description. Claude Code project-memory
frontmatter similarly preserves name, description, and
metadata.originSessionId. Re-running the backup backfills this metadata for
existing items without changing IDs or adding content snapshots. It also moves
existing live items and source-deleted rollout history without changing
snapshot history. Codex's recursive memory-file walk accepts readable UTF-8
text only; binary and cache artifacts are ignored, and previously archived
local artifacts are pruned without deleting the provider file.
The web viewer links this provenance in both directions. A conversation with one linked memory has a direct Memory action; conversations with several linked memories expose a compact picker. Memory detail links back to the originating conversation when that session is present in the local archive and clearly marks unavailable origins otherwise. These links are provider-qualified, so matching identifiers from different providers are never joined.
Search the current version of those memories from the CLI or the Memories tab:
hansard memory search "deployment preference"
hansard memory search "project convention" --provider codex --repo github.com/example/project --jsonIn the Memories tab, use the rail search button or press / to search across
all current memory content and metadata. Cmd/Ctrl+F remains a separate,
in-memory finder for the currently selected item.
Memory search uses its own small FTS sidecar, so a changed memory does not rebuild the conversation index. It searches complete current text (up to the archive's 5 MB item limit) plus title, description, name, ID, origin session, provider, source, account, path, and scope metadata. Historical snapshots remain visible in memory detail but are intentionally excluded from search results.
For large multi-session stores such as Cursor SQLite, the per-session raw
manifest may reference one shared copy under raw-sources/ instead of copying
the same database into every session folder.
Web chat imports may also reference a shared raw export archive; ChatGPT
attachments remain preserved there and fresh imports render image/file cards in
the readable transcript when the export includes the file bytes.
Claude Code images/documents and Codex inline images are copied into the
content-addressed .attachments/ sidecar. Transcript and viewer JSON retain
compact attachment descriptors only, so inline base64 is not duplicated across
normalized files.
Installs that imported Claude Code or Codex sessions before attachment support must perform a clean provider reimport to recover those bytes from the original history:
hansard import --sources codex-cli,codex-desktop,codex-sdk,claude,claude-code-desktop,claude-cowork,claude-sdk --since allevents.jsonl uses the local agentlog.events.v7 canonical event shape:
session.started, prompt.submitted, response.generated, tool.called,
tool.completed, memory.read, memory.write, memory.loaded,
delegation.updated, and delegation.completed;
completed tool events link back to the matching call when the source exposes
stable ids or matching names. Tool calls and results may include provider diff
hunk metadata such as structuredPatch for numbered viewer diffs. Parser
versions are stamped by source type so importer output changes can trigger
reimport with a new fingerprint. Recall/search builds the interactive index
from canonical event text; legacy transcript/markdown scanning is an explicit
recovery path rather than a hidden fallback during normal queries. The normal
local search rebuild
writes a small JSON summary plus a SQLite FTS5 sidecar for fast web, terminal,
and MCP queries. The older full BM25 JSON index remains available only to
explicit compatibility callers, avoiding huge JSON serializations during
hansard update on large web-chat archives. Both index paths are keyed by the
session-list fingerprint so stale checks avoid statting every archived file.
When the index contract changes, run
hansard index rebuild to rederive search and common stats payloads from the
archive without a full source reimport.
When canonical diff metadata changes, run
hansard update --yes --since all --sources codex or
hansard import --source codex --since all for older Codex archives that need
fresh patch_apply_end structured hunks from raw source events; the bumped
view schema rederives .view.json and its windowed page cache on read.
The first open after a view-schema bump performs that work lazily; run
hansard rebuild --since all to prewarm every session instead.
Memory read/write/load events are also rendered as memory activity in the web
and Markdown views, so memory-file edits do not appear as ordinary source-code
diffs after the view payload is regenerated.
Stats are import-time metadata, not viewer-time transcript repair. Archive
metadata stores message counts, user-message counts, token usage, and models for
each session, and the web stats view reads those fields directly. Token totals
include cache-read/cache-write tokens when providers report them, while the
stats payload and UI also keep input, output, cache, and reasoning sub-counts
separately when available. New archive metadata also stores compact tool-usage
summaries and provider/export cost fields when present; the stats view uses
those fields for most-used tools, session-time summaries, and estimated spend
charts. Archive schema v6 also stores compact output-token work metadata
(text, toolUse, reasoning, unknown) and lightweight outcome counts
(editToolCalls, filesTouched, knowledgeCaptures, memoryReads,
memoryWrites, memoryLoads) at import time. Canonical events v5 include
memory.read, memory.write, and memory.loaded events for provider memory
files and instruction files such as AGENTS.md and CLAUDE.md. The web stats
view uses only those metadata fields for output job-mix charts and
tokens-per-meaningful-event ratios; it does not reread transcripts or events in
the request path. Existing archives need hansard update --yes --since all or
a clean reimport before these new charts have full coverage. Subagent child
sessions stay direct-addressable. Bare CLI/API stats exclude them unless asked,
while the web Stats page includes them by default so delegated work is visible;
its Subagents toggle supports parent-only comparisons. Spend is labeled
as an estimate unless a provider supplied an actual
cost field, and unpriced tokens stay visible instead of being folded into a
blended-rate guess. The model-pricing table lives in src/pricing.js with an
explicit pricing_version, and the stats payload exposes a 30-day usage capsule
with latest-day spend, 7-day spend, 30-day spend, tokens, prompts, sessions, and
top model. The web stats endpoint keeps the first payload light by shipping the
current chart window and rolling activity heatmap first; all-time daily series
are fetched only when the viewer asks for the all-time range or an older
activity year. The Usage page's browser-local Subscription value calculator lazily requests
the same all-time daily series and attributes usage to explicit source-level
harnesses, so Codex CLI and Desktop usage can be counted without silently
including Codex SDK jobs. Each period stores a start date, either an inclusive
fixed end date or an ongoing state, the exact total paid, and selected harnesses
in local browser storage. New periods are ongoing by default and evaluate
matching usage through the viewer's current local day; switching to a fixed end
date preserves inclusive bounds. API-equivalent value is
the selected usage priced at public API rates; net value is API-equivalent value
minus paid cost, value multiple is value divided by paid cost, and the ROI proxy
is net value divided by paid cost. Shared-harness date overlaps are rejected,
zero-cost periods leave ratio metrics undefined, and unpriced tokens make the
reported value a lower bound. The proxy does not estimate time saved or business
outcomes.
The Usage page has a Plan usage dashboard for local Codex and Claude
CLI accounts. Codex capture uses the official app-server account and rate-limit
reads, including every reported rolling quota window. Claude capture combines
claude auth status --json for account and plan metadata with a bounded,
best-effort claude /usage read for the quota windows reported by the installed
CLI. On macOS, a signed-out standalone Claude binary falls back to an installed
cmux Claude wrapper so captures match the account available in cmux terminals.
A failed or unsupported Claude usage read leaves the detected account and plan
intact. A capture updates the account currently signed in to each CLI and
keeps previously seen accounts, so switching accounts does not erase their last
status or utilization history. Hansard prefers provider account or user IDs,
then falls back to a lowercased email; usernames are not used. Claude membership
identity also includes the reported organization ID, which separates individual,
team, or enterprise memberships that use the same email. The raw identity is
replaced with a keyed, installation-local opaque fingerprint before persistence.
If a CLI exposes no identity, that provider uses one unresolved entry and
captures from different sign-ins may be combined. The 0600 fingerprint key and
normalized usage state are stored separately and should be preserved together
to retain account continuity. Raw emails, provider account identifiers, Claude
usage output, browser cookies, and tokens are never written to the usage history.
Plan usage can be captured manually or tracked every 15 minutes by the Hansard
supervisor. Interactive init selects continuous tracking by default when Codex
or Claude is selected; unattended setup requires --track-usage. Tracking can
also be changed with hansard config setup --track-usage,
hansard config setup --no-track-usage, or from the Usage page. Each reported
reset boundary creates a quota cycle, and Hansard retains the highest
utilization observed in that cycle. These captured peaks are lower bounds when
samples are missed;
unobserved cycles are never recorded as zero or presented as exact maxima. A
Claude capture that temporarily omits a previously reported window retains it
as last reported without advancing that window's observation history. A
detected catalog plan can be added to the Subscription value calculator as a
new ongoing period, but subscription start dates and exact charges remain
manual because the provider surfaces do not expose billing history. Claude
captures only labeled current-session and weekly quota sections; activity
statistics printed by /usage are discarded.
Codex imports preserve threads.tokens_used as the
provider total and split rollout token_count events into fresh input, cache
read, output, and reasoning metadata. Codex SDK and
Claude SDK batch jobs are kept out of primary activity totals, streaks, folder
rankings, and provider/model charts; the stats payload and web view expose them
as a separate SDK jobs section so high-volume automation does not drown out
interactive work. If SDK jobs are explicitly enabled, they re-enter resource,
provider/model, tool, and command accounting totals, but conversation metrics
still exclude them from conversation shape, user activity, session timing,
human-normalized ratios, and the Behavior page. The SDK-inclusive JSON payload
carries those batch-free values under behavior_stats. Cursor sessions
without provider-reported token usage can also carry separately labeled
estimatedUsage, which the stats view includes while reporting estimated token
coverage. Cursor agent-transcripts imports also preserve Composer model
configuration in session metadata, including the canonical model and reasoning
effort parameters. They infer working directories from explicit transcript
paths before falling back to Cursor's project-folder slug, which avoids stale
slug attribution when Cursor writes an agent transcript under the wrong project
directory; existing Cursor transcript archives need hansard update --yes
--since all or a clean Cursor reimport to gain that metadata and attribution.
Cursor transcript imports are the preferred v1 source when both
modern transcript files and legacy Cursor SQLite rows describe the same
Composer conversation, and a full Cursor reimport prunes those older duplicate
copies. ChatGPT and Claude.ai exports without provider usage get estimated
metadata.usage on their native chat messages, split into non-assistant input,
assistant output, and Claude thinking output where the export provides separate
parts. During pre-v1 development, if those stats fields or parser semantics
change, rebuild the local archive with
hansard update --yes --since all.
Classify
hansard classify labels archived sessions by work mode, outcome, and the
strength of success and failure evidence. Those four related judgments share one
structured model request per session. An optional task_value classifier adds a
separate estimated-value request. Classification is off by default; enable it
with hansard classify enable, then run hansard classify run --limit 25 to
process pending sessions.
The classifier builds a bounded evidence digest from genuine human turns, final assistant responses, canonical tool outcomes, and deterministic session telemetry. Stored results include evidence references, prompt and digest hashes, resolved-model and usage provenance where the backend supplies it, and explicit complete, partial, retry, or configuration-error state. Changes to the source session, digest contract, prompt, model, or temperature make the result pending again. A result counts as verified success only when a high model score cites digest evidence and the archive also contains a concrete successful command, edit, or code-change signal.
Provider/user session titles are excluded from the digest because archive titles are not guaranteed to have passed through transcript redaction. Loopback Ollama requests refuse HTTP redirects, and web consent is bound to the exact configured backend destination so a changed destination cannot reuse an earlier approval.
The default local-model backend keeps digests on this machine when its Ollama
endpoint is loopback. A non-loopback Ollama endpoint, claude-cli, codex-cli,
anthropic, and openai send the digest off-machine and require confirmation
before a run. CLI backends use the existing signed-in session and may leave the
model blank to use the provider default; local and API backends require a model
id. Hansard stores a separate model choice for each backend so switching
providers does not reuse an incompatible model name. The viewer presents the
five destinations as explicit grouped choices, keeps the four core judgments
fixed, and exposes task value as a separate opt-in.
ChatGPT and Claude.ai are manual export providers. Run hansard import chatgpt
or hansard import claude-web for current export instructions; after the
provider emails a download link, pass the official .zip, unzipped export
folder, or direct JSON file back to hansard. These imports are stored as local
scoped web-chat archives and displayed through virtual conversation roots such
as [chatgpt]conversations/<account-id> and
[claude]conversations/<account-id>/<project>. The importer records account
metadata in ~/.hansard/state/web-accounts.json; use
hansard import accounts list to inspect mappings and
hansard import accounts rename <provider> <account-id-or-username> --display-name <name>
to change the viewer display name.
For newer OpenAI privacy exports named OpenAI-export, unzip the download and
import the User Online Activity folder. Running hansard import chatgpt
without a path starts a walkthrough that asks for export paths one at a time,
then account username/email and display name. ChatGPT
conversations may be split across multiple
Conversations__...chatgpt...part-000N ZIPs or folders; passing the parent
folder is best, but the walkthrough can also collect the split part folders
individually and preserve chat.html, manifests, ZIPs, and attached files in
the shared raw export archive. Claude.ai exports preserve conversation summaries
and split structured
thinking parts from visible assistant answers when the export includes that
detail. Repeated manual uploads are incremental: unchanged conversations are
skipped, and updated conversations replace the stable session for that
provider/account/conversation id. Existing malformed pre-v1 web-chat archives
are not migrated automatically; reimport from the original export after a reset
or cleanup.
Tool calls and tool results are normalized before archive write where provider
data is available. For example, Devin tool calls live in
message.metadata.toolCalls[] and render as tool cards in the viewer without
being appended to assistant prose as synthetic Grep(...) text. Canonical
tool events also carry viewer-facing category, categoryLabel, icon,
inputPreview, and target fields so the web viewer can render Bash, edit,
read, search, web, task, skill, and MCP calls consistently across providers.
Injected context is normalized the same way: compound Codex and Claude records
become separate context/system cards plus the genuine user prompt, while Devin
hidden ACP resources are separated from text blocks. Codex multi-agent routing
envelopes become payload-only subagent-result cards, opaque encrypted delivery
stubs stay raw-only by default, and child transcripts start at their own task
boundary instead of replaying the forked parent history. Archives created before
this normalization need a full source reimport, for example
hansard import --sources codex-cli,codex-desktop,codex-sdk,claude,claude-sdk,devin-cli --since all.
Codex v2 subagent NEW_TASK assignments, MESSAGE follow-ups, and outbound
send_message/followup_task arguments are encrypted in the local rollout, so
Hansard cannot decrypt them locally. An explicit repair
command can ask the authenticated Codex Responses path to recover selected
collaboration messages:
hansard repair codex-subagent-prompts --dry-run
hansard repair codex-subagent-prompts --yes
hansard repair codex-subagent-prompts --session <id> --since all --limit all --yes
hansard repair codex-subagent-prompts --session <id> --model gpt-5.6-luna --effort low --yesThe full command is
hansard repair codex-subagent-prompts [--session <id>] [--since 30d|all] [--limit 25|all] [--batch-size 1..8] [--model <id>] [--effort <level>] [--force] [--dry-run] [--yes] [--json].
It groups selected messages from the same child and source model into bounded
batches (at most eight messages and 256 KiB of ciphertext), replays each batch
through one isolated, ephemeral Codex turn, requests verbatim model-mediated
extraction under fixed schema keys, and stores
the recovered plaintext locally in
~/.hansard/state/codex-subagent-prompts.json with mode 0600, and reimports
the affected Codex sessions. A batch is validated and saved atomically, cached
ciphertext is never replayed unless --force is explicit, and token usage is
reported when Codex emits it. Recovery defaults to the source session model at
low reasoning effort; --model can select a lighter compatible model for this
copy-only task. The first NEW_TASK remains the child boundary and
parent run preview; later tasks and messages reappear in transcript order,
outbound collaboration tool cards expose their recovered message argument, and
matching receiver-side copies render as provider-generated subagent updates
without inflating human prompt counts. This
is not local decryption: it sends archived
ciphertext to OpenAI and may incur model usage. The original rollout and its raw
Hansard backup remain unchanged.
That targeted reimport applies to the parser-level transcript normalization in
the preceding paragraph. It is not required solely for the relationship fields
below.
Archive schema v8 also gives linked child transcripts a canonical
parentSessionId and gives parent summaries a versioned subagentRuns list,
while retaining provider-specific details alongside it. Existing local archives
need hansard update --yes --since all before project disclosure and subagent
navigation have complete relationship metadata.
Factory worker sessions are now imported as factory_subagent; existing
Factory archives need hansard import --source factory --since all (or the
all-source update above) to gain the same stats, search, and viewer behavior.
Devin CLI imports also read tool_call_state and same-session subagent roots
from sessions.db; existing Devin CLI archives created before parser v2 should
be rebuilt with hansard import --source devin-cli --since all.
Use hansard reset to stop hansard, disable autostart, and remove hansard's
local home, config, state, cache, logs, and archive objects. Source application
histories and MCP configuration files are not changed. Add --keep-autostart
to leave the login item in place.
Use hansard update after installing a newer npm package to rebuild the local
archive with the new importer/parser logic without redoing setup:
npm install -g hansard@latest
hansard update --yeshansard update preserves config.json, redaction settings, web account
labels, manually imported ChatGPT/Claude.ai archives, local archives whose
original source files are no longer present, source histories, and recall
integrations. It removes derived local archive, import, index, cache, and sync
bookkeeping, then reimports configured local sources from the stored
preferences. If a source application has already aged out a transcript, such as
Claude Code's default local cleanup, the previous hansard archive is restored
instead of being silently dropped, with a source/provider/sourceType breakdown.
When Hansard has raw backups for missing Claude Code source files, repair them
explicitly before a clean reimport:
hansard repair claude-code-backups --dry-run
hansard repair claude-code-backups --yes
hansard update --yes --since all --sources claudehansard doctor reports preserved unavailable source archives and recommends
the same repair helper when applicable. The rebuild window comes from the initial
backfill or an explicit all-source import such as
hansard import --source all --since all; the fallback for legacy configs is
all. The watcher's rolling
imports.defaultSinceDays is not used by hansard update. It does not touch
remote sync objects by default; use hansard sync replace when the remote
should match the rebuilt local archive.
Use hansard config to change ~/.hansard/config.json without rerunning the
init wizard:
hansard config path
hansard config setup
hansard config setup --watch-sources codex-cli,cursor --default-since-days 90
hansard config setup --sync-interval-minutes manual --no-autostart
hansard config setup --track-usage
hansard config set imports.defaultSinceDays 90
hansard config set sync.intervalMinutes manual
hansard config sources edit
hansard config sources set codex-cli,cursor,cline
hansard config sources add gemini-cli
hansard config sources remove claude-coworkhansard config setup reopens the preferences parts of init, including what
the watcher polls, the rolling import window, autosync cadence, login startup,
and plan usage tracking. It does not run an import.
hansard config sources edit opens just the watcher source picker from init,
with the current config preselected.
Import Windows
hansard init starts with interactive setup: name this device, choose archive
destinations, choose the full local archive/cache path, choose whether the local
watcher starts at login and samples plan usage, and install recall commands or
skills, then discover sources and optionally backfill history.
After backfill, init asks which sources the background watcher should keep
polling, then offers local OTel bridges for Claude Code, Gemini CLI, and Cline.
Local archive storage is always enabled; R2, S3, and custom remote sync targets
can be added as upload-only optional destinations. The configured device name is
stored in config.json, stamped into new session metadata, and used for the
remote namespace. Discovery and import phases show progress bars while they scan
local stores.
After discovery, init offers a checkbox-style source picker. Rows marked [x]
are selected; type one or more row numbers, such as 1 3 8, to toggle sources
on or off, then press Enter with no input to accept the current selection.
Codex SDK jobs and Claude SDK jobs are shown as separate opt-in sources because
batch SDK traffic can exceed interactive sessions. The selected sources are
saved in config and used by later hansard import --source all runs unless
--sources is provided explicitly.
Default init sources:
- Codex CLI sessions and Codex Desktop sessions from Codex state, shown as
separate toggles, including linked Codex subagent child sessions when
thread_spawn_edgesmetadata is present; Codex SDK jobs are available as an opt-in batch source - Claude Code CLI transcripts from
~/.claude/projects, including subagent definition snapshots andsubagents/*.jsonlruns imported as child sessions - Claude Code Desktop metadata and Claude Cowork/local-agent sessions from the Claude app data, shown as separate toggles
- Gemini CLI saved chats/checkpoints under
~/.gemini/tmp, plus session/export JSONL stores with tool, usage, and checkpoint metadata - Antigravity transcript logs and task/plan/walkthrough artifacts under
~/.gemini/antigravity/brain, including linked subagent child sessions whenINVOKE_SUBAGENTsteps point at spawned conversations, plus partial trajectory summaries from Antigravity app state when no readable artifacts exist - Antigravity CLI sessions (the Gemini CLI successor) from
~/.gemini/antigravity-cli/braintranscript logs and artifacts, with workspace attribution from the CLI'shistory.jsonlprompt log, imported as a separate source from the 2.0 app - Antigravity IDE sessions from
~/.gemini/antigravity-ide/brain, imported as a third separate source; stale migration copies of 2.0 conversations are skipped - Devin for Terminal sessions from
~/.local/share/devin/cli/sessions.db, including tool-call state, visible thinking-duration traces, and same-session subagent roots imported as linked child sessions - Devin Desktop ACP event logs from
~/Library/Application Support/Devin/User/acp-events, indexed by Devin's globalstate.vscdb - Cursor chats from older workspace
state.vscdbSQLite stores and globalcursorDiskKVComposer/Agent rows, includingaiServiceprompt/generation fallbacks, raw SQLite salvage from Cursor global/workspace backups and WAL files, conservative matching of raw assistant/tool companion fragments back to same-project workspace sessions, duplicate prefix pruning, and newer~/.cursor/projects/<project>/agent-transcriptsfiles, including subagent child transcripts when Cursor writessubagents/folders
