Skip to main content

Command overview

Some agents run their hooks in a sandbox that confines writes to the session workspace: DeepSeek Harness in workspace-write, Gemini CLI and Qwen Code in a container sandbox, or an operator’s own sandbox that mounts nothing of Beacon’s. A hook there cannot reach the ingest socket, the runtime log, or the account spool. Instead of dropping the event, it stages it in the workspace spool:
Hooks stage only when every other route failed. The spool is readable only by your account and capped at 8 MiB per session, and a .gitignore inside .beacon/spool/ keeps it out of git add, whatever the repository’s layout. Beacon later drains it into the runtime log with the harness.collection_method=hook label the event was captured under. Hooks never stage:
  • actions only Beacon writes, such as an auto-share request, which are reported as not recorded, as before;
  • as root in a workspace another account owns, such as a rootful container over your checkout: you could neither drain nor delete root’s files. Run the container as your own uid, or mount the ingest socket. Root that owns the workspace (a rootless container) stages as usual;
  • in a working directory that exists only on the sandbox’s own root filesystem, where the spool would disappear with the sandbox;
  • next to a .beacon/spool/.gitignore that does not ignore everything there. A repository can commit its own to switch the protection off; Beacon leaves it alone and stages nothing beside it.
The first event a session stages prints one note to the hook’s stderr, naming the spool and a drain command for that one session and the log the hook writes:
Later events of the session stage without a note, so the agent’s hook output is not flooded. A hook cannot tell whether a sweep will find the session, so the note always names the command. The command names the session, so running it never drains a spool a cloned repository planted beside it, and it names the log, so the events go where the hook meant them, whichever mode it runs in. DeepSeek Harness stages silently, because beacon endpoint dsh sync finds its sessions through its own session store.
Command syntax

Commands

Flags

When staged events are drained

Staged events reach the runtime log in one of three ways:
  1. The user-mode sweep. The scheduled session sync and the install backfill drain every spool they find.
  2. beacon endpoint spool drain, run by hand or from your own scheduler.
  3. The next hook of the same session that gets through, for example once the session leaves the sandbox, delivers what that session staged first.
Drain spools
In system mode, the root sweep never drains workspace spools, so use (2) or (3). drain refuses to run as root: run it as the account that owns the workspace. It appends through the ingest socket like a hook, so an ordinary account does not need write access to the system log. If that account cannot read the system log, Beacon cannot list the sessions it has recorded; drain prints a note and still drains configured roots and --workspace. If it cannot read the system config.json (root-only when it holds a destination secret), Beacon does not know which log to write: drain asks for --log-path, and status lists only --workspace spools. status lists each session with staged bytes: workspace, harness, session, and size. beacon endpoint doctor reports the same state as workspace_spool_pending_drain.

Security model

Anything running in a workspace can write to it, so Beacon treats every staged line as untrusted:
  • Lines are checked one by one. A line is appended only if it is a valid hook or plugin event from Beacon, for the harness and session whose spool holds it. Actions only Beacon writes, such as session.share_requested, are refused. Everything else is counted as rejected and removed. Each admitted line is written again through Beacon’s own encoder, so it is redacted and size-capped like any other event.
  • Only known sessions are drained. Beacon drains a spool only for a session it has already recorded in the runtime log, with that workspace as its working directory. A cloned repository that carries a planted .beacon/spool/<harness>/<id>/ is never drained.
  • Configured roots are vouched for by the operator. Sessions under workspace_spool_roots or a --workspace are drained even if Beacon has not recorded them, because the operator named those directories. Add --harness and --session to vouch for one session only.
  • Links are never followed. Every directory below the workspace must be a real directory and pending.jsonl a regular file, so a sandboxed agent cannot plant its session directory as a link to the account’s own spool and have a drain outside the sandbox empty it. A pending.jsonl larger than a spool can grow (8 MiB) was written by hand and is left unread, so it cannot tie up a sweep.
  • Root never drains for a user. Neither the system-mode sweep nor beacon endpoint spool drain drains as root.
Delivery is at least once: a drain killed between appending a batch and removing it from the spool appends that batch again.

Configuring roots

For sandboxes whose sessions Beacon has not recorded yet, such as a container sandbox where no hook ever reached the log, list the workspaces in workspace_spool_roots in the endpoint config.json. Each entry is a glob of workspace directories:
config.json
Every <workspace>/.beacon/spool/<harness>/<session>/ under a matching directory is drained.

DeepSeek Harness

DeepSeek Harness hooks stage in .beacon/spool/deepseek_harness/<session>/. Spools an earlier hook left in .beacon/dsh-spool/<session>/ are still drained. beacon endpoint dsh sync drains them too, and doctor reports them as dsh_spool_pending_drain.