> ## Documentation Index
> Fetch the complete documentation index at: https://docs.asymptotelabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# beacon handoff

> Pick up a local agent session again, in the same runtime or another one

## Command overview

`beacon handoff` finds the agent sessions stored on this machine so you can pick one up again.

```bash title="Command syntax" theme={null}
beacon handoff list [flags]
beacon handoff export <session-id> [flags]
beacon handoff resume <session-id> [flags]
```

Sessions come from each runtime's own local session store, not from Beacon's runtime log. That store is what the runtime resumes from, and it keeps content the runtime log truncates. Every read is local and read-only: nothing is uploaded, and no runtime file is changed.

## Supported runtimes

| Runtime | Session store | `--harness` |
| - | - | - |
| Claude Code | `~/.claude/projects` (`$CLAUDE_CONFIG_DIR/projects` when set) | `claude` |
| Codex CLI | `$CODEX_HOME/sessions` when set; otherwise `~/.codex/sessions` | `codex` |
| OpenCode | `~/.local/share/opencode` (or `$XDG_DATA_HOME/opencode`, and `OPENCODE_DB` when set; SQLite and the legacy file store) | `opencode` |
| Cline | `~/.cline/data` | `cline` |
| Pi | `~/.pi/agent/sessions` (or under `PI_CODING_AGENT_DIR`) | `pi` |
| Prime Agent | `~/.prime/agent`, both `sessions` and `session-artifacts` (or `PRIME_AGENT_CODING_AGENT_DIR`) | `prime` |
| Factory Droid | `~/.factory/sessions` | `droid` |
| GitHub Copilot CLI | `$COPILOT_HOME/session-state` when set; otherwise `~/.copilot/session-state` | `copilot` |
| Hermes Agent | `state.db` in the [Hermes home](https://docs.asymptotelabs.ai/runtimes/hermes-agent.md#hermes-home-and-profiles): `$HERMES_HOME` or the active profile when set; otherwise `~/.hermes/state.db` (Windows: `%LOCALAPPDATA%/hermes/state.db`) | `hermes` |
| DeepSeek Harness | `~/.dsh/sessions` (or under `DSH_HOME`) | `dsh` |
| fx | `~/.fx/sessions` | `fx` |
| Grok Build | `~/.grok/sessions` | `grok` |
| OpenClaw Gateway | `~/.openclaw/agents/<agent>/sessions` for every agent (or under `OPENCLAW_STATE_DIR`) | `openclaw` |
| Cursor | `~/.cursor/projects` (agent transcripts) and Cursor's global storage `state.vscdb` (Composer conversations) | `cursor` |

When a Codex thread has more than one rollout file, `beacon handoff` lists it once, under its newest file. Cline can hold the same task both as a CLI session and in its older task history; the CLI session is the copy that is listed. Cline kanban cards are not sessions and are not listed. Prime Agent subagent transcripts, under `session-artifacts`, are listed as subagents of the session that started them. Hermes Agent delegate subagents are listed as subagents of the session that spawned them. When Hermes compresses a long conversation it continues it in a new session; each of those sessions is listed, and reopening any of them reopens the latest. A DeepSeek Harness session that DSH spawned for delegated work is listed as a subagent of the session that started it; a fork of a session is listed as a session of its own. Grok Build keeps its subagents' child sessions in the same tree as other sessions; those whose `summary.json` has a subagent `session_kind` are listed as subagents. OpenClaw transcript ids are unique only within one agent. When two agents hold the same id, both are listed, and `export` and `resume` report the id as ambiguous rather than pick one. A Cursor conversation can be stored both as a transcript and as a Composer record under one id; it is listed once, as the transcript when `cursor-agent` can reopen it and as the Composer record otherwise. Cursor subagent transcripts are listed as subagents of the conversation that started them.

OpenClaw Gateway sessions are read from the JSONL transcripts and the `sessions.json` index in each agent's `sessions` directory, as `beacon endpoint openclaw sessions sync` reads them. Recent OpenClaw releases keep live sessions in a per-agent SQLite database and treat that directory as legacy, so sessions that exist only in the database are not listed.

`--store-dir <runtime>=<path>` takes the location in the table: for Pi its sessions directory, as `beacon endpoint pi sync --sessions-dir` does, and for Prime Agent the agent directory holding both of its roots. For Factory Droid it is the sessions directory, as `beacon endpoint factory sync --sessions-dir` does. For GitHub Copilot CLI it is the `~/.copilot` directory that holds `session-state`, as `beacon endpoint copilot sync --copilot-dir` does. For Hermes Agent it is the `state.db` file, as `beacon endpoint hermes sync --db` does. For DeepSeek Harness it is its `DSH_HOME`, as `beacon endpoint dsh sync --dsh-home` does. For fx it is its sessions directory, as `beacon endpoint fx sync --sessions-dir` does. For Grok Build it is its sessions directory, as `beacon endpoint grok sync --sessions-dir` does. Like that sync, `beacon handoff` does not read `GROK_HOME`; pass `--store-dir grok=$GROK_HOME/sessions` when it is set. For OpenClaw Gateway it is its state directory, as `beacon endpoint openclaw sessions sync --openclaw-dir` does. For Cursor it is the projects directory, as `beacon endpoint cursor sync --projects-dir` does. Cursor's global storage database is always read from its default location.

fx records no session title, so an fx session is listed under its first prompt, and its directory is the workspace fx last bound it to.

### Runtimes Beacon starts but does not read

Beacon keeps no reader for these runtimes' own session stores, so `list` does not show their sessions. `export` and `resume` still find one by id in Beacon's runtime log, and they can be the runtime another session continues in (`--agent`):

| Runtime | `--agent` |
| - | - |
| Oh My Pi | `omp` |
| Gemini CLI | `gemini` |
| Qwen Code | `qwen` |
| Kiro | `kiro` |
| Antigravity CLI | `agy` |
| Devin CLI | `devin` |
| Muse Code | `muse` |
| OpenHands | `openhands` |
| goose | `goose` |

Kimi Code can only take a prompt non-interactively, and Senpi (`omo`) moves Pi's agent directory into its own the first time it runs, so Beacon does not start them.

## `beacon handoff list`

Lists resumable sessions, newest first.

```bash theme={null}
beacon handoff list
beacon handoff list --here            # sessions that ran in this directory or below it
beacon handoff list --harness codex   # one runtime
beacon handoff list --json
```

| Flag | Description |
| - | - |
| `--harness` | Only this runtime: `claude`, `codex`, `opencode`, `cline`, `pi`, `prime`, `droid`, `copilot`, `hermes`, `dsh`, `fx`, `grok`, `openclaw` or `cursor`. |
| `--here` | Only sessions that ran in the current directory or below it. |
| `--dir` | Only sessions that ran in this directory or below it. |
| `--subagents` | Include subagent sessions. They are hidden by default, because resuming one resumes a fragment of its parent's task. |
| `--limit` | Maximum sessions to list. Default `25`; `0` lists all. |
| `--json` | Print `{"sessions": [...], "warnings": [...]}`. |
| `--store-dir <runtime>=<path>` | Read a runtime's store from a non-default location. Repeatable. |
| `--claude-projects-dir`, `--codex-dir`, `--opencode-dir`, `--cline-dir` | The same for one runtime; each wins over `--store-dir` for its runtime. |

A store that cannot be read is reported as a warning (on stderr, or under `warnings` in JSON), and the sessions from the other runtimes are still listed.

## `beacon handoff export`

Writes a handoff brief for one session: a Markdown summary of what it asked for, what it changed, and where it stopped, for another agent or person to continue from.

```bash theme={null}
beacon handoff export 7f3c2a91              # writes ~/.beacon/endpoint/handoffs/<runtime>-<id>-<time>.md
beacon handoff export 7f3c2a91 --print      # write to stdout instead
```

The session id can be a unique prefix of at least six characters. Pass `--harness` when two runtimes use the same id.

A brief has these sections:

* **Source session**: runtime, session id, title, directory, branch, last activity, session file, and activity counts.
* **Where it stood**: the first request, the latest request, and the last agent message.
* **Files changed** and **Recent commands**, with exit codes where the runtime reports them.
* **Recent activity**: the last 30 steps (prompts, agent messages, commands, file changes, tool failures, MCP calls), each cut to 1,200 characters.
* **Before continuing**: check the working tree, and confirm the task before changing anything.

A brief is a summary, not the full transcript. Every event goes through the same secret redaction and truncation as the runtime log before any of it is used. Agent reasoning and file reads are left out. A brief is at most 48 KiB; older steps are dropped first to stay under that. Content is placed in code fences longer than any run of backticks inside it, so quoted text can't close its block and pass itself off as part of the brief.

When a session is no longer in its runtime's store (for example, a deleted transcript), `export` reads it from Beacon's runtime log instead. This works for any runtime Beacon captured, but the log keeps less of each step, and the brief says so.

Briefs are written to `~/.beacon/endpoint/handoffs`. The directory is `0700` and each file is `0600`, because a brief carries prompt text and command output. An existing brief is never overwritten.

| Flag | Description |
| - | - |
| `--print` | Write the brief to stdout instead of a file. |
| `--output-dir` | Directory to write the brief into. |
| `--log-path` | Runtime log to fall back to. Defaults to the local runtime log. |
| `--json` | Print `{"path", "from", "session"}`. `from` is `session_store` or `runtime_log`. |
| `--harness`, `--store-dir` and the `--*-dir` flags | As for `list`. |

## `beacon handoff resume`

Picks a session up again, in its own runtime or another one.

```bash theme={null}
beacon handoff resume 7f3c2a91                                    # reopen it where it ran
beacon handoff resume 7f3c2a91 --agent codex                      # continue a Claude Code session in Codex
beacon handoff resume 7f3c2a91 --new                              # a fresh session in the same runtime, from a brief
beacon handoff resume 7f3c2a91 --agent codex --launch auto --yes  # detached handoff from an agent harness
beacon handoff resume 7f3c2a91 --print                            # show what would run; writes and launches nothing
```

There are two ways to resume:

* **Reopen natively.** Beacon reopens the session with its full context when all of these hold: it continues in the same runtime, that runtime's CLI can load this kind of session, and the session file is still on this machine.

  | Runtime | Command |
  | - | - |
  | Claude Code | `claude --resume <id>` |
  | Codex CLI | `codex resume <id>` |
  | OpenCode | `opencode --session <id>` |
  | Cline | `cline --tui --auto-approve false --id <id>` |
  | Pi | `pi --session <session file>` |
  | Prime Agent | `prime-agent --resume <session file>` |
  | Factory Droid | `droid --resume <id>` |
  | GitHub Copilot CLI | `copilot --resume=<id>` |
  | Hermes Agent | `hermes --resume <id>` |
  | fx | `fx --resume <id>`, in the session's workspace |
  | Grok Build | `grok --permission-mode default --resume <id>` |
  | OpenClaw Gateway | `openclaw tui --session <session key>` |
  | Cursor | `cursor-agent --resume <chat id>` (chats `cursor-agent` started only) |

* **Start a new session from a brief.** Otherwise, or with `--agent` naming another runtime or with `--new`, Beacon writes a brief (see `export`) and starts a new session. The first message of that session points at the brief:

  | Runtime | Command |
  | - | - |
  | Claude Code | `claude "<prompt>"` |
  | Codex CLI | `codex "<prompt>"` |
  | OpenCode | `opencode --prompt "<prompt>"` |
  | Cline | `cline --tui --auto-approve false "<prompt>"` |
  | Pi | `pi -- "<prompt>"` |
  | Prime Agent | `prime-agent -- "<prompt>"` |
  | Factory Droid | `droid "<prompt>"` |
  | GitHub Copilot CLI | `copilot -i "<prompt>"` |
  | Grok Build | `grok --permission-mode default "<prompt>"` |
  | OpenClaw Gateway | `openclaw tui --session beacon-handoff-<hash> --message "<prompt>"` |
  | Cursor | `cursor-agent "<prompt>"` |
  | Oh My Pi | `omp --approval-mode=always-ask "<prompt>"` |
  | Gemini CLI | `gemini --approval-mode default --prompt-interactive "<prompt>"` |
  | Qwen Code | `qwen --approval-mode default --prompt-interactive "<prompt>"` |
  | Kiro | `kiro-cli chat "<prompt>"` |
  | Antigravity CLI | `agy --prompt-interactive "<prompt>"` |
  | Devin CLI | `devin --permission-mode auto -- "<prompt>"` |
  | Muse Code | `muse --approval-mode on-request "<prompt>"` |
  | OpenHands | `openhands --task "<prompt>"` |
  | goose | `GOOSE_MODE=approve goose run --interactive --text "<prompt>"` |

  The prompt names the brief by its path and asks the agent to read it, then confirm the task before changing anything. The brief is never inlined into the command line, because a command line is visible to every local user through the process table. The prompt ends with a handoff marker (see below).

Hermes Agent has no row in the second table: its CLI has no way to start an interactive session with a first message, only one-shot runs, so it can't continue a session from a brief. A Hermes session continues from a brief in another runtime (`--agent`), and `--agent hermes` reopens only Hermes's own sessions.

Claude Code, Cline, Prime Agent, Hermes Agent and Grok Build subagent sessions, and Cline's older task history, can't be reopened by their CLIs, so they always continue from a brief. A session known only from Beacon's runtime log also continues from a brief, and it needs `--agent` if its own runtime isn't one Beacon can start.

Beacon cannot start DeepSeek Harness, because it has no interactive terminal session to start: `dsh --profile headless` answers one task and exits, and `dsh web` is a browser UI. A DeepSeek Harness session therefore continues in another runtime, named with `--agent`, and `--agent dsh` is refused.

fx has no interactive session that starts from a prompt (`fx ask` runs one request and exits), so it is not in the new-session table. Beacon reopens an fx session natively, and continues it in another runtime with `--agent`; `--agent fx` for another runtime's session, `--new` on an fx session, and an fx session whose files are gone stop with an error rather than launching anything. fx looks a session id up within the workspace it runs in, so Beacon starts it in the session's workspace, and `--cwd` naming another directory stops with an error too, since fx could not find the session there. fx's default permission mode is `auto`, where a reviewer resolves permission requests, and fx has no command-line flag to change it. Beacon passes none: it reopens your own session under whatever permission mode your fx configuration sets.

OpenClaw's TUI is a client of the Gateway, so the Gateway must be running (`openclaw gateway run`, or its installed service) for either command to connect; Beacon does not start it. The Gateway reopens a conversation by its session key, such as `agent:main:main`, not by its transcript id, so an OpenClaw session is reopened natively only when the agent's `sessions.json` files the transcript under a key qualified with that agent (`agent:<agent>:...`); otherwise it continues from a brief. The Gateway answers for that key with whatever conversation it now holds under it: a key it no longer knows can open as an empty conversation. A new session gets a key of its own, `beacon-handoff-` and a hash of its prompt, because without `--session` the TUI would post into the agent's shared main session. Beacon never passes `--deliver`, so replies stay in the TUI and are not sent out through the Gateway's chat channels.

`cursor-agent --resume` reopens only chats the CLI itself started, which it keeps under `~/.cursor/chats` (or `CURSOR_CONFIG_DIR`, or `$XDG_CONFIG_HOME/cursor`) keyed by the directory the chat ran in. Beacon reopens a Cursor session natively only when that chat store exists for the session's recorded directory, and only from that directory: with `--cwd` pointing elsewhere, it continues from a brief. Conversations from the Cursor IDE, Composer records, and subagent transcripts continue from a brief. Beacon never passes `--force` or `--yolo`, so `cursor-agent` still asks before running commands.

Cline approves every tool call by default. Beacon always passes `--auto-approve false`, so a session Beacon starts never gets auto-approval on Beacon's say-so. You can still turn it on inside the Cline TUI.

The same holds for every runtime Beacon starts. Oh My Pi and goose approve every tool call by default, so Beacon passes `--approval-mode=always-ask` to Oh My Pi and sets `GOOSE_MODE=approve` for goose. Where a CLI takes an approval mode, Beacon names the mode that asks (Gemini CLI, Qwen Code, Muse Code, and Devin CLI, whose `DEVIN_PERMISSION_MODE` could otherwise hold `dangerous`), so a user or project setting that approves everything does not carry over. Beacon never passes a flag that turns approvals off.

GitHub Copilot CLI asks before each tool call unless `COPILOT_ALLOW_ALL=true` is set, which also trusts the directory, or `COPILOT_PLAN_THEN_AUTOPILOT` starts it in plan mode and carries the plan out without waiting. Beacon removes both from the environment it launches Copilot with (`--print` shows them under `env`), and never passes `--allow-all-tools` or `--yolo`. Copilot sessions are not tied to a directory, but Beacon still starts Copilot in the session's recorded directory.

Hermes Agent asks before dangerous commands by default. Beacon never passes `--yolo`, and it removes `HERMES_YOLO_MODE` from the environment it starts Hermes in, so an exported variable can't turn those prompts off. `hermes --resume` looks the id up in the state database Hermes itself uses (under `HERMES_HOME` when that is set), not in a `--store-dir` path.

Grok Build's configured permission mode can be always-approve. Beacon always passes `--permission-mode default`, the mode that asks before running tools, so a session Beacon starts or reopens never inherits always-approve from that config. You can still change the mode inside the Grok TUI. Grok looks a session id up under the directory it runs in, which is why the resume runs in the session's recorded directory. With `--cwd` naming another directory, a Grok session continues from a brief instead.

By default, the runtime runs in this terminal. `--launch auto` instead opens a new terminal surface
on a desktop and creates a detached tmux session on SSH or headless Linux. When Beacon runs inside
cmux on macOS, it opens another terminal in the calling workspace; otherwise it uses Terminal.app.
This is useful from an agent harness, whose command subprocess generally does not own a TTY that
another interactive runtime can use.

The launch modes are:

| Mode | Behavior |
| - | - |
| `current` | Run in the calling terminal and wait for the runtime. This is the default. |
| `auto` | Use another terminal in the calling cmux workspace or Terminal.app on macOS, Windows Terminal or PowerShell on Windows, a supported Linux terminal emulator on a desktop, or tmux on headless Linux/SSH. |
| `new-terminal` | Require a graphical terminal and fail explicitly when none can be opened. |
| `tmux` | Create a detached `beacon-handoff-<id>` session and print its `tmux attach` command. |

Linux desktop detection supports `xdg-terminal-exec`, GNOME Terminal, Konsole, Kitty, WezTerm,
Alacritty, foot, `x-terminal-emulator`, xterm, and a simple executable named by `$TERMINAL`.

Detached launches use a private, expiring `0600` specification under
`~/.beacon/endpoint/handoffs`. The new terminal runs a hidden Beacon trampoline that consumes the
specification, starts the exact approval-safe argument vector, and confirms startup to the calling
process. Prompt or brief content is never interpolated into a terminal-launch shell command.
If the runtime does not confirm startup within 15 seconds, Beacon removes the specification and
reports the launch as cancelled, so a terminal that opens late starts nothing and a retry cannot
start the runtime twice. The trampoline claims the specification by renaming it before it reads
it, so only one side wins: if the terminal had already claimed it, Beacon says the runtime may
still start there instead of reporting it cancelled. Check that terminal before retrying.

A new terminal starts from its own environment rather than the calling process's. Beacon carries
the variables that locate a runtime's configuration and sessions (`CLAUDE_CONFIG_DIR`,
`CLINE_DIR`, `CODEX_HOME`, `COPILOT_HOME`, `CURSOR_CONFIG_DIR`, `GEMINI_CLI_HOME`,
`HERMES_DATA_DIR_SUFFIX`, `HERMES_HOME`, `KIRO_HOME`, `OH_PERSISTENCE_DIR`, `OMP_PROFILE`,
`OPENCLAW_STATE_DIR`, `OPENCODE_DB`, `PI_CODING_AGENT_DIR`, `PI_CONFIG_DIR`, `PI_PROFILE`,
`PRIME_AGENT_CODING_AGENT_DIR`, `QWEN_HOME` and `XDG_CONFIG_HOME`/`XDG_DATA_HOME`/`XDG_STATE_HOME`)
into the specification. It never writes other variables there, so credentials such as API keys must be
available in the new terminal's own environment, for example from your shell profile.

