<div align="center">

# Sphica

**Local memory of past implementation and decisions for Claude Code and Codex.**

English | [日本語](https://github.com/iroha924/sphica/blob/main/README.ja.md)

Sphica records your coding sessions, and keeps what was decided, rejected, deferred, and built, each record quoting the words it came from.
Your agent finds those records when it searches, and sees the relevant ones on its own when it reads or edits a file they apply to (in Codex, before a shell command that names the file and before `apply_patch`).
The database is a single SQLite file on your machine.

[![npm](https://img.shields.io/npm/v/sphica?logo=npm&label=sphica)](https://www.npmjs.com/package/sphica)
[![License](https://img.shields.io/github/license/iroha924/sphica)](https://github.com/iroha924/sphica/blob/main/LICENSE)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/iroha924/sphica/badge)](https://scorecard.dev/viewer/?uri=github.com/iroha924/sphica)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/14787/badge)](https://www.bestpractices.dev/projects/14787)
[![SLSA Build L2](https://img.shields.io/badge/SLSA-Build%20L2-green)](https://www.npmjs.com/package/sphica#provenance)

<a href="https://github.com/iroha924/sphica#install"><img alt="Install Sphica for free" src="https://img.shields.io/badge/Sphica-Install_for_free-2ea44f?style=for-the-badge" height="44"></a>

<sub>What the badges cover: how each release is built and published is in [Security](https://github.com/iroha924/sphica#security). A badge does not mean the code is free of bugs or vulnerabilities.</sub>

</div>

## Features

- **Automatic recording.** Sphica keeps your prompts, the agent's final reply for each turn, and the paths of the files a turn changed (by the edit tools, or seen in `git status` at the turn's end).
- **Records with their sources.** `/sphica:trace` turns a session into records: decisions with the options rejected and why, constraints, implementations, findings, dead ends, and open questions. Every record quotes the exact words it came from. A decision counts as adopted in two ways: you said so (your decision), or the agent decided it itself in that session and its reply says so (an AI's decision). An AI's decision is shown as one, never replaces yours, and is never used for public interfaces, security and permissions, releases, or forgetting.
- **Traced without asking.** When you start a new session (an interactive one in Claude Code; one started from the terminal in Codex) and sessions from the last 14 days wait to be traced, the agent is asked to trace up to two of them, oldest first, after your request is done. Run `/sphica:trace` yourself to keep something right away, and `/sphica:trace pending` for older sessions. To turn the automatic trace off, see [Settings](https://github.com/iroha924/sphica#settings); recording and your own `/sphica:trace` keep working.
- **Pull requests too.** `/sphica:harvest <number>` keeps a GitHub pull request (body, comments, reviews, review comments, commits, and up to five issues the body says it closes) and records what it decided. A reviewer's suggestion stays a proposal unless the owner or a maintainer adopted it; a merge alone adopts nothing.
- **Evidence found later.** `/sphica:glean` adds evidence and corrections to existing records. It asks you for the source (an issue URL, the file and line, meeting notes) before saving; a claim without one is kept only as unsourced and never used as fact.
- **Forget what should not have been kept.** `/sphica:forget` removes the messages, pull request items, or file excerpts you pick, with their search entries and the bytes left in the database file, after you confirm in a dialog. Records that cited them are judged again and leave active when nothing else supports them; a record's own text stays as it was.
- **Shown when it matters.** At session start, the current work; before the agent reads or edits a file, or runs a shell command that names it, the decisions tied to that file; when your prompt names a recorded option or code symbol, that record. An AI's decision is marked as one, with a note that the agent may depart from it for a stated reason.
- **A command that failed before is stopped once.** When a dead end is recorded with the command it is about (such as `pnpm test --watch`), the first shell call in a conversation that runs that command is stopped, with the dead end as the reason. The agent checks the record and takes another way, or runs the call again, which then goes through.
- **Search in Japanese and English.** Records are made with search words in both languages, so a question in either language is more likely to find them.
- **Find what you asked before.** Ask the agent whether you asked something like this before: `search` with `asked: true` shows your earlier messages in other sessions, the records that quote them (with what replaced them), and says "no recorded decision" when none was recorded, including a matter you raised in several sessions.
- **See how a decision changed, and what held at a past time.** Ask the agent how a decision came to be: `read` with `timeline: true` shows the decisions it replaced or was replaced by, when each change took effect and why, and the findings, dead ends, and questions saved by the same trace or harvest or tied to the same code, oldest first. Ask what was decided as of a time: `search` with `as_of` (a time with its offset, such as `2026-10-01T09:00:00+09:00`) searches the records as Sphica held them then, by the states and replacements of that time, and names apart the ones saved later that match now (oldest first, up to ten, then a count). The time is when Sphica saved a record, not when the words were said, so a session traced late or a pull request harvested late counts from the day it was saved.
- **See what is live, and what needs a look.** Ask for the overview: `view: "live"` lists every active decision and constraint by the directory it applies to; `view: "look"` lists records whose file is gone or whose symbol is not found, conditions you said would bring a rejected option back, and lines in your instruction files whose record was replaced. Nothing is expired or changed on its own.
- **See what Sphica showed your agent.** Ask what Sphica has been showing: `view: "delivery"` (with `days`, 1 to 90, 7 by default) counts the deliveries the hooks logged by event (a shell call a dead end stopped, or was shown on without a stop, counted apart) and by main agent or subagent, lists the records shown most, and gives recent sessions with each record's key, so you can judge whether each helped. It says what the log leaves out, and marks a record "named later" only when a later reply wrote its key.
- **Track your own fields on records (trial).** Say in a session which extra field to keep on records (the affected tenant, a p95 figure); trace then saves a value only when the conversation writes it, with the quote. `/sphica:fields` shows each field and how many records carry a value.
- **Share decisions as a file.** `/sphica:export` writes the live decisions you pick, with the words quoted for them and the older decisions they replaced, to a Markdown file in your repository for people who do not run Sphica. You see the whole file before it is written, and it is never committed for you. One file holds about 29 KB, what a single reply to the agent carries whole; more decisions go into several files.
- **Rule text from records.** `/sphica:rules` drafts lines for CLAUDE.md, AGENTS.md, or `.claude/rules` from the constraints and decisions you pick, each marked with its record key so the overview flags it once the record changes. It never edits the file.
- **Reviews check past decisions.** `/sphica:review` runs a reviewer per focus (correctness, security, written conventions, and past decisions by default; redundancy with `full`), and checks the diff against the records it touches.

Records are never rewritten: a correction is a new record that supersedes the old one, and the history stays.
The agent is told to treat records as history, not instructions, and to trust the code when a record and the current code disagree.

## Requirements

- Node.js 24.15 or later
- Claude Code 2.1.139 or later, or Codex, or both. Older Claude Code skips Sphica's hooks without a word, so nothing is captured or delivered (`sphica doctor` flags it)
- `git`, to identify the repositories you register
- For `/sphica:harvest` and fetching GitHub sources in `/sphica:glean`: the GitHub CLI (`gh`), signed in with `gh auth login`

## Install

The plugin ships the MCP servers, hooks, and skills. The `sphica` CLI comes from npm and is installed separately. You need both.

**1. Install the CLI**

```bash
npm i -g sphica
```

**2. Add the plugin**

Claude Code:

```bash
claude plugin marketplace add iroha924/sphica
claude plugin install sphica@sphica
```

Codex:

```bash
codex plugin marketplace add iroha924/sphica --ref main
codex plugin add sphica@sphica
```

In Codex, open `/hooks` and mark Sphica's hooks as trusted. Automatic recording does not start until you do. If a plugin update changes the hooks, trust them again. The "Codex hooks" line of `sphica doctor` shows how many are trusted (for Codex 0.160.0).

**3. Set up in your repository**

```bash
cd path/to/your-repo
sphica init
```

This creates `~/.sphica/sphica.db` and registers the repository. Running it again keeps the database and the registration as they are. If the repository has no `origin` remote, give it a name: `sphica init --name <name>`.

If `gh` is signed in, init also binds that GitHub account as yours, so your words adopt a decision even in someone else's repository where you are only a contributor (in pull requests harvested after binding). Only the first account is bound, and there is no command to change it: move the database aside and run init again. Without `gh`, init still sets up and says why nothing was bound; `sphica doctor` shows the bound account.

**4. Check the setup**

```bash
sphica doctor
```

`doctor` checks Node.js, the CLI and plugin versions, the database, the recording queue, and the registered projects. Start here whenever something looks wrong.

## Quick start

Only sessions in repositories you register (projects) go into Sphica's database; run `sphica init` in each one.

Work as usual. A new session asks the agent to trace earlier sessions on its own (in Codex, a session started from the terminal). To keep something right away, run `/sphica:trace` (`$sphica:trace` in Codex).
`/sphica:trace pending` lists earlier sessions not traced yet. To keep what a pull request decided, run `/sphica:harvest 123`.
When you find evidence later ("the ops notes say…", "Kimura said the team agreed"), run `/sphica:glean` with what you found.

To bring back earlier decisions, ask the agent:

- "Did we already decide how to handle retries here?"
- "Why did we choose this approach, and what did we reject?"
- "Did we try generating thumbnails in a worker before?"

### What the agent sees on its own

Without being asked, Sphica adds a few past records to what the agent sees, each marked as a past record rather than an instruction:

- At session start: the current work and project-wide constraints, and, when earlier sessions wait to be traced, a request to trace them after your request is done.
- On a prompt that names a recorded code symbol, file path, or option.
- Before the agent reads or edits a file a decision applies to, and before a shell command that names such a file (naming it is not proof the command reads it). A read shows each record once per session.
- Before a shell command that runs the command a recorded dead end is about: the call is stopped, with the dead end as the reason. A dead end stops a conversation once (once more after a compaction or `/clear`), and a conversation is stopped at most three times. Running the call again goes through. Past three stops, or for a dead end the conversation has already seen, the dead end is shown without stopping, within the read limit.
- Before a review. When you run your own review command (any name containing `review`, or a name listed in the review commands [setting](https://github.com/iroha924/sphica#settings)), it gets the decisions your local change touches. `/sphica:review` checks them itself.

In Codex the same happens at session start, on a prompt, before an `apply_patch` edit, before a shell command that names such a file (Codex reads files through shell commands, so this covers reads), and before a shell command that runs a dead end's command (stopped once, as in Claude Code).
Before a review, Codex gets the same decisions in three cases: its built-in review (`/review`, `codex review`), a review skill you name in the prompt (`$my-review`), and a review skill the agent picks itself, when it prints the skill's file with one plain command such as `cat <path>/SKILL.md`. A skill is a review when its name contains `review` or is listed in `SPHICA_REVIEW_COMMANDS`. `$sphica:review` checks them itself.

The agent searches with Sphica's `search` and opens full records with `read`. `status` tells it how much of the history has been traced, so an empty search is less likely to be mistaken for "never decided".

## Settings

| Setting | Default | Claude Code option | Environment variable |
|---|---|---|---|
| Automatic trace | on | `auto_trace` | `SPHICA_AUTO_TRACE` (`off`) |
| Your own review commands whose names do not contain `review`, comma-separated | none | `review_commands` | `SPHICA_REVIEW_COMMANDS` |

- **Claude Code.** Set the plugin's options in `/config` (Claude Code 2.1.269 or later), or set the variables in the environment Claude Code starts from; for the automatic trace, `"SPHICA_AUTO_TRACE": "off"` under `env` in `settings.json` works too. The automatic trace stays off when either the option or the variable says off, so to turn it back on, turn the option on and unset the variable. When `review_commands` names any command, `SPHICA_REVIEW_COMMANDS` is not read.
- **Codex.** Codex plugins have no options page, so the settings are the environment variables. In a session that runs through Codex's background server (the terminal does by default), Sphica's hooks see the environment that server started with, not the terminal you open later: a value you set afterwards takes effect the next time the server starts (`codex app-server daemon restart` starts it again; work running in Codex may be interrupted). `codex exec` does not use that server, so its hooks see the environment it was started from. A background server started from inside an agent's shell (for example `! codex` in Claude Code) keeps that agent's variables too: Sphica then takes every session that runs through it for that agent's child and records nothing until the server starts again.

## What gets recorded and where it goes

- **Where.** The database is `~/.sphica/sphica.db`. Captured sessions wait in a local queue, `~/.sphica/spool`, until they are written to it. Each machine has its own database; nothing is shared between machines.
- **What.** Your prompts, the agent's final reply for each turn, and the paths of changed files. In Codex, a session is recorded only when Codex's own session log shows the messages come from the session's own thread: what Codex's built-in review is asked (its instructions, the subject of the commit it names) is not recorded, and neither is a `codex exec --ephemeral` run, which keeps no log. When Sphica cannot confirm a session, it records nothing of it; Codex's terminal shows Sphica's notice once, and `codex exec` shows none. Background-task notifications and messages from other agents are skipped when Sphica recognizes their format. Replies in the middle of a turn are not kept, nor are files created and deleted within one turn without the edit tools.
- **What was shown.** Each automatic delivery is logged by which records it showed, not their text.
- **Unregistered repositories.** Sessions in a repository you have not registered stay in the queue and are written after you register it. Held sessions are dropped after 30 days, and when more than 1,000 are waiting the oldest go first.
- **Secrets.** In your prompts, pull request text, and the file lines `/sphica:glean` cites, only secrets with a recognizable shape are masked:
  - keys with known prefixes
  - `KEY=…` and `"password": …` assignments
  - credentials in URLs
  - authorization headers
  - `mysql -p`

  **Anything else is stored as typed, so do not paste secrets into a session.** If one got in, remove the source holding it with `/sphica:forget` (`$sphica:forget` in Codex). It asks you to confirm in a dialog, so it works only in an interactive session: in a headless run (`claude -p`, `codex exec`) nothing is removed. A record that repeated it keeps its own text.
- **Network.** Sphica has no account, no hosted service, and no telemetry, and makes no network connections itself. `/sphica:harvest` and `/sphica:glean` run `gh api` with your credentials to read pull requests and issues, `sphica init` runs `gh api user` to read which GitHub account is yours, and `sphica doctor` runs `npm`, `claude`, and `codex` to check installed versions. `gh api` is always sent to github.com.
- **Text written by others.** Pull request and issue text may come from anyone. It is kept as a source and passed to the agent as data, never as instructions. A decision is adopted by your words, those of the repository's owner or a maintainer, or what the agent decided itself; what the agent quotes or sums up from someone else's text is not adopted.

## What Sphica can't do yet

- Only what you traced, harvested, or gleaned becomes a structured record. The rest of a conversation is searchable as captured text (`search` with `sources: true`).
- For shell commands, Sphica only sees whether a command names a file. It may show decisions for a file the command never reads, and a shell command that edits a file is not treated as an edit (in Codex, a patch passed to `apply_patch` through the shell is treated as an edit).
- A dead end's command is matched by its words, whole and in a row, within one command of one line. Sphica reads no other shell syntax: a mention outside quotes (`echo pnpm test --watch`) matches, and a command joined to the one before it without a space (`npm ci;pnpm test --watch`) or written another way (`npm.cmd`) does not.
- Before a review, Sphica compares your working tree with the default branch, whatever the review itself was given (a commit or a base branch in Codex's built-in review). In Codex, a review skill the agent reads in any other way than one plain command (after `cd`, through a pipe, inside a longer script) gets nothing, and neither does the built-in review of a `codex exec --ephemeral` run.
- In Codex, `$sphica:trace`, `$sphica:harvest`, and `$sphica:glean` can write only when Codex tells Sphica which directory the session is in. Current Codex does. When it does not, they write nothing and tell you why.
- Showing a record does not make the agent follow it.
- Whether the agent decided something itself is judged from what its reply says, so it can be wrong.
- An AI's decision is adopted only when the trace runs in an interactive session: Claude Code's terminal, or a Codex turn a person started in the session's own thread (measured with Codex's terminal). In headless and SDK runs, `codex exec`, and a Codex child thread it stays a candidate (recorded, but not adopted), and once a Codex child thread has called one of Sphica's record tools, no reply of that session is adopted.
- The automatic trace is a request the agent may skip, and it uses your subscription's usage. In Codex it goes only to new sessions started from the terminal; in other Codex clients and in `codex exec`, Codex tells you when sessions wait.
- When Sphica cannot tell which turn ran one of its record tools, every reply Claude Code's agent writes in that project from then on stays a candidate, and records already saved as candidates are not adopted afterwards. If the hook only timed out, replies of other turns can be adopted again once the next recording sends what the hook saw (when the hook knew the turn). If the hook was turned off, the host sent no tool call id, or the call came before Sphica was installed, the stop stays for good. `sphica doctor` lists the calls behind a stop and since when. Your decisions are not affected.
- The agent never withdraws an AI's decision. When a later record contradicts it, the AI's decision is held back from being shown until you settle it, and so is the later record unless it is your decision.
- A code location in a record is checked against your working tree when it is read. Finding the code name (a function name, say) the record points to does not mean the decision still holds.

## Updating

The CLI and the plugin are updated separately.

```bash
npm i -g sphica@latest
```

Claude Code:

```bash
claude plugin marketplace update sphica
claude plugin update sphica@sphica
```

Codex:

```bash
codex plugin marketplace upgrade sphica
codex plugin add sphica@sphica
```

Restart open sessions afterwards. When an update changes the database, update the CLI first, then run `sphica init` once: it migrates the database in place and keeps your records. Until then Sphica is unavailable and tells you so. An older CLI cannot read the migrated database: if one still says to move it aside, update that CLI instead.

## Uninstalling

```bash
sphica uninstall
```

This deletes `~/.sphica` (the database and the recording queue) after asking, and shows the commands that remove the rest:

```bash
claude plugin uninstall sphica@sphica && claude plugin marketplace remove sphica
codex plugin remove sphica@sphica && codex plugin marketplace remove sphica
npm uninstall -g sphica
```

## Troubleshooting

Run `sphica doctor` first. It shows which part is out of date or not working. Common cases:

- **`sphica: command not found`.** The plugin does not put the CLI on your PATH. Run `npm i -g sphica`.
- **Nothing is recorded.** Check that `sphica doctor` lists the repository under Projects. In Codex, also check the "Codex hooks" line, or `/hooks`, for hooks that are not trusted; if Codex was started from inside another agent's shell, see Codex under [Settings](https://github.com/iroha924/sphica#settings).
- **The MCP servers report an older version.** Restart the session, or run `/reload-plugins` in Claude Code.
- **A search finds nothing.** Search matches words. Try other words, the other language, an identifier, or fewer words. Ask the agent to check `status`: sessions not traced yet are searchable only as captured text.
- **`doctor` says the full-text index is broken.** Run `sphica doctor --reindex`.
- **A review says Sphica's git worker would not start, or `doctor` shows it under `Git worker`.** Sphica reads your working tree with git in a folder of its own, `.sphica/git` under your home directory, and refuses to when that folder overlaps the temp directory or the working tree itself (an agent in a sandbox can write there), or is a link. `sphica doctor`, run inside the repository, says which. Until it is fixed, the check before a review cannot read your local change.

## Commands

| Command | What it does |
|---|---|
| `sphica init` | Create the database, bind the GitHub account `gh` is signed in to, and register the current repository |
| `sphica doctor` | Check versions, the database, recording, and each registered project |
| `sphica doctor --reindex` | Rebuild the full-text index when `doctor` says it is broken |
| `sphica doctor --audit-quotes` | List saved adoptions and reasons that rest on quoted words. It only reads: nothing is changed |
| `sphica uninstall` | Delete `~/.sphica` and show how to remove the plugin and the CLI |

Everything else runs inside Claude Code and Codex, through the `/sphica:*` commands and Sphica's MCP tools.

## Security

Report vulnerabilities privately as described in [SECURITY.md](https://github.com/iroha924/sphica/blob/main/SECURITY.md).

Each release is built by GitHub Actions from a tag on the head of a pull request whose CI has passed.
It is published only after the maintainer approves the release environment on GitHub, and it reaches npm through trusted publishing, with no stored token.
The [npm page](https://www.npmjs.com/package/sphica#provenance) links to the workflow and the commit each release was built from.

Releases are signed. To check one yourself, run `npm init -y && npm install sphica && npm audit signatures` in an empty directory: it verifies npm's registry signature and the provenance attestation, which is signed through Sigstore, so there is no key to fetch. The attestation of the SBOM (the list of dependencies bundled into the package) for a tarball from `npm pack sphica@<version>` can be checked with `gh attestation verify sphica-<version>.tgz --repo iroha924/sphica --predicate-type https://cyclonedx.org/bom`.

How the parts fit together is in [ARCHITECTURE.md](https://github.com/iroha924/sphica/blob/main/ARCHITECTURE.md), and why the security claims hold is in [ASSURANCE.md](https://github.com/iroha924/sphica/blob/main/ASSURANCE.md).

Dependabot opens weekly pull requests to update the GitHub Actions used in CI, and Renovate opens monthly ones for the npm dependencies bundled into the package.

## Contributing

Come build something interesting together. Sphica is still small and there is plenty left to make. Ideas, bug reports, questions, and pull requests are all welcome, and small ones count: a typo fix or a "this part confused me" is a fine first contribution.

- **Not sure where to start?** Open an issue and say what you would like to try, or what got in your way. For a larger change, it is also the place to agree on the direction before you write any code.
- **Have a change ready?** Fork the repository and open a pull request. [CONTRIBUTING.md](https://github.com/iroha924/sphica/blob/main/CONTRIBUTING.md) walks through the setup, the checks, the commit rules, and how review works.
- **What happens next?** A pull request goes in once the maintainer has read and accepted it, so expect questions or requests for changes along the way. Only the maintainer merges.

Changes that add or change behavior include automated tests in the same pull request. CI runs them with `bun run verify` on every pull request.

Everyone taking part follows the [code of conduct](https://github.com/iroha924/sphica/blob/main/CODE_OF_CONDUCT.md). Who decides what is in [GOVERNANCE.md](https://github.com/iroha924/sphica/blob/main/GOVERNANCE.md), and where the project is headed is in [ROADMAP.md](https://github.com/iroha924/sphica/blob/main/ROADMAP.md).

## License

[MIT](https://github.com/iroha924/sphica/blob/main/LICENSE). The published package bundles its dependencies. Their licenses are listed in `THIRD_PARTY_NOTICES.md` inside the package.
