One inventory, one set of verbs, for the coding-agent sessions scattered across
your machine. asm reads the on-disk stores that Claude Code, OpenCode,
jcode, Codex and Antigravity keep for themselves, presents
every session in one list, and lets
you rename, move, archive, delete, export — and carry a conversation from one
agent into the other.
$ asm
AGENT ID TITLE PROJECT UPDATED STATUS
claude-code fb779332 Build cross-agent session manager system ~/projects/rust/asm just now live
claude-code 20011bb2 fsl-phase-1-compiler-runtime ~/projects/rust/fdl 6h ago idle
opencode ses_32d1 OpenRPC monorepo tooling plan ~/projects/node/openrpc 2026-07-11 idle
Three frontends over one core: a CLI, a terminal UI (asm tui), and a local web
UI (asm serve). There is a tour of it here.
Agents are good at keeping their own history and bad at everything around it.
Sessions pile up in per-agent stores with per-agent identity schemes; there is no
cross-agent list, no way to retitle a session you can no longer identify, no way
to move one after you renamed the project directory, and no way to continue a
conversation in a different agent. asm is that layer.
| Milestone | What | State |
|---|---|---|
| M0 | Core model + Claude Code read adapter, list/show/projects |
done |
| M1 | OpenCode adapter, management verbs, doctor, worktrees, Session IR + export |
done |
| M2 | Cross-agent import (both directions, verified live) |
done |
| M3 | Terminal UI | done |
| M4 | Local web UI | done |
| M5 | Full-text search, sync groundwork, docs | done |
Verified against Claude Code 2.1.234 and OpenCode 1.17.18 on Linux — "verified" meaning a real session was imported and then resumed in the target agent's own CLI with its conversation intact.
jcode 0.78.0 is supported too, with two exceptions noted in
Agent support.
asm doctor warns when your installed versions have drifted from those.
curl -fsSL https://raw.githubusercontent.com/samishal1998/agent-sessions-manager/main/install.sh | shThat downloads the binary for your platform from the latest release, checks it
against the release's SHA256SUMS, and installs it to ~/.local/bin. A failed
download or a checksum mismatch stops before anything is written, so an
existing install is never left broken.
Once installed, asm update does the same thing without the pipeline: it
checks the release, verifies the download against SHA256SUMS, and replaces
the running binary in place. A bad download leaves the working one alone, and
it refuses rather than installing something it could not verify. It needs curl
or wget, the same as the installer.
| Variable | |
|---|---|
ASM_VERSION |
install a specific tag instead of the latest |
ASM_INSTALL_DIR |
where the binary goes (default ~/.local/bin) |
ASM_BASE_URL |
fetch assets from a mirror or a local directory instead |
GITHUB_TOKEN |
only needed if the repository is ever made private again |
Releases are cut by tagging: git tag v0.1.0 && git push origin v0.1.0 builds
Linux and macOS binaries for x86_64 and arm64, publishes them with checksums,
and is what install.sh reads. The workflow can also be dispatched without a
tag to dry-run the build.
# The web UI's assets are embedded at compile time, so build them first.
cd crates/asm-web/frontend && bun install && bun run build && cd -
cargo build --releaseRequires a recent stable Rust (developed on 1.94) and, for the web UI only, Node or Bun. The binary is self-contained: SQLite is bundled, and the frontend is embedded.
asm # interactive TUI on a terminal; plain table when piped
asm list --agent opencode # filter by agent, --project, --all (include subagents)
asm projects --worktrees # projects are repositories; show each one's checkouts
asm show 4c93a826 # metadata card; refs are unique id prefixes or agent:prefix
asm resume 4c93a826 # hands off to the native agent, in the right directory
asm rename 4c93a826 "New title"
asm move 4c93a826 ~/projects/renamed-dir
asm archive 4c93a826 # Claude: moved into asm's archive; OpenCode: native flag
asm unarchive 4c93a826
asm delete 4c93a826 # backs everything up first
asm import 4c93a826 --to opencode # the flagship
asm import 4c93a826 --to opencode --dry-run --mode seed
asm export 4c93a826 -o session.ir.json # versioned, agent-neutral JSON
asm search "path encoder" # full-text across every transcript, every agent
asm search --agent opencode "jsonrpc"
asm index # refresh the index and report on it
asm update # replace this binary with the latest release
asm update --check # just say whether there is a newer one
asm doctor # store health, duplicate ids, stale locks
asm worktrees # git worktrees of a repo, with the sessions in each
asm sync init && asm sync status
asm serve # web UI on http://127.0.0.1:7433
asm serve --port 8080
asm serve --host 0.0.0.0 # every interface — read the warning it printsEvery command takes --json.
asm serve gives the same verbs a mouse: sessions as cards with the agent shown
as an icon (its name is one hover away), per-row actions, multi-select filtering
by agent, project filters in the sidebar, full-text search with highlighted
snippets, and a transcript panel that renders text, reasoning, tool calls and
expandable tool output. Tick several sessions and the bar above the list
archives, imports, moves, exports or deletes the whole set at once, reporting
per session what did not work. It is responsive down to a phone, where the sidebar
becomes an overlay and the transcript takes the full screen.
To see it without a browser — or to re-check a change — bun run shots in
crates/asm-web/frontend screenshots the running UI at three widths and fails
on any console error (needs bunx playwright install chromium once).
Every per-session verb is a keystroke, so the terminal UI is not a read-only view of the CLI:
| Key | |
|---|---|
⏎ |
resume in the native agent (the TUI steps aside and comes back) |
r a d |
rename · archive/unarchive · delete (confirmed, backed up) |
m i e |
move to another project · import into the other agent · export IR |
␣ * |
tick this session · tick everything the filter shows |
s / |
full-text search across transcripts · filter the list |
D |
store health (the same report as asm doctor) |
⇥ R q |
focus the transcript · rescan · quit |
With anything ticked, a d m e i run over the whole selection instead
of the row under the cursor; with nothing ticked they behave as before. A batch
attempts every session, so one failure cannot strand the rest, and what did not
work is listed per session afterwards.
Bulk unarchive only reaches OpenCode sessions, because only they stay listed
once archived — Claude and jcode sessions leave their store for asm's archive
and are restored by reference with asm unarchive <id>. That is the same
division the single-session verb has always had.
asm import converts through a documented intermediate representation (see
docs/ir-schema.md) and writes a native session in the
target agent, so the target's own picker lists it and its own resume works:
$ asm import 36405fad --to opencode
Imported as opencode:ses_7bdfed0f167b6c507797045ba6.
Loss report:
2 of 3 messages converted
1 opaque reasoning blocks dropped (provider-bound; summaries only)
conversation re-attributed to openai/gpt-5.6-sol (the target install's last-used model)
Resume with: opencode -s ses_7bdfed0f167b6c507797045ba6 (run in the project dir)
Two modes:
--mode full(default) translates the transcript into the target's native records. Highest fidelity, and the most exposed to the target's format changing under it.--mode seeddistills the session into a narrative handoff document that becomes the first message of a fresh session. Lower fidelity, essentially immune to format churn.
Imports are idempotent: target ids are derived deterministically from the
source session, so re-importing reports In sync instead of creating a
duplicate.
Some things cannot cross and asm says so rather than pretending:
provider-signed reasoning blocks, tools the target does not have (the names are
kept verbatim so the history still reads), and nested subagent transcripts.
| Claude Code | OpenCode | jcode | Codex | Antigravity | |
|---|---|---|---|---|---|
| List, show, search, projects | yes | yes | yes | yes | yes |
| Liveness | yes | yes | yes | no | no |
| Resume | yes | yes | yes | yes | yes |
| Export to Session IR | yes | yes | yes | yes | yes |
| Rename | yes | yes | yes | no | no |
| Archive / unarchive | yes | yes | yes | no | no |
| Delete | yes | yes | yes | no | no |
| Move to another directory | yes | yes | no | no | no |
| Import from (source) | yes | yes | yes | yes | yes |
| Import into (target) | yes | yes | no | no | no |
| Send a message into a session | yes | yes | no | yes | yes |
| Verified against a real install | 2.1.234 | 1.17.18 | 0.78.0 | 0.148.0 | 1.1.16 |
asm send <ref> <message> sends a message into an existing session and
streams the reply; the TUI binds it to c and the web transcript has a
composer at the bottom. It goes through each agent's own headless resume —
claude --resume … -p, opencode run -s, codex exec resume — so the turn
is the agent's, with the agent's tools, permissions and model.
Three things worth knowing before you use it:
- It appends to the session, it does not fork. Verified against each agent: same native id, same transcript, no new row.
- The agent can edit files in that project. This is a real turn, not a read-only query, and it spends tokens.
- Live sessions are refused. If another terminal is driving the session right now, asm will not send into it — none of these CLIs promise to handle two writers.
jcode is missing here for a different reason than below: its stream format has not been captured from a real run, and a normalizer written from a flag name is a guess, not support.
Codex is read-only. Its metadata lives in state_5.sqlite, a schema 48 sqlx
migrations deep and still gaining columns, and codex ships no rename,
archive or import command to write through instead — so every mutation
would be a raw write to a moving target that its own picker might then
disagree with. Reading is safe and complete: the threads table drives the
listing, and asm additionally sweeps the sessions/ tree for rollouts that
have no threads row, which codex hides until you resume them by id.
Antigravity is what Gemini CLI became — Google now refuses Gemini Code
Assist for individuals outright, telling you to migrate — and its store is
different in kind: one SQLite database per conversation, with every step
held as a protobuf blob against a schema Google does not publish. asm reads
the conversation instead from the JSONL rendering antigravity writes
alongside it, under brain/<id>/.system_generated/logs/. Same steps, same
author, no guessed field numbers.
One real gap: an Antigravity conversation does not record its project
directory. Neither the conversation database nor the summaries index keeps
one (workspace_uris is empty), and the only mapping on disk,
cache/last_conversations.json, remembers just the most recent conversation
per directory. Sessions it does not cover are listed with no project rather
than a guessed one.
Two jcode verbs are missing, for the same reason: its whole session — metadata
and the entire conversation — is one JSON document, and jcode ships no
command for either job. Moving a session means editing working_dir inside
that document, and importing means writing a whole one. Rewriting another
tool's file to change one field, with no sanctioned path and no way to check
the result, is not a trade this project makes. Everything else goes through
jcode's own session rename or moves whole files without touching their
contents.
asm doctor --json reports each agent's capabilities, and the web UI greys out
what an agent cannot do, so the limits are visible rather than discovered by
error.
A project is a git repository, not a directory. Every worktree of a
repository is the same project, and so is a session started in a subdirectory
of one — an agent whose working directory wandered into crates/foo has not
started working on a different codebase. Sessions from different agents in the
same repository share one project too.
Identity comes from git rev-parse --git-common-dir, which every worktree of a
repository agrees on. Directories outside any repository stand alone. Worktrees
with no sessions are still listed, so an idle checkout is visible rather than
missing.
Agents embed XML-ish envelopes in message text — injected context, slash-command echoes, background-task notifications, subagent results, tool errors. The web UI parses the ones it knows into labelled, collapsible blocks, and renders anything else as a nested tree rather than a wall of angle brackets.
Detection is deliberately narrow, because transcripts are mostly tool output and
tool output is mostly code: a scan of the transcripts on this machine found 400
distinct "tags", nearly all of them generics like Vec<T> and Option<String>.
So a tag counts only if it is known or lowercase-with-a-separator, starts a line,
and has a matching close tag. Everything else stays plain text, and nothing is
ever dropped.
asm search runs SQLite FTS5 over every message of every session, in an index
kept in asm's own data directory — the agents' stores are never written to.
The index is incremental: each session carries an opaque content fingerprint,
and only sessions whose fingerprint moved are re-extracted. On this machine, 30
sessions index in about 5 seconds cold and refresh in ~0.15s warm, so
asm search refreshes by default; pass --no-refresh to skip it.
Fingerprints are per-agent because the naive choice is wrong for OpenCode: its
session.time_updated lags behind its own message rows, so keying on it would
silently lose streamed tool output. File-backed sessions key on size and mtime;
row-backed ones on the message table's own count and high-water mark.
Subagent transcripts are indexed, which matters more than it sounds — in delegating sessions they are the majority of the searchable text, so indexing only the parent conversation hides most of the corpus.
The index is disposable: anything unreadable, or written by a different schema
version, is rebuilt rather than migrated. Tool inputs are indexed in full
(commands, paths, patterns); tool outputs only in part, since they dominate
transcript bulk. asm index also reclaims space after re-extraction (FTS5's
optimize restructures but does not return pages to the filesystem; VACUUM
does).
s opens the same search in the TUI, and the web UI has a transcript search box
next to its filter.
Archived sessions stay searchable even though they have left their agent's
store and no longer appear in asm list; results mark them (archived) so it
is clear they need restoring before they can be resumed.
- Deleting a session's rows from the FTS table is a scan of that table, so a full rebuild is linear in sessions × messages. At personal scale (seconds) this is fine; it would need a rowid map to scale further.
- OpenCode staleness is judged by the session's
time_updated. If OpenCode ever writes a message without bumping it, that session would look unchanged.
This tool writes into stores owned by other programs, so the rules are strict:
- Never touch a live session. Mutating a session whose agent is running is refused outright.
- Never rewrite transcript bytes. Claude Code's background jobs hold raw
byte offsets into transcript files;
asmonly appends or renames whole files. - Never duplicate a session id. Claude Code's cross-project
--resumehard-fails when an id exists in two project directories, soasmmoves rather than copies and refuses an import that would collide.asm doctorreports pre-existing duplicates. - Back up before destroying.
asm deletecopies every affected path into~/.local/share/asm/backups/<agent>/<id>/<timestamp>/first. - Never write into a busy store. OpenCode mutations are refused while an OpenCode instance holds its lock directory.
- Only ever write through sanctioned paths where they exist — imports into
OpenCode go through
opencode import, not raw SQL.
The web UI has no authentication and is a personal dashboard, not a service.
It binds 127.0.0.1 by default. --host accepts any IP or hostname, and
0.0.0.0 / :: bind every interface — but anyone who can reach that address
can read every conversation and rename, move, archive, import, or delete
sessions, so serve prints a warning whenever it binds outside loopback. On a
machine with a public IP, "every interface" means the internet, not just your
LAN. Mutating endpoints do reject cross-origin browser requests, but that only
stops other web pages; it does not stop a direct request.
crates/asm-core domain model, agent adapters, Session IR, import engine (no UI deps)
crates/asm-cli clap frontend
crates/asm-tui ratatui terminal UI
crates/asm-web axum API + embedded Vue frontend
crates/asm the single `asm` binary
Architecture and the per-agent format details worth knowing before touching an adapter are in docs/agent-formats.md.
Only inside its own directory ($XDG_DATA_HOME/asm, override with ASM_DATA_DIR):
archive/<agent>/<id>/ archived sessions (manifest.json + native/)
backups/<agent>/<id>/ pre-delete backups, timestamped
index/sessions.db the search index (derived; safe to delete)
asm sync init turns archive/ into a git repository so archived sessions can
be versioned and pushed to a remote of your choosing. asm does not manage the
transport — sync status prints the git command to run.
MIT or Apache-2.0, at your option — LICENSE-MIT, LICENSE-APACHE.