@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.
@@ -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
@@ -86,6 +86,7 @@ export interface Strings {
86
86
  cmdStatusline: string;
87
87
  cmdKeymap: string;
88
88
  cmdRemote: string;
89
+ cmdCognitive: string;
89
90
  cmdReview: string;
90
91
  cmdModelHint: string;
91
92
  /** palette footer key hint: how to drive the slash/model picker (TUI) */
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.0",
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/domain-harmony": "0.19.0",
19
- "@hmharness/domain-ops": "0.19.0",
20
- "@hmharness/evolution": "0.19.0",
21
- "@hmharness/kernel": "0.19.0",
22
- "@hmharness/observability": "0.19.0",
23
- "@hmharness/sandbox": "0.19.0"
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"