Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -575,10 +575,11 @@ migration message:
set-version version scope="all":
python3 scripts/update_versions.py "{{version}}" --scope "{{scope}}"

# Pin the Basic Memory Git ref used by both Codex uv hook scripts.
# Pin the Basic Memory Git ref used by all Codex uv hook scripts.
set-codex-hook-version ref:
uv add --script plugins/codex/hooks/session_start.py --raw "basic-memory @ git+https://github.com/basicmachines-co/basic-memory@{{ref}}"
uv add --script plugins/codex/hooks/pre_compact.py --raw "basic-memory @ git+https://github.com/basicmachines-co/basic-memory@{{ref}}"
uv add --script plugins/codex/hooks/stop.py --raw "basic-memory @ git+https://github.com/basicmachines-co/basic-memory@{{ref}}"

# Preview a version update without writing (scope: all | core | packages)
set-version-dry-run version scope="all":
Expand Down Expand Up @@ -664,6 +665,7 @@ release version:
plugins/codex/.codex-plugin/plugin.json \
plugins/codex/hooks/session_start.py \
plugins/codex/hooks/pre_compact.py \
plugins/codex/hooks/stop.py \
integrations/hermes/plugin.yaml \
integrations/hermes/__init__.py \
integrations/openclaw/package.json
Expand Down Expand Up @@ -784,6 +786,7 @@ beta version:
plugins/codex/.codex-plugin/plugin.json \
plugins/codex/hooks/session_start.py \
plugins/codex/hooks/pre_compact.py \
plugins/codex/hooks/stop.py \
integrations/hermes/plugin.yaml \
integrations/hermes/__init__.py \
integrations/openclaw/package.json
Expand Down
10 changes: 5 additions & 5 deletions plugins/claude-code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ local checkout. Two disclosures:
- **Event capture is opt-in and off by default.** Setting `captureEvents: true`
(the JSON boolean — strings never enable it) records redacted lifecycle-event
envelopes to a local inbox under your Basic Memory home. Inspect with
`basic-memory hook status`, project with `basic-memory hook flush`.
`basic-memory hook status`, archive locally with `basic-memory hook flush`.
The lifecycle trace never becomes a graph note.

Every failure path exits 0 — the hooks stay invisible rather than disrupt a
session.
Expand Down Expand Up @@ -160,10 +161,9 @@ general setup seeds `session`; a **coding setup** (persisted as
`sessionProfile: "coding"` with a user-confirmed `repository`) seeds
`coding_session`, whose required repository, repo-root, working-directory,
branch, and Git SHA frontmatter make checkpoints queryable by structured
filters; typed pull-request fields are added when a PR exists. Optional flush projection also writes
normalized `session` and `tool_ledger` artifacts. Those projection contracts are
owned and tested by Basic Memory core rather than copied into separate
host-plugin schemas.
filters; typed pull-request fields are added when a PR exists. Lifecycle-event
envelopes are operational trace rather than knowledge; `bm hook flush` archives
them locally and never creates session or tool-ledger notes.

To customize how Claude writes memory, edit `skills/bm-writing/SKILL.md` in the
plugin source. `bm-remember` and the output style's capture reflexes apply that
Expand Down
5 changes: 3 additions & 2 deletions plugins/claude-code/skills/bm-orient/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ of a session; this is the deliberate mid-session version with deeper reads.
report that setup is incomplete. Merge and deduplicate the results, sort them
newest first, and prefer the highest-signal checkpoint regardless of which
producer wrote it. `coding_session` carries schema-required, queryable Git
context; `session` covers general checkpoints, PreCompact captures, and
normalized `bm hook flush` projections.
context; `session` covers general checkpoints and PreCompact captures. Do not
query lifecycle trace: `bm hook flush` archives it locally rather than
promoting it into the graph.

3. Query configured `secondaryProjects` read-only for open decisions. Do not write
to shared projects during orientation.
Expand Down
12 changes: 6 additions & 6 deletions plugins/claude-code/skills/bm-setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,11 @@ Ask only what you can't infer. Cover:
schemas" below.)

