@kernel.chat/kbot 3.99.22 → 3.99.24

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/agent.js CHANGED
@@ -41,6 +41,8 @@ import { CheckpointManager, newSessionId } from './checkpoint.js';
41
41
  import { TelemetryEmitter } from './telemetry.js';
42
42
  import { loadSkills } from './skills-loader.js';
43
43
  import { getSelfAwarenessPrompt } from './self-awareness.js';
44
+ import { buildMathGuardBlock } from './math-guard.js';
45
+ import { buildIdentityGuardBlock } from './identity-guard.js';
44
46
  import { queueSignal, getCollectiveRecommendation, isCollectiveEnabled } from './collective.js';
45
47
  import { subscribeToBlackboard } from './agent-protocol.js';
46
48
  import { ActiveInferenceEngine } from './free-energy.js';
@@ -946,6 +948,8 @@ export async function runAgent(message, options = {}) {
946
948
  const contextSnippet = options.context ? formatContextForPrompt(options.context) : '';
947
949
  const skillsSnippet = loadSkills(process.cwd(), message);
948
950
  const selfAwarenessSnippet = getSelfAwarenessPrompt();
951
+ const mathGuardSnippet = buildMathGuardBlock(message);
952
+ const identityGuardSnippet = buildIdentityGuardBlock(message);
949
953
  const memorySnippet = getMemoryPrompt();
950
954
  const learningContext = buildFullLearningContext(message, process.cwd());
951
955
  const synthesisSnippet = getSynthesisContext(8); // Three-tier memory: reflection layer insights
@@ -1065,7 +1069,7 @@ Always quote file paths that contain spaces. Never reference internal system nam
1065
1069
  const promptSections = createPromptSections({
1066
1070
  persona: PERSONA,
1067
1071
  matrixPrompt: matrixPrompt || undefined,
1068
- contextSnippet: (contextSnippet || '') + repoMapSnippet + graphSnippet + skillsSnippet + skillLibrarySnippet + '\n\n' + selfAwarenessSnippet || undefined,
1072
+ contextSnippet: (contextSnippet || '') + repoMapSnippet + graphSnippet + skillsSnippet + skillLibrarySnippet + '\n\n' + selfAwarenessSnippet + (mathGuardSnippet ? '\n\n' + mathGuardSnippet : '') + (identityGuardSnippet ? '\n\n' + identityGuardSnippet : '') || undefined,
1069
1073
  memorySnippet: (memorySnippet || '') + getDreamPrompt(8) + reflectionSnippet || undefined,
1070
1074
  learningContext: ((learningContext || '') + (synthesisSnippet ? '\n\n' + synthesisSnippet : '') + (correctionsSnippet ? '\n\n' + correctionsSnippet : '')) || undefined,
1071
1075
  });
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Identity guard — injects ground-truth self-facts into the user message
3
+ * when the message is a self-query.
4
+ *
5
+ * Reality probes (2026-04-20) showed that with a small local model
6
+ * (gemma4:latest, 4B-class), "what version are you?" returns a different
7
+ * fabricated version number on each invocation — v3.99.14, v3.99.12, etc.
8
+ * The self-awareness system-context block exists and is injected, but
9
+ * small models ignore system context for identity queries.
10
+ *
11
+ * This module mirrors math-guard: detect the query in the USER message and
12
+ * prepend a ground-truth block to the context snippet. Local models respect
13
+ * user-message content more than system prompts, so the answer shows up
14
+ * directly in the input the model conditions on.
15
+ *
16
+ * Scope: version, product name, provider, model. Not capabilities — those
17
+ * belong to self-awareness.ts and its 200-token budget.
18
+ */
19
+ export type IdentityQueryKind = 'version' | 'product' | 'provider' | 'model';
20
+ export declare function detectIdentityQuery(message: string): Set<IdentityQueryKind>;
21
+ /**
22
+ * Build the ground-truth preamble for a user message. Returns empty string
23
+ * when the message is not a self-query, so callers can concatenate
24
+ * unconditionally.
25
+ */
26
+ export declare function buildIdentityGuardBlock(message: string): string;
27
+ //# sourceMappingURL=identity-guard.d.ts.map
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Identity guard — injects ground-truth self-facts into the user message
3
+ * when the message is a self-query.
4
+ *
5
+ * Reality probes (2026-04-20) showed that with a small local model
6
+ * (gemma4:latest, 4B-class), "what version are you?" returns a different
7
+ * fabricated version number on each invocation — v3.99.14, v3.99.12, etc.
8
+ * The self-awareness system-context block exists and is injected, but
9
+ * small models ignore system context for identity queries.
10
+ *
11
+ * This module mirrors math-guard: detect the query in the USER message and
12
+ * prepend a ground-truth block to the context snippet. Local models respect
13
+ * user-message content more than system prompts, so the answer shows up
14
+ * directly in the input the model conditions on.
15
+ *
16
+ * Scope: version, product name, provider, model. Not capabilities — those
17
+ * belong to self-awareness.ts and its 200-token budget.
18
+ */
19
+ import { readFileSync } from 'node:fs';
20
+ import { fileURLToPath } from 'node:url';
21
+ import { dirname, join } from 'node:path';
22
+ import { getByokProvider, getProvider, getProviderModel } from './auth.js';
23
+ const QUERY_PATTERNS = [
24
+ { re: /\bwhat\s+version\b/i, kinds: ['version', 'product'] },
25
+ { re: /\bwhich\s+version\b/i, kinds: ['version', 'product'] },
26
+ { re: /\byour\s+version\b/i, kinds: ['version'] },
27
+ { re: /\bversion\s+(are|is)\s+you\b/i, kinds: ['version'] },
28
+ { re: /\bwho\s+are\s+you\b/i, kinds: ['product', 'version'] },
29
+ { re: /\bwhat\s+are\s+you\b/i, kinds: ['product', 'provider', 'model'] },
30
+ { re: /\bwhat\s+(model|LLM)\b/i, kinds: ['model', 'provider'] },
31
+ { re: /\bwhich\s+(model|LLM)\b/i, kinds: ['model', 'provider'] },
32
+ { re: /\bwhat\s+provider\b/i, kinds: ['provider', 'model'] },
33
+ ];
34
+ export function detectIdentityQuery(message) {
35
+ const kinds = new Set();
36
+ if (!message)
37
+ return kinds;
38
+ for (const p of QUERY_PATTERNS) {
39
+ if (p.re.test(message)) {
40
+ for (const k of p.kinds)
41
+ kinds.add(k);
42
+ }
43
+ }
44
+ return kinds;
45
+ }
46
+ function readPackageVersion() {
47
+ try {
48
+ const here = dirname(fileURLToPath(import.meta.url));
49
+ const pkgPath = join(here, '..', 'package.json');
50
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
51
+ return pkg.version ?? null;
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
57
+ function collectFacts() {
58
+ const facts = {
59
+ version: readPackageVersion(),
60
+ product: '@kernel.chat/kbot',
61
+ provider: null,
62
+ model: null,
63
+ };
64
+ try {
65
+ const provId = getByokProvider();
66
+ facts.provider = getProvider(provId).name;
67
+ facts.model = getProviderModel(provId, 'default');
68
+ }
69
+ catch {
70
+ // Provider config unreadable — leave null; caller formats gracefully.
71
+ }
72
+ return facts;
73
+ }
74
+ /**
75
+ * Build the ground-truth preamble for a user message. Returns empty string
76
+ * when the message is not a self-query, so callers can concatenate
77
+ * unconditionally.
78
+ */
79
+ export function buildIdentityGuardBlock(message) {
80
+ const kinds = detectIdentityQuery(message);
81
+ if (kinds.size === 0)
82
+ return '';
83
+ const facts = collectFacts();
84
+ const lines = [
85
+ '[IDENTITY GUARD — the user is asking about your identity. Use these values VERBATIM. Do not invent version numbers, providers, or model names.]',
86
+ ];
87
+ if (kinds.has('product') || kinds.has('version')) {
88
+ if (facts.version) {
89
+ lines.push(` product: ${facts.product} v${facts.version}`);
90
+ }
91
+ else {
92
+ lines.push(` product: ${facts.product}`);
93
+ }
94
+ }
95
+ if (kinds.has('version') && facts.version) {
96
+ lines.push(` version: v${facts.version} (exact string — no other number is correct)`);
97
+ }
98
+ if (kinds.has('provider') && facts.provider) {
99
+ lines.push(` provider: ${facts.provider}`);
100
+ }
101
+ if (kinds.has('model') && facts.model) {
102
+ lines.push(` model: ${facts.model}`);
103
+ }
104
+ return lines.join('\n') + '\n';
105
+ }
106
+ //# sourceMappingURL=identity-guard.js.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Math guard — pre-computes arithmetic in user messages.
3
+ *
4
+ * Reality-check probes (2026-04-20) showed kbot answering "847 × 239 = 1,985,633"
5
+ * (correct: 202,433) — an order-of-magnitude RF-16 arithmetic error. This module
6
+ * scans the user message for basic arithmetic expressions, evaluates them in JS,
7
+ * and returns a ground-truth block to prepend so the LLM sees the correct answer
8
+ * as input rather than relying on learned priors.
9
+ *
10
+ * Scope: single-operator expressions only — `a op b` where op ∈ {+,-,*,×,/,÷,%}.
11
+ * Compound expressions, parentheses, unary minus: not handled. The failure mode
12
+ * when this module doesn't match is "guard did not fire" — the LLM sees the raw
13
+ * message unchanged, same as today. So false negatives are safe.
14
+ *
15
+ * No eval(), no Function(). Pure regex + arithmetic.
16
+ */
17
+ export interface ComputedExpression {
18
+ expression: string;
19
+ result: number;
20
+ }
21
+ /**
22
+ * Find every `a op b` in the message and compute it.
23
+ *
24
+ * Returns at most 10 distinct expressions per message — more than that and the
25
+ * user is probably pasting a table, not asking for arithmetic help.
26
+ */
27
+ export declare function extractArithmetic(message: string): ComputedExpression[];
28
+ /**
29
+ * Ground-truth preamble to prepend to the user message (or system prompt).
30
+ * Returns empty string when no arithmetic is detected — callers can
31
+ * concatenate unconditionally.
32
+ */
33
+ export declare function buildMathGuardBlock(message: string): string;
34
+ //# sourceMappingURL=math-guard.d.ts.map
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Math guard — pre-computes arithmetic in user messages.
3
+ *
4
+ * Reality-check probes (2026-04-20) showed kbot answering "847 × 239 = 1,985,633"
5
+ * (correct: 202,433) — an order-of-magnitude RF-16 arithmetic error. This module
6
+ * scans the user message for basic arithmetic expressions, evaluates them in JS,
7
+ * and returns a ground-truth block to prepend so the LLM sees the correct answer
8
+ * as input rather than relying on learned priors.
9
+ *
10
+ * Scope: single-operator expressions only — `a op b` where op ∈ {+,-,*,×,/,÷,%}.
11
+ * Compound expressions, parentheses, unary minus: not handled. The failure mode
12
+ * when this module doesn't match is "guard did not fire" — the LLM sees the raw
13
+ * message unchanged, same as today. So false negatives are safe.
14
+ *
15
+ * No eval(), no Function(). Pure regex + arithmetic.
16
+ */
17
+ const EXPR_RE = /(?<!\w)(-?\d+(?:\.\d+)?)\s*([+\-*×/÷%])\s*(-?\d+(?:\.\d+)?)(?!\w)/g;
18
+ function normalizeOp(op) {
19
+ switch (op) {
20
+ case '+': return '+';
21
+ case '-': return '-';
22
+ case '*':
23
+ case '×': return '*';
24
+ case '/':
25
+ case '÷': return '/';
26
+ case '%': return '%';
27
+ default: return null;
28
+ }
29
+ }
30
+ function compute(a, op, b) {
31
+ const norm = normalizeOp(op);
32
+ if (norm === null)
33
+ return null;
34
+ switch (norm) {
35
+ case '+': return a + b;
36
+ case '-': return a - b;
37
+ case '*': return a * b;
38
+ case '/': return b === 0 ? null : a / b;
39
+ case '%': return b === 0 ? null : a % b;
40
+ }
41
+ }
42
+ function formatResult(n) {
43
+ if (!Number.isFinite(n))
44
+ return String(n);
45
+ if (Number.isInteger(n))
46
+ return n.toString();
47
+ // Trim long floats; six digits is plenty for user-facing answers.
48
+ return Number.parseFloat(n.toFixed(6)).toString();
49
+ }
50
+ /**
51
+ * Find every `a op b` in the message and compute it.
52
+ *
53
+ * Returns at most 10 distinct expressions per message — more than that and the
54
+ * user is probably pasting a table, not asking for arithmetic help.
55
+ */
56
+ export function extractArithmetic(message) {
57
+ if (!message)
58
+ return [];
59
+ const out = [];
60
+ const seen = new Set();
61
+ for (const m of message.matchAll(EXPR_RE)) {
62
+ const raw = m[0].trim();
63
+ if (seen.has(raw))
64
+ continue;
65
+ seen.add(raw);
66
+ const a = Number.parseFloat(m[1]);
67
+ const b = Number.parseFloat(m[3]);
68
+ const r = compute(a, m[2], b);
69
+ if (r === null || !Number.isFinite(r))
70
+ continue;
71
+ out.push({ expression: raw, result: r });
72
+ if (out.length >= 10)
73
+ break;
74
+ }
75
+ return out;
76
+ }
77
+ /**
78
+ * Ground-truth preamble to prepend to the user message (or system prompt).
79
+ * Returns empty string when no arithmetic is detected — callers can
80
+ * concatenate unconditionally.
81
+ */
82
+ export function buildMathGuardBlock(message) {
83
+ const exprs = extractArithmetic(message);
84
+ if (exprs.length === 0)
85
+ return '';
86
+ const lines = exprs.map(e => ` ${e.expression} = ${formatResult(e.result)}`);
87
+ return ('[MATH GUARD — computed deterministically in JS; use these values verbatim, ' +
88
+ 'do not recompute and do not contradict]\n' +
89
+ lines.join('\n') +
90
+ '\n');
91
+ }
92
+ //# sourceMappingURL=math-guard.js.map
@@ -31,6 +31,7 @@ export function getSelfAwarenessPrompt() {
31
31
  const pkgPath = join(here, '..', 'package.json');
32
32
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
33
33
  lines.push(`- Product: @kernel.chat/kbot v${pkg.version} — MIT-licensed, open-source terminal AI agent from kernel.chat group.`);
34
+ lines.push(`- VERSION RULE: when asked your version, the answer is EXACTLY "${pkg.version}". Not 3.99.14, not 3.97.0, not any other number. If you cite a version number that is not "${pkg.version}" you are wrong — the value above is read at startup from the installed package.json.`);
34
35
  }
35
36
  catch {
36
37
  lines.push('- Product: @kernel.chat/kbot — MIT-licensed, open-source terminal AI agent.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kernel.chat/kbot",
3
- "version": "3.99.22",
3
+ "version": "3.99.24",
4
4
  "description": "Open-source terminal AI agent. 787+ tools, 35 agents, 20 providers. Dreams, learns, watches your system. Controls your phone. Fully local, fully sovereign. MIT.",
5
5
  "type": "module",
6
6
  "repository": {