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
76 changes: 76 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,82 @@ commits and tag annotations of those releases.

---

## [Unreleased]

Longhand now keeps one archive for Claude Code **and** Codex. This is the
first release that captures a second client, and it holds that client to the
same rules as the first: exact records, stable IDs, nothing stored twice,
drift never silent.

### Added

- **`longhand codex-sync`** captures Codex Desktop and Codex CLI threads (the
rollouts under `~/.codex/sessions`) into the same `~/.longhand` archive the
Claude Code hooks write, under the same ingest lock, with `codex:`-namespaced
session and tool IDs. Exact-record capture by default — every message,
reasoning summary, tool call, and output, verbatim and searchable, without
loading the embedding model; `--semantic` runs the full pipeline so `recall`
and semantic `search` see the sessions. Per run it imports up to 50 sessions
of up to 16 MiB and 20,000 events each; larger ones are reported as
`deferred`, never partially imported. `--dry-run` lists rollouts without
opening the archive; `--watch` polls every 60 seconds.
- **`reconcile --fix` captures Codex too.** New or changed rollouts are picked
up alongside the Claude buckets, so an installed reconciler keeps both
clients current with no extra setup. The report and the `reconcile` MCP
tool gained `codex_*` fields.
- **`longhand shared-mcp`** — a four-tool, read-only MCP server
(`list_sessions` with `source` and `project` filters, `search`,
`get_session_timeline`, `get_event_text`) over the SQLite archive: no
Chroma, no model. Keyword matching, not semantic. It is the server Codex
talks to, and the way Claude searches Codex history by phrase.
`python -m longhand.lightweight_mcp` runs the same server.
- **Commits made from Codex show up in `find_commits` and `git-log`** on the
default capture path — including commands run inside Codex's `exec`
scripts, whose `cmd:` literals are decoded for git and error detection.
- **A Codex shapes gate.** `tests/fixtures/codex_shapes/` holds every rollout
record shape seen in the wild (both Codex CLI generations), and
`tests/test_codex_shapes.py` fails the moment one is undispositioned. Unknown
shapes are preserved as `unknown` events with raw JSON intact, and `doctor`'s
"Transcript format" row names Codex drift by its nested kind
(`event_msg/<kind>`).
- **`doctor` knows about Codex.** A "Codex capture" row — only on machines
that have Codex — shows rollouts captured vs. changed since. Archived Codex
sessions are split out of "Sessions needing analysis" with the remedy that
actually works for them: `codex-sync --semantic`. (`analyze --all` was the
old advice; it never embeds events, so it could not have made them
searchable — a Promise 5 defect caught before it shipped.)
- `scripts/com.longhand.codex-sync.plist.template` — a macOS LaunchAgent for
60-second capture, for anyone who wants faster than the reconciler.

### Changed

- **What Codex capture stores.** Codex writes every message twice — a
canonical `response_item` and a UI `event_msg` mirror — plus token
accounting and turn bookkeeping. Only the canonical items are stored; the
mirrors and bookkeeping are skipped (the same rule `KNOWN_SKIP_ENTRY_TYPES`
applies to Claude transcripts), and reasoning without a readable summary is
skipped rather than stored as an empty row. On a real corpus this removed
six of every ten rows without losing a searchable character.
- **Subagent threads are skipped by default.** Codex spawns threads for
itself (its "guardian" approval reviewer) that re-quote the parent
conversation, so capturing them returned every search hit twice.
`--include-subagents` captures them.
- The `mcp` dependency floor is now `>=1.2.0`, the first release with FastMCP.

### Fixed

- **Embedding no longer takes the CoreML path on macOS.** Every Chroma
collection now shares one `ONNXMiniLM_L6_V2` pinned to the CPU execution
provider, instead of each collection loading its own default model. The
CoreML build is the `Failed to create MLModel … .mlmodelc` ingest failure
that `doctor` had been surfacing from `hook-errors-*.log`.
- **`frame-link` is dispositioned.** Claude Code's new entry for a published
page — the Artifact URL plus the local `.html` it came from — is preserved
as a triaged unknown (recallable, so a promotion candidate beside `pr-link`)
instead of tripping the drift row on every `doctor` run.

---