The runtime starts in the session's recorded directory. If that directory no longer exists, Beacon
stops rather than starting somewhere else; pass `--cwd` to choose a directory. Beacon prints the
plan and asks `Continue? [Y/n]` before launching. From a non-TTY tool, pass `--yes` with a detached
launch mode. A `current` launch exits with the runtime's own exit code.

| Flag | Description |
| - | - |
| `--agent` | Runtime to continue in, by the names in the tables above. Defaults to the session's own runtime. |
| `--new` | Start a new session from a brief even when the session could be reopened. |
| `--cwd` | Directory to start the runtime in. |
| `--launch` | `current`, `auto`, `new-terminal`, or `tmux`. Defaults to `current`. |
| `--print` | Show the plan and command without writing a brief or launching anything. |
| `--json` | With `--print`, describe the plan as JSON (`mode`, `reason`, `target`, `executable`, `args`, `env`, `dir`, `brief_path`). |
| `--yes` | Launch without asking. |
| `--output-dir`, `--log-path`, `--harness`, `--store-dir`, and the `--*-dir` flags | As for `export`. |

## Linking a new session to the one it continues

A session started from a brief has a new session id. So that the two stay connected in the telemetry, the first prompt of the new session ends with a handoff marker:

```text theme={null}
[beacon-handoff from=claude_code session=ea7f210f-402b-53b1-aec2-864a488dfcd3]
```

