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
34 changes: 30 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ in full in `plugins/past/README.md`; this file is the rules the code keeps, for
```
Codex
hooks/hooks.json five hooks every one passes --host codex
.mcp.json one MCP server past_recall, the script's mcp mode
skills/* six skills every command passes --host codex
|
bin/past-hook.mjs <mode> --host codex
|
session start brief recall into context; sends idle sessions left behind
every prompt prompt recall into context
every prompt prompt recall into context: ten memories, the block naming the tool
on demand mcp past_recall, the model's own recall, on a local MCP server
every turn stop stamps the activity file, keeps one waiter on the session
compaction precompact sends the session so far
session end end makes sure a waiter runs
Expand All @@ -34,6 +36,7 @@ Codex
plugins/past/
.codex-plugin/plugin.json the manifest: names the hooks file and the skills folder
hooks/hooks.json SessionStart, UserPromptSubmit, Stop, PreCompact, SessionEnd
.mcp.json past_recall: the script's mcp mode, started by Codex
bin/past-hook.mjs the whole connector, one file, no dependencies
skills/ search, status, connect, cut, ingest, backfill
LICENSE the same license: an install copies this folder and nothing else
Expand Down Expand Up @@ -109,6 +112,16 @@ plugins/past/
text from the sessions it sends, so a changed header would change how old sessions render and
send them all again (rule 9). The hooks file keeps its status message, "Recalling from past", for
the reason it is written once (see "The hooks file is written once").
13. **The recall tool speaks JSON-RPC alone on stdout.** `mcp` mode is a stdio MCP server, started by
Codex from `.mcp.json` and alive as long as the session. Nothing else may print there: like
every mode it swallows its own failures, and a failed recall is a tool result that says so
(`isError`), never a crash. A call is one recall, redacted as every query is (rule 5), and reads
the same `~/.past/config.json`. Its page (up to a hundred memories within 2,000 tokens, each with
its moment, a "replaced" mark and the source excerpts) and the prompt hook's ten memories were
set by measurement: change them with a measurement. The block before a prompt names the tool in
one line, which is how the model learns to call it; the line rides inside a block the script
already drops from what it sends (rule 12), so it changes nothing that travels. Adding the server
touched neither `hooks/hooks.json` nor any hook's definition, so no person's trust is reset.

## Beside other past.dev plugins

Expand Down Expand Up @@ -182,11 +195,21 @@ activity files in `activity/codex/` and its hook stamp in `hosts/codex.json`. Th
drawn from the item, `label` names the session, `metadata` carries `conversationId`,
`sessionId`, `project`, `gitBranch`, `sitting`.
- `POST /api/v1/recall` — **`identity` is required**. Recall stays off until `$past:connect` sets
one.
one. The prompt hook sends `limit` 10; the tool sends `limit` 100 and `maxTokens` 2,000, and reads
its sources' `excerpts` and `metadata.localTime` and its artifact's `supersededAt` besides each
result's `content` and `occurredAt`.
- `audience` on ingest — a slug; absent means the whole project. `connect --audience` checks it
with `GET /api/v1/audiences/{slug}`.
- Codex gives a hook `session_id`, `transcript_path` and `cwd`, the prompt as `prompt`, and takes
`hookSpecificOutput.additionalContext` at session start and on a prompt.
- Codex starts a plugin's MCP server from the `mcpServers` file its manifest names. It expands
`${PLUGIN_ROOT}` in a hook's command but not in an MCP server's arguments, so `.mcp.json` names the
script relative to the plugin's root and sets `cwd` to `.`, which Codex resolves to that root. The
server's environment is Codex's short default list plus the names in `env_vars`, so `PAST_API_KEY`,
`PAST_IDENTITY` and `PAST_API_URL` are listed there. `default_tools_approval_mode: "approve"`
approves the one read-only tool in advance: `codex exec` approves nothing, and a person would be
asked at every call. The server runs outside the sandbox, so the tool reaches past.dev where a
skill's command would need an escalation.

If any of these contracts changes, this file changes with it.

Expand All @@ -203,7 +226,9 @@ node plugins/past/bin/past-hook.mjs backfill --host codex # the estimate, send
For the hooks and the waiter, point a throwaway home at a local stand-in for the API, so nothing
touches your real `~/.past` or a real project: `HOME=/tmp/past-test` with a `config.json` whose
`apiUrl` is `http://127.0.0.1:<port>`, a few lines of Node answering `POST /api/v1/ingest/batch`, and the
mode under test fed its stdin JSON by hand.
mode under test fed its stdin JSON by hand. For the tool, pipe JSON-RPC lines (`initialize`,
`tools/list`, `tools/call`) into `node plugins/past/bin/past-hook.mjs mcp` in the same home, with a
stand-in answering `POST /api/v1/recall`, and check what the request carried.

On a real Codex:

