Using tape
Tape archives coding-agent sessions into a local store the user owns, makes
them searchable, and can replay them into another agent. Run tape like any
shell command.
Output contract
- stdout is one JSON object
{"schema_version":1,"data":{...}}whenever it is piped (always the case for you). Parsedata, ignore unknown fields. - Errors arrive on stderr as JSON:
{"error":"<type>","message":"...","suggestion":"...","retryable":bool}. - Exit codes:
0ok ·1error ·2usage error ·3no results ·10dry-run passed. Branch on exit codes, not output text. Exit 3 still emits valid data withcount: 0— empty is not a failure. tape schema [command...]returns the full command/flag tree as JSON. Prefer it over--helpparsing.
Core workflows
Refresh the archive first (cheap and idempotent — checksums skip unchanged sessions):
tape sync # local agents
tape sync --remote user@host # also pull from an SSH machine
tape sync --full # re-archive everything (after a parser bump)
tape ls --host local # hide remote-mirrored sessions
tape ls --host user@host # only sessions from that remote
tape search "auth" --host user@host
Picker rows from remote hosts are tagged with a dim @host badge, and the
Resume action on a remote row execs ssh <host> -t 'cd <cwd> && <agent> --resume <id>' automatically — you land inside the conversation on the
right machine without copy-pasting anything.
Find past context (CJK queries fully supported):
tape search "jwt refactor" --limit 5
tape search "为什么不用 oauth2" --dir . --since 30d --agent claude-code
tape search codex # also matches by agent / title / project
tape search "memory leak" --sort relevance # classic BM25 ranking
Default sort is recent — newest session first, capped at 5 hits per
session so one long conversation can't push fresher matches off the
page. Use --sort relevance for classic BM25 ranking when mining old
archives. Each hit has session_id, snippet, title, project,
timestamp. Every session also has a synthetic @meta row in the index
so agent names, session titles and project names are searchable too.
Every session has a synthetic @meta row in the index, so agent names,
session titles and project names are searchable too — useful for "all my codex sessions about auth" style queries. Combine with --agent /
--dir for precise filtering. --dir <path> (use . for the current
directory) narrows by the session's working directory.
Paginate large result sets — both ls and search accept --limit N --page P (1-based) and return page, page_size, has_more, total
(ls only) in JSON. Iterate until has_more is false.
Read a session:
tape ls --dir . --since 7d # list; ids accept any unique fragment
tape show <session-id> # full session with messages
tape overview # archive-wide stats
Continue a session in another agent (the user hit a token limit, or wants to switch tools):
tape restore <session-id> --to codex --dry-run # preview plan, exit 10 = ok
tape restore <session-id> --to codex # returns resume_command
tape restore @last --to cursor --strategy brief --llm none # handoff file
Agents must always pass <session-id> and --to. Run with no args
only on a real TTY; in that case tape opens a numbered picker (session
→ target agent → strategy). Piped or --json invocations always demand
both args and exit 2 otherwise, so scripts stay deterministic.
Strategies, highest fidelity first:
| Strategy | What it does | Best for |
|---|---|---|
native |
Rewrites as a real session of the target agent; returns a resume_command. claude-code ↔ codex only. |
Same-agent-family resume |
memory |
Writes a full transcript and @-references it from the target's project memory file (CLAUDE.md for claude-code; AGENTS.md for codex/cursor/opencode/qoder/kimi-code; GEMINI.md for gemini/antigravity; MEMORY.md for mimocode; QWEN.md / IFLOW.md / CONVENTIONS.md for the rest) so the agent auto-loads it. |
Cross-agent resume that "just works" |
transcript |
Writes the full verbatim conversation as markdown; user/agent reads on demand. | When you don't want to touch memory files |
brief |
LLM-condensed handoff. --llm none falls back to a deterministic template. |
Token-constrained handoffs |
auto (default) picks native if the target supports it, otherwise
memory. For agents the recommended call is explicit, e.g.
tape restore @last --to cursor --strategy memory --json.
Export the archive (or a filtered slice) as one file — or split across several. Tape only generates the snapshot — uploading to S3/git/Drive is the caller's job, by design:
tape export # tape-export-<ts>.tar.zst in cwd
tape export snapshot.tar.gz --compress gzip # explicit name + codec
tape export --agent codex --since 7d # filter-scoped slice
tape export --exclude-agent cursor,opencode # everything except those two
tape export --exclude-host local # only remote-mirrored sessions
tape export --dir . --format zip --compress none # current project as a .zip
tape export --split-by agent # one file per agent
tape export --split-by month --since 1y # one file per calendar month
tape export --split-by size --split-size 200M # 200 MiB buckets
tape export --split-by agent --jobs 4 # 4 chunks at a time
tape export --scan-only # audit secrets, write nothing
--format tar|zip × --compress zstd|gzip|xz|none (zip implies its own
DEFLATE so --compress zstd etc. is a usage error). Default is tar+zstd.
For large archives use --split-by none|size|agent|month to fan out;
the chunk discriminator (agent name / YYYY-MM / part-001 …) is
inserted before the extension and every chunk is listed under parts[]
in the JSON envelope.
--jobs N controls parallelism for chunked exports: chunks run
concurrently (capped at min(N, chunk-count)) and the remaining
budget feeds each chunk's zstd encoder, so total threads stay ≈ N.
--jobs 0 (default) is GOMAXPROCS; --jobs 1 is the
reproducible single-thread baseline. JSON output adds
split.jobs + split.encoder_concurrency so agents can verify the
actual run shape.
Every filter flag has an --exclude-* counterpart on ls / search
/ export: --exclude-agent, --exclude-dir, --exclude-host.
Each is repeatable (--exclude-agent cc --exclude-agent oc) or
comma-separated (--exclude-agent cc,oc); the same agent-name
normalization (canonical + shorthand + did-you-mean) applies.
--exclude-host local is the natural "remote-only" idiom; positive
and negative on the same axis layer as AND-NOT
(--agent codex --exclude-dir /tmp).
Secrets in session.json are redacted in stream
([REDACTED:<rule>] for text, length-preserving masks for binaries);
pass --no-redact for verbatim bytes. --scan-only is exit 0 even when
findings exist — it's an audit mode, not a gate (the redactor is the
gate, and it always runs on real exports).
Keep the archive fresh — tape sync is idempotent and cheap, but
the user has to remember to run it. Three options, lowest to highest
effort:
tape sync # ad-hoc, on demand
tape sync --install --interval 1h # systemd timer / LaunchAgent / schtasks
tape sync --status # is one installed?
tape sync --uninstall # remove it
--install writes a user-scoped job — systemd user timer on Linux
(~/.config/systemd/user/tape-sync.timer), LaunchAgent on macOS
(~/Library/LaunchAgents/com.tapeai.sync.plist), or a printed
schtasks /Create command on Windows. The job runs as the user,
never root. Repeat --remote user@host flags are preserved in the
unit file so scheduled syncs cover remote machines too.
Versioning & updates — no background pings; checks are explicit:
tape version # version, commit, build date, install method
tape update --check # any newer release? exit non-zero if so
tape update # upgrade in place via the matching installer
tape update --channel beta --dry-run
tape update --mirror gitee # pin a release mirror (or TAPE_MIRROR=gitee)
tape update reads the install method from tape version and runs
the right thing: npm install -g @tapeai/tape@<tag> for npm,
go install github.com/chenhg5/tape/cmd/tape@<tag> for go install.
Homebrew / manual installs get the suggested command printed but
nothing is executed.
Release mirrors — every release lands on both
github.com/chenhg5/tape and gitee.com/cg33/tape. The CLI races a
HEAD probe at update time and uses whichever responds first, so
mainland-China users get the Gitee path automatically. Agents that
need deterministic behavior should pin with TAPE_MIRROR=github or
TAPE_MIRROR=gitee; the tape update --json payload reports
mirror so audit logs and bug reports can include it without an
extra round-trip.
Operations log — tape history reads ~/.tape/operations.log
(one JSONL line per sync / export / restore / update with scope,
counts, bytes, duration, exit_code). Useful for "what did the
agent's last sync archive?" without piecing it together from
stdout. Disable with TAPE_NO_OPLOG=1.
Persistent defaults — tape config is the place to store
preferences that would otherwise live in shell aliases:
tape config set defaults.exclude_agents cursor,opencode
tape config set defaults.jobs 4
tape config list --json
tape config unset defaults.jobs
CLI flags still win when explicitly passed; for slice values (exclude_*) configured defaults are merged with command-line flags. Agents that need deterministic behavior should pass every relevant flag explicitly and not rely on whatever config the human left behind.
Completion — tape completion {bash,zsh,fish,powershell} for
shell tab-completion (see the command's --help for per-shell install
locations). Re-run after upgrades.
Uninstall — tape uninstall [--dry-run|--force|--keep-archive]
removes the scheduler unit, the archive + index, the operations
log, and prints the install-method-specific command for the binary
itself (we never auto-delete /usr/local/bin/tape).
Verbose diagnostics — -v / --debug on any command sends
diagnostic lines (source detect paths, scheduler payloads, GitHub
URL, chunk plans) to stderr. Useful when "tape sync didn't pick
up iflow" surfaces — the debug line tells you exactly which path
was probed and what was returned.
Conventions
- Session ids look like
codex/019ea0af-...; any unique fragment resolves,@lastmeans the most recent session. --agent/--toaccept full names, the obvious aliases (claude/kimi/mimo), and a 2-letter shorthand:ccclaude-code,cxcodex,cucursor,ocopencode,gmgemini,agantigravity,qwqwen,qdqoder,ififlow,adaider,mimimocode,kckimi-code. Case and separators are ignored (Claude_Codeworks); unknown names produce a usage error with a "did you mean…" hint. Agents should still pass the canonical spelling — the shorthand exists for humans typing in a terminal.--dry-runfirst for anything that writes (restore, export).- All timestamps are RFC 3339;
--sinceaccepts24h,7d,2026-01-31. - The archive lives in
$TAPE_HOME(default~/.tape; legacy$TAPE_DIRis still honored). There is no--dirfor tape's storage location on purpose:--dironls/search/restorefilters by the project directory, not tape's data dir. - Subcommand names accept any unambiguous prefix (
tape sy→ sync,tape sho→ show,tape re→ restore). For agents, always spell out the full name to stay forward-compatible if new commands are added.