6. **Lifecycle-event capture.** "Should I also keep a local, redacted trail of
SessionStart and PreCompact events for later projection?" Default to **off**.
SessionStart and PreCompact events for diagnostics?" Default to **off**.
Explain that the normal session brief and PreCompact checkpoint work either
way; enabling this adds envelopes to a local inbox until `bm hook flush`
projects them. Only the JSON boolean `true` enables capture.
archives them locally. It never creates knowledge-graph notes. Only the JSON
boolean `true` enables capture.
- If enabled, optionally ask for repo-specific `redactKeys` (additional payload
keys) and `redactPaths` (working directories or path-bearing content). The
built-in redaction floor still applies when these lists are empty.
Expand Down Expand Up @@ -144,10 +145,9 @@ from this skill's directory, then `schemas/`** (this skill is at
`<plugin>/skills/bm-setup/`). Read `coding-session.md` for a coding profile or
`session.md` for a general profile, then read `decision.md` and `task.md`.

These schemas cover notes the Claude integration writes directly. The normalized
`session` and `tool_ledger` artifacts written by `bm hook flush` are core-owned
projections; their shape belongs to the core projector and its tests, not a
duplicated per-plugin `tool_ledger` schema.
These schemas cover notes the Claude integration writes directly. Lifecycle
envelopes are not notes: `bm hook flush` archives that operational trace locally,
so there is no projected session or tool-ledger schema to seed.

For each one:
- Check whether the chosen project already has a schema for that type
Expand Down
6 changes: 4 additions & 2 deletions plugins/claude-code/skills/bm-status/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ This is a quick diagnostic — gather the facts and lay them out; don't over-inv

3. **Core hook health.** With the first available launcher, run
`basic-memory hook status --harness claude --project-dir <project-root>`.
Report its shared inbox path, pending envelopes, processed envelopes, last
Report its shared inbox path, pending envelopes, archived envelopes, pending
checkpoint requests, last
flush, settings state, resolved primary project, capture state, capture folder,
Basic Memory version, and uv version. Inbox counts are global across supported
harnesses; do not attribute a backlog solely to Claude. Treat this command's
Expand Down Expand Up @@ -76,7 +77,8 @@ you couldn't determine, rather than failing the whole report):
- Redact paths: <configured count or none>
- Shared hook inbox: <path or unavailable>
- Shared pending envelopes: <count or unavailable>
- Shared processed envelopes: <count or unavailable>
- Shared archived envelopes: <count or unavailable>
- Pending checkpoint requests: <count or unavailable>
- Last flush: <timestamp, never, or unavailable>
- Hook runtime: basic-memory <version>; uv <version or missing>
- Recent checkpoints: <n across session and coding_session>
Expand Down
2 changes: 1 addition & 1 deletion plugins/codex/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ projects on the same machine. Repo-specific memory routing still comes from each

## Iteration Loop

Pin both hook scripts to the Basic Memory revision under test:
Pin all three hook scripts to the Basic Memory revision under test:

