Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
1e831ea
Add archive entry types and transcript reader port
prineycom Nov 7, 2026
5974284
Expose the archive module types through its public entry
prineycom Nov 7, 2026
0b46e59
Add a forward JSONL walker from a byte offset for transcript archives
prineycom Nov 7, 2026
e60088b
Read Claude transcripts into archive entries from an offset
prineycom Nov 7, 2026
14302e3
Read Pi sessions into archive entries from an offset
prineycom Nov 7, 2026
7ac2969
Test the Claude and Pi archive readers on synthetic transcripts
prineycom Nov 7, 2026
acca34b
Merge MP-95: Архив разговоров: читатели транскриптов Claude и Pi
prineycom Nov 7, 2026
70da6fd
Resolve the archive database path beside the state file
prineycom Nov 7, 2026
fa4cffa
Add the archive database schema with full-text index
prineycom Nov 7, 2026
95eb227
Open and initialize the archive database
prineycom Nov 7, 2026
dc89afc
Add the archive store port and the scope key binding rule
prineycom Nov 7, 2026
5e3e306
Store archive reads in SQLite and wire the archive factory
prineycom Nov 7, 2026
b9f5872
Test the archive database, store and key binding
prineycom Nov 7, 2026
8997034
Check that backup leaves the archive database out
prineycom Nov 7, 2026
624f6e0
Merge MP-96: Архив разговоров: модуль archive и база archive.sqlite3
prineycom Nov 7, 2026
b7da3c9
Derive transcript folder names and native ids from paths
prineycom Nov 7, 2026
2eedc71
Count archive sessions, messages and bound keys
prineycom Nov 7, 2026
e4ec8dc
Add the per-file archive import use case
prineycom Nov 7, 2026
58fd1ee
List top-level transcript files of a folder
prineycom Nov 7, 2026
ebd4d5c
Add archive import and status application commands
prineycom Nov 7, 2026
0c5ae64
Test archive import and status on disposable state
prineycom Nov 7, 2026
9ba3364
Parse mypi archive import and status on the CLI
prineycom Nov 7, 2026
ab2a356
Document archive import and status syntax
prineycom Nov 7, 2026
bece0ea
Test archive import and status through the CLI process
prineycom Nov 7, 2026
279a17b
Merge MP-97: Архив разговоров: догрузка и статус — mypi archive impor…
prineycom Nov 7, 2026
ddd4f24
Quote archive search words and parse archive dates
prineycom Nov 7, 2026
98f732d
Read archive hits, session lists, windows and pages from SQLite
prineycom Nov 7, 2026
06b2d72
Search the archive and show its messages in the archive use case
prineycom Nov 7, 2026
321b260
Add archive_search and archive_show application commands
prineycom Nov 7, 2026
4121002
Parse mypi archive search and show on the CLI
prineycom Nov 7, 2026
e4a7a36
Test archive search and show on disposable state
prineycom Nov 7, 2026
f586274
Expose archive_search and archive_show as read-only MCP tools
prineycom Nov 7, 2026
67f2f31
Allow the archive tools in Claude and expose archive_search directly …
prineycom Nov 7, 2026
4d0a974
Point the mypi howto at the conversation archive
prineycom Nov 7, 2026
749612c
Document the archive MCP tools
prineycom Nov 7, 2026
b6ec354
Test archive search and show through the MCP and CLI processes
prineycom Nov 7, 2026
39d67fd
Name archive_search among the direct Pi tools in the README
prineycom Nov 7, 2026
fc70c10
Merge MP-98: Архив разговоров: поиск — CLI, MCP archive_search/archiv…
prineycom Nov 7, 2026
9a69260
Spell out archive search scope so global is not read as all sessions
prineycom Nov 7, 2026
b669d3d
Merge MP-98: scope description fix from the stand
prineycom Nov 7, 2026
f42762a
Parse Stop and SessionEnd hook input for archive capture
prineycom Nov 7, 2026
6a8d169
Add the archive_capture command that records one transcript file
prineycom Nov 7, 2026
79a4237
Parse mypi archive capture from a Claude hook or an explicit file
prineycom Nov 7, 2026
8fe7a6d
Capture the Claude transcript into the archive on Stop and SessionEnd
prineycom Nov 7, 2026
e0fc57d
Keep archive_capture out of the MCP tools
prineycom Nov 7, 2026
2868da5
Test archive capture through the CLI process with hook stdin
prineycom Nov 7, 2026
de743b2
Document the archive capture hooks and command
prineycom Nov 7, 2026
fa26128
Merge MP-99: Архив разговоров: живой захват Claude
prineycom Nov 7, 2026
8c3bbe2
Type the Pi session file and the agent_settled context in the extension
prineycom Nov 7, 2026
7c7f026
Capture the Pi session file into the archive after every settled turn
prineycom Nov 7, 2026
10e28cd
Document the Pi archive capture on agent_settled
prineycom Nov 7, 2026
6960940
Merge MP-100: Архив разговоров: живой захват Pi и session_info
prineycom Nov 7, 2026
2a302a0
Add a forget method to the archive store that deletes matching messages
prineycom Nov 7, 2026
5c9b1c3
Add the archive forget use case with its filter checks
prineycom Nov 7, 2026
40c7f6e
Add the archive_forget application command
prineycom Nov 7, 2026
00eae52
Parse mypi archive forget in the CLI
prineycom Nov 7, 2026
b5dc81e
Test archive forget on a disposable archive
prineycom Nov 7, 2026
ff1dd77
List archive forget among the CLI-only maintenance commands in the howto
prineycom Nov 7, 2026
4ea11c1
Describe the conversation archive and its retention in the architecture
prineycom Nov 7, 2026
46fd320
Document the conversation archive commands and retention in the CLI g…
prineycom Nov 7, 2026
f24829d
Record the conversation archive in the changelog
prineycom Nov 7, 2026
caf4c4c
Merge MP-101: Архив разговоров: политика хранения, archive forget, до…
prineycom Nov 7, 2026
85d9bdd
Point agents to the journal before the archive for past decisions
prineycom Nov 7, 2026
b6fba0e
Add the retro skill for retrospectives from the journal and archive
prineycom Nov 7, 2026
85613de
Link the retro skill for Claude Code
prineycom Nov 7, 2026
afb905c
List the retro skill in the README storage layout
prineycom Nov 7, 2026
fbbf5e0
Record the retro skill and the journal-first howto clause in the chan…
prineycom Nov 7, 2026
c4ea494
Merge MP-102: Архив разговоров: сценарии в инструкциях агента
prineycom Nov 7, 2026
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
20 changes: 19 additions & 1 deletion .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
"mcp__mypi__sessions",
"mcp__mypi__open_session",
"mcp__mypi__inbox",
"mcp__mypi__send_message"
"mcp__mypi__send_message",
"mcp__mypi__archive_search",
"mcp__mypi__archive_show"
]
},
"hooks": {
Expand All @@ -39,6 +41,11 @@
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/dist/src/cli/main.js\" turn end --pid \"$PPID\" >/dev/null 2>&1",
"timeout": 10
},
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/dist/src/cli/main.js\" archive capture --agent claude --hook >/dev/null 2>&1",
"timeout": 10
}
]
}
Expand All @@ -54,6 +61,17 @@
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/dist/src/cli/main.js\" archive capture --agent claude --hook >/dev/null 2>&1",
"timeout": 10
}
]
}
]
}
}
1 change: 1 addition & 0 deletions .claude/skills/retro
11 changes: 7 additions & 4 deletions .pi/extensions/mypi-lib/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface ExtensionContext {
onTerminalInput(handler: (data: string) => { consume?: boolean; data?: string } | undefined): () => void;
};
isIdle(): boolean;
sessionManager: { getSessionId(): string; getSessionName(): string | undefined; getHeader(): { parentSession?: string } | null };
sessionManager: { getSessionId(): string; getSessionName(): string | undefined; getSessionFile(): string | undefined; getHeader(): { parentSession?: string } | null };
}
interface McpServer {
command: string; args: string[]; cwd: string; env: Record<string, string>;
Expand All @@ -29,7 +29,7 @@ export interface ExtensionAPI {
on(name: 'agent_start', handler: () => void): void;
on(name: 'before_agent_start', handler: (event: { systemPromptOptions: { sections: Record<string, string>; toolGuidelines?: Record<string, string[]> } }) => void): void;
on(name: 'agent_end', handler: (event: { messages: { role: string; content?: unknown }[] }) => void): void;
on(name: 'agent_settled', handler: () => Promise<void>): void;
on(name: 'agent_settled', handler: (event: unknown, ctx: ExtensionContext) => Promise<void>): void;
on(name: 'ui_prompt_start', handler: (event: { kind: string; title?: string }) => Promise<void>): void;
}

Expand Down Expand Up @@ -123,7 +123,7 @@ export function install(pi: ExtensionAPI, root: string, serve: ServeEvents, runt
pi.registerMcpServer('mypi', {
command: 'node', args: [server], cwd: root, env: { PI_SESSION_ID: session },
description: 'Local mypi tools for memory, projects, tasks and history. No automatic initialization or writes.',
exposure: 'codemode', toolExposure: { projects: 'direct', warmup: 'direct' },
exposure: 'codemode', toolExposure: { projects: 'direct', warmup: 'direct', archive_search: 'direct' },
});
registered = session;
}
Expand Down Expand Up @@ -179,14 +179,17 @@ export function install(pi: ExtensionAPI, root: string, serve: ServeEvents, runt
.filter((part): part is { type: 'text'; text: string } => part?.type === 'text' && typeof part.text === 'string')
.map(part => part.text).join('\n');
});
pi.on('agent_settled', async () => {
pi.on('agent_settled', async (_event, ctx) => {
const report = parentTurn, reply = last, to = parentFrom;
parentTurn = false;
parentFrom = undefined;
last = '';
if (key !== undefined && report) await pi.exec('node', [cli, 'event', 'emit', '--from', key, ...(to === undefined ? [] : ['--to', to]),
'--message=' + clip(reply)], { cwd: root, timeout: 5000 })
.catch(() => undefined);
const file = ctx.sessionManager.getSessionFile();
if (file) await pi.exec('node', [cli, 'archive', 'capture', '--agent', 'pi', '--file', file], { cwd: root, timeout: 5000 })
.catch(() => undefined);
});
pi.on('ui_prompt_start', async event => {
if (key !== undefined && parentTurn) await pi.exec('node', [cli, 'event', 'emit', '--from', key, ...(parentFrom === undefined ? [] : ['--to', parentFrom]),
Expand Down
16 changes: 14 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,13 @@ logging or Herdr tab is a prerequisite. Follow the user's requested scope and se
`mypi event emit` with the registered key and `--to` the sender of that frame,
only in a turn that a message in the mypi parent-message frame started through
its socket; the event goes to that sender when it is a parent of the task, else
to the task's project. From `session_start` to
to the task's project. After that, on every `agent_settled` whose session has a
file (`getSessionFile()`; none under `--no-session`) it runs `mypi archive capture
--agent pi --file <that file>` with a 5-second limit and ignores its result: the
file is recorded into the conversation archive from its saved offset and bound
to a key by the session's `sessions` row, else by its `session_info` name
(`--name <key>` from /open); user messages that start with a mypi frame are
stored with origin `frame` and hidden from archive search. From `session_start` to
`session_shutdown` it holds `$XDG_RUNTIME_DIR/mypi/<key>-<pid>.sock` (0600),
one socket per Pi process so a displaced session never removes the new one's,
and registers its path; `mypi event emit` then sends all undelivered events of the
Expand Down Expand Up @@ -188,7 +194,13 @@ logging or Herdr tab is a prerequisite. Follow the user's requested scope and se
`elicitation_dialog`, runs `mypi event emit --waiting --hook`; both create an
event only when the current turn was started by a parent message (`turnOrigin`
`peer` with the mypi parent-message frame in the transcript), and discard their
output.
output. A second Stop command and the SessionEnd hook (every reason, including
/clear and exit) run `mypi archive capture --agent claude --hook`: it reads
`session_id` and `transcript_path` from stdin and records that transcript file
into the conversation archive from its saved offset, so an answer the file had
not yet written at Stop arrives on the next Stop or at SessionEnd. Without a
transcript it writes nothing; its output is discarded, and its failure (exit 1,
never 2) blocks neither Stop nor exit.
- When the MCP tool list changes, update the allow list in the same change; a fast
test compares them. Do not add `enableAllProjectMcpServers`,
`enabledMcpjsonServers` or `crossSessionInbound` to the tracked settings: project
Expand Down
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ match the GitHub releases.

Upgrading: schema 15, run `mypi db init` once after the update (back up the database
first); reload or restart Claude Code and Pi sessions so the new roles and the Pi
extension take effect.
extension take effect. Run `mypi archive import` once to fill the conversation archive
from existing transcripts.

### Added

- Conversation archive (MP-85: MP-95–MP-102): a local SQLite archive `archive.sqlite3` beside the state DB (0600, own `user_version`, not part of the state schema) with the human turns and final answers of Claude and Pi sessions — readers for both transcript formats skip reasoning, tool calls and subagents and keep parent messages and mypi frames with their origin; each session is bound to a scope key through `sessions`, else its registered name. `mypi archive import` fills it from the engine root's transcript folders of both agents (or `--dir … --agent …`) from each file's saved offset, `mypi archive status` counts it; live capture runs `mypi archive capture` from Claude's Stop and SessionEnd hooks and from the Pi extension after each settled turn (`agent_settled`). MCP `archive_search` (FTS5, ranked snippets, scope, agent and date filters; Pi exposes it directly) and `archive_show` (around a hit or a session page) read it, also as CLI `mypi archive search|show`, and a howto line points agents to them, the journal first for past decisions. `mypi archive forget --session|--key|--before` deletes messages on the user's explicit request and keeps the session offsets so the text does not come back. The archive is kept indefinitely, is never part of backup, `--publish` or restore, and needs no `db init`: the first import or capture creates it.
- Skill `retro` (MP-102): a retrospective of a project, organization or task for a period — the journal as the skeleton, `archive_search` without a query to list the period's sessions, `archive_show` page by page, one session per clean-context subagent where the agent has subagents, topic searches for the gaps, and one context note with the sources; the archive is never changed. Claude Code reaches it through `.claude/skills/retro`, Pi through `.pi/skills`.
- Journal facts of the engine (MP-88): five event types — `worktree_created` (task scope, `task worktree` when it creates the worktree: `worktree <path>, branch <branch>, base <integration>`), `session_opened` (task, project or organization scope, `/open` when it starts or resumes a session: `started <agent>` / `resumed <agent>`, with `, worktree <path>` for a task; nothing on focus), `message_sent` (the recipient's scope, every accepted parent message: `message <id> from <parent>: <first line, up to 120 characters>`), `title_changed` (task scope, a task rename; the text `Title: "a" → "b". <reason>` is unchanged) and `clone_prepared` (project scope, a checkout cloned, initialized or adopted by `/open`: the same text as its `notes` line). The list lives once in `EVENT_TYPES`; `journal read --type`, MCP `journal` and `GET /v1/journal?type=` accept the new types. A type other than note is accepted only in the scopes of its fact. `mypi project remove` now refuses a project that was opened (`session_opened`) or cloned by `/open` (`clone_prepared`), because those entries are project-scope journal rows and journal rows block removal.
- Journal in the warmup (MP-91): a project warmup ends its tail with «Журнал» — the number of journal entries of the project and all its tasks with the local date of the newest one («Записей в журнале проекта нет.» when none) — and a task warmup with the task's last five entries in the start message's line format (date, time, type, text up to 300 characters, artifacts; «Записей в журнале задачи нет.» when none); both name the `journal` call with `limit` 20 and its CLI form. Over the budget a task warmup drops the shown entries from the oldest before memory facts, and after memory facts clips the end of its card with a pointer to `context_read`, so a long card no longer pushes the Claude hook past 10,000 characters; the project line, the task heading with its count and the loader are never cut. The counts come from one SQL statement of the journal store (`stats`, `last`), not from a scope-resolving read, so the Claude hook stays fast. The task start message is unchanged.
- Organization scope and chat (MP-78): the bare organization slug (`tb-ff`) is a session key. `/open tb-ff` (MCP `open_session`, CLI `mypi open`) opens, focuses or resumes the organization chat in the Herdr tab `org-tb-ff` of the workspace `tb-ff`, in the engine root, with no clone or worktree; `global`, `all`, an unknown organization and an organization without registered projects are refused. The organization chat leads the tasks of all registered projects of its organization: it opens their task chats and sends them parent messages directly. Its warmup (role `org`) carries global and organization memory, one line per project and the events addressed to it; its start message lists each project's open tasks by status. Pi completes `/open` with the organization's projects and their open tasks; the pipeline skills accept the organization scope (`mp-task scope` adds `org` and `codes`).
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ codemode guideline that `searchTools()`, `describeTool()` and `describeNamespace
return Promises to be awaited. The key is the one saved for
this conversation, else `MYPI_SCOPE` of the Pi process, else `global`. If warmup fails, the section states the error, Pi shows
a warning and the MCP tools stay available. Tool schemas use the configured exposure
(mostly codemode; `projects` and `warmup` are direct).
(mostly codemode; `projects`, `warmup` and `archive_search` are direct).

Start Pi through `mise exec -- pi` so `node` on PATH is Node 24; Pi itself may be a
standalone executable rather than Node. `pi -ne` starts without extensions. A
Expand Down Expand Up @@ -466,7 +466,7 @@ mypi/
├── test/ # fast and boundary
├── scripts/ # bounded build/checks
├── integrations/prompts/ # warmup role and how-to texts
├── integrations/skills/ # built-in skills: open, inbox, project-management
├── integrations/skills/ # built-in skills: open, inbox, project-management, retro
├── .pi/ # tracked Pi configuration
│ ├── extensions/mypi.ts # MCP registration and warmup per session
│ └── skills -> ../integrations/skills
Expand Down
10 changes: 9 additions & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ Git, including symlink aliases; created parent directories 0700 and DB 0600.

The live session registry is not state: $XDG_RUNTIME_DIR/mypi/sessions/<key>.json, owner-only, one record per live agent process; a record is live only while its pid and process start match /proc; reads skip dead records and the next registration, unregistration or forget deletes them.

The conversation archive is not state either: `archive.sqlite3` beside `state.sqlite3` (same directory, refused inside the engine or context Git like the DB), file 0600 in a 0700 directory, its own `user_version` and migrations (`src/infrastructure/database/archive-migrations/`), three tables `archive_sessions` (one row per agent and native id with the key, its source and the read offset `bytes_read`), `archive_messages` and the FTS5 index `archive_fts` kept in step by triggers. It holds only the human turns and final answers that the readers in `src/infrastructure/transcript/` take from Claude and Pi transcripts — no reasoning, tool calls or subagents — bound to a scope key through the `sessions` row, else a registered session name, else unbound. `archive import` and live capture (Claude Stop and SessionEnd hooks, the Pi extension on `agent_settled`) fill it from each file's saved offset; `archive_search` and `archive_show` read it; the key is a search filter, not an ACL. It is kept indefinitely with no age or size limit and nothing to configure; text is stored as written, without redaction or publication. Only `mypi archive forget` deletes from it; deleting the whole file is safe: the next import or capture recreates it and rereads every native transcript that still exists.

Files own knowledge and original source:

```text
Expand Down Expand Up @@ -153,6 +155,8 @@ Stop old writers before cutover. Import rejects changed or corrupt input and ret
legitimate duplicate entries. Imported plain-path artifacts keep fingerprints that
require unchanged, published files; reconcile dirty legacy files separately.

Journal and archive: the journal keeps short scoped append-only facts and outcomes — what was decided or done, under which key, with which artifact; the conversation archive keeps the verbatim human turns and final answers, bound to a key through `sessions`. A journal entry may point to a conversation (session key and time) but never quotes it; the archive never replaces a status or an outcome entry. The warmup and the start message stay on the journal and `events`; the archive is reached only through search.

Selection: global only global; org includes projects/tasks; project includes its
tasks; task only itself; all is explicit. Relations resolve through DB; relevant
months are selected and filtered before the overall sort/limit.
Expand Down Expand Up @@ -234,7 +238,11 @@ after releasing the DB writer lock. Publication can be partial, not cross-store
older binary snapshots remain in Git history. Restore from a selected commit in a
local clone verifies the snapshot and references before installing into absent targets.
Git is not whole-system backup and cannot restore unsaved data. Scheduling, retention,
automatic network sync and personal initialization require explicit action. See [M1-CLI](M1-CLI.md).
automatic network sync and personal initialization require explicit action. The conversation
archive `archive.sqlite3` is outside backup, `backup --publish` and restore: a backup
snapshots only `state.sqlite3` and the context bundle, and restore accepts only those; the
archive is a local copy of native transcripts and `mypi archive forget` is its only deletion.
See [M1-CLI](M1-CLI.md).

## 8. Verification and future execution

Expand Down
Loading
Loading