@hmharness/agent 0.19.0 → 0.19.2
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/dist/cognitive-recorder.d.ts +23 -0
- package/dist/cognitive-recorder.js +97 -0
- package/dist/cognitive-tools.d.ts +11 -0
- package/dist/cognitive-tools.js +53 -0
- package/dist/i18n.d.ts +1 -0
- package/dist/i18n.js +2 -0
- package/dist/runner.js +10 -0
- package/dist/tools.js +2 -0
- package/package.json +8 -7
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare class CognitiveRunRecorder {
|
|
2
|
+
private home;
|
|
3
|
+
private cwd;
|
|
4
|
+
private rec;
|
|
5
|
+
private store;
|
|
6
|
+
private open;
|
|
7
|
+
private seq;
|
|
8
|
+
private failures;
|
|
9
|
+
private successes;
|
|
10
|
+
constructor(home: string, task: string, sessionId: string, cwd: string);
|
|
11
|
+
/** loop onToolCall: open a step for this tool invocation */
|
|
12
|
+
call(toolName: string, args: Record<string, unknown>): void;
|
|
13
|
+
/** loop onToolResult: close the matching step (same tool, FIFO) */
|
|
14
|
+
result(toolName: string, _output: string, isError: boolean): void;
|
|
15
|
+
/** task end: persist trajectory + episodic memory index entry */
|
|
16
|
+
finish(success: boolean, meta: {
|
|
17
|
+
turns: number;
|
|
18
|
+
toolUses: number;
|
|
19
|
+
task: string;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
trajectoryId: string;
|
|
22
|
+
} | null>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hmharness/agent - cognitive run recorder (blueprint CL-001 / MEM-002)
|
|
3
|
+
*
|
|
4
|
+
* Bridges the LIVE agent loop into the Cognitive OS: every tool call becomes
|
|
5
|
+
* a cognitive trajectory step (action type = tool name, reason = the loop's
|
|
6
|
+
* own event context), and at task end the trajectory lands in the episodic
|
|
7
|
+
* store (HMH_HOME/cognitive/trajectories/) PLUS an episodic memory index
|
|
8
|
+
* entry with full provenance (source/provenance/confidence/timestamp/
|
|
9
|
+
* environment/session — MEM-007). Everything here is best-effort: cognitive
|
|
10
|
+
* recording must never break a task run.
|
|
11
|
+
*/
|
|
12
|
+
import { TrajectoryStore, TrajectoryRecorder, CognitiveMemory } from '@hmharness/cognitive';
|
|
13
|
+
export class CognitiveRunRecorder {
|
|
14
|
+
home;
|
|
15
|
+
cwd;
|
|
16
|
+
rec;
|
|
17
|
+
store;
|
|
18
|
+
open = new Map();
|
|
19
|
+
seq = 0;
|
|
20
|
+
failures = 0;
|
|
21
|
+
successes = 0;
|
|
22
|
+
constructor(home, task, sessionId, cwd) {
|
|
23
|
+
this.home = home;
|
|
24
|
+
this.cwd = cwd;
|
|
25
|
+
this.store = new TrajectoryStore(home);
|
|
26
|
+
this.rec = new TrajectoryRecorder(`trj-${sessionId}-${Date.now().toString(36)}`, sessionId, { id: 'terminal', version: '1.0.0' }, { id: `goal-${sessionId}`, description: task });
|
|
27
|
+
}
|
|
28
|
+
/** loop onToolCall: open a step for this tool invocation */
|
|
29
|
+
call(toolName, args) {
|
|
30
|
+
const key = `${toolName}#${++this.seq}`;
|
|
31
|
+
this.open.set(key, {
|
|
32
|
+
action: { id: key, type: toolName, args: clip(args), reason: 'agent loop tool call' },
|
|
33
|
+
startedAt: Date.now(),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** loop onToolResult: close the matching step (same tool, FIFO) */
|
|
37
|
+
result(toolName, _output, isError) {
|
|
38
|
+
for (const [key, open] of this.open) {
|
|
39
|
+
if (!key.startsWith(`${toolName}#`))
|
|
40
|
+
continue;
|
|
41
|
+
this.open.delete(key);
|
|
42
|
+
if (isError)
|
|
43
|
+
this.failures += 1;
|
|
44
|
+
else
|
|
45
|
+
this.successes += 1;
|
|
46
|
+
this.rec.record({
|
|
47
|
+
action: open.action,
|
|
48
|
+
outcome: isError ? 'failure' : 'success',
|
|
49
|
+
evidence: [],
|
|
50
|
+
durationMs: Date.now() - open.startedAt,
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// result without a matching open call (e.g. preflight rejection): still
|
|
55
|
+
// record it so denials are visible in the trajectory
|
|
56
|
+
this.rec.record({
|
|
57
|
+
action: { id: `${toolName}#${++this.seq}`, type: toolName, args: {}, reason: 'tool result without recorded call' },
|
|
58
|
+
outcome: isError ? 'failure' : 'success',
|
|
59
|
+
evidence: [],
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/** task end: persist trajectory + episodic memory index entry */
|
|
63
|
+
async finish(success, meta) {
|
|
64
|
+
try {
|
|
65
|
+
const traj = this.rec.finish(success);
|
|
66
|
+
// recovery heuristic: a success step that follows a failure step was
|
|
67
|
+
// already counted by TrajectoryRecorder; metrics carry it
|
|
68
|
+
const written = await this.store.append(traj);
|
|
69
|
+
if (!written.ok)
|
|
70
|
+
return null;
|
|
71
|
+
const mem = new CognitiveMemory(this.home);
|
|
72
|
+
await mem.load();
|
|
73
|
+
await mem.write({
|
|
74
|
+
layer: 'episodic',
|
|
75
|
+
content: `${success ? 'OK' : 'FAIL'} ${meta.toolUses} tools/${meta.turns} turns: ${meta.task.slice(0, 120)}`,
|
|
76
|
+
payload: { trajectoryId: traj.id, tools: meta.toolUses, turns: meta.turns, successes: this.successes, failures: this.failures },
|
|
77
|
+
source: 'agent-run',
|
|
78
|
+
provenance: `trajectory:${traj.id}`,
|
|
79
|
+
confidence: success ? 0.9 : 0.6,
|
|
80
|
+
environment: 'terminal',
|
|
81
|
+
session: traj.sessionId,
|
|
82
|
+
tags: ['run', success ? 'ok' : 'fail'],
|
|
83
|
+
});
|
|
84
|
+
return { trajectoryId: traj.id };
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null; // cognitive recording is best-effort, never fatal
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function clip(args) {
|
|
92
|
+
const out = {};
|
|
93
|
+
for (const [k, v] of Object.entries(args ?? {}).slice(0, 12)) {
|
|
94
|
+
out[k] = typeof v === 'string' ? v.slice(0, 200) : v;
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hmharness/agent - cognitive query tool (blueprint §18/UX-004 seed)
|
|
3
|
+
*
|
|
4
|
+
* Gives the LIVE model read access to its own accumulated world model:
|
|
5
|
+
* which tool types historically succeed, how calibrated its predictions
|
|
6
|
+
* are, and what the diagnosis layer thinks is worth learning. Read-only by
|
|
7
|
+
* design — the model cannot write beliefs; those update only from real
|
|
8
|
+
* trajectories (no self-grading channel).
|
|
9
|
+
*/
|
|
10
|
+
import type { Tool } from '@hmharness/kernel';
|
|
11
|
+
export declare const cognitiveQueryTool: Tool;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export const cognitiveQueryTool = {
|
|
2
|
+
name: 'cognitive_query',
|
|
3
|
+
description: 'Query the cognitive layer\'s accumulated knowledge about past runs: which action/tool types historically succeed (belief table with confidence and evidence counts), prediction calibration, planner-trusted action types, learning opportunities, and memory contradictions. Read-only. Args: focus=beliefs|calibration|diagnosis|memory (optional; omit for a summary).',
|
|
4
|
+
parameters: {
|
|
5
|
+
type: 'object',
|
|
6
|
+
properties: {
|
|
7
|
+
focus: { type: 'string', description: 'beliefs | calibration | diagnosis | memory (optional)' },
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
async execute(args, ctx) {
|
|
11
|
+
const focus = String(args.focus ?? 'summary');
|
|
12
|
+
try {
|
|
13
|
+
const { analyzeWorldModel, diagnoseOpportunities, CognitiveMemory } = await import('@hmharness/cognitive');
|
|
14
|
+
const lines = [];
|
|
15
|
+
if (focus === 'summary' || focus === 'beliefs') {
|
|
16
|
+
const wm = await analyzeWorldModel(ctx.home);
|
|
17
|
+
lines.push(`world model (${wm.trajectoriesReplayed} trajectories, ${wm.stepsReplayed} steps replayed):`);
|
|
18
|
+
if (wm.beliefs.length === 0)
|
|
19
|
+
lines.push(' (no beliefs yet — run a few tasks first)');
|
|
20
|
+
for (const b of wm.beliefs.slice(0, 12)) {
|
|
21
|
+
lines.push(` ${b.actionType.padEnd(24)} conf=${b.confidence.toFixed(2)} n=${b.evidenceCount}`);
|
|
22
|
+
}
|
|
23
|
+
if (focus === 'beliefs') {
|
|
24
|
+
lines.push(`planner gate: trusted=[${wm.plannerGate.trusted.join(',')}] untrusted=[${wm.plannerGate.untrusted.join(',')}] unknown=[${wm.plannerGate.unknown.join(',')}]`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (focus === 'summary' || focus === 'calibration') {
|
|
28
|
+
const wm = await analyzeWorldModel(ctx.home);
|
|
29
|
+
const cal = wm.calibration;
|
|
30
|
+
lines.push(`calibration: ${cal.resolved} resolved predictions${cal.meanError !== undefined ? `, mean |error|=${cal.meanError}` : ' (no predictions recorded yet — steps carry no confidence)'}`);
|
|
31
|
+
}
|
|
32
|
+
if (focus === 'summary' || focus === 'diagnosis') {
|
|
33
|
+
const d = await diagnoseOpportunities(ctx.home);
|
|
34
|
+
lines.push(`diagnosis (${d.trajectories} trajectories):`);
|
|
35
|
+
if (d.opportunities.length === 0)
|
|
36
|
+
lines.push(' (no learning opportunity meets the evidence threshold yet)');
|
|
37
|
+
for (const o of d.opportunities.slice(0, 6)) {
|
|
38
|
+
lines.push(` [${o.suggestedTargets.join('/')}] ${o.signal}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (focus === 'summary' || focus === 'memory') {
|
|
42
|
+
const mem = new CognitiveMemory(ctx.home);
|
|
43
|
+
await mem.load();
|
|
44
|
+
const stats = mem.stats();
|
|
45
|
+
lines.push(`memory: working:${stats.working} episodic:${stats.episodic} semantic:${stats.semantic} procedural:${stats.procedural} world:${stats.world}, contradictions pending: ${mem.detectContradictions().length}`);
|
|
46
|
+
}
|
|
47
|
+
return { output: lines.join('\n') };
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
return { output: 'cognitive_query failed: ' + String(err).slice(0, 200), isError: true };
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
};
|
package/dist/i18n.d.ts
CHANGED
package/dist/i18n.js
CHANGED
|
@@ -70,6 +70,7 @@ const zh = {
|
|
|
70
70
|
cmdStatusline: '自定义底栏字段(/statusline "{model} · {cwd}")',
|
|
71
71
|
cmdKeymap: '查看/重映射动作键(/keymap <action>=<key>)',
|
|
72
72
|
cmdRemote: '手机配对二维码/远程连接(/remote lan|wan)',
|
|
73
|
+
cmdCognitive: '认知子系统状态面板(/cognitive)',
|
|
73
74
|
cmdReview: '一键发起「审查当前工作树」任务',
|
|
74
75
|
cmdModelHint: '切换 chat 路由: /model <name>',
|
|
75
76
|
panelHint: '↑↓/滚轮/点击 选择 · Enter 确认 · Esc 关闭',
|
|
@@ -221,6 +222,7 @@ const en = {
|
|
|
221
222
|
cmdStatusline: 'custom bottom-line fields (/statusline "{model} · {cwd}")',
|
|
222
223
|
cmdKeymap: 'view/remap action keys (/keymap <action>=<key>)',
|
|
223
224
|
cmdRemote: 'phone pairing QR / remote connect (/remote lan|wan)',
|
|
225
|
+
cmdCognitive: 'cognitive subsystem status panel (/cognitive)',
|
|
224
226
|
cmdReview: 'one-click "review the current worktree" task',
|
|
225
227
|
cmdModelHint: 'switch chat route: /model <name>',
|
|
226
228
|
panelHint: '↑↓ / wheel / click to select · Enter confirms · Esc closes',
|
package/dist/runner.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { homeDir, loadConfig, resolveProvider, getGoal, mcpServerTools, Registry, runLoop, Session, } from '@hmharness/kernel';
|
|
9
9
|
import { brief, createTrajectoryRecorder } from '@hmharness/observability';
|
|
10
|
+
import { CognitiveRunRecorder } from "./cognitive-recorder.js";
|
|
10
11
|
import { readFile } from 'node:fs/promises';
|
|
11
12
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
12
13
|
import { join, dirname } from 'node:path';
|
|
@@ -259,6 +260,10 @@ export async function runAgentTask(opts) {
|
|
|
259
260
|
// are swallowed inside the recorder and can never fail the task itself.
|
|
260
261
|
const traj = createTrajectoryRecorder(ctx.home, { task: opts.task, model: cfg.provider.model, cwd: ctx.cwd });
|
|
261
262
|
traj.emit('run.started', 'user', { task: brief(opts.task, 400) });
|
|
263
|
+
// Cognitive OS (blueprint 2026-09 CL-001): the same tool-call stream also
|
|
264
|
+
// feeds the COGNITIVE trajectory store + episodic memory index, so goal/
|
|
265
|
+
// exploration/learning layers have real data from day one. Best-effort.
|
|
266
|
+
const cogRec = new CognitiveRunRecorder(ctx.home, opts.task, session.id, ctx.cwd);
|
|
262
267
|
// V2 M10 shadow router: log what the adaptive router WOULD pick vs the live
|
|
263
268
|
// static route - data for a later, gated switch (never steers traffic now)
|
|
264
269
|
{
|
|
@@ -381,6 +386,7 @@ export async function runAgentTask(opts) {
|
|
|
381
386
|
onToolCall: (name, args) => {
|
|
382
387
|
toolsUsed.push(name);
|
|
383
388
|
traj.emit('tool.requested', 'tool', { name, args: brief(args, 120) });
|
|
389
|
+
cogRec.call(name, args);
|
|
384
390
|
events.onToolCall?.(name, args);
|
|
385
391
|
},
|
|
386
392
|
onToolResult: (name, output, isError) => {
|
|
@@ -392,6 +398,7 @@ export async function runAgentTask(opts) {
|
|
|
392
398
|
}
|
|
393
399
|
void session.tool(name, output, isError);
|
|
394
400
|
traj.emit('tool.completed', 'tool', { name, isError, preview: brief(output, 120) });
|
|
401
|
+
cogRec.result(name, output, isError);
|
|
395
402
|
events.onToolResult?.(name, output, isError);
|
|
396
403
|
},
|
|
397
404
|
onApproval: (name, args, granted) => {
|
|
@@ -416,6 +423,9 @@ export async function runAgentTask(opts) {
|
|
|
416
423
|
if (injectChannel)
|
|
417
424
|
injectChannel.active = false;
|
|
418
425
|
traj.finish({ success: result.reason === 'final', reason: result.reason }, { turns: result.turns, toolUses: result.toolUses, promptTokens: result.usage.promptTokens, completionTokens: result.usage.completionTokens });
|
|
426
|
+
// cognitive trajectory + episodic index land AFTER the observability
|
|
427
|
+
// recorder settles, still before onFinal so callers can rely on both
|
|
428
|
+
await cogRec.finish(result.reason === 'final', { turns: result.turns, toolUses: result.toolUses, task: opts.task });
|
|
419
429
|
// routing.outcome backfill: did the ACTUAL route succeed? (shadow stats)
|
|
420
430
|
{
|
|
421
431
|
const routing = cfg.routing ?? {};
|
package/dist/tools.js
CHANGED
|
@@ -9,6 +9,7 @@ import { copyFile, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
|
9
9
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
10
10
|
import { promisify } from 'node:util';
|
|
11
11
|
import { chatVision, homeDir, isBareProbe, isVisionRefusal, loadConfig, visionProviderChain } from '@hmharness/kernel';
|
|
12
|
+
import { cognitiveQueryTool } from "./cognitive-tools.js";
|
|
12
13
|
const execCb = promisify(exec);
|
|
13
14
|
const DENY_PATTERNS = [
|
|
14
15
|
// recursive deletes aimed at ROOT/HOME/SYSTEM targets only - relative
|
|
@@ -662,4 +663,5 @@ export const seeImageTool = {
|
|
|
662
663
|
export const baseTools = [
|
|
663
664
|
readFileTool, editFileTool, writeFileTool, listDirTool, runCommandTool, rememberTool, seeImageTool,
|
|
664
665
|
webSearchTool, webFetchTool, browserOpenTool, desktopScreenshotTool, desktopClickTool, desktopTypeTool, sshRunTool,
|
|
666
|
+
cognitiveQueryTool,
|
|
665
667
|
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hmharness/agent",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.2",
|
|
4
4
|
"description": "hmharness agent execution layer: base tools, system prompt, sub-agent spawn, and the shared task runner that frontends (cli, web) drive.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -15,12 +15,13 @@
|
|
|
15
15
|
"build": "tsc -p tsconfig.build.json"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@hmharness/
|
|
19
|
-
"@hmharness/domain-
|
|
20
|
-
"@hmharness/
|
|
21
|
-
"@hmharness/
|
|
22
|
-
"@hmharness/
|
|
23
|
-
"@hmharness/
|
|
18
|
+
"@hmharness/cognitive": "0.19.2",
|
|
19
|
+
"@hmharness/domain-harmony": "0.19.2",
|
|
20
|
+
"@hmharness/domain-ops": "0.19.2",
|
|
21
|
+
"@hmharness/evolution": "0.19.2",
|
|
22
|
+
"@hmharness/kernel": "0.19.2",
|
|
23
|
+
"@hmharness/observability": "0.19.2",
|
|
24
|
+
"@hmharness/sandbox": "0.19.2"
|
|
24
25
|
},
|
|
25
26
|
"files": [
|
|
26
27
|
"dist"
|