Expand All @@ -214,7 +239,8 @@ codex exec --dangerously-bypass-hook-trust "a prompt" # runs the hooks for t
```

`codex exec` waits on its input unless it is closed (`< /dev/null`), and fires `SessionEnd` at the
end of every run. `CODEX_HOME=<a folder>` gives Codex a home of its own, enough for everything that
end of every run. `codex mcp list` shows the plugin's server, `past`, with its working folder
resolved to the installed plugin. `CODEX_HOME=<a folder>` gives Codex a home of its own, enough for everything that
calls no model: installing, and asking the app server what it thinks of the hooks (`codex
app-server`, then `hooks/list`), which is how to see a hook's key, its hash and whether it is
trusted. The ChatGPT desktop app carries the same `codex` inside its bundle, for a machine that has
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ reasons behind them, what was already tried and dropped.
```

That block is what Codex reads before your prompt. It arrives on its own: Codex does not have to
decide to look anything up.
decide to look anything up. When a request needs more than the block holds, Codex asks for it
itself, with the `past_recall` tool the plugin serves.

It runs in the Codex CLI and in local threads of the ChatGPT desktop app, on macOS and Linux.

Expand Down Expand Up @@ -69,12 +70,14 @@ environment instead of step 4. The desktop app reads them from a login shell, so
```text
session opens recall what past.dev knows about this project ──► into context
every prompt recall what past.dev knows about this prompt ──► into context
any time Codex asks past.dev itself, with past_recall ──► into context
every reply nothing is sent; the session is marked active
compaction the session so far is sent, before its detail is summarised away
session over the session is sent: its Codex process exited, or it was quiet for 12 hours
```

Five hooks do this, and one script runs them all. A recall that has not answered within 4 seconds
Five hooks and one tool do this, and one script runs them all. The block before a prompt holds
up to ten memories; the tool returns up to a hundred, within 2,000 tokens. A recall that has not answered within 4 seconds
(8 at session start) is dropped, so it never holds a prompt up, and a prompt shorter than 12
characters ("yes", "go on") recalls nothing.

Expand Down
3 changes: 2 additions & 1 deletion plugins/past/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"name": "past",
"version": "0.1.0",
"version": "0.2.0",
"description": "Your Codex sessions become memory, and every session starts briefed.",
"author": { "name": "past.dev", "url": "https://past.dev" },
"homepage": "https://github.com/pastdotdev/codex-plugin",
"repository": "https://github.com/pastdotdev/codex-plugin",
"license": "MIT",
"keywords": ["memory", "recall", "context", "past.dev"],
"hooks": "./hooks/hooks.json",
"mcpServers": "./.mcp.json",
"skills": "./skills/",
"interface": {
"displayName": "past.dev",
Expand Down
11 changes: 11 additions & 0 deletions plugins/past/.mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"mcpServers": {
"past": {
"command": "node",
"args": ["bin/past-hook.mjs", "mcp"],
"cwd": ".",
"env_vars": ["PAST_API_KEY", "PAST_IDENTITY", "PAST_API_URL"],
"default_tools_approval_mode": "approve"
}
}
}
11 changes: 9 additions & 2 deletions plugins/past/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ is nothing to do: `$past:status` shows it.
| Hook | When | What |
|---|---|---|
| `SessionStart` | a session opens | recalls this project's history and puts it in context |
| `UserPromptSubmit` | every prompt | recalls against the prompt and puts it in context |
| `UserPromptSubmit` | every prompt | recalls the ten memories closest to the prompt and puts them in context |
| `Stop` | after each reply | stamps the session's activity; sends nothing itself |
| `PreCompact` | the context is about to be compacted | sends the session so far, before the detail is summarised away |
| `SessionEnd` | Codex unloads a session | makes sure the session will be sent; sends nothing itself |
Expand Down Expand Up @@ -93,7 +93,14 @@ Codex plugins carry skills rather than commands. Name one with `$`:
| `$past:cut [session]` | print exactly what would be sent from this session, or from one you name |
| `$past:ingest [session]` | send one session now |
| `$past:backfill` | list and estimate, then send, the Codex sessions already on this machine |
| `$past:search` | search past.dev on demand; Codex may pick it by itself when you ask about history |
| `$past:search` | search past.dev from the terminal, for you; Codex uses its `past_recall` tool for itself |

**And a recall tool.** The plugin also serves `past_recall`, on a small MCP server of its own
(`.mcp.json`, run by the same script). The block before a prompt holds what was recalled for it;
when the request depends on something that block does not hold, Codex calls `past_recall` with a
question of its own and reads up to a hundred dated memories, within 2,000 tokens. Each call is one
recall. The server runs outside Codex's sandbox, so it reaches past.dev without asking, and it only
reads, so its tool is approved in advance. `codex mcp list` shows it as `past`.

Codex runs a command in a sandbox with no network. The skills that reach past.dev ask for the
permission, and Codex asks you. `connect`, `ingest` and `backfill` run only when you name them.
Expand Down
Loading
Loading