```bash
just set-codex-hook-version "$(git rev-parse origin/main)"
Expand Down
48 changes: 31 additions & 17 deletions plugins/codex/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ verification, decision capture, and resumable checkpoints.

- **Orient from memory.** The `bm-orient` skill reads active tasks, open
decisions, and recent Codex checkpoints before substantial work.
- **Checkpoint work.** The `bm-checkpoint` skill and `PreCompact` hook write
general `codex_session` notes or schema-backed `coding_session` notes with
structured repository and pull-request context.
- **Checkpoint work.** `PreCompact` records a private request and the `Stop`
hook asks the active Codex turn to run `bm-checkpoint` once after compaction.
The resulting `codex_session` or `coding_session` note is agent-authored from
the compacted working context, with repository and pull-request evidence.
- **Capture decisions.** The `bm-decide` skill records durable engineering
decisions with rationale, alternatives, and consequences.
- **Remember lightly.** The `bm-remember` skill saves small facts without turning
Expand All @@ -32,16 +33,18 @@ verification, decision capture, and resumable checkpoints.
| --- | --- |
| `.codex-plugin/plugin.json` | Codex plugin manifest |
| `.mcp.json` | Basic Memory MCP server configuration |
| `hooks/hooks.json` | SessionStart and PreCompact hook registration |
| `hooks/hooks.json` | SessionStart, PreCompact, and Stop hook registration |
| `hooks/session_start.py` | uv script: runs `basic-memory hook session-start --harness codex` |
| `hooks/pre_compact.py` | uv script: runs `basic-memory hook pre-compact --harness codex` |
| `hooks/stop.py` | uv script: runs `basic-memory hook stop --harness codex` |
| `skills/` | Codex-native Basic Memory workflows |
| `schemas/` | Seed schemas for Codex sessions, decisions, and tasks |

The hook scripts carry no logic: the brief, the checkpoint, and lifecycle-event
capture all live in the pinned Basic Memory revision behind `bm hook`. Each is
a self-contained PEP 723 script pinned to a Basic Memory Git ref. Both refs
are updated together with `just set-codex-hook-version <sha-or-tag>`.
The hook scripts carry no logic: the brief, checkpoint coordination, and
lifecycle-event capture all live in the pinned Basic Memory revision behind
`bm hook`. Each is a self-contained PEP 723 script pinned to a Basic Memory Git
ref. All refs are updated together with
`just set-codex-hook-version <sha-or-tag>`.

## Requirements

Expand Down Expand Up @@ -69,6 +72,13 @@ Plugin installation is user-level in Codex, so one install makes the plugin
available across projects on the same machine. Start a new Codex thread after
installing so Codex can load the plugin skills, MCP configuration, and hooks.

When adding the marketplace from the Git repository UI, leave **Sparse paths**
empty. If a sparse checkout is required, include both `.agents/plugins` and
`plugins/codex`. Selecting only `plugins/codex` omits
`.agents/plugins/marketplace.json`, so Codex correctly reports that the checked
out marketplace root has no supported manifest. The marketplace file should not
be moved into the plugin directory.

Configuration can live at user level in `~/.codex/basic-memory.json` or at
project level in `.codex/basic-memory.json`. User-level settings are the base;
the nearest project file overrides only the keys it declares. `redactKeys` and
Expand Down Expand Up @@ -103,9 +113,16 @@ Run the setup skill, or create `~/.codex/basic-memory.json` for shared defaults:

Codex event capture is on by default. Set the JSON boolean `false` at user or
project level to opt out; malformed values fail closed. Captured, redacted
lifecycle-event envelopes land in a local inbox under your Basic Memory home
(`basic-memory hook status` / `basic-memory hook flush`). Add `redactKeys` and
`redactPaths` arrays to extend the built-in redaction floor.
lifecycle-event envelopes land in a local inbox under your Basic Memory home.
The lifecycle trace stays local: `basic-memory hook flush` only moves valid
envelopes into the local retention archive and never creates graph notes. Add
`redactKeys` and `redactPaths` arrays to extend the built-in redaction floor.

Codex ignores PreCompact stdout, so PreCompact cannot ask the model to write a
note directly. It leaves a private request for the Stop hook. Stop then blocks
the turn once with a request to run `bm-checkpoint`; the active model writes an
agent-authored checkpoint from its compacted context, and the next Stop is a
no-op to prevent loops.

When `captureFolder` is omitted, Codex resolves the Git top-level directory and
writes to `codex/<repo-dir>`. An explicit folder still wins.
Expand All @@ -125,12 +142,9 @@ identifier in the project file without duplicating the shared settings:
The plugin's seed schemas cover notes Codex writes directly: `codex_session`,
`coding_session`, `decision`, and `task`. Coding sessions require structured
repository, repository-root, working-directory, branch, and Git SHA frontmatter;
current pull-request fields are added when a PR exists. Optional flush projection
also writes normalized `session` and `tool_ledger` artifacts. Those are
core-owned contracts implemented and tested with the projector, not duplicate
schema files maintained by each host plugin. `bm-orient` and `bm-status` still
recall normalized `session` notes
alongside Codex checkpoints.
current pull-request fields are added when a PR exists. Lifecycle envelopes are
operational trace rather than knowledge, so orientation only recalls authored
checkpoint types.

Codex plugin hooks must be reviewed and trusted before they run. Open `/hooks` in
Codex after enabling the plugin and trust the Basic Memory hook definitions.
Expand Down
14 changes: 13 additions & 1 deletion plugins/codex/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"description": "Basic Memory for Codex hooks - orient from the graph on session start and checkpoint before compaction.",
"description": "Basic Memory for Codex hooks - orient from the graph, record local lifecycle trace, and request an authored checkpoint after compaction.",
"hooks": {
"SessionStart": [
{
Expand All @@ -26,6 +26,18 @@
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "uv run --quiet --script \"${PLUGIN_ROOT}/hooks/stop.py\"",
"statusMessage": "Finishing the Basic Memory checkpoint",
"timeout": 30
}
]
}
]
}
}
2 changes: 1 addition & 1 deletion plugins/codex/hooks/pre_compact.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "basic-memory @ git+https://github.com/basicmachines-co/basic-memory@bd5d145d5be3bab3c73291ae2a698f8e5a1e54cb",
# "basic-memory @ git+https://github.com/basicmachines-co/basic-memory@1cb7a541fbb43c5a4f1b552c0123a1c55beb8dfb",
# ]
# ///
"""PreCompact hook launcher backed by a pinned Basic Memory revision.
Expand Down
2 changes: 1 addition & 1 deletion plugins/codex/hooks/session_start.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "basic-memory @ git+https://github.com/basicmachines-co/basic-memory@bd5d145d5be3bab3c73291ae2a698f8e5a1e54cb",
# "basic-memory @ git+https://github.com/basicmachines-co/basic-memory@1cb7a541fbb43c5a4f1b552c0123a1c55beb8dfb",
# ]
# ///
"""SessionStart hook launcher backed by a pinned Basic Memory revision.
Expand Down
37 changes: 37 additions & 0 deletions plugins/codex/hooks/stop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env -S uv run --quiet --script
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "basic-memory @ git+https://github.com/basicmachines-co/basic-memory@1cb7a541fbb43c5a4f1b552c0123a1c55beb8dfb",
# ]
# ///
"""Stop hook launcher backed by a pinned Basic Memory revision.

Stop must always return valid JSON. Running Typer without Click's standalone
mode avoids treating its normal completion as an exception; real failures emit
a fail-open response so Basic Memory can never strand a Codex turn.
"""

import sys

VERB = "stop"
HARNESS = "codex"


def hook_args() -> list[str]:
return ["hook", VERB, "--harness", HARNESS]


def main() -> None:
from basic_memory.cli.main import app

sys.argv = ["basic-memory", *hook_args()]
app(standalone_mode=False)


if __name__ == "__main__":
try:
main()
except BaseException: # noqa: BLE001 - the documented fail-open boundary
print('{"continue":true}')
sys.exit(0)
49 changes: 49 additions & 0 deletions plugins/codex/hooks/test_codex_stop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Tests for the Codex Stop uv hook script."""

import json
import os
import re
import subprocess
import sys
from pathlib import Path


SCRIPT = Path(__file__).with_name("stop.py")
GIT_DEPENDENCY_RE = re.compile(
r'"basic-memory @ git\+https://github\.com/'
r'basicmachines-co/basic-memory@([^"]+)"'
)


def test_script_fails_open_with_valid_json(tmp_path: Path) -> None:
env = os.environ.copy()
env["HOME"] = str(tmp_path)
env["USERPROFILE"] = str(tmp_path)
env["BASIC_MEMORY_CONFIG_DIR"] = str(tmp_path / "basic-memory")

result = subprocess.run(
[sys.executable, str(SCRIPT)],
input='{"session_id":"none","stop_hook_active":false}',
env=env,
capture_output=True,
text=True,
timeout=60,
)

assert result.returncode == 0
assert json.loads(result.stdout) == {"continue": True}


def test_dependency_is_pinned_to_a_basic_memory_git_ref() -> None:
text = SCRIPT.read_text(encoding="utf-8")

assert len(GIT_DEPENDENCY_RE.findall(text)) == 1


def test_metadata_and_command_shape() -> None:
text = SCRIPT.read_text(encoding="utf-8")

assert text.count("# /// script") == 1
assert re.search(r'^# requires-python = ">=3\.12"$', text, re.MULTILINE)
assert 'VERB = "stop"' in text
assert 'HARNESS = "codex"' in text
Loading
Loading