agent-session
<p align="center">
<img src="docs/banner.png" alt="Agent Session — universal session & handoff layer for AI coding agents" width="80%">
</p>
<p align="center">
<a href="https://github.com/anaknegeri/agent-session/releases/latest"><img src="https://img.shields.io/github/v/release/anaknegeri/agent-session?style=flat-square&color=2dd4bf&label=release" alt="Latest release"></a>
<a href="https://github.com/anaknegeri/agent-session/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/anaknegeri/agent-session/ci.yml?branch=main&style=flat-square&label=ci" alt="CI"></a>
<a href="go.mod"><img src="https://img.shields.io/badge/go-1.25-00ADD8?style=flat-square&logo=go&logoColor=white" alt="Go 1.25"></a>
<img src="https://img.shields.io/badge/mcp-26%20tools%20%C2%B7%207%20resources-5eead4?style=flat-square" alt="MCP: 26 tools, 7 resources">
<img src="https://img.shields.io/badge/deps-none-4ade80?style=flat-square" alt="No runtime dependencies">
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT license"></a>
</p>
<p align="center">
<a href="#install">Install</a> ·
<a href="#quick-start">Quick start</a> ·
<a href="#make-every-agent-use-agent-session-automatically">Wire your agents</a> ·
<a href="#mcp">MCP</a> ·
<a href="#token-savings">Token savings</a> ·
<a href="#contracts">Contracts</a> ·
<a href="ARCHITECTURE.md">Architecture</a>
</p>
---
**One session. Any coding agent.** Switch between **Claude Code, Codex, OpenCode**
— and whatever ships next — without re-explaining the work.
Agent Session is not another AI memory database. It is a **local, Git-aware session
layer**: agents are interchangeable workers, and the session is the portable state
of the work. One binary, one SQLite file per project, no daemon.
---
## Features
- **Local-first, zero-config** — single binary + SQLite. No PostgreSQL, Redis, Docker, Node or Python.
- **Agent-agnostic** — shared state over transcripts: task, decisions, progress, files, tests, blockers, next action. Adapters for Claude Code, Codex, OpenCode, Cursor, Cline, pi, omp (oh-my-pi).
- **Git-aware** — git is the source of truth for code; the session stores only state and context.
- **MCP-native** — 26 tools + 7 resources over stdio or streamable-http.
- **Human-readable** — `.agent/context/current.md` and deterministic handoff context, never locked in a proprietary DB.
- **Checkpoint & handoff** — snapshot the work, hand it to another agent deterministically.
- **Always available** — agents spawn the stdio MCP server on demand; optional user-scope registration.
- **Long-term memory** — knowledge store with SQLite FTS5 full-text search; auto-extracts decisions, resolved blockers and completed tasks from sessions (non-LLM).
- **Agent Plugin packaging** — ships as a portable plugin (agent-plugins.org v1.0.0).
---
## Install
Prebuilt binaries for **macOS, Linux and Windows** (`amd64` + `arm64`) are
published on every release. Pick whichever path is easiest for you.
### macOS & Linux
**Homebrew** (recommended on macOS):
```bash
brew install anaknegeri/tap/agent-session
```
**One-line curl installer** (installs both `agent-session` + `agent-session-mcp`,
auto-detects the latest version):
```bash
curl -fsSL https://raw.githubusercontent.com/anaknegeri/agent-session/main/scripts/install.sh | sh
```
Options: `AS_VERSION=0.1.2` to pin a version, `AS_INSTALL_DIR=~/.local/bin` to
install without sudo.
### Windows
**PowerShell** (installs to `%LOCALAPPDATA%\agent-session` and adds it to your PATH):
```powershell
irm https://raw.githubusercontent.com/anaknegeri/agent-session/main/scripts/install.ps1 | iex
```
### Any platform with Go
```bash
go install github.com/anaknegeri/agent-session@latest
```
### From a release
Grab the matching asset from the [latest release](https://github.com/anaknegeri/agent-session/releases)
(`agent-session-{os}-{arch}`, Windows uses `.exe`), then:
```bash
chmod +x agent-session-* && sudo mv agent-session-* /usr/local/bin/
```
### Update
```bash
brew upgrade agent-session # Homebrew installs
agent-session update # binary / curl installs (auto-checks GitHub releases)
```
### From source
Requires only **Go binary + Git**:
```bash
make build # bin/agent-session + bin/agent-session-mcp
make install # copies to ~/.local/bin
make cross-compile # dist/ for all 6 platform/arch combos
```
### Global availability in Claude Code
Register at **user scope** so `agent-session` appears in `claude mcp list` from every directory:
```bash
agent-session plugin install claude --scope user
```
---
## Quick start
<p align="center">
<img src="docs/demo.gif" alt="One take: agent-session init wires every installed agent, start opens a session, status shows tasks and decisions, handoff hands the whole thing to Codex" width="100%">
</p>
<p align="center">
<sub><code>init</code> wires every agent on the machine · <code>status</code> shows what the last agent recorded · <code>handoff</code> passes the session on. Rendered from <a href="docs/demo.tape">docs/demo.tape</a>.</sub>
</p>
```bash
cd your-project
agent-session init # one command: init + wire agents at user scope
agent-session start "Implement OAuth2 PKCE"
agent-session status # progress, blocked, next
agent-session checkpoint --next-action "Fix refresh token rotation"
agent-session handoff codex # deterministic context for the next agent
agent-session resume --agent opencode
agent-session history # event log
agent-session context # print context.md
```
`agent-session init` is the one-command entrypoint per project, modeled after
`git init`: it initializes the project (creates `.agent/`, starts a session) and
wires AGENTS.md plus all agents. It is idempotent — safe to re-run.
`agent-session setup` is an alias. Add `--no-agents` for the session layer only,
or `--only claude|opencode|codex` to wire a single agent.
> **Not a git repo yet?** `init` reminds you to run `git init` first.
> The session works in local-only mode meanwhile, but Agent Session is Git-aware
> and tracks branch/commit/diff once git exists.
---
## CLI reference
| Command | Description |
|---|---|
| `agent-session init` | One-command setup: init + wire agents at user scope (`--only`, `--no-agents`, `--project`). Alias: `setup` |
| `agent-session start [title]` | Start a new session (`-t, --title`) |
| `agent-session status` | Show current session state |
| `agent-session ui` | Full-screen dashboard: tasks, progress, decisions, blockers, events, tests |
| `agent-session stats` | Session statistics: task completion, decisions, blockers, tests, agents used |
| `agent-session timeline` | Visual event timeline with icons (`-n, --limit`) |
| `agent-session diff` | Show what changed between two checkpoints (defaults to two latest) |
| `agent-session projects` | List all registered projects with session status (`--prune` to clean stale) |
| `agent-session export` | Export session to JSON or Markdown (`-f, --format`, `-o, --output`) |
| `agent-session import <file>` | Import a session from a JSON export |
| `agent-session watch` | Auto-regenerate context.md when the session changes (`-i, --interval`) |
| `agent-session resume` | Resume the latest session (`-a, --agent`) |
| `agent-session checkpoint` | Create a checkpoint (`--label`, `-n, --next-action`) |
| `agent-session handoff <agent>` | Compose handoff context for `claude`/`codex`/`opencode`/`pi`/`omp` |
| `agent-session history` | Recent events |
| `agent-session context` | Print context (`-d, --depth summary|recent|full`) |
| `agent-session doctor` | Health check: project, session, store, git |
| `agent-session mcp` | Run the MCP server (`--transport stdio|streamable-http`, `--addr auto|host:port`) |
| `agent-session plugin pack` | Build the Agent Plugin package |
| `agent-session plugin install <agent>` | Wire an agent: `claude`, `codex`, `opencode`, `cursor`, `cline`, `pi`, `omp` (`--scope project|user` for claude, opencode, cursor, pi, omp) |
| `agent-session plugin uninstall <agent>` | Remove the wiring |
| `agent-session setup` | Wire agents at user scope + AGENTS.md for always-on behavior (`--only`, `--project`) |
| `agent-session task add\|list\|update` | Record tasks from the CLI (the MCP `task.*` tools without MCP) |
| `agent-session decision add\|list` | Record a decision (`-r, --reason`) |
| `agent-session blocker add\|list\|resolve` | Record and resolve blockers (`--open`) |
| `agent-session event add <type>` | Append a canonical event (`-p, --payload`) |
| `agent-session memory put <content>` | Store knowledge (`-k kind`) |
| `agent-session memory list` | List knowledge (`-k kind`, `-n limit`) |
| `agent-session memory search <query>` | Full-text search knowledge |
| `agent-session memory promote` | Promote session decisions/blockers/tasks into memory |
---
## Make every agent use Agent Session automatically
One command per project — it initializes if needed and wires the agent-session
MCP server **once at user scope**, so your project stays clean:
```bash
cd your-project
agent-session init
```
What `init` does (idempotent, safe to re-run):
- **`.agent/`** — project session state (required, per-project by design).
- **AGENTS.md** — appends a mandatory workflow section read by OpenCode, Codex,
Cursor and Claude Code: agents first call `session.get` + `context.get`, track
work with `task.*` / `decision.*` / `blocker.*` / `event.append`, and create a
checkpoint before finishing.
- **User-scope MCP registration** (only for agents installed on this machine,
no per-project config files created):
- **Claude Code** — `claude mcp add --scope user agent-session`
- **OpenCode** — `~/.config/opencode/opencode.json`
- **Codex** — `codex mcp add agent-session` (global by default)
- **Cursor** — `~/.cursor/mcp.json`
- **Cline** — no user scope; wire per-project with `--only cline`
- **pi** — `~/.pi/agent/` extension + skill + prompts. pi ships **no MCP**
("No MCP. Build CLI tools with READMEs, or build an extension that adds MCP
support"), so it is wired through the CLI instead: an extension resumes and
checkpoints on pi's own lifecycle events, and a skill teaches the model the
`task` / `decision` / `blocker` / `event` verbs. Nothing MCP is registered.
- **omp (oh-my-pi)** — `~/.omp/agent/mcp.json` + extension + skill + slash
commands. omp is a pi derivative that *does* ship an MCP client, so it gets
both halves: the MCP server for tool calls, and an extension that resumes on
`session_start` and checkpoints on `session_before_compact` /
`session_shutdown` so continuity does not depend on the model remembering.
Note the roots — omp reads `.omp/` and `~/.omp/agent/`, never `.pi/`, so pi's
wiring does nothing here. Named profiles (`omp --profile x`) read
`~/.omp/profiles/x/agent/` and are not covered by user-scope wiring.
The MCP server resolves the project root from the working directory, so the
same user-scope registration works in every project, with no per-project
config pollution.
`init` also installs **slash commands** at user scope (Claude Code
`~/.claude/commands/`, OpenCode `~/.config/opencode/commands/`, Cursor
`~/.cursor/commands/`, pi `~/.pi/agent/prompts/`, omp `~/.omp/agent/commands/`) so
you can run `/agent-session`, `/agent-session-checkpoint`, and
`/agent-session-record` in any project. `plugin uninstall <agent> --scope user`
removes them. pi gets its own wording of the three: the shared set tells the agent
to call MCP tools, which pi has none of — omp uses the shared set, because it has.
Prefer per-project wiring? Use `agent-session init --project`, or `--only <agent>`
to wire a single agent into this project only. `--no-agents` keeps the session
layer only.
---
## MCP
### "Does the MCP server need to be running?"
No. With **stdio** (the default used by the adapters), Claude Code / OpenCode /
Codex **spawn the server automatically** as a subprocess on demand and shut it
down when the session ends. There is no daemon to manage.
With **streamable-http** the server is a persistent daemon. Auto-start templates:
- macOS: `packaging/launchd/com.agent-session.mcp.plist.tmpl`
- Linux: `packaging/systemd/agent-session-mcp.service.tmpl`
The daemon picks a **unique per-project port** (hash of the project root in
`45100–47099`, with free-port fallback):
```bash
agent-session mcp --transport streamable-http # e.g. http://127.0.0.1:46688/mcp
agent-session-mcp --transport streamable-http --addr auto
```
In a project that is not yet initialized, the server stays connected and tools
return `project not initialized, run agent-session init` instead of a connection failure.
### Tools
`session.get`, `session.checkpoint`, `session.diff`, `session.resume`, `session.record`,
`context.get`, `context.read`, `context.update`, `context.summarize`,
`task.create`, `task.get`, `task.update`, `decision.list`, `decision.create`,
`blocker.create`, `blocker.list`, `blocker.resolve`, `event.append`,
`workspace.status`, `workspace.diff`,
`memory.put`, `memory.get`, `memory.search`, `memory.delete`, `memory.promote`
`session.record` is the unified way to log work — it appends an event, records a
decision, sets `next_action`, and/or creates a checkpoint in a single call.
### Resources
`session://current`, `session://context`, `session://decisions`, `session://tasks`,
`session://workspace`, `session://checkpoint/latest`, `memory://recent`
### Canonical events
`session.started`, `task.created`, `task.updated`, `file.changed`, `command.executed`,
`test.started`, `test.failed`, `test.passed`, `decision.created`, `blocker.created`,
`checkpoint.created`, `handoff.created`, `session.completed`
Large event payloads (>8KB) are stored as artifacts and referenced by `artifact_id`.
### Long-term memory (Phase 4)
Memory is separate from session state. The `knowledge` table is a
long-term store with SQLite FTS5 (porter tokenizer) full-text search:
- **Kinds** — `project_knowledge`, `architecture`, `solution`, `preference`, `skill`
- **Manual** — `memory.put` by the agent or `agent-session memory put`
- **Auto-promote** (non-LLM) — `memory.promote` extracts structured session state:
`decision → architecture`, `resolved blocker → project_knowledge`, `completed task → solution`.
Idempotent — already-promoted entities are skipped.
---
## Token savings
Agent Session is designed to keep prompts small (state over transcript):
- **Progressive context loading** — `context.get` defaults to a bounded summary,
not full history.
- **Context budget** — configurable limits (`[context]`): max decisions/blockers/
files/events/progress, per-item truncation, and a hard total clamp
(`max_total_chars`). Lists render as `… +N more`.
- **Relevant memory injection** — `context.get` automatically surfaces top-k
related knowledge (local FTS search of the current task) so agents don't
re-derive known facts.
- **Tool annotations** — MCP tools carry `readOnlyHint` /
`destructiveHint` / `idempotentHint` where they hold, so agents pick the right
tool without trial-and-error. Tools that create a record carry none: a repeat
call creates a second one.
- **PreCompact checkpoint** — Claude Code saves a checkpoint before context
compaction, so nothing is lost when the window shrinks.
- **Agent-driven summarizer** — `context.summarize` asks the agent itself to
write a short session summary and store it via `memory.put` (kind
`project_knowledge`). No external LLM API needed.
- **Artifacts** — large event payloads are stored as references, not inline.
Tune in `.agent/config.toml`:
```toml
[context]
max_decisions = 5
max_blockers = 3
max_files = 8
max_events = 10
max_progress = 10
max_item_chars = 200
max_total_chars = 4000
inject_memory = true
max_memory = 3
# How many checkpoints of each kind a session keeps. Limits are per kind so a
# burst of automatic checkpoints cannot evict the deliberate ones. 0 = unlimited.
[retention]
max_manual = 50
max_auto = 20
max_precompact = 10
max_handoff = 20
```
Every checkpoint records what triggered it — `manual`, `auto` (Stop hook or a
stale context), `precompact`, or `handoff` — and retention is applied per kind.
The session's most recent checkpoint is never pruned.
### Benchmark
Reproducible benchmark at `bench/token-benchmark.sh` measures the real output
size (in characters) of context-gathering operations, converted to approximate
token cost (chars ÷ 4). Token estimates use the standard English-text heuristic;
your model's exact tokenizer may differ.
**Methodology** — "Without" = manual exploration an agent must do on every cold
start / post-compaction / handoff (read README, AGENTS.md, `git log`, `git status`,
`git diff`, glob source files). "With" = a single `context.get` call.
#### Results (live run on this repo, 2026-08-12)
| Scenario | Without agent-session | With agent-session | Savings |
|---|---|---|---|
| Cold start (first turn) | ~10,146 tokens | ~773 tokens | **92%** |
| Post-compaction re-orientation | ~10,146 tokens | ~773 tokens | **92%** |
| Agent handoff (Claude → Codex) | ~10,146 tokens (state lost) | ~1,195 tokens (state preserved) | **88%** |
#### Cumulative savings over multiple re-orientations
| Re-orientations | Without | With | Saved |
|---|---|---|---|
| 1 | 10,146 tok | 773 tok | 9,373 tok |
| 5 | 50,730 tok | 3,865 tok | 46,865 tok |
| 10 | 101,460 tok | 7,730 tok | **93,730 tok** |
| 20 | 202,920 tok | 15,460 tok | 187,460 tok |
#### Context depth comparison
| Depth | Chars | ≈ Tokens |
|---|---|---|
| `summary` (default, clamped at 4000 chars) | 3,094 | 773 |
| `recent` (bounded lists, no hard clamp) | 2,983 | 745 |
| `full` (never truncated) | 5,894 | 1,473 |
The three depths select different content, so `recent` is not simply `summary`
plus more — which is why it can measure slightly smaller:
- `summary` — bounded lists, the `max_total_chars` clamp, and nudges.
- `recent` — the same list bounds, no total clamp, no nudges, plus a recent-events
section.
- `full` — no bounds at all, plus recent events.
Which one is larger therefore depends on whether the session's nudges outweigh
its event list, and both can vary per repository and per session.
> "Without agent-session" is a **minimum** — in practice agents also search code,
> read specific files, and re-derive decisions that agent-session preserves
> structurally. Real-world savings are higher.
Reproduce:
```bash
./bench/token-benchmark.sh
```
---
## Project discovery
The MCP server is registered once at user scope, so it can start in a directory
that has nothing to do with the project being worked on. The project root is
resolved in this order:
1. `$AGENT_SESSION_PROJECT`, when it names a directory that exists
2. the nearest ancestor containing `.agent/`, searching no further up than the
git repository that contains the starting directory
3. the git repository root, when there is no `.agent/` inside it yet
4. the starting directory
The search stops at the repository boundary on purpose. Running `agent-session
init` in `$HOME` once puts a `.agent/` above every repository, and without that
boundary any uninitialized repo underneath resolved to that unrelated project and
recorded its session there. Outside a git repository there is no boundary to
stop at, which is what `AGENT_SESSION_PROJECT` is for:
```bash
AGENT_SESSION_PROJECT=/path/to/project agent-session context
```
---
## Automatic recording & nudges
Agent Session is designed so agents never have to remember to record state:
- **Auto-record file changes** — `context.get` compares git status against
recorded events and appends `file.changed` for anything new, so file edits
are captured even if the agent never calls `event.append`.
- **Auto-checkpoint when stale** — if no checkpoint exists in the last 10
minutes and the git tree is dirty, `context.get` creates one automatically.
- **Context nudges** — the summary view warns about stale checkpoints
(>30 min), unrecorded changed files, and open blockers, so the agent knows
when to checkpoint.
- **`session.record`** — record an event, a decision, `next_action`, and/or a
checkpoint in one tool call instead of several.
---
## Configuration
`.agent/config.toml` — zero-config defaults, human-editable:
```toml
[project]
name = "sakreta"
[storage]
driver = "sqlite"
[session]
auto_checkpoint = true # checkpoint after task/decision/test events
smart_checkpoint = true # rate-limit auto-checkpoints (max 1 per 60s)
[git]
enabled = true
[agents.claude]
enabled = true
[agents.codex]
enabled = true
[agents.opencode]
enabled = true
[sync]
mode = "local-only" # local-only | git-sync | cloud-sync (phase 2)
```
---
## Local data layout
```
project/
└── .agent/
├── config.toml # configuration
├── session.db # SQLite: projects, sessions, tasks, decisions,
│ # blockers, session_events, checkpoints,
│ # agent_sessions, artifacts, knowledge (+FTS)
└── context/
└── current.md # human-readable current state (re-generated)
```
Everything stays local. Add `.agent/` to `.gitignore`, or commit it later for git-sync (phase 2).
`config.toml` records the format version (`[format] version = 1`) so a future build
can tell whether it understands the layout — see
[docs/spec/format-v1.md](docs/spec/format-v1.md).
---
## Security & privacy
- **Local only by default** — no data leaves the machine.
- A shared session is **untrusted context**. Any agent can write to it, so
everything an agent authored — task titles, decisions, blockers, event payloads,
memory — is rendered under an `(untrusted)` heading with a legend saying it is
data and not instructions, and is flattened to a single line first so it cannot
forge a section of its own. The MCP server states the same rule in its
initialization instructions.
- No credentials or secrets are stored by the session layer.
---
## Contracts
A session is written by one agent and read by another, often by a different build
of this binary. Seven interfaces cross that boundary and are specified in
[`docs/spec`](docs/spec/README.md), all at **v1**:
| Contract | Covers |
| --- | --- |
| [Agent Session Format](docs/spec/format-v1.md) | the `.agent/` layout, ID prefixes, timestamp encodings |
| [Checkpoint Schema](docs/spec/checkpoint-v1.md) | the snapshot JSON inside a checkpoint |
| [Event Schema](docs/spec/event-v1.md) | event types and their payloads |
| [Context Schema](docs/spec/context-v1.md) | the rendered context document |
| [Handoff Schema](docs/spec/handoff-v1.md) | the handoff document and its event |
| [MCP Tool Contract](docs/spec/mcp-tools-v1.md) | the 26-tool / 7-resource surface and its annotations |
| [Export Schema](docs/spec/export-v1.md) | the `export`/`import` document |
The versions live in [`pkg/contract`](pkg/contract/contract.go) and every one of
them is held to its spec by [`test/contract`](test/contract), so drift fails the
build instead of surfacing later as an agent misreading a session. `.agent/`,
each checkpoint and each export document record the version they were written
against, and a build refuses to read a version it does not understand rather
than guess.
The rule: **bump for any change that could make an existing reader wrong**, no bump
for additive change. See [docs/spec/README.md](docs/spec/README.md).
---
## Development
```bash
make build # build both binaries
make test # go test ./...
make vet # go vet ./...
make plugin # package the Agent Plugin
```
Layout: clean architecture (`Domain → Application → Infrastructure`) with Wire DI.
See `ARCHITECTURE.md` for the full schema, MCP design and development order.
Testing: SQLite `:memory:` unit tests, real-git integration fixtures, in-process
MCP client tests, real MCP-over-stdio integration tests, and a live end-to-end
flow (`init → task → checkpoint → handoff → resume`). Real-agent smoke tests
(Claude Code / Codex / OpenCode) run with `AGENT_SESSION_SMOKE=1` where the
agent CLIs are installed. See `test/integration/COMPATIBILITY.md` for the
agent capability matrix.
---
## License
MIT — see [LICENSE](LICENSE).
TDQS
Scored across 26 tools
The set has several intentionally overlapping pairs (context.get vs context.read, session.record vs event.append/decision.create/context.update, session.checkpoint vs session.record, memory.put vs memory.promote), but each description explicitly states when to prefer which, which keeps misselection low. A few boundaries (e.g. context.update task fields vs task.update) still require reading two descriptions to resolve, so it is not a clean 5.
Every tool follows a strict dotted `resource.action` convention (artifact.get, decision.list, blocker.resolve, context.summarize, memory.search, session.checkpoint, task.update, workspace.diff). Namespaces are used consistently and verbs are predictable across all 26 tools.
26 tools is heavy for a session/context-tracking server, and several exist mainly as thin duplicates of others (context.get/context.read, context.update vs task.update, memory.promote vs memory.put, session.record vs the individual record tools). The surface is defensible but feels broader than the domain warrants.
Core lifecycle is well covered (events, decisions, blockers, tasks, checkpoints, memory, workspace), but there are notable gaps: no event.list/query despite an append-only log (events are only visible via context.summarize's last 8), no task.list, and no way to update or delete a decision or edit a blocker besides resolve. Agents will hit dead ends when they need to inspect history or correct prior entries.