## [1.0.1] — 2026-08-12

Three `doctor` rows told you the wrong thing. Found by dogfooding 1.0.0 on a
Expand Down
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@ When a user asks about past work:
## Deeper Tools (less common starting points)

Beyond the decision tree above: `get_session_timeline` with `tail` (the last N events, replaces get_latest_events), `find_episodes` with `episode_id` (full detail: referenced events, diff, post-fix file state), `list_projects` with `match` (fuzzy candidates with scored reasons — "which project did you mean?"), `list_plans` (browse plan-file writes), `get_stats` (store health), and `reconcile` (re-ingest drift) — **`reconcile` defaults to a dry run; pass `fix=true` to actually heal.**

## Codex sessions (1.1.0+)

Codex Desktop / CLI threads live in the same archive with `codex:`-prefixed session ids. This server lists and pages them (`list_sessions`, `get_session_timeline`) and `find_commits` sees commits made from Codex — but `recall` and semantic `search` only see a Codex session after `longhand codex-sync --semantic` has indexed it. For "what did I do in Codex" questions, use the `longhand-shared` server's keyword search (literal phrases; its session listing takes source="codex"), or tell the user to run the semantic sync.
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

# Longhand

**Using Codex too?** [Share one memory archive between Claude Code and Codex](docs/codex.md).

