@agfpd/iapeer-memory 0.1.13 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/cli.ts +12 -0
- package/src/commands/init.ts +105 -38
- package/src/commands/provision-peer.ts +172 -0
- package/src/commands/uninstall.ts +60 -23
- package/src/commands/update.ts +118 -47
- package/src/commands/verify.ts +110 -5
- package/src/fleet.ts +72 -7
- package/src/paths.ts +5 -0
- package/src/slot.ts +67 -22
- package/src/surfaces/claude.ts +494 -0
- package/src/surfaces/codex.ts +155 -0
- package/src/surfaces/lock.ts +72 -0
- package/src/surfaces/sweep.ts +170 -0
- package/src/templates/skills.ts +196 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-wide provision lock — the iapeer v1.2 contract obliges the provision
|
|
3
|
+
* command to TOLERATE PARALLEL CALLS (the locking the plugin manager used to
|
|
4
|
+
* give moved to the provider; §7 requirement 3). The core may fire
|
|
5
|
+
* provision-peer concurrently (peer births race sweeps); two unsynchronised
|
|
6
|
+
* read-merge-writes of the SAME settings.json would lose one writer's keys.
|
|
7
|
+
*
|
|
8
|
+
* Form: mkdir-based exclusive lock (atomic on every POSIX fs, no flock in
|
|
9
|
+
* Bun's stable API). One lock for the whole host — provision bodies are
|
|
10
|
+
* milliseconds of file I/O, serialising them is simpler and strictly safer
|
|
11
|
+
* than per-cwd granularity. Stale detection: a lock directory older than
|
|
12
|
+
* STALE_MS belongs to a crashed run — broken and re-taken (provision is
|
|
13
|
+
* idempotent by contract, a double-run repairs, never corrupts).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
|
|
19
|
+
const RETRY_MS = 50;
|
|
20
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
21
|
+
export const STALE_MS = 120_000;
|
|
22
|
+
|
|
23
|
+
export type LockResult<T> =
|
|
24
|
+
| { acquired: true; result: T }
|
|
25
|
+
| { acquired: false; detail: string };
|
|
26
|
+
|
|
27
|
+
function tryTake(lockDir: string): boolean {
|
|
28
|
+
try {
|
|
29
|
+
fs.mkdirSync(lockDir); // atomic: EEXIST when held
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function breakIfStale(lockDir: string): void {
|
|
37
|
+
try {
|
|
38
|
+
const stat = fs.statSync(lockDir);
|
|
39
|
+
if (Date.now() - stat.mtimeMs > STALE_MS) fs.rmdirSync(lockDir);
|
|
40
|
+
} catch {
|
|
41
|
+
// raced away or unreadable — the next tryTake decides
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function withProvisionLock<T>(opts: {
|
|
46
|
+
stateDir: string;
|
|
47
|
+
fn: () => T;
|
|
48
|
+
timeoutMs?: number;
|
|
49
|
+
}): LockResult<T> {
|
|
50
|
+
const lockDir = path.join(opts.stateDir, "provision.lock.d");
|
|
51
|
+
fs.mkdirSync(opts.stateDir, { recursive: true });
|
|
52
|
+
const deadline = Date.now() + (opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
53
|
+
while (!tryTake(lockDir)) {
|
|
54
|
+
breakIfStale(lockDir);
|
|
55
|
+
if (Date.now() >= deadline) {
|
|
56
|
+
return {
|
|
57
|
+
acquired: false,
|
|
58
|
+
detail: `provision lock busy for ${Math.round((opts.timeoutMs ?? DEFAULT_TIMEOUT_MS) / 1000)}s (${lockDir}) — another provision hung? stale locks self-break after ${STALE_MS / 1000}s`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
Bun.sleepSync(RETRY_MS);
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
return { acquired: true, result: opts.fn() };
|
|
65
|
+
} finally {
|
|
66
|
+
try {
|
|
67
|
+
fs.rmdirSync(lockDir);
|
|
68
|
+
} catch {
|
|
69
|
+
// already gone (stale-broken by a peer) — nothing to release
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fleet-wide surfaces sweep — the package's own rail over fleet.json
|
|
3
|
+
* (ADR-009 v1.2). The core's birth-hook covers NEWBORNS via the slot's
|
|
4
|
+
* provision command; everything fleet-wide (init coverage of the existing
|
|
5
|
+
* fleet, update's «всё на местах» duty, verify --repair self-healing) walks
|
|
6
|
+
* the fleet map HERE — peer × session-runtime, claude and codex forms.
|
|
7
|
+
*
|
|
8
|
+
* Session runtimes are exactly {claude, codex}: telegram/notifier and other
|
|
9
|
+
* infra runtimes carry no session config surfaces. A peer entry without a
|
|
10
|
+
* runtimes array (pre-v1.2 map) is SKIPPED and reported — the next map
|
|
11
|
+
* re-write (same command) picks it up.
|
|
12
|
+
*
|
|
13
|
+
* The caller holds the provision lock around the WHOLE sweep (one
|
|
14
|
+
* acquisition, not per-peer — the sweep body is pure file I/O).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
import {
|
|
19
|
+
checkClaudePeer,
|
|
20
|
+
provisionClaudePeer,
|
|
21
|
+
unprovisionClaudePeer,
|
|
22
|
+
type SurfaceOutcome,
|
|
23
|
+
} from "./claude.js";
|
|
24
|
+
import { checkCodexPeer, provisionCodexPeer, unprovisionCodexPeer } from "./codex.js";
|
|
25
|
+
import type { FleetPeer } from "../fleet.js";
|
|
26
|
+
|
|
27
|
+
export const SESSION_RUNTIMES = ["claude", "codex"] as const;
|
|
28
|
+
export type SessionRuntime = (typeof SESSION_RUNTIMES)[number];
|
|
29
|
+
|
|
30
|
+
export type PeerSweepResult = {
|
|
31
|
+
personality: string;
|
|
32
|
+
runtime: SessionRuntime;
|
|
33
|
+
cwd: string;
|
|
34
|
+
/** worst action across the peer-runtime's surfaces */
|
|
35
|
+
ok: boolean;
|
|
36
|
+
outcomes: SurfaceOutcome[];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type SweepSummary = {
|
|
40
|
+
results: PeerSweepResult[];
|
|
41
|
+
/** peers skipped: no session runtimes in the map entry / vanished cwd */
|
|
42
|
+
skipped: Array<{ personality: string; reason: string }>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function sessionRuntimesOf(peer: FleetPeer): SessionRuntime[] {
|
|
46
|
+
return SESSION_RUNTIMES.filter((r) => peer.runtimes.includes(r));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function sweepProvision(opts: {
|
|
50
|
+
fleet: FleetPeer[];
|
|
51
|
+
hooksDir: string;
|
|
52
|
+
port: number;
|
|
53
|
+
}): SweepSummary {
|
|
54
|
+
const results: PeerSweepResult[] = [];
|
|
55
|
+
const skipped: SweepSummary["skipped"] = [];
|
|
56
|
+
for (const peer of opts.fleet) {
|
|
57
|
+
const runtimes = sessionRuntimesOf(peer);
|
|
58
|
+
if (runtimes.length === 0) {
|
|
59
|
+
skipped.push({
|
|
60
|
+
personality: peer.personality,
|
|
61
|
+
reason: peer.runtimes.length
|
|
62
|
+
? `no session runtime (${peer.runtimes.join(",")})`
|
|
63
|
+
: "no runtimes in fleet map (pre-v1.2 entry) — re-write the map",
|
|
64
|
+
});
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (!fs.existsSync(peer.cwd)) {
|
|
68
|
+
skipped.push({ personality: peer.personality, reason: `cwd missing: ${peer.cwd}` });
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
for (const runtime of runtimes) {
|
|
72
|
+
const outcomes =
|
|
73
|
+
runtime === "codex"
|
|
74
|
+
? provisionCodexPeer({ cwd: peer.cwd, port: opts.port })
|
|
75
|
+
: provisionClaudePeer({
|
|
76
|
+
cwd: peer.cwd,
|
|
77
|
+
hooksDir: opts.hooksDir,
|
|
78
|
+
port: opts.port,
|
|
79
|
+
personality: peer.personality,
|
|
80
|
+
});
|
|
81
|
+
results.push({
|
|
82
|
+
personality: peer.personality,
|
|
83
|
+
runtime,
|
|
84
|
+
cwd: peer.cwd,
|
|
85
|
+
ok: outcomes.every((o) => o.action !== "failed"),
|
|
86
|
+
outcomes,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { results, skipped };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function sweepUnprovision(opts: { fleet: FleetPeer[] }): SweepSummary {
|
|
94
|
+
const results: PeerSweepResult[] = [];
|
|
95
|
+
const skipped: SweepSummary["skipped"] = [];
|
|
96
|
+
for (const peer of opts.fleet) {
|
|
97
|
+
const runtimes = sessionRuntimesOf(peer);
|
|
98
|
+
if (runtimes.length === 0) {
|
|
99
|
+
skipped.push({ personality: peer.personality, reason: "no session runtime" });
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// a vanished cwd is fine on the off-path — surfaces report `absent`
|
|
103
|
+
for (const runtime of runtimes) {
|
|
104
|
+
const outcomes =
|
|
105
|
+
runtime === "codex"
|
|
106
|
+
? unprovisionCodexPeer({ cwd: peer.cwd })
|
|
107
|
+
: unprovisionClaudePeer({ cwd: peer.cwd });
|
|
108
|
+
results.push({
|
|
109
|
+
personality: peer.personality,
|
|
110
|
+
runtime,
|
|
111
|
+
cwd: peer.cwd,
|
|
112
|
+
ok: outcomes.every((o) => o.action !== "failed"),
|
|
113
|
+
outcomes,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { results, skipped };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export type PeerCheckResult = {
|
|
121
|
+
personality: string;
|
|
122
|
+
runtime: SessionRuntime;
|
|
123
|
+
cwd: string;
|
|
124
|
+
ok: boolean;
|
|
125
|
+
problems: string[];
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export function checkFleetSurfaces(opts: {
|
|
129
|
+
fleet: FleetPeer[];
|
|
130
|
+
hooksDir: string;
|
|
131
|
+
port: number;
|
|
132
|
+
}): { checks: PeerCheckResult[]; skipped: Array<{ personality: string; reason: string }> } {
|
|
133
|
+
const checks: PeerCheckResult[] = [];
|
|
134
|
+
const skipped: Array<{ personality: string; reason: string }> = [];
|
|
135
|
+
for (const peer of opts.fleet) {
|
|
136
|
+
const runtimes = sessionRuntimesOf(peer);
|
|
137
|
+
if (runtimes.length === 0) {
|
|
138
|
+
skipped.push({
|
|
139
|
+
personality: peer.personality,
|
|
140
|
+
reason: peer.runtimes.length
|
|
141
|
+
? `no session runtime (${peer.runtimes.join(",")})`
|
|
142
|
+
: "no runtimes in fleet map (pre-v1.2 entry)",
|
|
143
|
+
});
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (!fs.existsSync(peer.cwd)) {
|
|
147
|
+
skipped.push({ personality: peer.personality, reason: `cwd missing: ${peer.cwd}` });
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
for (const runtime of runtimes) {
|
|
151
|
+
const surfaceChecks =
|
|
152
|
+
runtime === "codex"
|
|
153
|
+
? checkCodexPeer({ cwd: peer.cwd, port: opts.port })
|
|
154
|
+
: checkClaudePeer({
|
|
155
|
+
cwd: peer.cwd,
|
|
156
|
+
hooksDir: opts.hooksDir,
|
|
157
|
+
port: opts.port,
|
|
158
|
+
personality: peer.personality,
|
|
159
|
+
});
|
|
160
|
+
checks.push({
|
|
161
|
+
personality: peer.personality,
|
|
162
|
+
runtime,
|
|
163
|
+
cwd: peer.cwd,
|
|
164
|
+
ok: surfaceChecks.every((c) => c.ok),
|
|
165
|
+
problems: surfaceChecks.filter((c) => !c.ok).map((c) => `${c.surface}: ${c.detail}`),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return { checks, skipped };
|
|
170
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedded skill files — the DIRECT-surface form of the four session skills
|
|
3
|
+
* (ADR-009 v1.2: direct per-peer surfaces instead of the plugin socket).
|
|
4
|
+
* Bodies are the boris-accepted plugin skills (adapters/claude/skills, spot-
|
|
5
|
+
* checked against the live CLI 10.06) with exactly two deltas:
|
|
6
|
+
*
|
|
7
|
+
* 1. names are namespaced `iapeer-memory-*` (boris design input: direct
|
|
8
|
+
* skills lose the plugin namespace `/iapeer-memory:name` — the prefix
|
|
9
|
+
* replaces it; the `copywriter` collision class);
|
|
10
|
+
* 2. "plugin" wording → "session surfaces" where it described the socket
|
|
11
|
+
* form (the socket is now files merged into the peer's cwd).
|
|
12
|
+
*
|
|
13
|
+
* provision-peer materialises them to `<cwd>/.claude/skills/<name>/SKILL.md`
|
|
14
|
+
* (bytes-compare, package-owned — overwritten on version change; the
|
|
15
|
+
* `iapeer-memory-` directory prefix is OUR namespace, unprovision removes
|
|
16
|
+
* every directory matching it).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export type SkillName =
|
|
20
|
+
| "iapeer-memory-init"
|
|
21
|
+
| "iapeer-memory-status"
|
|
22
|
+
| "iapeer-memory-migrate"
|
|
23
|
+
| "iapeer-memory-distill";
|
|
24
|
+
|
|
25
|
+
export const SKILL_NAMES: readonly SkillName[] = [
|
|
26
|
+
"iapeer-memory-init",
|
|
27
|
+
"iapeer-memory-status",
|
|
28
|
+
"iapeer-memory-migrate",
|
|
29
|
+
"iapeer-memory-distill",
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
/** Directory-name prefix that marks a skill directory as OURS (the removal
|
|
33
|
+
* glob of unprovision — the namespace promise of the `iapeer-memory-*`
|
|
34
|
+
* naming). */
|
|
35
|
+
export const SKILL_DIR_PREFIX = "iapeer-memory-";
|
|
36
|
+
|
|
37
|
+
const SKILL_INIT = `---
|
|
38
|
+
name: iapeer-memory-init
|
|
39
|
+
description: "Use when the user asks to install, provision or initialize iapeer-memory on this host (\\"set up iapeer-memory\\", \\"init memory\\", \\"provision the vault\\"). Thin facade over \`iapeer-memory init\`: the procedure lives in the package CLI, not here."
|
|
40
|
+
allowed-tools: ["Bash", "AskUserQuestion"]
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
# Provision iapeer-memory on this host
|
|
44
|
+
|
|
45
|
+
The session surfaces are only a socket (ADR-009) — provisioning is owned by
|
|
46
|
+
the package CLI. Do not improvise installation steps around it.
|
|
47
|
+
|
|
48
|
+
1. Locate the CLI: \`command -v iapeer-memory || ls ~/.local/bin/iapeer-memory\`.
|
|
49
|
+
Missing → run via \`npx @agfpd/iapeer-memory\` instead.
|
|
50
|
+
2. Init is two-mode. On a tty it prompts; your Bash calls have NO tty, so
|
|
51
|
+
without \`--vault\` init refuses (silently provisioning a default storage
|
|
52
|
+
path is forbidden). Collect the answers from the user first
|
|
53
|
+
(AskUserQuestion), then run:
|
|
54
|
+
\`iapeer-memory init --vault PATH --locale en|ru
|
|
55
|
+
[--embedding-endpoint URL] [--reranker-endpoint URL]\`.
|
|
56
|
+
Do NOT ask for the human owner: init reads the iapeer registry and uses
|
|
57
|
+
the single natural peer by itself (don't ask what the stack already
|
|
58
|
+
knows). Pass \`--human NAME\` only when the registry can't answer (zero or
|
|
59
|
+
several natural peers) and the user wants a human role.
|
|
60
|
+
3. Init prints a step table (deps → vault → config → binary → templates →
|
|
61
|
+
roles → fleet → watcher → surfaces → slot → sweep → guide) and is
|
|
62
|
+
idempotent: on exit 1 re-running init is the official repair path,
|
|
63
|
+
together with \`iapeer-memory verify --repair\`.
|
|
64
|
+
4. A host that is already provisioned and only version-stale wants the
|
|
65
|
+
update story, not init: \`npx @agfpd/iapeer-memory@latest update\`.
|
|
66
|
+
|
|
67
|
+
After success, check the chain with the \`iapeer-memory-status\` skill.
|
|
68
|
+
(\`iapeer onboard\` runs this same init from the core's host phase —
|
|
69
|
+
full-stack onboarding already covers memory.)
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
const SKILL_STATUS = `---
|
|
73
|
+
name: iapeer-memory-status
|
|
74
|
+
description: "Use when the user asks for the iapeer-memory status (\\"memory status\\", \\"is the vault index alive\\", \\"check iapeer-memory\\", \\"is memoryd running\\"). Read-only facade over \`iapeer-memory status\`: package ↔ surfaces link first, then the CLI's own diagnostics. Never repairs anything."
|
|
75
|
+
allowed-tools: ["Bash"]
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
# iapeer-memory status — read-only diagnostics
|
|
79
|
+
|
|
80
|
+
The session surfaces are the socket, the package is the system (ADR-009).
|
|
81
|
+
This skill's first duty is to DIAGNOSE A BROKEN LINK between them — a
|
|
82
|
+
session whose surfaces are wired but whose system is missing must say so
|
|
83
|
+
explicitly.
|
|
84
|
+
|
|
85
|
+
1. **Socket → system link**: \`command -v iapeer-memory || ls ~/.local/bin/iapeer-memory\`.
|
|
86
|
+
Missing → report: "session surfaces present, package missing — the socket
|
|
87
|
+
has no system behind it; run: npx @agfpd/iapeer-memory init". Stop here.
|
|
88
|
+
2. **Everything else**: run \`iapeer-memory status\` and relay its table —
|
|
89
|
+
verify checks (config, memory-slot, memoryd heartbeat, notifier watcher,
|
|
90
|
+
role doctrine versions, per-peer surfaces), slot-file, mcp-endpoint
|
|
91
|
+
probe, search pipeline, inbox load. Exit 1 = something needs attention.
|
|
92
|
+
|
|
93
|
+
Reading the table: \`search\` shows the LIVE per-component pipeline from the
|
|
94
|
+
running memoryd (bm25/embedding/reranker/graph) and falls back to the
|
|
95
|
+
static config view when memoryd is down; a growing \`inbox\` count means the
|
|
96
|
+
Index curator is not keeping up.
|
|
97
|
+
`;
|
|
98
|
+
|
|
99
|
+
const SKILL_MIGRATE = `---
|
|
100
|
+
name: iapeer-memory-migrate
|
|
101
|
+
description: "Use when the user asks to migrate harness auto-memory into iapeer-memory (\\"migrate memory\\", \\"move auto-memory to the vault\\", \\"перенеси auto-memory\\"), or when connecting a peer that has accumulated Claude auto-memory. Facade over \`iapeer-memory migrate\`: the skill resolves the claude-specific SOURCE directory, the deterministic engine does the rest (dry-run → confirm → apply, with backups)."
|
|
102
|
+
argument-hint: "<agent> [<project-dir>]"
|
|
103
|
+
allowed-tools: ["Bash", "AskUserQuestion"]
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
# Migrate Claude auto-memory into the vault
|
|
107
|
+
|
|
108
|
+
The engine (\`iapeer-memory migrate\`) is source-agnostic — THIS skill owns the
|
|
109
|
+
claude-specific knowledge of where auto-memory lives. (The codex source is
|
|
110
|
+
NOT wired yet: its live format is unverified — never guess it.)
|
|
111
|
+
|
|
112
|
+
## Resolve the source directory
|
|
113
|
+
|
|
114
|
+
- **Launchd/persistent peer** (no \`<project-dir>\` argument):
|
|
115
|
+
\`SOURCE=~/.claude/agent-memory/<agent>/\`
|
|
116
|
+
- **Project session** (\`<project-dir>\` given): the slug is the absolute
|
|
117
|
+
path with every non-alphanumeric character replaced by \`-\` — dots too:
|
|
118
|
+
\`/a/b.c\` → \`-a-b-c\` (so \`~/.iapeer/...\` yields a double dash). When in
|
|
119
|
+
doubt, \`ls ~/.claude/projects/\` and match.
|
|
120
|
+
\`SOURCE=~/.claude/projects/<slug>/memory/\`
|
|
121
|
+
|
|
122
|
+
No directory or no \`.md\` files inside → nothing to migrate; say so and stop.
|
|
123
|
+
|
|
124
|
+
## Run
|
|
125
|
+
|
|
126
|
+
1. Dry-run first: \`iapeer-memory migrate --source "$SOURCE" --agent <agent>\`
|
|
127
|
+
— show the user the plan verbatim (per-file type → subtype mapping,
|
|
128
|
+
skip lists, totals).
|
|
129
|
+
2. Ask for confirmation (AskUserQuestion).
|
|
130
|
+
3. Apply: same command + \`--apply\`. Per-file backups land under
|
|
131
|
+
\`~/.iapeer/state/iapeer-memory/migrate-backups/\` before conversion; an
|
|
132
|
+
existing target note is never overwritten.
|
|
133
|
+
4. Report: migrated/skipped/errors + backup path.
|
|
134
|
+
|
|
135
|
+
## After migration
|
|
136
|
+
|
|
137
|
+
A \`feedback\` note that is semantically a pitfall cannot be told apart
|
|
138
|
+
deterministically — re-filing such notes to \`pitfall\` is the agent's manual
|
|
139
|
+
step afterwards (the iapeer-memory-distill skill covers it).
|
|
140
|
+
`;
|
|
141
|
+
|
|
142
|
+
const SKILL_DISTILL = `---
|
|
143
|
+
name: iapeer-memory-distill
|
|
144
|
+
description: "Use when the user asks the agent to clean up its own memory (\\"distill your memory\\", \\"прибери свою память\\", \\"clean up your operative notes\\"). Deep MANUAL distillation of the agent's own agent-memory folder, in-session, user in the loop — deeper than the DreamWeaver weekly tick: fact-checks, re-filing, promoting team knowledge to canon."
|
|
145
|
+
allowed-tools: ["Read", "Edit", "Write", "Bash", "Glob", "Grep"]
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
# Distill your own agent memory
|
|
149
|
+
|
|
150
|
+
You are cleaning YOUR OWN folder: \`<vault>/06_Agent_Memory/<your personality>/\`
|
|
151
|
+
(RU locale: \`06_Оперативка_агентов/<…>/\`). Identity comes from
|
|
152
|
+
\`PEER_PERSONALITY\` — if it is empty, refuse: you cannot know whose memory
|
|
153
|
+
you are touching. An absent/empty folder = nothing to distill; say so and stop.
|
|
154
|
+
|
|
155
|
+
## The link-watershed rule (before ANY identity-changing operation)
|
|
156
|
+
|
|
157
|
+
Deleting, renaming or replacing a note splits on the wikilink graph — query
|
|
158
|
+
the note's connections first (vault_graph MCP tool):
|
|
159
|
+
|
|
160
|
+
- **0 incoming + 0 outgoing** → isolated; act directly (\`rm\` / rename).
|
|
161
|
+
- **≥1 link in either direction** → the note is part of the graph; set
|
|
162
|
+
\`status\` to the deprecated token instead and (if needed) write a
|
|
163
|
+
replacement note. The Index archives it on its PERMANENT_CHANGED pass.
|
|
164
|
+
|
|
165
|
+
Body edits that keep identity (rewording, updating description, switching
|
|
166
|
+
subtype) need no graph check.
|
|
167
|
+
|
|
168
|
+
## Passes
|
|
169
|
+
|
|
170
|
+
1. **Inventory**: list every note; for each — subtype, status, age, one-line
|
|
171
|
+
gist.
|
|
172
|
+
2. **Dedup**: near-duplicate notes about one topic → merge into the
|
|
173
|
+
strongest one, deprecate the rest (watershed rule).
|
|
174
|
+
3. **Compress**: bloated notes → tighten to the essentials; notes are
|
|
175
|
+
injected into readers' contexts, bloat costs the whole team tokens.
|
|
176
|
+
4. **Verify**: notes asserting local facts (paths, flags, versions) —
|
|
177
|
+
re-check the fact cheaply where possible; stale → fix or deprecate.
|
|
178
|
+
5. **Re-file**: \`feedback\` notes that are semantically pitfalls (a rule
|
|
179
|
+
born from one incident) → subtype \`pitfall\`; other mis-filed subtypes
|
|
180
|
+
likewise.
|
|
181
|
+
6. **Promote**: material useful to the whole team → draft into the inbox
|
|
182
|
+
folder (canon style: self-contained, objective), keep the personal
|
|
183
|
+
angle in your memory note with an inline \`[[draft title]]\` link.
|
|
184
|
+
7. **Report**: summary to the user — counts per pass, anything that needs
|
|
185
|
+
their decision.
|
|
186
|
+
|
|
187
|
+
Confirm with the user between passes 6 and 7 when the promote list is
|
|
188
|
+
non-empty — moving knowledge to canon is visible to the whole team.
|
|
189
|
+
`;
|
|
190
|
+
|
|
191
|
+
export const SKILL_BODIES: Record<SkillName, string> = {
|
|
192
|
+
"iapeer-memory-init": SKILL_INIT,
|
|
193
|
+
"iapeer-memory-status": SKILL_STATUS,
|
|
194
|
+
"iapeer-memory-migrate": SKILL_MIGRATE,
|
|
195
|
+
"iapeer-memory-distill": SKILL_DISTILL,
|
|
196
|
+
};
|