When Beacon's hook adapter records a prompt carrying this marker, it writes a second event, `session.handoff`, next to the usual `prompt.submitted`:

```json theme={null}
{
  "event": { "action": "session.handoff", "category": "session", "fidelity": "observed" },
  "session": { "id": "<the new session>", "working_directory": "/work/api" },
  "handoff": { "source_harness": "claude_code", "source_session_id": "ea7f210f-402b-53b1-aec2-864a488dfcd3" }
}
```

The link event keeps the new session's id, workspace and harness, and leaves the prompt text to the prompt event. Rules can match on `e.handoff.source_harness` and `e.handoff.source_session_id`.

Two paths record the link:

* **Live hooks and plugins.** Every runtime whose prompts Beacon captures as they are sent records it: the shared prompt hook (Claude Code, Cursor, Factory Droid, Qwen Code, Kiro, Kimi Code, Devin, Muse Code, OpenHands, Grok Build, DeepSeek Harness, Hermes Agent, VS Code), Antigravity (including the prompt it recovers from its transcript when the prompt hook does not fire), the Cline, OpenCode and OpenClaw plugins, and the Pi, Oh My Pi, Prime Agent and Senpi extensions (`harness.collection_method` `hook` or `plugin`).
* **Session-store sync.** `beacon endpoint claude sync`, `codex sync`, `opencode sync`, `cline sync`, `pi sync`, `prime sync`, `factory sync`, `copilot sync`, `hermes sync`, `dsh sync`, `fx sync`, `grok sync`, `openclaw sessions sync` and `cursor sync` record it when they read the prompt back from the runtime's store (`harness.collection_method: poll`).

Codex CLI prompts reach Beacon over OTLP and through `beacon endpoint codex sync`, not a prompt hook, so a session continued in Codex is linked by the sync. The same holds for GitHub Copilot CLI, whose live path is OTLP: `beacon endpoint copilot sync` records the link. For a runtime that also has a prompt hook or plugin, a sync can record the same link the hook already did; the two carry different `collection_method` values, as every other event those runtimes report through both paths does. A sync's link has a deterministic `event.id`, so syncing again never duplicates it.

The marker is a claim made in prompt text, not a verified identity: anyone can type it. `fidelity: observed` records that the marker was seen, not that `beacon handoff resume` wrote it. A native resume keeps the session's own id, so it needs no link.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.