[![Longhand MCP server](https://glama.ai/mcp/servers/Wynelson94/longhand/badges/score.svg)](https://glama.ai/mcp/servers/Wynelson94/longhand)
[![PyPI version](https://img.shields.io/pypi/v/longhand?label=PyPI&color=blue)](https://pypi.org/project/longhand/)
![Python](https://img.shields.io/badge/python-3.10+-blue)
![License](https://img.shields.io/badge/license-MIT-green)
![Tests](https://img.shields.io/badge/tests-546%20passing-brightgreen)
![Tests](https://img.shields.io/badge/tests-580%20passing-brightgreen)
![Local](https://img.shields.io/badge/100%25-local-informational)
[![SafeSkill 93/100](https://img.shields.io/badge/SafeSkill-93%2F100_Verified%20Safe-brightgreen)](https://safeskill.dev/scan/wynelson94-longhand)

Expand Down Expand Up @@ -85,7 +87,7 @@ longhand analyze --all # fill in episodes + vectors whenever, safe to

Exact-text search, timelines, file history, and commit lookup all work after `--skip-analysis`. Semantic `recall` needs the `analyze --all` pass to complete. Typical throughput on an M-class Mac is ~1–2 sessions/sec for full analysis.

> *Status: v1.0.1 — stable, daily-driver tested, security-audited (zero critical findings), on PyPI, available as a Claude Code plugin. Validated against 433 real Claude Code sessions across 37 inferred projects (measured 2026-08-12). 546 unit tests passing.*
> *Status: v1.0.1 — stable, daily-driver tested, security-audited (zero critical findings), on PyPI, available as a Claude Code plugin. Validated against 433 real Claude Code sessions across 37 inferred projects (measured 2026-08-12). 580 unit tests passing.*

**Full docs:** [Longhand Wiki](https://github.com/Wynelson94/longhand/wiki) — getting started, CLI reference, MCP tools reference, architecture, and troubleshooting.

Expand Down Expand Up @@ -127,6 +129,8 @@ Longhand pins `chromadb<1.0` for **every** Python version, not just 3.14. The pi

**Windows: CI-tested, best-effort.** A `windows-latest × py3.12` leg runs on every PR and has gone green on every run since v0.13.0, but it is non-blocking and covers one Python version on GitHub's runners. That is honest evidence, not a support tier — Linux and macOS are the tested platforms. Windows bugs are welcome as issues; they just aren't release-blocking.

**Codex Desktop and Codex CLI** threads are captured into the same archive from 1.1.0 — see [Works with Codex](#works-with-codex).

---

## Compatibility
Expand Down Expand Up @@ -433,6 +437,21 @@ Both are non-blocking and run in one to two seconds. You don't have to think abo

---

## Works with Codex

Use Codex Desktop or the Codex CLI too? Longhand captures those threads into the same archive, so a question asked in Claude Code can be answered from work done in Codex, and the other way round.

```sh
pip install -U longhand
longhand codex-sync # capture every Codex thread on this machine
claude mcp add --scope user longhand-shared -- longhand shared-mcp # keyword search across both clients, from Claude
codex mcp add longhand -- longhand shared-mcp # the same server from Codex (Desktop: config.toml, see the docs)
```

From then on `reconcile --fix` captures new Codex threads too, so the scheduled reconciler keeps both clients current. Codex sessions are stored exact-record-only — verbatim and searchable, no model loaded — until `longhand codex-sync --semantic` makes them recallable. Threads Codex spawns for itself are skipped, UI mirrors are never stored twice, and unknown record shapes surface in `doctor` like any other drift. Setup, bounds, and the macOS launchd template: **[docs/codex.md](docs/codex.md)**.

---

## Architecture

```
Expand Down Expand Up @@ -528,7 +547,7 @@ Longhand is flat-cost: the cap is per-call, not per-corpus. Recalling across 10

---

546 unit tests passing. All 13 MCP tools stress-tested. Full security audit: zero critical findings, zero high findings. `~/.longhand/` created with 0700 permissions, all SQL parameterized, all inputs bounded. Dependencies: chromadb, typer, rich, pydantic, mcp.
580 unit tests passing. All 13 MCP tools stress-tested. Full security audit: zero critical findings, zero high findings. `~/.longhand/` created with 0700 permissions, all SQL parameterized, all inputs bounded. Dependencies: chromadb, typer, rich, pydantic, mcp.

---

Expand Down
159 changes: 159 additions & 0 deletions docs/codex.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Shared memory for Claude Code and Codex

Longhand keeps one archive for both clients. Claude Code sessions arrive
through the hooks you already have; Codex Desktop and Codex CLI threads (the
"rollouts" under `~/.codex/sessions`) arrive through `longhand codex-sync`.
Both land in `~/.longhand`, with Codex sessions namespaced `codex:<thread-id>`
so nothing collides. A question asked in either client can be answered from
work done in the other.

Requires Longhand 1.1.0 or newer.

## Set up in three commands

```sh
pip install -U longhand
longhand codex-sync # capture every Codex thread on this machine
longhand doctor # the "Codex capture" row confirms it
```

Then connect the shared keyword server to each client:

```sh
# Claude Code — alongside the existing `longhand` server
claude mcp add --scope user longhand-shared -- longhand shared-mcp

# Codex CLI
codex mcp add longhand -- longhand shared-mcp
```

Codex Desktop does not put `codex` on your PATH. Add the server to
`~/.codex/config.toml` instead and restart the app:

```toml
[mcp_servers.longhand]
command = "longhand"
args = ["shared-mcp"]

# Only if your archive is not at ~/.longhand:
[mcp_servers.longhand.env]
LONGHAND_DATA_DIR = "/Users/you/.longhand"
```

Use an absolute path to `longhand` (`which longhand`) when the desktop app's
PATH differs from your terminal's. If you relocated the archive with
`LONGHAND_DATA_DIR`, set the same value for both clients and for the capture
commands — one archive is the whole point.

## What each client sees

| | Claude Code `longhand` server | `longhand shared-mcp` |
| ------------------------------------------------- | ----------------------------- | -------------------------- |
| Lists and pages Codex sessions | yes | yes |
| Keyword search across both clients | no | yes (`search`, literal) |
| `recall` and semantic search over Codex sessions | after `codex-sync --semantic` | no |
| Commits made in Codex | yes (`find_commits`) | through `search` |
| Loads the embedding model | yes | never |

The shared server exposes four read-only tools: `list_sessions` (with
`source="codex"|"claude"` and `project` substring filters), `search`,
`get_session_timeline`, and `get_event_text`. Search matches literal phrases,
not meaning. It reads the same SQLite rows both clients write, never opens
Chroma, and never loads a model; long texts and raw records page in
8,000-character slices. A broad query on a large archive can hit the server's
instruction budget — narrow it to a session.

Codex sessions are captured exact-record-only by default (ingestion stage
`archived`): every message, reasoning summary, tool call, and output is stored
verbatim and searchable, but not embedded. `longhand codex-sync --semantic`
runs the full pipeline — embeddings, episodes, project inference — on captured
sessions so `recall` and semantic `search` see them. It is the remedy `doctor`
names for archived sessions; `analyze` is not, because it never embeds events.

## Keeping capture current

`longhand reconcile --fix` captures new or changed Codex rollouts along with
everything it already does for Claude transcripts, so the scheduled reconciler
(`longhand schedule install-reconciler`, every 30 minutes on macOS) keeps Codex
current with no extra setup. For an immediate capture run `longhand codex-sync`;
for a foreground loop run `longhand codex-sync --watch` (every 60 seconds until
interrupted).

### Faster capture with launchd (macOS)

`scripts/com.longhand.codex-sync.plist.template` is a ready-made user
LaunchAgent that runs `codex-sync` at login and every 60 seconds. Fill in the
interpreter that has Longhand installed and your home directory, then load it:

```sh
PY="$(command -v python3)" # must be the Python that has longhand installed
mkdir -p ~/.longhand/logs
sed "s|__PYTHON__|$PY|g; s|__HOME__|$HOME|g" \
scripts/com.longhand.codex-sync.plist.template \
> ~/Library/LaunchAgents/com.longhand.codex-sync.plist
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/com.longhand.codex-sync.plist
```

The job exits between scans, so `state = not running` with `last exit code = 0`
is normal. Inspect it and its capture reports with:

```sh
launchctl print "gui/$(id -u)/com.longhand.codex-sync"
tail -n 5 ~/.longhand/logs/codex-sync.log
```

launchd does not read your shell profile, so a relocated archive needs
`LONGHAND_DATA_DIR` in the plist's `EnvironmentVariables`. To stop capture
without deleting any archived history, `launchctl bootout "gui/$(id -u)"` the
plist and remove it.

On Linux, run `--watch` in a persistent terminal or schedule `longhand
codex-sync` with a systemd timer or cron. On Windows, `--watch` or Task
Scheduler.

## What is captured, and what is not

- **Threads you drove.** Codex also spawns threads for itself — its "guardian"
approval reviewer, for one — that re-quote the parent thread. `codex-sync`
skips them by default so every search hit appears once; `--include-subagents`
captures them.
- **Canonical items only.** Codex writes every message twice: once as a
canonical `response_item` and once as a UI `event_msg` mirror. The mirrors,
token accounting, and turn bookkeeping are skipped so nothing is stored
twice. Reasoning is stored when Codex provides a readable summary; encrypted
reasoning has no readable content and is skipped.
- **Bounds.** Per run: up to 50 sessions, each up to 16 MiB and 20,000 events.
Larger rollouts are reported as `deferred`, never partially imported. Raise
the bounds with `--limit`, `--max-file-kb`, and `--max-events`. These are
input bounds, not a memory ceiling — the default capture never loads a model.
- **Drift is never silent.** A record shape Longhand does not recognize is
preserved as an `unknown` event with its raw JSON intact, and surfaces in
`longhand doctor`'s "Transcript format" row as `response_item/<kind>` or
`event_msg/<kind>`. `tests/fixtures/codex_shapes/` regression-gates every
known shape.
- **Shell commands are understood; scripts are text.** Commands run through
Codex's shell tools, and the `cmd:` literals inside its `exec` scripts, feed
error detection and git extraction, so commits made from Codex show up in
`find_commits` and `longhand git-log`. Patches and the rest of a script stay
recorded text — they are not translated into Claude-style file replay.
- **Redaction and locks apply.** Opt-in secret redaction covers Codex records.
Capture runs under the same ingest lock as the hooks; a Claude hook that
fires while a capture holds the lock skips that turn, and the reconciler
heals it.

This is shared, retrievable history — not a transfer of a model's live
context. Only locally saved rollouts are available.

## Verify

```sh
python3 -m pytest tests/test_codex.py tests/test_codex_shapes.py -q
longhand codex-sync --dry-run
```

The tests use synthetic records and a sanitized fixture of real rollout
shapes; they cover cross-client storage and keyword retrieval, stable IDs,
redaction, subagent skipping, the skip rules, git extraction, bounded capture,
and reconcile's capture path.

Codex MCP configuration: [official documentation](https://learn.chatgpt.com/docs/extend/mcp).
Loading
Loading