kongbrain 0.4.1 → 0.4.3
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/.github/workflows/ci.yml +45 -0
- package/.github/workflows/pr-check.yml +16 -0
- package/CHANGELOG.md +64 -0
- package/README.github.md +40 -1
- package/SKILL.md +1 -1
- package/TOKEN_FLOW.md +184 -0
- package/package.json +1 -1
- package/src/acan.ts +28 -5
- package/src/causal.ts +18 -25
- package/src/cognitive-bootstrap.ts +6 -6
- package/src/cognitive-check.ts +17 -19
- package/src/config.ts +1 -1
- package/src/context-engine.ts +105 -50
- package/src/daemon-manager.ts +70 -19
- package/src/deferred-cleanup.ts +12 -10
- package/src/embeddings.ts +6 -7
- package/src/errors.ts +5 -3
- package/src/graph-context.ts +281 -178
- package/src/hooks/after-tool-call.ts +2 -1
- package/src/hooks/before-tool-call.ts +15 -11
- package/src/hooks/llm-output.ts +18 -10
- package/src/index.ts +39 -18
- package/src/intent.ts +9 -8
- package/src/log.ts +11 -0
- package/src/memory-daemon.ts +1 -0
- package/src/orchestrator.ts +11 -4
- package/src/prefetch.ts +2 -2
- package/src/reflection.ts +9 -2
- package/src/schema.surql +7 -0
- package/src/skills.ts +32 -10
- package/src/soul.ts +17 -1
- package/src/state.ts +31 -0
- package/src/supersedes.ts +99 -0
- package/src/surreal.ts +174 -110
- package/src/tools/introspect.ts +1 -1
- package/src/wakeup.ts +0 -142
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
services:
|
|
14
|
+
surrealdb:
|
|
15
|
+
image: surrealdb/surrealdb:latest
|
|
16
|
+
ports:
|
|
17
|
+
- 8000:8000
|
|
18
|
+
options: >-
|
|
19
|
+
--health-cmd "curl -sf http://localhost:8000/health || exit 1"
|
|
20
|
+
--health-interval 5s
|
|
21
|
+
--health-timeout 5s
|
|
22
|
+
--health-retries 10
|
|
23
|
+
env:
|
|
24
|
+
SURREAL_USER: root
|
|
25
|
+
SURREAL_PASS: root
|
|
26
|
+
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
|
|
30
|
+
- uses: actions/setup-node@v4
|
|
31
|
+
with:
|
|
32
|
+
node-version: 20
|
|
33
|
+
cache: npm
|
|
34
|
+
|
|
35
|
+
- run: npm ci
|
|
36
|
+
|
|
37
|
+
- name: Run unit tests
|
|
38
|
+
run: npx vitest run --exclude test/integration.test.ts
|
|
39
|
+
|
|
40
|
+
- name: Run integration tests
|
|
41
|
+
run: npx vitest run test/integration.test.ts
|
|
42
|
+
env:
|
|
43
|
+
SURREAL_URL: ws://localhost:8000/rpc
|
|
44
|
+
SURREAL_USER: root
|
|
45
|
+
SURREAL_PASS: root
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: PR Check
|
|
2
|
+
|
|
3
|
+
on: pull_request
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
lint:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
steps:
|
|
9
|
+
- uses: actions/checkout@v4
|
|
10
|
+
- uses: actions/setup-node@v4
|
|
11
|
+
with:
|
|
12
|
+
node-version: 20
|
|
13
|
+
cache: npm
|
|
14
|
+
- run: npm ci
|
|
15
|
+
- run: npx tsc --noEmit || true # Type check (peer deps may be missing)
|
|
16
|
+
- run: npx vitest run --exclude test/integration.test.ts
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to KongBrain are documented here.
|
|
4
|
+
|
|
5
|
+
## [0.4.2] - 2026-04-03
|
|
6
|
+
|
|
7
|
+
### Performance
|
|
8
|
+
- **DB query batching**: `queryBatch()` sends N SQL statements in 1 round-trip. `graphExpand` (208 queries → 1-2/hop), `queryCausalContext` (120 → 1-2), `vectorSearch` (7 → 1).
|
|
9
|
+
- **Embedding reuse**: User embeddings from ingest stashed in session state and reused in retrieval, eliminating 1-4 redundant BGE-M3 calls per turn.
|
|
10
|
+
- **Token estimation**: Aligned with Claude Code — 4 bytes/token (was 3.4), JSON at 2 bytes/token, images at 2000 tokens, 33% safety margin.
|
|
11
|
+
- **Content stripping**: Old thinking blocks, images, tool results, and assistant filler text surgically replaced with compact stubs. Saves 20-80k tokens/session.
|
|
12
|
+
- **Prompt compression**: Rules suffix (~400 → ~80 tokens), planning gate (~250 → ~60), IKONG description (~120 → ~20), cognitive check (~300 → ~120).
|
|
13
|
+
- **Structured output**: All internal LLM calls use `json_schema` output format when supported. Eliminates markdown fencing and preamble.
|
|
14
|
+
- **Budget model**: 4-way split (conversation 23%, retrieval 38.5%, core 15.5%, tools 23%) with SPA cap at 8% of context window.
|
|
15
|
+
- **Parallel DB calls**: `scoreResults` parallelizes utility cache + reflection session lookups. Single `getSessionTurns` fetch in `afterTurn` reused by all consumers.
|
|
16
|
+
- **Tier 0 dedup**: Core memory fetched once per `assemble()`, passed to inner transform (was fetched twice).
|
|
17
|
+
- **Cognitive check frequency**: Every 5 turns (was 3), skipped when `skipRetrieval=true`.
|
|
18
|
+
|
|
19
|
+
### Security
|
|
20
|
+
- **Edge name validation**: `VALID_EDGES` whitelist + `assertValidEdge()` prevents SQL injection via edge interpolation in `graphExpand` and `queryCausalContext`.
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
- Tool limit enforcement: `>` → `>=` (was allowing 1 extra call past limit).
|
|
24
|
+
- Daemon batch merge instead of overwrite (prevents turn data loss when batches arrive faster than extraction).
|
|
25
|
+
- Reflection dedup: `typeof` check on score (prevents undefined bypass creating duplicates).
|
|
26
|
+
- Extraction fallback: Warns on no-JSON and regex fallback failure (was silent).
|
|
27
|
+
- Shutdown errors logged instead of swallowed.
|
|
28
|
+
- Config comment: `midSessionCleanupThreshold` documented as 100k, actual default 25k — fixed.
|
|
29
|
+
- Cognitive bootstrap importance: Float scale (0.85) → integer scale (9) matching rest of codebase.
|
|
30
|
+
|
|
31
|
+
### Documentation
|
|
32
|
+
- JSDoc on all critical exported functions.
|
|
33
|
+
- Named constants replacing magic numbers (`DEDUP_COSINE_THRESHOLD`, `MAX_FRONTIER_SEEDS`, `EDGE_NEIGHBOR_LIMIT`, etc.).
|
|
34
|
+
- README: Added Performance section with batching, estimation, stripping, and structured output details.
|
|
35
|
+
|
|
36
|
+
### Tests
|
|
37
|
+
- 88 → 415 tests (21 test files). Full coverage: ACAN scorer, hooks, memory daemon extraction, skills, soul system, wakeup, concept extraction, session persistence, tools, subagent lifecycle, and SurrealDB integration tests against live database.
|
|
38
|
+
|
|
39
|
+
## [0.4.1] - 2026-04-02
|
|
40
|
+
|
|
41
|
+
### Performance
|
|
42
|
+
- Inline intent classification: Parse LOOKUP/EDIT/REFACTOR from assistant text to set tool limits without extra LLM call.
|
|
43
|
+
- Default tool budget reduced to 9.
|
|
44
|
+
- LRU embedding cache (512 entries).
|
|
45
|
+
- System prompt caching split (static vs dynamic sections).
|
|
46
|
+
- Token delta computation (prevent quadratic overcounting).
|
|
47
|
+
- Concept backfilling with embedding similarity.
|
|
48
|
+
|
|
49
|
+
### Bug Fixes
|
|
50
|
+
- 17 bugs resolved from deep codebase review.
|
|
51
|
+
|
|
52
|
+
## [0.4.0] - 2026-04-01
|
|
53
|
+
|
|
54
|
+
### Features
|
|
55
|
+
- Spawned subagent edge wiring.
|
|
56
|
+
- Soul graduation in mid-session cleanup.
|
|
57
|
+
- Fibonacci memory resurfacing.
|
|
58
|
+
- ACAN (Attentive Cross-Attention Network) for learned retrieval scoring.
|
|
59
|
+
- Cognitive checks with directive injection.
|
|
60
|
+
- Handoff file emergency persistence.
|
|
61
|
+
|
|
62
|
+
## [0.3.x] - 2026-03
|
|
63
|
+
|
|
64
|
+
Initial release series. Graph-backed persistent memory engine with SurrealDB, BGE-M3 embeddings, 9-type knowledge extraction, soul graduation system, and adaptive intent classification.
|
package/README.github.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
[](https://nodejs.org)
|
|
12
12
|
[](https://surrealdb.com)
|
|
13
13
|
[](https://github.com/openclaw/openclaw)
|
|
14
|
-
[](https://vitest.dev)
|
|
15
15
|
|
|
16
16
|
**A graph-backed cognitive engine for [OpenClaw](https://github.com/openclaw/openclaw).**
|
|
17
17
|
|
|
@@ -378,6 +378,45 @@ Three tools are registered for the LLM:
|
|
|
378
378
|
|
|
379
379
|
---
|
|
380
380
|
|
|
381
|
+
## Performance
|
|
382
|
+
|
|
383
|
+
KongBrain is aggressively optimized for token efficiency and latency, informed by analysis of the Claude Code source.
|
|
384
|
+
|
|
385
|
+
### DB Query Batching
|
|
386
|
+
|
|
387
|
+
All graph operations use batched multi-statement queries (`queryBatch`). A single `assemble()` call sends ~5 round-trips to SurrealDB instead of ~337 individual queries:
|
|
388
|
+
|
|
389
|
+
| Operation | Before | After |
|
|
390
|
+
|-----------|--------|-------|
|
|
391
|
+
| vectorSearch (7 tables) | 7 queries | 1 batched |
|
|
392
|
+
| graphExpand (26 edge types x N nodes) | 130-208 queries | 1-2 batched (per hop) |
|
|
393
|
+
| queryCausalContext (8 edge types x N nodes) | 80-120 queries | 1-2 batched (per hop) |
|
|
394
|
+
|
|
395
|
+
### Token Estimation
|
|
396
|
+
|
|
397
|
+
Token counting is aligned with the Anthropic API's actual tokenizer characteristics:
|
|
398
|
+
- **4 bytes/token** for prose/code (not the common 3.2-3.5 underestimate)
|
|
399
|
+
- **2 bytes/token** for JSON content (denser single-char tokens)
|
|
400
|
+
- **33% safety margin** on aggregate estimates
|
|
401
|
+
- **2000 tokens** for images/documents (matching API billing)
|
|
402
|
+
|
|
403
|
+
### Context Window Efficiency
|
|
404
|
+
|
|
405
|
+
Every turn, old messages are surgically stripped to save tokens while preserving recent context:
|
|
406
|
+
- **Thinking blocks** replaced with `[thinking]` marker (saves 1-5k tokens each)
|
|
407
|
+
- **Old tool results** content-cleared to stubs (saves 20-80k tokens/session)
|
|
408
|
+
- **Old assistant filler** collapsed to first line (saves 5-15k/session)
|
|
409
|
+
- **Images** in old messages replaced with `[image]` marker (saves 2k tokens each)
|
|
410
|
+
- **System prompt additions** capped at 8% of context window with priority trimming
|
|
411
|
+
|
|
412
|
+
### Structured Output
|
|
413
|
+
|
|
414
|
+
All internal LLM calls (memory extraction, cognitive checks, soul generation, skill extraction) use `json_schema` structured output when the provider supports it. This eliminates markdown fencing, preamble text, and parsing failures.
|
|
415
|
+
|
|
416
|
+
### Embedding Reuse
|
|
417
|
+
|
|
418
|
+
User message embeddings computed at ingest time are stashed in session state and reused during context retrieval, eliminating 1-4 redundant BGE-M3 inference calls per turn.
|
|
419
|
+
|
|
381
420
|
## Development
|
|
382
421
|
|
|
383
422
|
```bash
|
package/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: kongbrain
|
|
3
3
|
description: Graph-backed persistent memory engine for OpenClaw. Replaces the default context window with SurrealDB + vector embeddings that learn across sessions.
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.3
|
|
5
5
|
homepage: https://github.com/42U/kongbrain
|
|
6
6
|
metadata:
|
|
7
7
|
openclaw:
|
package/TOKEN_FLOW.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Token Flow Trace
|
|
2
|
+
|
|
3
|
+
This document maps the complete lifecycle of tokens through the Claw CLI,
|
|
4
|
+
from user input to API consumption. It identifies every injection point,
|
|
5
|
+
the growth characteristics of each category, and the key findings that
|
|
6
|
+
inform optimization work.
|
|
7
|
+
|
|
8
|
+
## Complete Token Lifecycle
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
User input
|
|
12
|
+
|
|
|
13
|
+
v
|
|
14
|
+
[1] ConversationRuntime::run_turn(user_input)
|
|
15
|
+
@ rust/crates/runtime/src/conversation.rs:290-294
|
|
16
|
+
- ConversationMessage::user_text() pushed to session.messages
|
|
17
|
+
- TOKEN IN: user text stored verbatim (no truncation)
|
|
18
|
+
|
|
|
19
|
+
v
|
|
20
|
+
[2] API Request Assembly (inner loop start)
|
|
21
|
+
@ conversation.rs:312-315
|
|
22
|
+
- ApiRequest {
|
|
23
|
+
system_prompt: self.system_prompt.clone(), // Vec<String> cloned every call
|
|
24
|
+
messages: self.session.messages.clone(), // FULL deep clone of all messages
|
|
25
|
+
}
|
|
26
|
+
- TOKEN IN: entire system prompt + full message history
|
|
27
|
+
- PERF NOTE: O(n) deep clone on every API call
|
|
28
|
+
|
|
|
29
|
+
v
|
|
30
|
+
[3] Bridge to MessageRequest
|
|
31
|
+
@ main.rs (AnthropicRuntimeClient::stream)
|
|
32
|
+
- system: request.system_prompt.join("\n\n") // sections → single string
|
|
33
|
+
- messages: convert_messages(&request.messages) // ConversationMessage → InputMessage
|
|
34
|
+
- tools: filter_tool_specs() // ToolDefinition array (JSON schemas)
|
|
35
|
+
- TOKEN IN: tool definitions (~5-15K chars of JSON schema)
|
|
36
|
+
|
|
|
37
|
+
v
|
|
38
|
+
[4] System Prompt (assembled once per session, sent every call)
|
|
39
|
+
@ rust/crates/runtime/src/prompt.rs:134-156
|
|
40
|
+
|
|
41
|
+
STATIC sections (before SYSTEM_PROMPT_DYNAMIC_BOUNDARY):
|
|
42
|
+
┌──────────────────────────────────┬────────────┐
|
|
43
|
+
│ Section │ ~Chars │
|
|
44
|
+
├──────────────────────────────────┼────────────┤
|
|
45
|
+
│ Intro │ ~400 │
|
|
46
|
+
│ System guidelines │ ~600 │
|
|
47
|
+
│ Doing tasks guidelines │ ~600 │
|
|
48
|
+
│ Actions section │ ~300 │
|
|
49
|
+
│ DYNAMIC_BOUNDARY marker │ 37 │
|
|
50
|
+
└──────────────────────────────────┴────────────┘
|
|
51
|
+
|
|
52
|
+
DYNAMIC sections (after DYNAMIC_BOUNDARY):
|
|
53
|
+
┌──────────────────────────────────┬────────────┐
|
|
54
|
+
│ Section │ ~Chars │
|
|
55
|
+
├──────────────────────────────────┼────────────┤
|
|
56
|
+
│ Environment context │ ~150 │
|
|
57
|
+
│ Project context (date, cwd) │ ~100 │
|
|
58
|
+
│ Git status snapshot │ variable │
|
|
59
|
+
│ Git diff snapshot │ UNBOUNDED │
|
|
60
|
+
│ Instruction files │ ≤12,000 │
|
|
61
|
+
│ Runtime config │ variable │
|
|
62
|
+
└──────────────────────────────────┴────────────┘
|
|
63
|
+
|
|
|
64
|
+
v
|
|
65
|
+
[5] HTTP POST to Anthropic
|
|
66
|
+
@ rust/crates/api/src/providers/anthropic.rs:336-354
|
|
67
|
+
- Full system prompt + full message history + full tool definitions sent
|
|
68
|
+
- Anthropic prompt caching may cache the prefix (5-min TTL)
|
|
69
|
+
- API returns Usage { input_tokens, output_tokens,
|
|
70
|
+
cache_creation_input_tokens, cache_read_input_tokens }
|
|
71
|
+
|
|
|
72
|
+
v
|
|
73
|
+
[6] Streaming Response Processing
|
|
74
|
+
@ conversation.rs:316-330
|
|
75
|
+
- build_assistant_message() collects:
|
|
76
|
+
TextDelta, ToolUse, Usage, PromptCache events
|
|
77
|
+
- AssistantEvent::Usage carries the actual API-reported token counts
|
|
78
|
+
|
|
|
79
|
+
v
|
|
80
|
+
[7] Usage Recording
|
|
81
|
+
@ conversation.rs:331-333
|
|
82
|
+
- usage_tracker.record(usage) → cumulative counters updated
|
|
83
|
+
- TOKEN OBSERVATION POINT: where we learn actual consumption
|
|
84
|
+
|
|
|
85
|
+
v
|
|
86
|
+
[8] Assistant Message Storage
|
|
87
|
+
@ conversation.rs:351-354
|
|
88
|
+
- ConversationMessage with text blocks + tool_use blocks stored in session
|
|
89
|
+
- TOKEN IN: assistant text + tool_use blocks (id, name, input JSON)
|
|
90
|
+
|
|
|
91
|
+
v
|
|
92
|
+
[9] Tool Execution (if tool_uses present)
|
|
93
|
+
@ conversation.rs:360-458
|
|
94
|
+
For each pending tool use:
|
|
95
|
+
a. Pre-tool hook (may modify input)
|
|
96
|
+
b. Permission check (may deny)
|
|
97
|
+
c. tool_executor.execute(name, input) → output String
|
|
98
|
+
d. Post-tool hook (may append feedback)
|
|
99
|
+
e. ConversationMessage::tool_result(id, name, output, is_error)
|
|
100
|
+
- TOKEN IN: tool output stored VERBATIM — NO TRUNCATION
|
|
101
|
+
- This is the #1 source of context bloat
|
|
102
|
+
|
|
|
103
|
+
v
|
|
104
|
+
[10] Loop back to step [2] if tool_uses were present
|
|
105
|
+
- Each iteration re-sends ALL accumulated messages
|
|
106
|
+
- Context grows monotonically within a single turn
|
|
107
|
+
|
|
|
108
|
+
v
|
|
109
|
+
[11] Auto-Compaction Check
|
|
110
|
+
@ conversation.rs:462, 507-530
|
|
111
|
+
- Triggers when cumulative input_tokens >= threshold (default 100K)
|
|
112
|
+
- BUG: uses cumulative (lifetime) tokens, not current context size
|
|
113
|
+
- compact_session() preserves last 4 messages, summarizes the rest
|
|
114
|
+
- TOKEN REDUCTION: the only meaningful reduction mechanism in the system
|
|
115
|
+
|
|
|
116
|
+
v
|
|
117
|
+
[12] TurnSummary returned to caller
|
|
118
|
+
- Contains: assistant_messages, tool_results, prompt_cache_events,
|
|
119
|
+
iterations, cumulative usage, auto_compaction event (if any)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Token Contribution Breakdown
|
|
123
|
+
|
|
124
|
+
| Category | Per-Call Cost | Growth Pattern | Bounded? |
|
|
125
|
+
|---|---|---|---|
|
|
126
|
+
| System prompt (static sections) | ~2,000 chars / ~500 tokens | Constant per session | Yes |
|
|
127
|
+
| System prompt (environment) | ~150 chars | Constant per session | Yes |
|
|
128
|
+
| Git diff in system prompt | 0 – 50K+ chars | Changes rarely within session | **No** |
|
|
129
|
+
| Instruction files (CLAUDE.md etc) | 0 – 12,000 chars | Constant per session | Yes (budgeted) |
|
|
130
|
+
| Runtime config in system prompt | ~200 chars | Constant per session | Yes |
|
|
131
|
+
| Tool definitions | ~5,000 – 15,000 chars | Constant per session | Yes (fixed schema) |
|
|
132
|
+
| User messages | Variable | Linear with turns | No (until compaction) |
|
|
133
|
+
| Assistant text responses | Variable | Linear with turns | No (until compaction) |
|
|
134
|
+
| Tool use blocks (id+name+input) | ~100 – 500 chars each | Linear with tool calls | No (until compaction) |
|
|
135
|
+
| **Tool result outputs** | **1K – 100K+ chars each** | **Linear with tool calls** | **No (verbatim, unbounded)** |
|
|
136
|
+
|
|
137
|
+
## Static vs Dynamic vs Growing Context
|
|
138
|
+
|
|
139
|
+
### Static (per-session, never changes)
|
|
140
|
+
- System prompt static sections (intro, system, tasks, actions)
|
|
141
|
+
- Tool definitions
|
|
142
|
+
- Model, max_tokens configuration
|
|
143
|
+
|
|
144
|
+
### Dynamic (per-session, changes rarely)
|
|
145
|
+
- Environment context (cwd, date, platform)
|
|
146
|
+
- Git status/diff snapshot (captured once at session start)
|
|
147
|
+
- Instruction files (loaded once at session start)
|
|
148
|
+
|
|
149
|
+
### Growing (per-turn, monotonically increasing)
|
|
150
|
+
- User messages
|
|
151
|
+
- Assistant responses (text + tool_use blocks)
|
|
152
|
+
- Tool result outputs ← **dominant growth factor**
|
|
153
|
+
|
|
154
|
+
## Key Findings
|
|
155
|
+
|
|
156
|
+
### 1. Tool results are the #1 source of context bloat
|
|
157
|
+
Tool outputs are stored verbatim in `ContentBlock::ToolResult { output: String }`
|
|
158
|
+
(session.rs:36-40). A single `read_file` call can inject 50K+ characters.
|
|
159
|
+
A `grep_search` with many matches can inject thousands of lines. These persist
|
|
160
|
+
in the session and are re-sent on every subsequent API call until compaction.
|
|
161
|
+
|
|
162
|
+
### 2. Messages are deep-cloned on every API call
|
|
163
|
+
`self.session.messages.clone()` at conversation.rs:314 creates a full deep copy
|
|
164
|
+
of all message content on every API call. For sessions with large tool results,
|
|
165
|
+
this is significant memory allocation churn (O(n*m) where n=messages, m=avg size).
|
|
166
|
+
|
|
167
|
+
### 3. The len/4+1 token estimation heuristic systematically undercounts
|
|
168
|
+
The heuristic in compact.rs:392-404 uses `text.len() / 4 + 1` per content block.
|
|
169
|
+
This ignores:
|
|
170
|
+
- JSON serialization overhead (role markers, type tags, key names)
|
|
171
|
+
- Tool metadata fields (tool_use_id, is_error flag)
|
|
172
|
+
- System prompt tokens (not counted by estimate_session_tokens at all)
|
|
173
|
+
- The conversion overhead from ConversationMessage → InputMessage → JSON
|
|
174
|
+
|
|
175
|
+
### 4. Auto-compaction trigger uses cumulative instead of current tokens
|
|
176
|
+
`maybe_auto_compact()` at conversation.rs:508 checks
|
|
177
|
+
`self.usage_tracker.cumulative_usage().input_tokens` against the threshold.
|
|
178
|
+
Cumulative never decreases — after compaction, every subsequent turn
|
|
179
|
+
immediately exceeds the threshold, causing unnecessary re-compaction.
|
|
180
|
+
|
|
181
|
+
### 5. Git diff in the system prompt is unbounded
|
|
182
|
+
`read_git_diff()` in prompt.rs:245-263 captures the full staged + unstaged diff
|
|
183
|
+
with no size limit. A developer with many uncommitted changes can have 50K+
|
|
184
|
+
characters injected into the system prompt, sent on every single API call.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kongbrain",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Graph-backed persistent memory engine for OpenClaw. Replaces the default context window with SurrealDB + vector embeddings that learn across sessions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/acan.ts
CHANGED
|
@@ -101,6 +101,15 @@ function loadWeights(path: string): ACANWeights | null {
|
|
|
101
101
|
if (!Array.isArray(raw.W_q[i]) || raw.W_q[i].length !== ATTN_DIM) return null;
|
|
102
102
|
if (!Array.isArray(raw.W_k[i]) || raw.W_k[i].length !== ATTN_DIM) return null;
|
|
103
103
|
}
|
|
104
|
+
// Validate numeric values — NaN/Infinity from a bad training run would corrupt scoring.
|
|
105
|
+
// JSON.stringify(NaN) produces null, so we must also reject null/non-number values.
|
|
106
|
+
if (typeof raw.bias !== "number" || !isFinite(raw.bias)) return null;
|
|
107
|
+
if (!raw.W_final.every((v: unknown) => typeof v === "number" && isFinite(v as number))) return null;
|
|
108
|
+
// Spot-check W_q/W_k (full scan too expensive for 1024x64 matrices)
|
|
109
|
+
for (const i of checkIndices) {
|
|
110
|
+
if (!raw.W_q[i].every((v: unknown) => typeof v === "number" && isFinite(v as number))) return null;
|
|
111
|
+
if (!raw.W_k[i].every((v: unknown) => typeof v === "number" && isFinite(v as number))) return null;
|
|
112
|
+
}
|
|
104
113
|
return raw as ACANWeights;
|
|
105
114
|
} catch (e) {
|
|
106
115
|
swallow("acan:loadWeights", e);
|
|
@@ -196,16 +205,30 @@ async function fetchTrainingData(store: SurrealStore): Promise<TrainingSample[]>
|
|
|
196
205
|
|
|
197
206
|
const uniqueMemIds = [...new Set(outcomes.map((r: any) => String(r.memory_id)))];
|
|
198
207
|
const embeddingMap = new Map<string, number[]>();
|
|
208
|
+
|
|
209
|
+
// Group IDs by table for batched fetches instead of one query per ID
|
|
210
|
+
const byTable = new Map<string, string[]>();
|
|
199
211
|
for (const mid of uniqueMemIds) {
|
|
200
212
|
try {
|
|
201
213
|
assertRecordId(mid);
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
214
|
+
const table = mid.split(":")[0];
|
|
215
|
+
if (!byTable.has(table)) byTable.set(table, []);
|
|
216
|
+
byTable.get(table)!.push(mid);
|
|
217
|
+
} catch { /* skip invalid */ }
|
|
218
|
+
}
|
|
219
|
+
await Promise.all([...byTable.entries()].map(async ([table, ids]) => {
|
|
220
|
+
try {
|
|
221
|
+
// Direct interpolation — SurrealDB treats string-array bindings as
|
|
222
|
+
// literal strings, not record references, causing silent empty results.
|
|
223
|
+
const idList = ids.join(", ");
|
|
224
|
+
const rows = await store.queryFirst<{ id: string; embedding: number[] }>(
|
|
225
|
+
`SELECT id, embedding FROM ${table} WHERE id IN [${idList}] AND embedding != NONE`,
|
|
205
226
|
);
|
|
206
|
-
|
|
227
|
+
for (const row of rows) {
|
|
228
|
+
if (row.embedding) embeddingMap.set(String(row.id), row.embedding);
|
|
229
|
+
}
|
|
207
230
|
} catch (e) { swallow("acan:fetchEmb", e); }
|
|
208
|
-
}
|
|
231
|
+
}));
|
|
209
232
|
|
|
210
233
|
const samples: TrainingSample[] = [];
|
|
211
234
|
for (const row of outcomes) {
|
package/src/causal.ts
CHANGED
|
@@ -134,33 +134,26 @@ export async function queryCausalContext(
|
|
|
134
134
|
ELSE 0 END AS score`;
|
|
135
135
|
|
|
136
136
|
for (let hop = 0; hop < hops && frontier.length > 0; hop++) {
|
|
137
|
-
|
|
137
|
+
// Batch all edge traversals for this hop in a single round-trip
|
|
138
|
+
const selectFields = `SELECT id, text, importance, access_count AS accessCount,
|
|
139
|
+
created_at AS timestamp, category, meta::tb(id) AS table${scoreExpr}`;
|
|
140
|
+
const stmts: string[] = [];
|
|
141
|
+
for (const id of frontier) {
|
|
138
142
|
assertRecordId(id);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
bindings,
|
|
146
|
-
).catch(e => { swallow.warn("causal:edge-query", e); return [] as any[]; }),
|
|
147
|
-
);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const reverseQueries = frontier.flatMap((id) => {
|
|
151
|
-
assertRecordId(id);
|
|
152
|
-
// Direct interpolation safe: assertRecordId validates format above
|
|
153
|
-
return causalEdges.map((edge) =>
|
|
154
|
-
store.queryFirst<any>(
|
|
155
|
-
`SELECT id, text, importance, access_count AS accessCount,
|
|
156
|
-
created_at AS timestamp, category, meta::tb(id) AS table${scoreExpr}
|
|
157
|
-
FROM ${id}<-${edge}<-? LIMIT 3`,
|
|
158
|
-
bindings,
|
|
159
|
-
).catch(e => { swallow.warn("causal:edge-query", e); return [] as any[]; }),
|
|
160
|
-
);
|
|
161
|
-
});
|
|
143
|
+
for (const edge of causalEdges) {
|
|
144
|
+
if (!/^[a-z_]+$/.test(edge)) continue; // safety check
|
|
145
|
+
stmts.push(`${selectFields} FROM ${id}->${edge}->? LIMIT 3`);
|
|
146
|
+
stmts.push(`${selectFields} FROM ${id}<-${edge}<-? LIMIT 3`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
162
149
|
|
|
163
|
-
|
|
150
|
+
let allQueryResults: any[][];
|
|
151
|
+
try {
|
|
152
|
+
allQueryResults = await store.queryBatch<any>(stmts, bindings);
|
|
153
|
+
} catch (e) {
|
|
154
|
+
swallow.warn("causal:batch", e);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
164
157
|
const nextFrontier: string[] = [];
|
|
165
158
|
|
|
166
159
|
for (const rows of allQueryResults) {
|
|
@@ -51,27 +51,27 @@ const CORE_ENTRIES: { text: string; category: string; priority: number }[] = [
|
|
|
51
51
|
const IDENTITY_CHUNKS: { text: string; importance: number }[] = [
|
|
52
52
|
{
|
|
53
53
|
text: `KongBrain's memory daemon runs in the background and extracts 9 knowledge types from your conversations every ~4K tokens or 3 turns: causal chains (cause->effect from debugging), monologue traces (doubts, insights, tradeoffs, realizations — episodic reasoning moments), resolved memories (daemon marks issues done when mentioned as fixed), concepts (technical facts worth remembering), corrections (user correcting you — highest signal), preferences (user workflow/style signals), artifacts (files created/modified/read), decisions (choices with rationale), and skills (multi-step procedures that worked). Extraction is quality-gated — weak confidence extractions are skipped, so the same conversation may yield different extractions depending on signal strength.`,
|
|
54
|
-
importance:
|
|
54
|
+
importance: 9,
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
text: `Effective recall queries use specific terms that match how knowledge was stored. Search by: file paths ("/src/auth/login.ts"), error messages ("ECONNREFUSED"), concept names ("rate limiting"), decision descriptions ("chose PostgreSQL over MongoDB"), or skill names ("deploy to staging"). The recall tool does vector similarity search plus graph neighbor expansion — top results pull in related nodes via 25 edge types. Scope options: "all" (default), "memories" (decisions, corrections, preferences), "concepts" (extracted technical knowledge), "turns" (past conversation), "artifacts" (files), "skills" (learned procedures). Retrieval scoring improves automatically over time as the ACAN (learned scoring model) trains on retrieval outcomes — early sessions use heuristic scoring, later sessions benefit from learned weights.`,
|
|
58
|
-
importance:
|
|
58
|
+
importance: 9,
|
|
59
59
|
},
|
|
60
60
|
{
|
|
61
61
|
text: `KongBrain's memory lifecycle: During a session, the daemon extracts knowledge incrementally. At session end (or mid-session every ~25K tokens): a handoff note is written summarizing progress, skills are extracted from successful tasks, metacognitive reflections are generated (linked to the session via reflects_on edges), and causal chains may graduate to skills. At next session start: the wakeup system synthesizes a first-person briefing from the handoff + identity + monologues + depth signals. Context is also predictively prefetched each turn based on likely follow-up queries — relevant memories may appear in your context without you requesting them.`,
|
|
62
|
-
importance:
|
|
62
|
+
importance: 8,
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
text: `Graph connectivity determines recall quality. 25 edge types link nodes across the graph (26th, spawned, is deferred). Key edges: mentions (turn->concept), about_concept (memory->concept), artifact_mentions (artifact->concept), caused_by/supports/contradicts (memory<->memory), narrower/broader/related_to (concept<->concept), reflects_on (reflection->session), tool_result_of (turn->turn), part_of (turn->session), skill_from_task (skill->task). To maximize connectivity: mention specific artifact paths, reference existing concept names, describe cause-effect relationships explicitly, and note task context. Reuse existing concept names — use introspect or recall to discover what names exist.`,
|
|
66
|
-
importance:
|
|
66
|
+
importance: 8,
|
|
67
67
|
},
|
|
68
68
|
{
|
|
69
69
|
text: `Three persistence mechanisms serve different purposes. Core memory (Tier 0): you control directly via the core_memory tool. Always loaded every turn. Use for: permanent operational rules, learned patterns, identity refinements. Budget-constrained (~10% of context). Core memory (Tier 1): pinned for the current session only. Use for: session-specific context like "working on auth refactor" or "user prefers verbose logging". Identity chunks: self-knowledge seeded at bootstrap, vector-searchable but not always loaded — surfaces in wakeup briefings. Daemon extraction: automatic, runs on conversation content, writes to memory/concept/skill/artifact tables. You don't control extraction directly, but the quality of your conversation affects what gets extracted.`,
|
|
70
|
-
importance:
|
|
70
|
+
importance: 8,
|
|
71
71
|
},
|
|
72
72
|
{
|
|
73
73
|
text: `Soul graduation: KongBrain tracks your maturity across 5 stages — nascent (0-3/7 thresholds), developing (4/7), emerging (5/7), maturing (6/7), ready (7/7). The 7 thresholds are: sessions, reflections, causal chains, concepts, monologues, span days, and total memories. Reaching 7/7 is necessary but not sufficient — you must also pass a quality gate (score >= 0.6) based on retrieval utilization, skill success rate, critical reflection rate, and tool failure rate. On graduation, you author a Soul document — a self-assessment grounded in your actual experience, not aspirational claims. Use introspect with action "status" to check your current stage and progress. The Soul document becomes part of your identity once written.`,
|
|
74
|
-
importance:
|
|
74
|
+
importance: 8,
|
|
75
75
|
},
|
|
76
76
|
];
|
|
77
77
|
|
package/src/cognitive-check.ts
CHANGED
|
@@ -85,12 +85,14 @@ const VALID_RECORD_ID = /^[a-z_]+:[a-zA-Z0-9_]+$/;
|
|
|
85
85
|
|
|
86
86
|
// --- Public API ---
|
|
87
87
|
|
|
88
|
-
/** Returns true on turn 2, then every
|
|
88
|
+
/** Returns true on turn 2, then every 5 turns (2, 7, 12, 17...). False if in-flight or retrieval skipped. */
|
|
89
89
|
export function shouldRunCheck(turnCount: number, session: SessionState): boolean {
|
|
90
90
|
const state = getState(session);
|
|
91
91
|
if (state.checkInFlight) return false;
|
|
92
92
|
if (turnCount < 2) return false;
|
|
93
|
-
|
|
93
|
+
// Skip when retrieval is disabled — no context to evaluate
|
|
94
|
+
if (session.currentConfig?.skipRetrieval) return false;
|
|
95
|
+
return turnCount === 2 || (turnCount - 2) % 5 === 0;
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
export function getPendingDirectives(session: SessionState): CognitiveDirective[] {
|
|
@@ -140,28 +142,24 @@ export async function runCognitiveCheck(
|
|
|
140
142
|
sections.push(`[TRAJECTORY]\n${trajectory}`);
|
|
141
143
|
}
|
|
142
144
|
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
"grades": [{id, relevant, reason, score, learned, resolved}] — one per retrieved node. Score 0.0-1.0. "learned": true ONLY if the node is a [CORRECTION] memory AND the assistant's response already follows the correction without being prompted. "resolved": true if this memory's topic has been fully addressed/completed in the current conversation. Both default false.
|
|
155
|
-
|
|
156
|
-
"sessionContinuity": "repeat" | "continuation" | "new_topic" | "tangent"
|
|
157
|
-
|
|
158
|
-
"preferences": [{observation, confidence: "high"|"medium"}] — max 2. User communication style, values, or working preferences inferred from the conversation. Only include if clearly observable. Empty [] if nothing notable.
|
|
145
|
+
const cogCheckSchema = {
|
|
146
|
+
type: "object" as const,
|
|
147
|
+
properties: {
|
|
148
|
+
directives: { type: "array", items: { type: "object" } },
|
|
149
|
+
grades: { type: "array", items: { type: "object" } },
|
|
150
|
+
sessionContinuity: { type: "string", enum: ["repeat", "continuation", "new_topic", "tangent"] },
|
|
151
|
+
preferences: { type: "array", items: { type: "object" } },
|
|
152
|
+
},
|
|
153
|
+
required: ["directives", "grades", "sessionContinuity", "preferences"],
|
|
154
|
+
};
|
|
159
155
|
|
|
160
|
-
|
|
156
|
+
const response = await complete({
|
|
157
|
+
system: `Grade retrieved context. directives: max 3, types: repeat|continuation|contradiction|noise|insight. grades: one per node, learned=true only if CORRECTION already followed unprompted. preferences: max 2, [] if none.`,
|
|
161
158
|
messages: [{
|
|
162
159
|
role: "user",
|
|
163
160
|
content: sections.join("\n\n"),
|
|
164
161
|
}],
|
|
162
|
+
outputFormat: { type: "json_schema", schema: cogCheckSchema },
|
|
165
163
|
});
|
|
166
164
|
|
|
167
165
|
const responseText = response.text;
|
package/src/config.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface EmbeddingConfig {
|
|
|
18
18
|
export interface ThresholdConfig {
|
|
19
19
|
/** Tokens accumulated before daemon flushes extraction (default: 4000) */
|
|
20
20
|
daemonTokenThreshold: number;
|
|
21
|
-
/** Cumulative tokens before mid-session cleanup fires (default:
|
|
21
|
+
/** Cumulative tokens before mid-session cleanup fires (default: 25000) */
|
|
22
22
|
midSessionCleanupThreshold: number;
|
|
23
23
|
/** Per-extraction timeout in ms (default: 60000) */
|
|
24
24
|
extractionTimeoutMs: number;
|