@moxxy/reflector-default 0.27.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/LICENSE +21 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +368 -0
- package/dist/index.js.map +1 -0
- package/package.json +80 -0
- package/src/index.test.ts +461 -0
- package/src/index.ts +428 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Moxxy (moxxy.ai)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { type MoxxyEvent, type Plugin, type ReflectionProposal, type ReflectorDef, type SessionId } from '@moxxy/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* @moxxy/reflector-default — the default learning-loop block.
|
|
4
|
+
*
|
|
5
|
+
* TWO parts ship in one plugin:
|
|
6
|
+
* 1. a {@link ReflectorDef} named `'default'` (the strategy): one cheap
|
|
7
|
+
* side-channel LLM pass over a finished turn that returns 0-2
|
|
8
|
+
* {@link ReflectionProposal}s;
|
|
9
|
+
* 2. the DRIVER (lifecycle hooks): decides WHEN to reflect (a cheap gate +
|
|
10
|
+
* a one-per-session budget), runs the active reflector fire-and-forget on
|
|
11
|
+
* `onTurnEnd`, and delivers any proposals as a ONE-TIME nudge on the next
|
|
12
|
+
* `onBeforeProviderCall`.
|
|
13
|
+
*
|
|
14
|
+
* The load-bearing property is "propose, don't write": a proposal never mutates
|
|
15
|
+
* memory or skills. It is injected into the next request's system prompt phrased
|
|
16
|
+
* so the MODEL may call `memory_save` / `synthesize_skill` — which still hit
|
|
17
|
+
* their own permission prompts. The reflection itself is best-effort: it runs
|
|
18
|
+
* detached from the turn, catches everything, and skips silently when there is
|
|
19
|
+
* no provider or the provider errors.
|
|
20
|
+
*/
|
|
21
|
+
/** Reflect when a turn did at least this many tool calls (busy/procedural turn). */
|
|
22
|
+
export declare const REFLECT_MIN_TOOL_RESULTS = 5;
|
|
23
|
+
/** Reflect when a turn ran at least this many mode-loop rounds (a hard task). */
|
|
24
|
+
export declare const REFLECT_MIN_ITERATIONS = 8;
|
|
25
|
+
/** Upper bound on the whole reflection (side-channel LLM pass). */
|
|
26
|
+
export declare const REFLECT_TIMEOUT_MS = 30000;
|
|
27
|
+
export declare const REFLECT_SYSTEM_PROMPT = "You review a single finished agent turn and decide whether anything is worth remembering for the future.\n\nLook for exactly two things:\n- a durable FACT worth saving to long-term memory (a stable preference, a project detail, a hard-won answer) \u2192 kind \"memory\"\n- a repeated multi-step PROCEDURE worth capturing as a reusable skill \u2192 kind \"skill\"\n\nBe strict. Most turns warrant NOTHING. Never propose transient chit-chat, one-off values, or things already obvious from context.\n\nOutput ONLY a JSON array with 0, 1, or 2 items \u2014 no prose, no code fences. Each item:\n { \"kind\": \"memory\" | \"skill\", \"title\": \"<= 80 chars\", \"nudge\": \"one paragraph addressed to the assistant, suggesting it consider saving this\" }\nReturn [] when nothing is worth proposing.";
|
|
28
|
+
/**
|
|
29
|
+
* The cheap gate, run synchronously on `onTurnEnd` against the turn's events.
|
|
30
|
+
* A turn is worth reflecting on when it was busy (≥{@link REFLECT_MIN_TOOL_RESULTS}
|
|
31
|
+
* tool results), hit an error, or ground through ≥{@link REFLECT_MIN_ITERATIONS}
|
|
32
|
+
* mode-loop rounds. A quiet turn (a quick Q&A) is skipped.
|
|
33
|
+
*/
|
|
34
|
+
export declare function shouldReflect(events: ReadonlyArray<MoxxyEvent>): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Build a compact text digest of a turn from its events: the user prompt, the
|
|
37
|
+
* tool names invoked (with counts), any error snippets, and the final assistant
|
|
38
|
+
* text — each snippet clipped, the whole thing capped at {@link MAX_DIGEST_CHARS}.
|
|
39
|
+
* This is what the reflection model reads instead of the raw event stream.
|
|
40
|
+
*/
|
|
41
|
+
export declare function buildTurnDigest(events: ReadonlyArray<MoxxyEvent>): string;
|
|
42
|
+
/**
|
|
43
|
+
* Defensively parse the reflection model's reply into 0-2 proposals. Strips an
|
|
44
|
+
* optional ```json fence, takes the first `[` … last `]` span, refuses an
|
|
45
|
+
* oversized span, JSON-parses, validates each item, and clamps to 2. ANY
|
|
46
|
+
* failure (no array, malformed JSON, wrong shape) yields `[]` — a bad reply
|
|
47
|
+
* silently produces no nudge rather than throwing into the detached reflection.
|
|
48
|
+
*/
|
|
49
|
+
export declare function parseReflectionReply(text: string): ReflectionProposal[];
|
|
50
|
+
/**
|
|
51
|
+
* The one-time nudge injected into the next provider request's system prompt.
|
|
52
|
+
* Phrased as a suggestion the model MAY act on via `memory_save` /
|
|
53
|
+
* `synthesize_skill` (each of which asks the user for permission) — never a
|
|
54
|
+
* directive, never a silent write.
|
|
55
|
+
*/
|
|
56
|
+
export declare function buildNudgeBlock(proposals: ReadonlyArray<ReflectionProposal>): string;
|
|
57
|
+
/**
|
|
58
|
+
* The default reflector: one cheap side-channel LLM pass over the finished
|
|
59
|
+
* turn. Resolves the active provider from the service registry, streams a
|
|
60
|
+
* strict-JSON reply, and parses it into 0-2 proposals. Returns `[]` (never
|
|
61
|
+
* throws) when there is no active provider or the provider errors — reflection
|
|
62
|
+
* degrades to a no-op rather than surfacing anything into the session.
|
|
63
|
+
*/
|
|
64
|
+
export declare const reflectorDefaultDef: ReflectorDef;
|
|
65
|
+
/** Per-session reflection budget: window = the whole session for v1. */
|
|
66
|
+
export interface SessionBudget {
|
|
67
|
+
/** Reflections fired this window (v1 cap = 1). */
|
|
68
|
+
readonly count: number;
|
|
69
|
+
/** Seq of the last turn's last event reflected on (future windowing aid). */
|
|
70
|
+
readonly lastSeq: number;
|
|
71
|
+
}
|
|
72
|
+
/** Test-facing handle into the driver's private per-session state. */
|
|
73
|
+
export interface ReflectorInternals {
|
|
74
|
+
/** Await the (fire-and-forget) reflection kicked off for a session, if any. */
|
|
75
|
+
settle(sessionId: SessionId): Promise<void>;
|
|
76
|
+
/** The pending one-time nudge for a session, or null. */
|
|
77
|
+
pendingNudge(sessionId: SessionId): string | null;
|
|
78
|
+
/** The session's reflection budget, or undefined when it never fired the gate. */
|
|
79
|
+
budget(sessionId: SessionId): SessionBudget | undefined;
|
|
80
|
+
}
|
|
81
|
+
export interface BuildReflectorPluginResult {
|
|
82
|
+
readonly plugin: Plugin;
|
|
83
|
+
readonly internals: ReflectorInternals;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Build the reflector plugin. The default export calls this with no options;
|
|
87
|
+
* tests use the returned `internals` handle to await the detached reflection
|
|
88
|
+
* and inspect the budget / pending nudge deterministically.
|
|
89
|
+
*/
|
|
90
|
+
export declare function buildReflectorPlugin(): BuildReflectorPluginResult;
|
|
91
|
+
/** Discovery-loadable default export (the plain plugin, no test handle). */
|
|
92
|
+
declare const reflectorPlugin: Plugin;
|
|
93
|
+
export default reflectorPlugin;
|
|
94
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,UAAU,EACf,KAAK,MAAM,EAGX,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,SAAS,EACf,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;;GAkBG;AAGH,oFAAoF;AACpF,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAC1C,iFAAiF;AACjF,eAAO,MAAM,sBAAsB,IAAI,CAAC;AACxC,mEAAmE;AACnE,eAAO,MAAM,kBAAkB,QAAS,CAAC;AAUzC,eAAO,MAAM,qBAAqB,+xBAUS,CAAC;AAI5C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,OAAO,CAYxE;AAOD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,MAAM,CAyCzE;AASD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAevE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,aAAa,CAAC,kBAAkB,CAAC,GAAG,MAAM,CAQpF;AAyDD;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,EAAE,YAgCjC,CAAC;AAIF,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC5B,kDAAkD;IAClD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAOD,sEAAsE;AACtE,MAAM,WAAW,kBAAkB;IACjC,+EAA+E;IAC/E,MAAM,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,yDAAyD;IACzD,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAAC;IAClD,kFAAkF;IAClF,MAAM,CAAC,SAAS,EAAE,SAAS,GAAG,aAAa,GAAG,SAAS,CAAC;CACzD;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC;CACxC;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,0BAA0B,CA2GjE;AAED,4EAA4E;AAC5E,QAAA,MAAM,eAAe,EAAE,MAAsC,CAAC;AAC9D,eAAe,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { definePlugin, z, } from '@moxxy/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* @moxxy/reflector-default — the default learning-loop block.
|
|
4
|
+
*
|
|
5
|
+
* TWO parts ship in one plugin:
|
|
6
|
+
* 1. a {@link ReflectorDef} named `'default'` (the strategy): one cheap
|
|
7
|
+
* side-channel LLM pass over a finished turn that returns 0-2
|
|
8
|
+
* {@link ReflectionProposal}s;
|
|
9
|
+
* 2. the DRIVER (lifecycle hooks): decides WHEN to reflect (a cheap gate +
|
|
10
|
+
* a one-per-session budget), runs the active reflector fire-and-forget on
|
|
11
|
+
* `onTurnEnd`, and delivers any proposals as a ONE-TIME nudge on the next
|
|
12
|
+
* `onBeforeProviderCall`.
|
|
13
|
+
*
|
|
14
|
+
* The load-bearing property is "propose, don't write": a proposal never mutates
|
|
15
|
+
* memory or skills. It is injected into the next request's system prompt phrased
|
|
16
|
+
* so the MODEL may call `memory_save` / `synthesize_skill` — which still hit
|
|
17
|
+
* their own permission prompts. The reflection itself is best-effort: it runs
|
|
18
|
+
* detached from the turn, catches everything, and skips silently when there is
|
|
19
|
+
* no provider or the provider errors.
|
|
20
|
+
*/
|
|
21
|
+
// ── Gate thresholds ─────────────────────────────────────────────────────────
|
|
22
|
+
/** Reflect when a turn did at least this many tool calls (busy/procedural turn). */
|
|
23
|
+
export const REFLECT_MIN_TOOL_RESULTS = 5;
|
|
24
|
+
/** Reflect when a turn ran at least this many mode-loop rounds (a hard task). */
|
|
25
|
+
export const REFLECT_MIN_ITERATIONS = 8;
|
|
26
|
+
/** Upper bound on the whole reflection (side-channel LLM pass). */
|
|
27
|
+
export const REFLECT_TIMEOUT_MS = 30_000;
|
|
28
|
+
/** Cap on the model reply span we'll JSON-parse — past this is malformed/hostile. */
|
|
29
|
+
const MAX_JSON_SPAN = 32 * 1024;
|
|
30
|
+
/** Cap on the turn digest handed to the reflection model. */
|
|
31
|
+
const MAX_DIGEST_CHARS = 4000;
|
|
32
|
+
/** Cap on a single snippet (prompt / assistant text / one error) inside the digest. */
|
|
33
|
+
const MAX_SNIPPET_CHARS = 600;
|
|
34
|
+
/** Tokens for the reflection pass — proposals are tiny. */
|
|
35
|
+
const REFLECT_MAX_TOKENS = 1200;
|
|
36
|
+
export const REFLECT_SYSTEM_PROMPT = `You review a single finished agent turn and decide whether anything is worth remembering for the future.
|
|
37
|
+
|
|
38
|
+
Look for exactly two things:
|
|
39
|
+
- a durable FACT worth saving to long-term memory (a stable preference, a project detail, a hard-won answer) → kind "memory"
|
|
40
|
+
- a repeated multi-step PROCEDURE worth capturing as a reusable skill → kind "skill"
|
|
41
|
+
|
|
42
|
+
Be strict. Most turns warrant NOTHING. Never propose transient chit-chat, one-off values, or things already obvious from context.
|
|
43
|
+
|
|
44
|
+
Output ONLY a JSON array with 0, 1, or 2 items — no prose, no code fences. Each item:
|
|
45
|
+
{ "kind": "memory" | "skill", "title": "<= 80 chars", "nudge": "one paragraph addressed to the assistant, suggesting it consider saving this" }
|
|
46
|
+
Return [] when nothing is worth proposing.`;
|
|
47
|
+
// ── Pure helpers (exported for tests) ───────────────────────────────────────
|
|
48
|
+
/**
|
|
49
|
+
* The cheap gate, run synchronously on `onTurnEnd` against the turn's events.
|
|
50
|
+
* A turn is worth reflecting on when it was busy (≥{@link REFLECT_MIN_TOOL_RESULTS}
|
|
51
|
+
* tool results), hit an error, or ground through ≥{@link REFLECT_MIN_ITERATIONS}
|
|
52
|
+
* mode-loop rounds. A quiet turn (a quick Q&A) is skipped.
|
|
53
|
+
*/
|
|
54
|
+
export function shouldReflect(events) {
|
|
55
|
+
let toolResults = 0;
|
|
56
|
+
let errors = 0;
|
|
57
|
+
let iterations = 0;
|
|
58
|
+
for (const e of events) {
|
|
59
|
+
if (e.type === 'tool_result')
|
|
60
|
+
toolResults++;
|
|
61
|
+
else if (e.type === 'error')
|
|
62
|
+
errors++;
|
|
63
|
+
else if (e.type === 'mode_iteration')
|
|
64
|
+
iterations++;
|
|
65
|
+
}
|
|
66
|
+
return (toolResults >= REFLECT_MIN_TOOL_RESULTS || errors >= 1 || iterations >= REFLECT_MIN_ITERATIONS);
|
|
67
|
+
}
|
|
68
|
+
function clip(text, max) {
|
|
69
|
+
const t = text.trim();
|
|
70
|
+
return t.length > max ? t.slice(0, max) + '…' : t;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Build a compact text digest of a turn from its events: the user prompt, the
|
|
74
|
+
* tool names invoked (with counts), any error snippets, and the final assistant
|
|
75
|
+
* text — each snippet clipped, the whole thing capped at {@link MAX_DIGEST_CHARS}.
|
|
76
|
+
* This is what the reflection model reads instead of the raw event stream.
|
|
77
|
+
*/
|
|
78
|
+
export function buildTurnDigest(events) {
|
|
79
|
+
const parts = [];
|
|
80
|
+
const prompt = events.find((e) => e.type === 'user_prompt');
|
|
81
|
+
if (prompt && prompt.type === 'user_prompt') {
|
|
82
|
+
parts.push(`USER: ${clip(prompt.text, MAX_SNIPPET_CHARS)}`);
|
|
83
|
+
}
|
|
84
|
+
const toolCounts = new Map();
|
|
85
|
+
for (const e of events) {
|
|
86
|
+
if (e.type === 'tool_call_requested') {
|
|
87
|
+
toolCounts.set(e.name, (toolCounts.get(e.name) ?? 0) + 1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (toolCounts.size > 0) {
|
|
91
|
+
const list = [...toolCounts.entries()]
|
|
92
|
+
.map(([name, n]) => (n > 1 ? `${name}×${n}` : name))
|
|
93
|
+
.join(', ');
|
|
94
|
+
parts.push(`TOOLS: ${list}`);
|
|
95
|
+
}
|
|
96
|
+
const errorSnippets = [];
|
|
97
|
+
for (const e of events) {
|
|
98
|
+
if (e.type === 'tool_result' && e.ok === false && e.error?.message) {
|
|
99
|
+
errorSnippets.push(clip(e.error.message, 200));
|
|
100
|
+
}
|
|
101
|
+
else if (e.type === 'error') {
|
|
102
|
+
errorSnippets.push(clip(e.message, 200));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (errorSnippets.length > 0) {
|
|
106
|
+
parts.push(`ERRORS: ${errorSnippets.slice(0, 5).join(' | ')}`);
|
|
107
|
+
}
|
|
108
|
+
// Last finalized assistant message is the turn's conclusion.
|
|
109
|
+
let finalText = null;
|
|
110
|
+
for (const e of events) {
|
|
111
|
+
if (e.type === 'assistant_message' && e.content.trim())
|
|
112
|
+
finalText = e.content;
|
|
113
|
+
}
|
|
114
|
+
if (finalText)
|
|
115
|
+
parts.push(`ASSISTANT: ${clip(finalText, MAX_SNIPPET_CHARS)}`);
|
|
116
|
+
return clip(parts.join('\n'), MAX_DIGEST_CHARS);
|
|
117
|
+
}
|
|
118
|
+
const proposalSchema = z.object({
|
|
119
|
+
kind: z.enum(['memory', 'skill']),
|
|
120
|
+
title: z.string().min(1).max(200),
|
|
121
|
+
nudge: z.string().min(1).max(2000),
|
|
122
|
+
});
|
|
123
|
+
const proposalsSchema = z.array(proposalSchema);
|
|
124
|
+
/**
|
|
125
|
+
* Defensively parse the reflection model's reply into 0-2 proposals. Strips an
|
|
126
|
+
* optional ```json fence, takes the first `[` … last `]` span, refuses an
|
|
127
|
+
* oversized span, JSON-parses, validates each item, and clamps to 2. ANY
|
|
128
|
+
* failure (no array, malformed JSON, wrong shape) yields `[]` — a bad reply
|
|
129
|
+
* silently produces no nudge rather than throwing into the detached reflection.
|
|
130
|
+
*/
|
|
131
|
+
export function parseReflectionReply(text) {
|
|
132
|
+
try {
|
|
133
|
+
const fenced = /```(?:json)?\n?([\s\S]*?)```/.exec(text);
|
|
134
|
+
const candidate = fenced ? fenced[1] : text;
|
|
135
|
+
const start = candidate.indexOf('[');
|
|
136
|
+
const end = candidate.lastIndexOf(']');
|
|
137
|
+
if (start === -1 || end <= start)
|
|
138
|
+
return [];
|
|
139
|
+
const span = candidate.slice(start, end + 1);
|
|
140
|
+
if (span.length > MAX_JSON_SPAN)
|
|
141
|
+
return [];
|
|
142
|
+
const parsed = proposalsSchema.safeParse(JSON.parse(span));
|
|
143
|
+
if (!parsed.success)
|
|
144
|
+
return [];
|
|
145
|
+
return parsed.data.slice(0, 2).map((p) => ({ kind: p.kind, title: p.title, nudge: p.nudge }));
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The one-time nudge injected into the next provider request's system prompt.
|
|
153
|
+
* Phrased as a suggestion the model MAY act on via `memory_save` /
|
|
154
|
+
* `synthesize_skill` (each of which asks the user for permission) — never a
|
|
155
|
+
* directive, never a silent write.
|
|
156
|
+
*/
|
|
157
|
+
export function buildNudgeBlock(proposals) {
|
|
158
|
+
const lines = proposals.map((p) => `- (${p.kind}) ${p.title}: ${p.nudge}`).join('\n');
|
|
159
|
+
return (`\n\n[reflection] A background review of the last turn surfaced ${proposals.length} ` +
|
|
160
|
+
`suggestion(s) you MAY act on if genuinely useful (otherwise ignore):\n${lines}\n` +
|
|
161
|
+
'To persist one, you may call `memory_save` (a durable fact) or `synthesize_skill` ' +
|
|
162
|
+
'(a repeatable procedure) — each asks the user for permission first. Do not act unless it clearly helps.');
|
|
163
|
+
}
|
|
164
|
+
/** Collect a provider stream into text, honoring the abort signal. */
|
|
165
|
+
async function streamText(provider, req) {
|
|
166
|
+
let out = '';
|
|
167
|
+
const iterable = provider.stream(req);
|
|
168
|
+
const iterator = iterable[Symbol.asyncIterator]();
|
|
169
|
+
try {
|
|
170
|
+
for (;;) {
|
|
171
|
+
if (req.signal?.aborted)
|
|
172
|
+
break;
|
|
173
|
+
const step = await raceAbort(iterator.next(), req.signal);
|
|
174
|
+
if (step === ABORTED)
|
|
175
|
+
break;
|
|
176
|
+
if (step.done)
|
|
177
|
+
break;
|
|
178
|
+
const event = step.value;
|
|
179
|
+
if (event.type === 'text_delta')
|
|
180
|
+
out += event.delta;
|
|
181
|
+
else if (event.type === 'error')
|
|
182
|
+
throw new Error(event.message);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
if (req.signal?.aborted)
|
|
187
|
+
void iterator.return?.(undefined).catch(() => { });
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
const ABORTED = Symbol('aborted');
|
|
192
|
+
function raceAbort(step, signal) {
|
|
193
|
+
if (!signal)
|
|
194
|
+
return step;
|
|
195
|
+
if (signal.aborted) {
|
|
196
|
+
void step.catch(() => { });
|
|
197
|
+
return Promise.resolve(ABORTED);
|
|
198
|
+
}
|
|
199
|
+
return new Promise((resolve, reject) => {
|
|
200
|
+
const onAbort = () => {
|
|
201
|
+
void step.catch(() => { });
|
|
202
|
+
resolve(ABORTED);
|
|
203
|
+
};
|
|
204
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
205
|
+
step.then((v) => {
|
|
206
|
+
signal.removeEventListener('abort', onAbort);
|
|
207
|
+
resolve(v);
|
|
208
|
+
}, (e) => {
|
|
209
|
+
signal.removeEventListener('abort', onAbort);
|
|
210
|
+
reject(e);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* The default reflector: one cheap side-channel LLM pass over the finished
|
|
216
|
+
* turn. Resolves the active provider from the service registry, streams a
|
|
217
|
+
* strict-JSON reply, and parses it into 0-2 proposals. Returns `[]` (never
|
|
218
|
+
* throws) when there is no active provider or the provider errors — reflection
|
|
219
|
+
* degrades to a no-op rather than surfacing anything into the session.
|
|
220
|
+
*/
|
|
221
|
+
export const reflectorDefaultDef = {
|
|
222
|
+
name: 'default',
|
|
223
|
+
displayName: 'Default learning loop',
|
|
224
|
+
async reflect(ctx) {
|
|
225
|
+
const providers = ctx.services.get('providers');
|
|
226
|
+
// Graceful no-provider skip: nothing to reflect with.
|
|
227
|
+
if (!providers || providers.getActiveName() == null)
|
|
228
|
+
return [];
|
|
229
|
+
let provider;
|
|
230
|
+
try {
|
|
231
|
+
provider = providers.getActive();
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
const digest = buildTurnDigest(ctx.log.byTurn(ctx.turnId));
|
|
237
|
+
if (!digest)
|
|
238
|
+
return [];
|
|
239
|
+
const req = {
|
|
240
|
+
model: provider.models[0]?.id ?? 'unknown',
|
|
241
|
+
system: REFLECT_SYSTEM_PROMPT,
|
|
242
|
+
messages: [{ role: 'user', content: [{ type: 'text', text: digest }] }],
|
|
243
|
+
maxTokens: REFLECT_MAX_TOKENS,
|
|
244
|
+
signal: ctx.signal,
|
|
245
|
+
};
|
|
246
|
+
try {
|
|
247
|
+
const reply = await streamText(provider, req);
|
|
248
|
+
return parseReflectionReply(reply);
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// Provider error / abort → degrade to a no-op rather than surfacing.
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
/**
|
|
257
|
+
* Build the reflector plugin. The default export calls this with no options;
|
|
258
|
+
* tests use the returned `internals` handle to await the detached reflection
|
|
259
|
+
* and inspect the budget / pending nudge deterministically.
|
|
260
|
+
*/
|
|
261
|
+
export function buildReflectorPlugin() {
|
|
262
|
+
// All keyed by sessionId so one shared instance stays correct across the many
|
|
263
|
+
// sessions a runner hosts in one process; every map self-cleans in onShutdown.
|
|
264
|
+
const budgets = new Map();
|
|
265
|
+
const pending = new Map();
|
|
266
|
+
const inFlight = new Map();
|
|
267
|
+
const shutdownControllers = new Map();
|
|
268
|
+
function cleanup(sessionId) {
|
|
269
|
+
try {
|
|
270
|
+
shutdownControllers.get(sessionId)?.abort();
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
// aborting a controller never throws in practice; guard anyway.
|
|
274
|
+
}
|
|
275
|
+
shutdownControllers.delete(sessionId);
|
|
276
|
+
budgets.delete(sessionId);
|
|
277
|
+
pending.delete(sessionId);
|
|
278
|
+
inFlight.delete(sessionId);
|
|
279
|
+
}
|
|
280
|
+
const hooks = {
|
|
281
|
+
// AWAITED by the lifecycle dispatcher (with a per-plugin timeout), so this
|
|
282
|
+
// MUST return fast: the gate + budget check are synchronous, and the actual
|
|
283
|
+
// reflection is kicked off FIRE-AND-FORGET (a guarded detached promise) so
|
|
284
|
+
// it never blocks the turn and can never throw into it.
|
|
285
|
+
onTurnEnd(ctx) {
|
|
286
|
+
try {
|
|
287
|
+
const events = ctx.log.byTurn(ctx.turnId);
|
|
288
|
+
if (!shouldReflect(events))
|
|
289
|
+
return;
|
|
290
|
+
const prior = budgets.get(ctx.sessionId) ?? { count: 0, lastSeq: -1 };
|
|
291
|
+
// Budget: at most one reflection per session window (v1 = whole session).
|
|
292
|
+
if (prior.count >= 1)
|
|
293
|
+
return;
|
|
294
|
+
const reflectors = ctx.services.get('reflectors');
|
|
295
|
+
const reflector = reflectors?.getActive() ?? null;
|
|
296
|
+
// Nullable registry: no active reflector → reflection is off. Do NOT
|
|
297
|
+
// consume the budget (so activating one later still gets a chance).
|
|
298
|
+
if (!reflector)
|
|
299
|
+
return;
|
|
300
|
+
// Reserve the budget slot BEFORE going async so a rapid next turn can't
|
|
301
|
+
// double-fire while this reflection is still in flight.
|
|
302
|
+
const lastSeq = events.length > 0 ? events[events.length - 1].seq : prior.lastSeq;
|
|
303
|
+
budgets.set(ctx.sessionId, { count: prior.count + 1, lastSeq });
|
|
304
|
+
let sc = shutdownControllers.get(ctx.sessionId);
|
|
305
|
+
if (!sc) {
|
|
306
|
+
sc = new AbortController();
|
|
307
|
+
shutdownControllers.set(ctx.sessionId, sc);
|
|
308
|
+
}
|
|
309
|
+
const signal = AbortSignal.any([sc.signal, AbortSignal.timeout(REFLECT_TIMEOUT_MS)]);
|
|
310
|
+
const reflectCtx = {
|
|
311
|
+
sessionId: ctx.sessionId,
|
|
312
|
+
turnId: ctx.turnId,
|
|
313
|
+
cwd: ctx.cwd,
|
|
314
|
+
log: ctx.log,
|
|
315
|
+
services: ctx.services,
|
|
316
|
+
signal,
|
|
317
|
+
};
|
|
318
|
+
const p = (async () => {
|
|
319
|
+
try {
|
|
320
|
+
const proposals = await reflector.reflect(reflectCtx);
|
|
321
|
+
if (proposals.length > 0)
|
|
322
|
+
pending.set(ctx.sessionId, buildNudgeBlock(proposals));
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
// Best-effort: a no-provider skip, provider error, or reflector
|
|
326
|
+
// throw must never surface. The budget stays spent (one attempt).
|
|
327
|
+
}
|
|
328
|
+
})();
|
|
329
|
+
inFlight.set(ctx.sessionId, p);
|
|
330
|
+
// Detach: do NOT return `p` — onTurnEnd resolves immediately.
|
|
331
|
+
void p;
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
// The gate itself (a hostile log reader, etc.) must never take down the
|
|
335
|
+
// awaited onTurnEnd hook.
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
// Deliver a pending reflection as a ONE-TIME nudge on the next provider call.
|
|
339
|
+
onBeforeProviderCall(req, ctx) {
|
|
340
|
+
const nudge = pending.get(ctx.sessionId);
|
|
341
|
+
if (!nudge)
|
|
342
|
+
return;
|
|
343
|
+
pending.delete(ctx.sessionId); // one-shot: cleared after injection
|
|
344
|
+
return { ...req, system: (req.system ?? '') + nudge };
|
|
345
|
+
},
|
|
346
|
+
onShutdown(ctx) {
|
|
347
|
+
cleanup(ctx.sessionId);
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
const plugin = definePlugin({
|
|
351
|
+
name: '@moxxy/reflector-default',
|
|
352
|
+
version: '0.0.0',
|
|
353
|
+
reflectors: [reflectorDefaultDef],
|
|
354
|
+
hooks,
|
|
355
|
+
});
|
|
356
|
+
const internals = {
|
|
357
|
+
async settle(sessionId) {
|
|
358
|
+
await inFlight.get(sessionId)?.catch(() => { });
|
|
359
|
+
},
|
|
360
|
+
pendingNudge: (sessionId) => pending.get(sessionId) ?? null,
|
|
361
|
+
budget: (sessionId) => budgets.get(sessionId),
|
|
362
|
+
};
|
|
363
|
+
return { plugin, internals };
|
|
364
|
+
}
|
|
365
|
+
/** Discovery-loadable default export (the plain plugin, no test handle). */
|
|
366
|
+
const reflectorPlugin = buildReflectorPlugin().plugin;
|
|
367
|
+
export default reflectorPlugin;
|
|
368
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@moxxy/reflector-default",
|
|
3
|
+
"version": "0.27.0",
|
|
4
|
+
"description": "The default learning-loop reflector: watches finished turns and proposes memory/skill improvements as a one-time nudge on the next provider call — never a silent write. The model may act via memory_save/synthesize_skill, which still hit their own permission prompts.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"moxxy",
|
|
7
|
+
"agent",
|
|
8
|
+
"reflector",
|
|
9
|
+
"learning-loop",
|
|
10
|
+
"memory"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://moxxy.ai",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/moxxy-ai/moxxy/issues"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/moxxy-ai/moxxy.git",
|
|
19
|
+
"directory": "packages/reflector-default"
|
|
20
|
+
},
|
|
21
|
+
"author": "Michal Makowski <michal.makowski97@gmail.com>",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"src"
|
|
38
|
+
],
|
|
39
|
+
"moxxy": {
|
|
40
|
+
"plugin": {
|
|
41
|
+
"entry": "./dist/index.js",
|
|
42
|
+
"kind": "reflector"
|
|
43
|
+
},
|
|
44
|
+
"requirements": [
|
|
45
|
+
{
|
|
46
|
+
"kind": "tool",
|
|
47
|
+
"name": "memory_save",
|
|
48
|
+
"state": "active",
|
|
49
|
+
"optional": true,
|
|
50
|
+
"hint": "Install @moxxy/plugin-memory so the model can act on memory proposals."
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"kind": "tool",
|
|
54
|
+
"name": "synthesize_skill",
|
|
55
|
+
"state": "active",
|
|
56
|
+
"optional": true,
|
|
57
|
+
"hint": "synthesize_skill (bundled with the CLI) lets the model act on skill proposals."
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"zod": "^3.24.0",
|
|
63
|
+
"@moxxy/sdk": "0.27.0"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@types/node": "^22.10.0",
|
|
67
|
+
"typescript": "^5.7.3",
|
|
68
|
+
"vitest": "^2.1.8",
|
|
69
|
+
"@moxxy/testing": "0.0.45",
|
|
70
|
+
"@moxxy/vitest-preset": "0.0.0",
|
|
71
|
+
"@moxxy/tsconfig": "0.0.0",
|
|
72
|
+
"@moxxy/core": "0.27.0"
|
|
73
|
+
},
|
|
74
|
+
"scripts": {
|
|
75
|
+
"build": "tsc -p tsconfig.json",
|
|
76
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
77
|
+
"test": "vitest run",
|
|
78
|
+
"clean": "rm -rf dist .turbo"
|
|
79
|
+
}
|
|
80
|
+
}
|