Skip to content

Agent sessions

neo sessions uploads your Claude Code, Codex and Antigravity sessions to the forge, so a pull request or a commit can link to the conversation that produced it. Nothing is uploaded on its own until you turn it on: neo sessions setup, or y to the one question neo auth login asks when a supported agent is installed (the default is no, and the answer is remembered either way). A neo sessions upload you run by hand uploads either way.

With neo installed and signed in (neo auth login), run these where your agent runs:

Terminal window
neo sessions setup
neo sessions upload --since 7d

setup uploads every session from now on. It does not reach back, so the upload sends the sessions you already have: every one changed in the last seven days (any window works, e.g. 30d). A pull request then lists each uploaded session that made a commit on it under its Sessions tab, once that commit is pushed, as long as the commit’s author or committer email is one of your verified emails or your noreply address (neo sessions status warns when git config user.email is neither).

setup adds a neo sessions hook entry to each agent’s hook config:

  • Claude Code: Stop and SessionEnd in ~/.claude/settings.json (or $CLAUDE_CONFIG_DIR/settings.json);
  • Codex: Stop in ~/.codex/hooks.json and hooks = true under [features] in ~/.codex/config.toml (or under $CODEX_HOME);
  • Antigravity: Stop under "neosource" in ~/.gemini/config/hooks.json (or $ANTIGRAVITY_CONFIG_DIR/hooks.json).

Every other setting and hook in those files stays; any existing file is saved beside it as <name>.neo.bak before it is rewritten. A JSON file that is not plain JSON (comments included) is refused, not rewritten. The hook hands each event to a background neo sessions upload and returns at once: for Claude Code, a Stop within five minutes of the last upload is not lost but deferred to one upload at the end of those five minutes, and SessionEnd always runs; for Codex and Antigravity, Stop always checks whether the transcript changed. Each hook run also uploads up to three idle sessions whose transcript changed since their last upload, each only to the forge it was last uploaded to.

The bundle is built and redacted on your machine, from the transcript files the agent writes for the session (Claude Code’s <id>.jsonl, subagent transcripts and tool-results/ files; Codex’s rollout-*.jsonl under ~/.codex/sessions/; Antigravity’s .system_generated/logs/transcript_full.jsonl and step outputs under ~/.gemini/antigravity-cli/brain/<id>/):

  • your messages and the model’s replies: text, thinking/reasoning (without signatures or encrypted blobs), tool calls with their input, and tool results as text;
  • timing records, token usage and the session’s title;
  • each subagent’s type, description and transcript;
  • large tool output files as text, cut to 256 KiB each;
  • a header naming the repositories the session touched on this forge (owner/name), each work tree’s remote URL on this forge with any credentials removed, and a SHA-256 of the work tree’s local path (never the path itself);
  • commit claims: commit ids printed by the session’s git commands (a commit’s or push’s summary line, or a line starting with an id, as git log --oneline and git rev-parse print), expanded on your machine to their full 40-character id. A work tree removed since is looked up through the checkout it shared objects with. Only commits whose committer is you (the identity git var GIT_COMMITTER_IDENT reports) and whose committer time falls inside the session are claimed; each carries the branch it was made on when that is not the default branch. Claims sent once are sent again with each later upload, so a commit that stops resolving locally keeps its link;
  • pull requests it opened, readied or merged, as neo pr printed them.
  • record types the projection does not list: API request records, attachments (the system prompt, CLAUDE.md contents, your email address), file-history snapshots, and anything Claude Code adds later;
  • images (kept only as a placeholder), thinking signatures, working directories and branch names of each record;
  • the remote URL of a repository that is not on this forge (an employer’s GitHub, say): it counts as one repository the forge cannot resolve, and nothing about it is sent;
  • whole sessions that touched no repository on this forge: they are skipped, never uploaded;
  • sessions you deleted (see below).

Before anything leaves the machine, every string is redacted: your stored forge token and the value of every environment variable whose name contains token, secret, key, passw or credential are masked literally, then the shared rules mask private keys, JWTs, URL credentials, forge, GitHub, GitLab, AWS, Anthropic, OpenAI and Slack tokens, key=value secrets and high-entropy strings. The forge runs the same rules over what it receives and rejects a bundle they still match (redaction_hit); it never edits one.

  • You, always.
  • Everyone else only when signed in, only as the session’s visibility allows, and only if they have that access to every repository the session touched:
    • private: nobody else;
    • repo_writers (the default): whoever can write every touched repository;
    • repo_readers: whoever can read every one.

A session that also touched repositories the forge cannot resolve stays private to everyone else until you set its visibility yourself.

Change it with neo sessions visibility <id> private|writers|readers, where <id> is the forge id neo sessions list prints or the external session id. neo sessions delete <id> deletes it; the forge then refuses that session for good, and this machine stops offering it. neo sessions disable removes the hook and turns uploads off; what is already uploaded stays.

neo sessions upload --force [<id>…] [--since <window>] re-sends sessions even when nothing changed since their last upload, so the forge re-checks their links after it changes how it verifies them. neo sessions status and upload show how many claims the forge dropped (· N dropped): ids it found no trace of in the transcript, or that name a repository the session did not touch.

Uploading uses your neo auth login token: it needs user:write, a human account, and a token not locked to particular repositories or workspaces.

Reading someone else’s session through the API needs user:read plus the repository access the visibility names on every touched repository: repo:write for repo_writers, repo:read for repo_readers. A token with repo:read alone therefore cannot see a repo_writers session.

  • ~/.local/state/neosource/sessions.log (under $XDG_STATE_HOME when set): one line per upload or skip, with the session id, the outcome and the reason. It never holds transcript text, and it rotates at 1 MiB.
  • ~/.local/state/neosource/sessions/<id>.json: each session’s last upload, its counts and refusal code, and its last skip. neo sessions status reads these, and also warns when git config user.email is neither your verified email nor your noreply address, which keeps commits from linking.
  • ~/.local/state/neosource/sessions/<id>.tombstone: a session you deleted.
  • The consent per forge host is the sessions key of the CLI config (see Configuration).