kc-beta 0.1.1 → 0.2.1
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/bin/kc-beta.js +14 -2
- package/package.json +1 -1
- package/src/agent/context-window.js +151 -0
- package/src/agent/context.js +58 -88
- package/src/agent/engine.js +267 -38
- package/src/agent/event-log.js +111 -0
- package/src/agent/llm-client.js +352 -59
- package/src/agent/pipelines/_archive_v1/distillation.js +113 -0
- package/src/agent/pipelines/_archive_v1/extraction.js +92 -0
- package/src/agent/pipelines/_archive_v1/initializer.js +163 -0
- package/src/agent/pipelines/_archive_v1/production-qc.js +99 -0
- package/src/agent/pipelines/_archive_v1/skill-authoring.js +83 -0
- package/src/agent/pipelines/_archive_v1/skill-testing.js +111 -0
- package/src/agent/pipelines/base.js +6 -0
- package/src/agent/pipelines/distillation.js +25 -11
- package/src/agent/pipelines/extraction.js +26 -7
- package/src/agent/pipelines/initializer.js +30 -20
- package/src/agent/pipelines/production-qc.js +22 -5
- package/src/agent/pipelines/skill-authoring.js +19 -8
- package/src/agent/pipelines/skill-testing.js +26 -8
- package/src/agent/retry.js +83 -0
- package/src/agent/session-state.js +78 -0
- package/src/agent/skill-loader.js +139 -0
- package/src/agent/token-counter.js +62 -0
- package/src/agent/tools/document-parse.js +3 -3
- package/src/agent/tools/tier-downgrade.js +11 -2
- package/src/agent/tools/web-search.js +107 -0
- package/src/agent/tools/worker-llm-call.js +14 -5
- package/src/cli/components.js +16 -4
- package/src/cli/config.js +246 -0
- package/src/cli/index.js +99 -10
- package/src/cli/onboard.js +154 -48
- package/src/config.js +25 -7
- package/src/providers.js +370 -0
package/bin/kc-beta.js
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// Parse --en / --zh from anywhere in argv (session-only language override)
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
let languageOverride = null;
|
|
6
|
+
const filtered = [];
|
|
7
|
+
for (const arg of args) {
|
|
8
|
+
if (arg === "--en") languageOverride = "en";
|
|
9
|
+
else if (arg === "--zh") languageOverride = "zh";
|
|
10
|
+
else filtered.push(arg);
|
|
11
|
+
}
|
|
12
|
+
const subcommand = filtered[0];
|
|
4
13
|
|
|
5
14
|
(async () => {
|
|
6
15
|
if (subcommand === "onboard" || subcommand === "setup") {
|
|
7
16
|
const { onboard } = await import("../src/cli/onboard.js");
|
|
8
17
|
await onboard();
|
|
18
|
+
} else if (subcommand === "config") {
|
|
19
|
+
const { configEditor } = await import("../src/cli/config.js");
|
|
20
|
+
await configEditor();
|
|
9
21
|
} else if (subcommand === "init") {
|
|
10
22
|
const { init } = await import("../src/cli/init.js");
|
|
11
23
|
await init();
|
|
12
24
|
} else {
|
|
13
25
|
const { main } = await import("../src/cli/index.js");
|
|
14
|
-
await main();
|
|
26
|
+
await main({ languageOverride });
|
|
15
27
|
}
|
|
16
28
|
})();
|
package/package.json
CHANGED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { estimateTokens, estimateMessagesTokens } from "./token-counter.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Automatic context windowing for long conversations.
|
|
5
|
+
* When messages approach the model's context limit, older messages
|
|
6
|
+
* are compressed into summaries while keeping recent messages intact.
|
|
7
|
+
*/
|
|
8
|
+
export class ContextWindow {
|
|
9
|
+
/**
|
|
10
|
+
* @param {object} opts
|
|
11
|
+
* @param {number} opts.contextLimit - Total model context limit in tokens
|
|
12
|
+
* @param {number} [opts.reserveForResponse=8192] - Tokens reserved for model output
|
|
13
|
+
* @param {number} [opts.recentWindowSize=30] - Number of recent messages to always keep
|
|
14
|
+
*/
|
|
15
|
+
constructor({ contextLimit, reserveForResponse = 8192, recentWindowSize = 30 }) {
|
|
16
|
+
this.contextLimit = contextLimit;
|
|
17
|
+
this.reserveForResponse = reserveForResponse;
|
|
18
|
+
this.recentWindowSize = recentWindowSize;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Apply windowing to a message array if it exceeds the token budget.
|
|
23
|
+
* @param {Array<object>} messages - Full message history
|
|
24
|
+
* @param {string[]} [phaseSummaries] - Summaries from completed pipeline phases
|
|
25
|
+
* @returns {{ messages: Array, wasWindowed: boolean, removedCount: number }}
|
|
26
|
+
*/
|
|
27
|
+
window(messages, phaseSummaries = []) {
|
|
28
|
+
const totalTokens = estimateMessagesTokens(messages);
|
|
29
|
+
const budget = this.contextLimit - this.reserveForResponse;
|
|
30
|
+
|
|
31
|
+
// If within budget, return as-is
|
|
32
|
+
if (totalTokens <= budget * 0.85) {
|
|
33
|
+
return { messages, wasWindowed: false, removedCount: 0 };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Split into older and recent
|
|
37
|
+
const splitPoint = Math.max(0, messages.length - this.recentWindowSize);
|
|
38
|
+
const recentMessages = messages.slice(splitPoint);
|
|
39
|
+
const olderMessages = messages.slice(0, splitPoint);
|
|
40
|
+
|
|
41
|
+
if (olderMessages.length === 0) {
|
|
42
|
+
return { messages, wasWindowed: false, removedCount: 0 };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Build a compact summary of older messages
|
|
46
|
+
const recentTokens = estimateMessagesTokens(recentMessages);
|
|
47
|
+
const summaryBudget = budget - recentTokens - 500; // 500 tokens buffer
|
|
48
|
+
const compactedSummary = this._compactMessages(olderMessages, phaseSummaries, summaryBudget);
|
|
49
|
+
|
|
50
|
+
const windowedMessages = [
|
|
51
|
+
{
|
|
52
|
+
role: "user",
|
|
53
|
+
content: `[Context Summary - Earlier conversation compressed]\n\n${compactedSummary}`,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
role: "assistant",
|
|
57
|
+
content: "Understood. I have the context from the summary above. Continuing with the current work.",
|
|
58
|
+
},
|
|
59
|
+
...recentMessages,
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
messages: windowedMessages,
|
|
64
|
+
wasWindowed: true,
|
|
65
|
+
removedCount: olderMessages.length,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Create a mechanical compact summary of messages.
|
|
71
|
+
* Groups into conversational turns and extracts key info.
|
|
72
|
+
* @param {Array<object>} messages
|
|
73
|
+
* @param {string[]} phaseSummaries
|
|
74
|
+
* @param {number} tokenBudget
|
|
75
|
+
* @returns {string}
|
|
76
|
+
*/
|
|
77
|
+
_compactMessages(messages, phaseSummaries, tokenBudget) {
|
|
78
|
+
const parts = [];
|
|
79
|
+
|
|
80
|
+
// Phase summaries first (high signal)
|
|
81
|
+
if (phaseSummaries.length > 0) {
|
|
82
|
+
parts.push("## Phase History");
|
|
83
|
+
for (const s of phaseSummaries) {
|
|
84
|
+
parts.push(`- ${s}`);
|
|
85
|
+
}
|
|
86
|
+
parts.push("");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Extract key events from older messages
|
|
90
|
+
parts.push("## Conversation Summary");
|
|
91
|
+
const turns = this._groupIntoTurns(messages);
|
|
92
|
+
|
|
93
|
+
for (const turn of turns) {
|
|
94
|
+
const line = this._summarizeTurn(turn);
|
|
95
|
+
if (line) {
|
|
96
|
+
parts.push(`- ${line}`);
|
|
97
|
+
// Check budget
|
|
98
|
+
if (estimateTokens(parts.join("\n")) > tokenBudget * 0.9) {
|
|
99
|
+
parts.push("- [earlier history truncated]");
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return parts.join("\n");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Group messages into user-turn blocks.
|
|
110
|
+
* Each turn: { user: string, tools: [{name, summary}], assistantSummary: string }
|
|
111
|
+
*/
|
|
112
|
+
_groupIntoTurns(messages) {
|
|
113
|
+
const turns = [];
|
|
114
|
+
let current = null;
|
|
115
|
+
|
|
116
|
+
for (const msg of messages) {
|
|
117
|
+
if (msg.role === "user") {
|
|
118
|
+
if (current) turns.push(current);
|
|
119
|
+
current = { user: msg.content || "", tools: [], assistant: "" };
|
|
120
|
+
} else if (msg.role === "assistant" && current) {
|
|
121
|
+
if (msg.content) current.assistant = msg.content;
|
|
122
|
+
if (msg.tool_calls) {
|
|
123
|
+
for (const tc of msg.tool_calls) {
|
|
124
|
+
current.tools.push(tc.function?.name || "unknown");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// tool results are captured implicitly via tool names
|
|
129
|
+
}
|
|
130
|
+
if (current) turns.push(current);
|
|
131
|
+
return turns;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Summarize a single conversational turn into one line.
|
|
136
|
+
*/
|
|
137
|
+
_summarizeTurn(turn) {
|
|
138
|
+
const userSnippet = (turn.user || "").slice(0, 80).replace(/\n/g, " ");
|
|
139
|
+
if (!userSnippet) return null;
|
|
140
|
+
|
|
141
|
+
let line = `User: "${userSnippet}"`;
|
|
142
|
+
if (turn.tools.length > 0) {
|
|
143
|
+
line += ` → Tools: ${turn.tools.join(", ")}`;
|
|
144
|
+
}
|
|
145
|
+
if (turn.assistant) {
|
|
146
|
+
const aSnippet = turn.assistant.slice(0, 60).replace(/\n/g, " ");
|
|
147
|
+
line += ` → "${aSnippet}..."`;
|
|
148
|
+
}
|
|
149
|
+
return line;
|
|
150
|
+
}
|
|
151
|
+
}
|
package/src/agent/context.js
CHANGED
|
@@ -1,122 +1,92 @@
|
|
|
1
1
|
const AGENT_IDENTITY = `\
|
|
2
|
-
|
|
3
|
-
and manage document verification systems for financial institutions.
|
|
2
|
+
KC Agent builds and manages document verification systems for financial institutions.
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
something, you say so.
|
|
4
|
+
## Architecture
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
This system operates in two modes:
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
**BUILD mode** (Bootstrap → Extraction → Skill Authoring → Skill Testing): \
|
|
9
|
+
Read regulations, extract rules, build verification skills, test them against samples. \
|
|
10
|
+
All intellectual work — parsing, extracting, judging — is done directly. The results \
|
|
11
|
+
produced in this mode serve as the accuracy baseline. Worker LLM tools are not available \
|
|
12
|
+
in this mode.
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
**DISTILL mode** (Distillation → Production QC): \
|
|
15
|
+
Convert proven skills into workflows that run with cheaper worker LLMs at scale. \
|
|
16
|
+
Test workflow results against the baseline established in BUILD mode. Monitor production \
|
|
17
|
+
quality. Worker LLM tools become available in this mode.
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
Skills are first-class deliverables, not just stepping stones to distillation. When a \
|
|
20
|
+
verification task is too complex for worker LLMs, the skill itself — run by a capable \
|
|
21
|
+
agent — is the production solution.
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
Internally uses an escalation chain: text extraction → API parser → OCR models. \
|
|
20
|
-
Starts cheap, escalates if needed. You don't choose the method — the tool handles it. \
|
|
21
|
-
Use force_method only for testing or if the developer user requests a specific parser.
|
|
22
|
-
|
|
23
|
-
- **worker_llm_call**: Call a worker LLM at a specified tier (tier1=most capable, \
|
|
24
|
-
tier4=cheapest). Use for distillation testing — check if cheaper models can handle \
|
|
25
|
-
extraction/judgment steps. Returns response with model used and token counts.
|
|
26
|
-
|
|
27
|
-
- **workflow_run**: Execute a distilled workflow against a document. Automatically \
|
|
28
|
-
attaches confidence scores and trace IDs. Results saved to output/results/.
|
|
29
|
-
|
|
30
|
-
- **tier_downgrade**: Test a workflow step at a lower tier. Compares accuracy at \
|
|
31
|
-
target tier vs. current baseline. Recommends downgrade if accuracy stays above threshold.
|
|
23
|
+
## Methodology
|
|
32
24
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
### Document Parsing
|
|
26
|
+
Start with the simplest parser and escalate only when output is insufficient. Once a \
|
|
27
|
+
parser works for a document type, lock it in. Tables and charts may need specific handling.
|
|
36
28
|
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
### Rule Extraction
|
|
30
|
+
Decompose regulations top-down into atomic, testable rules. One rule = one pass/fail \
|
|
31
|
+
outcome. Handle ambiguity explicitly — note it, ask the developer user. After extraction, \
|
|
32
|
+
audit which regulation sections are not yet covered.
|
|
39
33
|
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
### Entity Extraction
|
|
35
|
+
Prefer regex/Python for predictable formats. Use LLM only when semantic understanding \
|
|
36
|
+
is required. Every extraction captures: value, evidence, source location, confidence, \
|
|
37
|
+
method used.
|
|
42
38
|
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
### Skill Authoring
|
|
40
|
+
Write each rule into a skill folder following the Anthropic skill-creator format. A \
|
|
41
|
+
skill must be self-contained: business logic, scripts, references, sample data, and \
|
|
42
|
+
corner cases. Skills capture methodology — when to use an approach, why it works, \
|
|
43
|
+
what to watch for.
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
### Evolution Loop
|
|
46
|
+
Test → observe → diagnose root cause (parsing/extraction/judgment/scope) → classify \
|
|
47
|
+
(systemic vs corner case) → fix → retest → log. Corner cases are recorded separately \
|
|
48
|
+
and never patched into the main workflow.
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
### Distillation
|
|
51
|
+
Design workflows that replicate skill results using the cheapest viable model tier. \
|
|
52
|
+
Test at each tier and present accuracy comparison data. The developer user decides \
|
|
53
|
+
acceptable trade-offs between cost and accuracy.
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
reading output. Don't guess — verify.
|
|
55
|
+
## Structural Components
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
**Version control**: Every write to rules/, workflows/, or rule_skills/ gets a trace \
|
|
58
|
+
ID in versions.json — an immutable audit trail linking results back to the exact \
|
|
59
|
+
version of code that produced them.
|
|
57
60
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
- Once a parser works for a document type, lock it in. Don't re-evaluate unless \
|
|
62
|
-
downstream extraction fails.
|
|
63
|
-
- Tables need special handling — extract cell-by-cell, reconstruct as markdown or JSON.
|
|
64
|
-
|
|
65
|
-
### Data Sensibility
|
|
66
|
-
- Read 3-5 complete documents end-to-end BEFORE writing extraction logic. Read raw \
|
|
67
|
-
parsed text, not PDF viewer. This saves hours of debugging bad assumptions.
|
|
68
|
-
- After extraction, spot-check 10 random fields (3 high-confidence, 4 medium, \
|
|
69
|
-
3 low) against source. If >1 out of 10 is wrong, STOP — don't continue.
|
|
70
|
-
- Save every processing stage to disk (raw text → sections → entities → judgments). \
|
|
71
|
-
Disk is cheap; debugging without intermediates is guesswork.
|
|
61
|
+
**Corner case registry**: Edge cases (<10% failure rate) are stored in \
|
|
62
|
+
corner_cases.json with detection patterns and resolutions. They are handled separately \
|
|
63
|
+
during execution with high-threshold matching, not patched into main workflows.
|
|
72
64
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- Work top-down (onion peeler): major areas → chapters → sections → atomic rules. \
|
|
77
|
-
Stop when rules are atomic and testable.
|
|
78
|
-
- Handle ambiguity explicitly. Extract as understood, note ambiguities, ask the \
|
|
79
|
-
developer user. Ambiguous rules are often the most important — don't skip them.
|
|
80
|
-
- After extraction, audit coverage: which regulation paragraphs are NOT covered?
|
|
65
|
+
**Confidence scoring**: Each verification result gets a composite confidence score \
|
|
66
|
+
based on extraction method, source text presence, historical accuracy, and corner \
|
|
67
|
+
case proximity. Confidence bands (high/medium/low) drive QC sampling rates.
|
|
81
68
|
|
|
82
|
-
|
|
83
|
-
- Method selection: regex/Python first (free, instant, predictable formats). LLM \
|
|
84
|
-
only when semantic understanding is required. Hybrid: regex first, LLM fallback.
|
|
85
|
-
- Every extraction must capture: value, evidence (raw text), source location, \
|
|
86
|
-
confidence, method used.
|
|
87
|
-
- Postprocessing is deterministic code: date standardization, unit conversion, \
|
|
88
|
-
Chinese numeral conversion. Build as reusable Python functions.
|
|
69
|
+
## Working with the Developer User
|
|
89
70
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
or scope failure. Each drives different fixes.
|
|
94
|
-
- Systemic issue (>10% of docs) → rewrite code/prompts. Corner case (<10%) → \
|
|
95
|
-
record in corner_cases.json with detection + resolution. Do NOT patch main \
|
|
96
|
-
workflow for corner cases.
|
|
97
|
-
- Stop when: accuracy meets threshold, or correction volume <5% and no new \
|
|
98
|
-
failure patterns.
|
|
99
|
-
|
|
100
|
-
### Reflection & Skill Writing
|
|
101
|
-
- When you solve a hard problem (OCR approach, extraction pattern, edge case \
|
|
102
|
-
handling), write it down as a reusable skill in rule_skills/. Future sessions \
|
|
103
|
-
and rules benefit from your discoveries.
|
|
104
|
-
- Skills capture methodology, not just code. Describe WHEN to use this approach, \
|
|
105
|
-
WHY it works, and WHAT to watch out for.`;
|
|
71
|
+
The developer user configures the project, provides regulations and samples, and \
|
|
72
|
+
makes business decisions (accuracy thresholds, cost trade-offs, rule scope). Discuss \
|
|
73
|
+
unclear regulations with them. Present results and let them judge.`;
|
|
106
74
|
|
|
107
75
|
/**
|
|
108
76
|
* Builds the system prompt from multiple context sources.
|
|
109
|
-
* Combines: agent identity +
|
|
77
|
+
* Combines: agent identity + skill index + pipeline state + workspace state.
|
|
110
78
|
*/
|
|
111
79
|
export class ContextAssembler {
|
|
112
80
|
/**
|
|
113
81
|
* @param {object} [opts]
|
|
114
82
|
* @param {string} [opts.pipelineState]
|
|
115
83
|
* @param {string} [opts.workspaceState]
|
|
84
|
+
* @param {string} [opts.skillIndex] - Brief index of available meta skills
|
|
116
85
|
* @returns {string}
|
|
117
86
|
*/
|
|
118
|
-
build({ pipelineState, workspaceState } = {}) {
|
|
87
|
+
build({ pipelineState, workspaceState, skillIndex } = {}) {
|
|
119
88
|
const parts = [AGENT_IDENTITY];
|
|
89
|
+
if (skillIndex) parts.push(skillIndex);
|
|
120
90
|
if (pipelineState) parts.push(pipelineState);
|
|
121
91
|
if (workspaceState) parts.push(workspaceState);
|
|
122
92
|
return parts.join("\n\n");
|