@sigrun/memory-honcho 0.0.0-stage → 0.1.1
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/CHANGELOG.md +64 -0
- package/LICENSE +21 -0
- package/README.md +103 -2
- package/dist/capture.d.ts +17 -0
- package/dist/capture.js +61 -0
- package/dist/chains.d.ts +18 -0
- package/dist/chains.js +33 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.js +43 -0
- package/dist/delivery.d.ts +58 -0
- package/dist/delivery.js +264 -0
- package/dist/fork-rules.d.ts +32 -0
- package/dist/fork-rules.js +42 -0
- package/dist/honcho.d.ts +65 -0
- package/dist/honcho.js +127 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +345 -0
- package/dist/representations.d.ts +42 -0
- package/dist/representations.js +81 -0
- package/dist/status.d.ts +57 -0
- package/dist/status.js +89 -0
- package/dist/stores.d.ts +83 -0
- package/dist/stores.js +244 -0
- package/dist/sweep.d.ts +60 -0
- package/dist/sweep.js +198 -0
- package/dist/tools.d.ts +41 -0
- package/dist/tools.js +157 -0
- package/dist/types.d.ts +139 -0
- package/dist/types.js +10 -0
- package/package.json +56 -4
package/dist/tools.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three recall tools: `honcho_search`, `honcho_chat`, `honcho_remember`.
|
|
3
|
+
*
|
|
4
|
+
* All three:
|
|
5
|
+
* - resolve the calling conversation's owner from `host.conversations.meta`
|
|
6
|
+
* (memoized in-process per conversation; ownerless conversations → `"user"`);
|
|
7
|
+
* - await `engine.establish(conversationId, owner)` first (the spec's second
|
|
8
|
+
* clone trigger — a rebuilt client re-establishes from the ledger);
|
|
9
|
+
* - convert Honcho failures to `memory-honcho: Honcho unavailable (<message>)`.
|
|
10
|
+
*
|
|
11
|
+
* `honcho_remember` passes NO `sessionId` in v1: a remembered fact is a
|
|
12
|
+
* user-level conclusion, matching the cross-conversation recall ruling.
|
|
13
|
+
* The README documents the visibility caveat.
|
|
14
|
+
*/
|
|
15
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
16
|
+
import { defineTool } from "@earendil-works/pi-durable";
|
|
17
|
+
/** The no-memory text returned by search (empty) and chat (null). */
|
|
18
|
+
const NO_MEMORY_TEXT = "No relevant memory found.";
|
|
19
|
+
/** Wrap a Honcho failure with the extension's error prefix. */
|
|
20
|
+
function honchoError(error) {
|
|
21
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
22
|
+
return new Error(`memory-honcho: Honcho unavailable (${message})`);
|
|
23
|
+
}
|
|
24
|
+
/** Build the three recall tools. */
|
|
25
|
+
export function createTools(deps) {
|
|
26
|
+
// Per-conversation owner memo: the owner does not change mid-conversation.
|
|
27
|
+
const ownerMemo = new Map();
|
|
28
|
+
async function resolveOwner(conversationId) {
|
|
29
|
+
const memoized = ownerMemo.get(conversationId);
|
|
30
|
+
if (memoized !== undefined)
|
|
31
|
+
return memoized;
|
|
32
|
+
const meta = await deps.host.conversations.meta(conversationId);
|
|
33
|
+
const owner = meta.owner ?? "user";
|
|
34
|
+
ownerMemo.set(conversationId, owner);
|
|
35
|
+
return owner;
|
|
36
|
+
}
|
|
37
|
+
/** Establish the session, then resolve the user peer. */
|
|
38
|
+
async function userPeer(conversationId) {
|
|
39
|
+
const owner = await resolveOwner(conversationId);
|
|
40
|
+
await deps.engine().then((engine) => engine.establish(conversationId, owner));
|
|
41
|
+
const client = deps.client();
|
|
42
|
+
if (client === undefined) {
|
|
43
|
+
throw new Error("memory-honcho: client not initialized");
|
|
44
|
+
}
|
|
45
|
+
return client.userPeerFor(owner);
|
|
46
|
+
}
|
|
47
|
+
/** Settings after the engine has resolved (guaranteed initialized). */
|
|
48
|
+
function currentSettings() {
|
|
49
|
+
const settings = deps.settings();
|
|
50
|
+
if (settings === undefined) {
|
|
51
|
+
throw new Error("memory-honcho: settings not initialized");
|
|
52
|
+
}
|
|
53
|
+
return settings;
|
|
54
|
+
}
|
|
55
|
+
const search = defineTool({
|
|
56
|
+
name: "honcho_search",
|
|
57
|
+
description: "Search the user's Honcho memory for messages relevant to the query. " +
|
|
58
|
+
"Returns matching messages, sliced to the preview length, annotated with " +
|
|
59
|
+
"their source conversation id.",
|
|
60
|
+
parameters: Type.Object({
|
|
61
|
+
query: Type.String({ description: "The search query." }),
|
|
62
|
+
}),
|
|
63
|
+
async execute(args, api, _context) {
|
|
64
|
+
try {
|
|
65
|
+
// Resolve the engine first so settings are initialized.
|
|
66
|
+
await deps.engine();
|
|
67
|
+
const settings = currentSettings();
|
|
68
|
+
const peer = await userPeer(Number(api.conversationId));
|
|
69
|
+
const results = await peer.search(args.query, {
|
|
70
|
+
limit: settings.searchLimit,
|
|
71
|
+
});
|
|
72
|
+
if (results.length === 0) {
|
|
73
|
+
return { output: [{ type: "text", text: NO_MEMORY_TEXT }] };
|
|
74
|
+
}
|
|
75
|
+
const lines = results.map((message) => {
|
|
76
|
+
const slice = message.content.slice(0, settings.toolPreviewLength);
|
|
77
|
+
const conversationId = message.metadata?.conversationId;
|
|
78
|
+
const annotation = typeof conversationId === "number" || typeof conversationId === "string"
|
|
79
|
+
? ` (conversation ${conversationId})`
|
|
80
|
+
: "";
|
|
81
|
+
return `[${message.peerId}] ${slice}${annotation}`;
|
|
82
|
+
});
|
|
83
|
+
return { output: [{ type: "text", text: lines.join("\n") }] };
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
throw honchoError(error);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
const chat = defineTool({
|
|
91
|
+
name: "honcho_chat",
|
|
92
|
+
description: "Ask the agent's Honcho representation of the user a natural-language " +
|
|
93
|
+
"question. Returns the answer, or the no-memory text when there is none.",
|
|
94
|
+
parameters: Type.Object({
|
|
95
|
+
query: Type.String({ description: "The question to ask." }),
|
|
96
|
+
reasoningLevel: Type.Optional(Type.Union([
|
|
97
|
+
Type.Literal("minimal"),
|
|
98
|
+
Type.Literal("low"),
|
|
99
|
+
Type.Literal("medium"),
|
|
100
|
+
Type.Literal("high"),
|
|
101
|
+
Type.Literal("max"),
|
|
102
|
+
], { description: "Reasoning level for the query; default \"low\"." })),
|
|
103
|
+
}),
|
|
104
|
+
async execute(args, api, _context) {
|
|
105
|
+
try {
|
|
106
|
+
const conversationId = Number(api.conversationId);
|
|
107
|
+
const targetPeer = await userPeer(conversationId);
|
|
108
|
+
const client = deps.client();
|
|
109
|
+
if (client === undefined) {
|
|
110
|
+
throw new Error("memory-honcho: client not initialized");
|
|
111
|
+
}
|
|
112
|
+
const agentPeer = await client.agentPeer();
|
|
113
|
+
const answer = await agentPeer.chat(args.query, {
|
|
114
|
+
target: targetPeer,
|
|
115
|
+
reasoningLevel: args.reasoningLevel ?? "low",
|
|
116
|
+
});
|
|
117
|
+
return {
|
|
118
|
+
output: [
|
|
119
|
+
{ type: "text", text: answer ?? NO_MEMORY_TEXT },
|
|
120
|
+
],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
throw honchoError(error);
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
const remember = defineTool({
|
|
129
|
+
name: "honcho_remember",
|
|
130
|
+
description: "Record a fact about the user as a Honcho conclusion. " +
|
|
131
|
+
"The fact is user-level (cross-conversation), not conversation-scoped.",
|
|
132
|
+
parameters: Type.Object({
|
|
133
|
+
content: Type.String({ description: "The fact to remember." }),
|
|
134
|
+
}),
|
|
135
|
+
async execute(args, api, _context) {
|
|
136
|
+
try {
|
|
137
|
+
const conversationId = Number(api.conversationId);
|
|
138
|
+
const targetPeer = await userPeer(conversationId);
|
|
139
|
+
const client = deps.client();
|
|
140
|
+
if (client === undefined) {
|
|
141
|
+
throw new Error("memory-honcho: client not initialized");
|
|
142
|
+
}
|
|
143
|
+
const agentPeer = await client.agentPeer();
|
|
144
|
+
await agentPeer.conclusionsOf(targetPeer).create({
|
|
145
|
+
content: args.content,
|
|
146
|
+
});
|
|
147
|
+
return {
|
|
148
|
+
output: [{ type: "text", text: "Remembered." }],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
throw honchoError(error);
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
return [search, chat, remember];
|
|
157
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The memory-seam contract restated structurally.
|
|
3
|
+
*
|
|
4
|
+
* `RunSettledEventLike` and `MemoryEventApiLike` mirror the host contract
|
|
5
|
+
* (sigrun `src/harness/event-bus.ts` / `src/extension.ts`) — declared
|
|
6
|
+
* structurally, with NO core runtime import, so this module compiles against
|
|
7
|
+
* the `@sigrun/sigrun` devDependency's types without pinning a release.
|
|
8
|
+
* The host passes real values; anything satisfying these shapes works.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* The settled-run event as the handler consumes it: ids as plain numbers
|
|
12
|
+
* (pi-durable ids are branded numbers — structurally identical), and the
|
|
13
|
+
* answered outcome's `answer` narrowed to the one member the capture path
|
|
14
|
+
* reads (`content` blocks with `type`/`text` — the real `AssistantMessage`
|
|
15
|
+
* satisfies this).
|
|
16
|
+
*/
|
|
17
|
+
export type RunSettledEventLike = {
|
|
18
|
+
readonly conversationId: number;
|
|
19
|
+
/** The conversation owner at delivery; absent when the conversation is ownerless. */
|
|
20
|
+
readonly owner?: string;
|
|
21
|
+
readonly inputs: readonly number[];
|
|
22
|
+
readonly inputTexts: readonly string[];
|
|
23
|
+
readonly outcome: {
|
|
24
|
+
readonly status: "answered";
|
|
25
|
+
readonly answerEntryId: number;
|
|
26
|
+
readonly answer: {
|
|
27
|
+
readonly content: readonly {
|
|
28
|
+
readonly type: string;
|
|
29
|
+
readonly text?: string;
|
|
30
|
+
}[];
|
|
31
|
+
};
|
|
32
|
+
} | {
|
|
33
|
+
readonly status: "unanswered";
|
|
34
|
+
readonly reason: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The narrowed read surface a `runSettled` handler receives (the host's
|
|
39
|
+
* `MemoryEventApi`). Only `document` is typed concretely here — Task 5's
|
|
40
|
+
* selection gate is the only consumer; `entry`/`entries` are declared as
|
|
41
|
+
* unknown-typed members so fakes can implement the full contract without
|
|
42
|
+
* restating pi-durable's query/page generics.
|
|
43
|
+
*/
|
|
44
|
+
export type MemoryEventApiLike = {
|
|
45
|
+
readonly entry: unknown;
|
|
46
|
+
readonly entries: unknown;
|
|
47
|
+
document<T>(token: unknown, conversationId: number, context: unknown): Promise<Readonly<T> | undefined>;
|
|
48
|
+
};
|
|
49
|
+
/** One message inside a capture batch: the peer side, the text, and the batch-level metadata. */
|
|
50
|
+
export type OutboxEntry = {
|
|
51
|
+
readonly peer: "user" | "agent";
|
|
52
|
+
readonly text: string;
|
|
53
|
+
readonly metadata: {
|
|
54
|
+
readonly conversationId: number;
|
|
55
|
+
readonly submissionIds: readonly number[];
|
|
56
|
+
readonly batchId: string;
|
|
57
|
+
/** Present on user entries of UNANSWERED runs. */
|
|
58
|
+
readonly unanswered?: true;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
/** One capture batch, JSONL-appended to `data/outbox/capture.jsonl`. */
|
|
62
|
+
export type OutboxRecord = {
|
|
63
|
+
readonly type: "capture";
|
|
64
|
+
readonly conversationId: number;
|
|
65
|
+
/** The resolved owner for this capture (`"user"` when the conversation is ownerless). */
|
|
66
|
+
readonly owner: string;
|
|
67
|
+
/** The run's answer anchor; set only for answered runs. */
|
|
68
|
+
readonly answerEntryId?: number;
|
|
69
|
+
readonly entries: readonly OutboxEntry[];
|
|
70
|
+
readonly batchId: string;
|
|
71
|
+
readonly status: "pending" | "delivered";
|
|
72
|
+
readonly createdAt: number;
|
|
73
|
+
};
|
|
74
|
+
/** One fork-mapping anchor: the run's `answerEntryId` → the Honcho message id delivery returned. */
|
|
75
|
+
export type LedgerAnchor = {
|
|
76
|
+
readonly entryId: number;
|
|
77
|
+
readonly messageId: string;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Per-conversation bookkeeping, `state/ledger/<conversationId>.json`:
|
|
81
|
+
* correctness-relevant but not user content. `deliveredBatchIds` and
|
|
82
|
+
* `anchors` are bounded windows (folded oldest-first on write).
|
|
83
|
+
*/
|
|
84
|
+
export type Ledger = {
|
|
85
|
+
readonly conversationId: number;
|
|
86
|
+
readonly honchoSessionId?: string;
|
|
87
|
+
readonly clonedFrom?: number;
|
|
88
|
+
/** Why a fresh session was started instead of a clone ("none-window" / "none-empty"). */
|
|
89
|
+
readonly inherit?: string;
|
|
90
|
+
/** The owner resolved at the conversation's first captured run. */
|
|
91
|
+
readonly ownerAtFirstCapture: string;
|
|
92
|
+
readonly deliveredBatchIds: string[];
|
|
93
|
+
readonly anchors: LedgerAnchor[];
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* `state/forks/pending/<childId>.json`: written by `conversationForked`,
|
|
97
|
+
* resolved on clone. Resolved records are kept (a `resolvedAt` stamp) so a
|
|
98
|
+
* late, duplicate establish never re-clones.
|
|
99
|
+
*/
|
|
100
|
+
export type PendingForkRecord = {
|
|
101
|
+
readonly childId: number;
|
|
102
|
+
readonly parentId: number;
|
|
103
|
+
/** The fork point (entry id in the parent). */
|
|
104
|
+
readonly at: number;
|
|
105
|
+
readonly createdAt: number;
|
|
106
|
+
readonly resolvedAt?: number;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* The cached representation snapshot, `cache/representations/<conversationId>.json`
|
|
110
|
+
* (SDK field names normalized: `peerRepresentation` → `userProfile`,
|
|
111
|
+
* `summary.content` → `projectSummary`).
|
|
112
|
+
*/
|
|
113
|
+
export type RepresentationSnapshot = {
|
|
114
|
+
readonly userProfile: string | null;
|
|
115
|
+
readonly projectSummary: string | null;
|
|
116
|
+
readonly fetchedAt: number;
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* `state/last-delivery.json`: the persistent marker of the newest DELIVERY
|
|
120
|
+
* (`at` = the delivered record's `createdAt`). The sweeper drops delivered
|
|
121
|
+
* records every 15 minutes, so the status route cannot derive
|
|
122
|
+
* `lastDeliveryAt` from retained records in steady state — the delivery
|
|
123
|
+
* engine writes this marker when it flips a record, inside the same outbox
|
|
124
|
+
* critical section as the flip (stores.ts / status.ts).
|
|
125
|
+
*/
|
|
126
|
+
export type LastDeliveryMarker = {
|
|
127
|
+
readonly at: number;
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* The worker-run bookkeeping the factory records and the status route
|
|
131
|
+
* projects. The ONE declaration lives here: `src/index.ts` (writer) and
|
|
132
|
+
* `src/status.ts` (reader) both alias it, so the two sides cannot drift.
|
|
133
|
+
*/
|
|
134
|
+
export type WorkerRunStatus = {
|
|
135
|
+
runs: number;
|
|
136
|
+
startedAt?: number;
|
|
137
|
+
endedAt?: number;
|
|
138
|
+
lastError?: string;
|
|
139
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The memory-seam contract restated structurally.
|
|
3
|
+
*
|
|
4
|
+
* `RunSettledEventLike` and `MemoryEventApiLike` mirror the host contract
|
|
5
|
+
* (sigrun `src/harness/event-bus.ts` / `src/extension.ts`) — declared
|
|
6
|
+
* structurally, with NO core runtime import, so this module compiles against
|
|
7
|
+
* the `@sigrun/sigrun` devDependency's types without pinning a release.
|
|
8
|
+
* The host passes real values; anything satisfying these shapes works.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
"name": "@sigrun/memory-honcho",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Honcho persistent-memory extension for sigrun (pi-durable)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22.19.0"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"CHANGELOG.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "node scripts/check-manifest.mjs --expect-name @sigrun/memory-honcho && vitest run",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"build": "tsc -p tsconfig.build.json",
|
|
25
|
+
"prepare": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@honcho-ai/sdk": "2.1.2"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"@earendil-works/chord": "1.3.0 || 1.4.0",
|
|
32
|
+
"@earendil-works/pi-ai": "1.3.0 || 1.4.0",
|
|
33
|
+
"@earendil-works/pi-durable": "1.3.0 || 1.4.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@earendil-works/chord": "1.4.0",
|
|
37
|
+
"@earendil-works/pi-ai": "1.4.0",
|
|
38
|
+
"@earendil-works/pi-durable": "1.4.0",
|
|
39
|
+
"@sigrun/sigrun": "0.3.0",
|
|
40
|
+
"@types/node": "^26.6.4",
|
|
41
|
+
"tsx": "^4.23.15",
|
|
42
|
+
"typescript": "^7.0.2",
|
|
43
|
+
"vitest": "^5.0.3"
|
|
44
|
+
},
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "github",
|
|
48
|
+
"url": "git+https://github.com/sigrun-ai/sigrun-memory-honcho.git"
|
|
49
|
+
},
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/sigrun-ai/sigrun-memory-honcho/issues"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://github.com/sigrun-ai/sigrun-memory-honcho#readme",
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public",
|
|
56
|
+
"provenance": true
|
|
57
|
+
}
|
|
6
58
|
}
|