@polylogicai/polycode 1.1.0
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/LICENSE +24 -0
- package/README.md +107 -0
- package/bin/polycode.mjs +317 -0
- package/lib/agency-receipt.mjs +45 -0
- package/lib/agentic.mjs +505 -0
- package/lib/canon.mjs +123 -0
- package/lib/commitment.mjs +59 -0
- package/lib/compiler.mjs +166 -0
- package/lib/context-builder.mjs +79 -0
- package/lib/hooks.mjs +118 -0
- package/lib/inference-router.mjs +67 -0
- package/lib/intent.mjs +31 -0
- package/lib/repl-ui.mjs +91 -0
- package/lib/slash-commands.mjs +83 -0
- package/lib/witness/conservativity.mjs +90 -0
- package/lib/witness/g-fidelity.mjs +80 -0
- package/lib/witness/ground-truth.mjs +56 -0
- package/lib/witness/index.mjs +70 -0
- package/lib/witness/rule-compliance.mjs +82 -0
- package/lib/witness/secret-scrubber.mjs +51 -0
- package/package.json +45 -0
- package/rules/default.yaml +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andrew Salvo / Polylogic AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
22
|
+
|
|
23
|
+
Polycode is inspired by Claude Code's public architecture. No Claude Code
|
|
24
|
+
code or system prompts are copied. Not affiliated with Anthropic.
|
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# polycode
|
|
2
|
+
|
|
3
|
+
An agentic coding CLI. Runs on your machine with your keys. Every turn is appended to a SHA-256 chained session log on disk, so your history is auditable, replayable, and portable across machines.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @polylogicai/polycode
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
export GROQ_API_KEY=gsk_...
|
|
15
|
+
polycode
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
That opens an interactive session. For one-shot mode, pass your prompt as an argument:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
polycode "read README.md and summarize it in one sentence"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
A free Groq API key is available at `console.groq.com`.
|
|
25
|
+
|
|
26
|
+
## Configuration
|
|
27
|
+
|
|
28
|
+
polycode reads configuration from environment variables and optionally from a `~/.polycode/secrets.env` file (chmod 600 recommended).
|
|
29
|
+
|
|
30
|
+
| Variable | Purpose | Required |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `GROQ_API_KEY` | Primary inference key for tool-use and reasoning | yes |
|
|
33
|
+
| `ANTHROPIC_API_KEY` | Optional high-quality tier for long sessions | no |
|
|
34
|
+
| `POLYCODE_MODEL` | Override the default model | no |
|
|
35
|
+
| `POLYCODE_CWD` | Override the working directory | no |
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
polycode runs a standard agentic loop: you ask, it thinks, it uses tools, it returns a result. Available tools are:
|
|
40
|
+
|
|
41
|
+
- `bash` run a shell command in the working directory
|
|
42
|
+
- `read_file` read a file relative to the working directory
|
|
43
|
+
- `write_file` write a file to the working directory
|
|
44
|
+
- `edit_file` replace a substring in a file
|
|
45
|
+
- `glob` list files matching a pattern
|
|
46
|
+
- `grep` search for a regex in files
|
|
47
|
+
|
|
48
|
+
All tool calls are sandboxed to the working directory. polycode refuses any path that escapes it.
|
|
49
|
+
|
|
50
|
+
## Session history
|
|
51
|
+
|
|
52
|
+
Every turn polycode takes is appended to a SHA-256 chained JSONL file at `~/.polycode/history/YYYY-MM-DD.jsonl`. You own this file. You can:
|
|
53
|
+
|
|
54
|
+
- Read it to see exactly what the agent did
|
|
55
|
+
- Back it up by copying it
|
|
56
|
+
- Hand it to a teammate, who can replay the session on their machine
|
|
57
|
+
- Run `polycode --verify` to confirm the chain is intact
|
|
58
|
+
|
|
59
|
+
## Slash commands
|
|
60
|
+
|
|
61
|
+
Inside the REPL:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
/help show all commands
|
|
65
|
+
/clear clear the terminal
|
|
66
|
+
/history show the session history file path and row count
|
|
67
|
+
/verify verify session history integrity
|
|
68
|
+
/exit leave polycode
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Command-line flags
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
polycode interactive REPL
|
|
75
|
+
polycode "<prompt>" one-shot mode
|
|
76
|
+
polycode --version print version
|
|
77
|
+
polycode --help print help
|
|
78
|
+
polycode --history print session history status
|
|
79
|
+
polycode --verify verify session history integrity
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Safety rules
|
|
83
|
+
|
|
84
|
+
polycode enforces a small set of non-negotiable safety rules out of the box:
|
|
85
|
+
|
|
86
|
+
- Shell commands referencing system paths (`/etc/passwd`, `~/.ssh`, private key files) are refused
|
|
87
|
+
- Code patterns that evaluate untrusted input are refused
|
|
88
|
+
- Tool output containing recognized secret patterns (AWS, GitHub, Stripe, Anthropic, Groq, PEM blocks) is redacted before it reaches the model or the terminal
|
|
89
|
+
|
|
90
|
+
Rules live in `~/.polycode/rules.yaml`. You can add your own.
|
|
91
|
+
|
|
92
|
+
## Requirements
|
|
93
|
+
|
|
94
|
+
- Node.js 20 or newer
|
|
95
|
+
- macOS or Linux
|
|
96
|
+
- A Groq API key
|
|
97
|
+
|
|
98
|
+
## Documentation and support
|
|
99
|
+
|
|
100
|
+
- Install and usage: `polylogicai.com/polycode`
|
|
101
|
+
- Issues and questions: `ajs10845@psu.edu`
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
MIT. See `LICENSE`. Built by Polylogic AI.
|
|
106
|
+
|
|
107
|
+
polycode is inspired by Claude Code's public architecture at `docs.claude.com`. polycode is not affiliated with Anthropic. No Claude Code code or system prompts are copied.
|
package/bin/polycode.mjs
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// bin/polycode.mjs
|
|
3
|
+
// polycode CLI entry point. Runs on your machine with your keys. Writes an
|
|
4
|
+
// append-only session log on disk.
|
|
5
|
+
//
|
|
6
|
+
// polycode is inspired by Claude Code's public architecture at docs.claude.com.
|
|
7
|
+
// polycode is not affiliated with Anthropic. No Claude Code code or system
|
|
8
|
+
// prompts are copied.
|
|
9
|
+
|
|
10
|
+
import { AgenticLoop } from '../lib/agentic.mjs';
|
|
11
|
+
import { createCanon } from '../lib/canon.mjs';
|
|
12
|
+
import { createRenderer, C } from '../lib/repl-ui.mjs';
|
|
13
|
+
import { dispatchSlash } from '../lib/slash-commands.mjs';
|
|
14
|
+
import { computeAgencyReceipt, formatReceipt } from '../lib/agency-receipt.mjs';
|
|
15
|
+
import { fireHook } from '../lib/hooks.mjs';
|
|
16
|
+
import { compilePacket } from '../lib/compiler.mjs';
|
|
17
|
+
import { loadAnthropicKeys, reportKeyStatus } from '../lib/inference-router.mjs';
|
|
18
|
+
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
19
|
+
import { homedir } from 'node:os';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
import { randomUUID } from 'node:crypto';
|
|
22
|
+
import * as readline from 'node:readline/promises';
|
|
23
|
+
import { stdin, stdout, exit, argv, env, cwd as getCwd } from 'node:process';
|
|
24
|
+
import 'dotenv/config';
|
|
25
|
+
|
|
26
|
+
function loadAmbientEnv() {
|
|
27
|
+
const candidates = [
|
|
28
|
+
join(homedir(), '.polycode/secrets.env'),
|
|
29
|
+
join(homedir(), 'orchestrator/.env'),
|
|
30
|
+
join(homedir(), '.polycode/.env'),
|
|
31
|
+
];
|
|
32
|
+
for (const p of candidates) {
|
|
33
|
+
if (!existsSync(p)) continue;
|
|
34
|
+
try {
|
|
35
|
+
const content = readFileSync(p, 'utf8');
|
|
36
|
+
for (const line of content.split('\n')) {
|
|
37
|
+
if (line.startsWith('#')) continue;
|
|
38
|
+
const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
39
|
+
if (match && !env[match[1]]) {
|
|
40
|
+
env[match[1]] = match[2].replace(/^['"]|['"]$/g, '');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
// ignore
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
loadAmbientEnv();
|
|
50
|
+
|
|
51
|
+
function loadRules() {
|
|
52
|
+
const candidates = [
|
|
53
|
+
join(homedir(), '.polycode', 'rules.yaml'),
|
|
54
|
+
join(homedir(), 'polycode', 'rules', 'default.yaml'),
|
|
55
|
+
];
|
|
56
|
+
for (const p of candidates) {
|
|
57
|
+
if (!existsSync(p)) continue;
|
|
58
|
+
try {
|
|
59
|
+
const content = readFileSync(p, 'utf8');
|
|
60
|
+
// Minimal YAML parser for the flat subset we use: top-level scalars, top-level
|
|
61
|
+
// lists, and one level of nested map or list under a top-level key.
|
|
62
|
+
const rules = {};
|
|
63
|
+
let currentTopKey = null;
|
|
64
|
+
let currentTopIsList = false;
|
|
65
|
+
let currentNestedList = null;
|
|
66
|
+
for (const rawLine of content.split('\n')) {
|
|
67
|
+
const line = rawLine.replace(/#.*$/, '').replace(/\s+$/, '');
|
|
68
|
+
if (!line.trim()) continue;
|
|
69
|
+
const topMatch = line.match(/^([A-Za-z_][A-Za-z0-9_]*):\s*(.*)$/);
|
|
70
|
+
if (topMatch && !line.startsWith(' ')) {
|
|
71
|
+
currentTopKey = topMatch[1];
|
|
72
|
+
currentNestedList = null;
|
|
73
|
+
const inlineVal = topMatch[2].trim();
|
|
74
|
+
if (inlineVal === '') {
|
|
75
|
+
// May be a map or a list; decided by the next indented line.
|
|
76
|
+
rules[currentTopKey] = undefined;
|
|
77
|
+
currentTopIsList = false;
|
|
78
|
+
} else if (/^\[.*\]$/.test(inlineVal)) {
|
|
79
|
+
rules[currentTopKey] = inlineVal.slice(1, -1).split(',').map((x) => x.trim().replace(/^['"]|['"]$/g, ''));
|
|
80
|
+
currentTopIsList = false;
|
|
81
|
+
} else if (/^-?\d+(\.\d+)?$/.test(inlineVal)) {
|
|
82
|
+
rules[currentTopKey] = Number(inlineVal);
|
|
83
|
+
currentTopIsList = false;
|
|
84
|
+
} else if (inlineVal === 'true' || inlineVal === 'false') {
|
|
85
|
+
rules[currentTopKey] = inlineVal === 'true';
|
|
86
|
+
currentTopIsList = false;
|
|
87
|
+
} else {
|
|
88
|
+
rules[currentTopKey] = inlineVal.replace(/^['"]|['"]$/g, '');
|
|
89
|
+
currentTopIsList = false;
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const topListMatch = line.match(/^\s{2}-\s*(.+)$/);
|
|
94
|
+
if (topListMatch && currentTopKey) {
|
|
95
|
+
if (!Array.isArray(rules[currentTopKey])) {
|
|
96
|
+
rules[currentTopKey] = [];
|
|
97
|
+
currentTopIsList = true;
|
|
98
|
+
}
|
|
99
|
+
rules[currentTopKey].push(topListMatch[1].trim().replace(/^['"]|['"]$/g, ''));
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const subMatch = line.match(/^\s{2}([A-Za-z_][A-Za-z0-9_]*):\s*(.*)$/);
|
|
103
|
+
if (subMatch && currentTopKey && !currentTopIsList) {
|
|
104
|
+
if (typeof rules[currentTopKey] !== 'object' || Array.isArray(rules[currentTopKey])) {
|
|
105
|
+
rules[currentTopKey] = {};
|
|
106
|
+
}
|
|
107
|
+
const subKey = subMatch[1];
|
|
108
|
+
const subVal = subMatch[2].trim();
|
|
109
|
+
if (subVal === '') {
|
|
110
|
+
rules[currentTopKey][subKey] = [];
|
|
111
|
+
currentNestedList = rules[currentTopKey][subKey];
|
|
112
|
+
} else if (/^-?\d+(\.\d+)?$/.test(subVal)) {
|
|
113
|
+
rules[currentTopKey][subKey] = Number(subVal);
|
|
114
|
+
currentNestedList = null;
|
|
115
|
+
} else if (subVal === 'true' || subVal === 'false') {
|
|
116
|
+
rules[currentTopKey][subKey] = subVal === 'true';
|
|
117
|
+
currentNestedList = null;
|
|
118
|
+
} else if (/^\[.*\]$/.test(subVal)) {
|
|
119
|
+
rules[currentTopKey][subKey] = subVal.slice(1, -1).split(',').map((x) => x.trim().replace(/^['"]|['"]$/g, ''));
|
|
120
|
+
currentNestedList = null;
|
|
121
|
+
} else {
|
|
122
|
+
rules[currentTopKey][subKey] = subVal.replace(/^['"]|['"]$/g, '');
|
|
123
|
+
currentNestedList = null;
|
|
124
|
+
}
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const nestedListMatch = line.match(/^\s{4}-\s*(.+)$/);
|
|
128
|
+
if (nestedListMatch && currentNestedList) {
|
|
129
|
+
currentNestedList.push(nestedListMatch[1].trim().replace(/^['"]|['"]$/g, ''));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return rules;
|
|
133
|
+
} catch {
|
|
134
|
+
// next candidate
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return {};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const VERSION = '1.1.0';
|
|
141
|
+
const DOCS_URL = 'https://polylogicai.com/polycode';
|
|
142
|
+
|
|
143
|
+
const BANNER = `${C.bold}${C.amber}polycode v${VERSION}${C.reset}
|
|
144
|
+
${C.dim}An agentic coding CLI. Your keys, your machine, your session log.${C.reset}
|
|
145
|
+
${C.dim}${DOCS_URL}${C.reset}
|
|
146
|
+
`;
|
|
147
|
+
|
|
148
|
+
const HELP = `${BANNER}
|
|
149
|
+
|
|
150
|
+
${C.bold}Usage${C.reset}
|
|
151
|
+
polycode Interactive session
|
|
152
|
+
polycode "fix the failing test" One-shot mode
|
|
153
|
+
polycode --version Print version
|
|
154
|
+
polycode --help Print this message
|
|
155
|
+
polycode --history Print session history status
|
|
156
|
+
polycode --verify Verify session history integrity
|
|
157
|
+
|
|
158
|
+
${C.bold}Environment${C.reset}
|
|
159
|
+
GROQ_API_KEY Required (free tier at console.groq.com)
|
|
160
|
+
ANTHROPIC_API_KEY Optional high-quality tier for long sessions
|
|
161
|
+
POLYCODE_MODEL Override the default model
|
|
162
|
+
POLYCODE_CWD Override the working directory
|
|
163
|
+
|
|
164
|
+
${C.bold}Documentation${C.reset}
|
|
165
|
+
${DOCS_URL}
|
|
166
|
+
`;
|
|
167
|
+
|
|
168
|
+
function resolveConfigDir() {
|
|
169
|
+
const dir = join(homedir(), '.polycode');
|
|
170
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
171
|
+
if (!existsSync(join(dir, 'canon'))) mkdirSync(join(dir, 'canon'), { recursive: true });
|
|
172
|
+
if (!existsSync(join(dir, 'hooks'))) mkdirSync(join(dir, 'hooks'), { recursive: true });
|
|
173
|
+
return dir;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function runOneShot(message, { loop, canon, cwd, renderer, state, sessionId, hookDir }) {
|
|
177
|
+
await fireHook('UserPromptSubmit', {
|
|
178
|
+
session_id: sessionId, cwd, hook_event_name: 'UserPromptSubmit', prompt: message,
|
|
179
|
+
}, hookDir);
|
|
180
|
+
|
|
181
|
+
const result = await loop.runTurn({
|
|
182
|
+
canon,
|
|
183
|
+
userMessage: message,
|
|
184
|
+
cwd,
|
|
185
|
+
onEvent: (ev) => renderer.onEvent(ev),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
state.lastTurnTokens = result.promptTokensUsed;
|
|
189
|
+
state.lastCompiler = result.compilerProvider;
|
|
190
|
+
|
|
191
|
+
const receipt = computeAgencyReceipt({
|
|
192
|
+
primitivesList: result.primitivesList,
|
|
193
|
+
wallClockMs: result.durationMs,
|
|
194
|
+
iterations: result.iterations,
|
|
195
|
+
});
|
|
196
|
+
stdout.write(`${C.dim}${formatReceipt(receipt)} . ${canon.size()} rows in log${C.reset}\n`);
|
|
197
|
+
|
|
198
|
+
await fireHook('Stop', { session_id: sessionId, cwd, hook_event_name: 'Stop' }, hookDir);
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function runRepl(opts) {
|
|
203
|
+
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
204
|
+
stdout.write(BANNER + '\n');
|
|
205
|
+
stdout.write(`${C.dim}session log: ${opts.canon.size()} rows . ${opts.canon.lastHash().slice(0, 12)}...${C.reset}\n`);
|
|
206
|
+
stdout.write(`${C.dim}type /help for commands. ctrl+c or /exit to leave.${C.reset}\n\n`);
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
while (true) {
|
|
210
|
+
const line = await rl.question(`${C.bold}${C.amber}> ${C.reset}`);
|
|
211
|
+
if (!line.trim()) continue;
|
|
212
|
+
|
|
213
|
+
if (line.startsWith('/')) {
|
|
214
|
+
const result = await dispatchSlash(line, { canon: opts.canon, state: opts.state, stdout });
|
|
215
|
+
if (result.exit) break;
|
|
216
|
+
stdout.write('\n');
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
await runOneShot(line, opts);
|
|
221
|
+
stdout.write('\n');
|
|
222
|
+
}
|
|
223
|
+
} finally {
|
|
224
|
+
rl.close();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function main() {
|
|
229
|
+
const args = argv.slice(2);
|
|
230
|
+
|
|
231
|
+
if (args.includes('--help') || args.includes('-h')) { stdout.write(HELP); exit(0); }
|
|
232
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
233
|
+
stdout.write(`polycode ${VERSION}\n`);
|
|
234
|
+
exit(0);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const configDir = resolveConfigDir();
|
|
238
|
+
|
|
239
|
+
const apiKey = env.GROQ_API_KEY;
|
|
240
|
+
if (!apiKey) {
|
|
241
|
+
stdout.write(`${C.red}polycode error${C.reset}: GROQ_API_KEY is not set.\n`);
|
|
242
|
+
exit(1);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const rules = loadRules();
|
|
246
|
+
const model = env.POLYCODE_MODEL || 'moonshotai/kimi-k2-instruct';
|
|
247
|
+
const cwd = env.POLYCODE_CWD || getCwd();
|
|
248
|
+
const hookDir = env.POLYCODE_HOOK_DIR || join(configDir, 'hooks');
|
|
249
|
+
const canonFile = env.POLYCODE_CANON_FILE || join(configDir, 'canon', `${new Date().toISOString().slice(0, 10)}.jsonl`);
|
|
250
|
+
|
|
251
|
+
const canon = createCanon(canonFile);
|
|
252
|
+
|
|
253
|
+
if (args.includes('--history') || args.includes('--log')) {
|
|
254
|
+
stdout.write(`session log: ${canonFile}\nrows: ${canon.size()}\nlast_hash: ${canon.lastHash()}\n`);
|
|
255
|
+
exit(0);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (args.includes('--verify')) {
|
|
259
|
+
const r = canon.verify();
|
|
260
|
+
if (r.valid) {
|
|
261
|
+
stdout.write(`${C.amber}session log verified${C.reset}: ${r.rows} rows, last ${r.last_hash}\n`);
|
|
262
|
+
exit(0);
|
|
263
|
+
} else {
|
|
264
|
+
stdout.write(`${C.red}session log broken${C.reset}: ${r.reason}\n`);
|
|
265
|
+
exit(1);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const packetFlagIndex = args.indexOf('--packet');
|
|
270
|
+
if (packetFlagIndex >= 0) {
|
|
271
|
+
const question = args.slice(packetFlagIndex + 1).join(' ').replace(/^["']|["']$/g, '') || 'default';
|
|
272
|
+
const pkt = await compilePacket(canon, question, cwd);
|
|
273
|
+
stdout.write(`--- polycode compile-packet preview ---\n`);
|
|
274
|
+
stdout.write(`compiler: ${pkt.compilerProvider}\n`);
|
|
275
|
+
stdout.write(`estimated_tokens: ${pkt.estimatedTokens}\n`);
|
|
276
|
+
stdout.write(`selected_rows: ${JSON.stringify(pkt.selectedRows)}\n`);
|
|
277
|
+
stdout.write(`fallback: ${pkt.fallback}\n`);
|
|
278
|
+
stdout.write(`---\n`);
|
|
279
|
+
stdout.write(pkt.prompt + '\n');
|
|
280
|
+
stdout.write(`---\n`);
|
|
281
|
+
exit(0);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const sessionId = randomUUID();
|
|
285
|
+
canon.append('session_start', { session_id: sessionId, cwd, model });
|
|
286
|
+
|
|
287
|
+
await fireHook('SessionStart', {
|
|
288
|
+
session_id: sessionId,
|
|
289
|
+
cwd,
|
|
290
|
+
hook_event_name: 'SessionStart',
|
|
291
|
+
matcher: 'startup',
|
|
292
|
+
canon_path: canonFile,
|
|
293
|
+
}, hookDir);
|
|
294
|
+
|
|
295
|
+
const loop = new AgenticLoop({ apiKey, model, rules });
|
|
296
|
+
const renderer = createRenderer(stdout);
|
|
297
|
+
const state = { lastTurnTokens: 0, lastCompiler: null };
|
|
298
|
+
const opts = { loop, canon, cwd, renderer, state, sessionId, hookDir };
|
|
299
|
+
|
|
300
|
+
const positional = args.filter((a) => !a.startsWith('--') && args[args.indexOf(a) - 1] !== '--packet');
|
|
301
|
+
if (positional.length > 0) {
|
|
302
|
+
await runOneShot(positional.join(' '), opts);
|
|
303
|
+
} else {
|
|
304
|
+
await runRepl(opts);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
canon.append('session_end', { session_id: sessionId });
|
|
308
|
+
await fireHook('SessionEnd', { session_id: sessionId, cwd, hook_event_name: 'SessionEnd' }, hookDir);
|
|
309
|
+
|
|
310
|
+
exit(0);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
main().catch((err) => {
|
|
314
|
+
stdout.write(`${C.red}polycode error${C.reset}: ${err.message}\n`);
|
|
315
|
+
if (env.POLYCODE_DEBUG) stdout.write(err.stack + '\n');
|
|
316
|
+
exit(1);
|
|
317
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// lib/agency-receipt.mjs
|
|
2
|
+
// Per-turn receipt computation. Aggregates verification results across all
|
|
3
|
+
// commitments in the turn into a small set of numeric factors and a product.
|
|
4
|
+
// Reported at the end of each REPL turn in plain-text form.
|
|
5
|
+
|
|
6
|
+
export function computeAgencyReceipt({ primitivesList, wallClockMs, iterations }) {
|
|
7
|
+
const weights = { PASS: 1, PENDING: 0.5, FAIL: 0 };
|
|
8
|
+
|
|
9
|
+
let witnessSum = 0;
|
|
10
|
+
let witnessCount = 0;
|
|
11
|
+
let fidelityScalarSum = 0;
|
|
12
|
+
let fidelityScalarCount = 0;
|
|
13
|
+
|
|
14
|
+
for (const p of primitivesList) {
|
|
15
|
+
if (!p) continue;
|
|
16
|
+
for (const [name, prim] of Object.entries(p)) {
|
|
17
|
+
if (name === 'g_fidelity') {
|
|
18
|
+
if (typeof prim.scalar === 'number') {
|
|
19
|
+
fidelityScalarSum += prim.scalar;
|
|
20
|
+
fidelityScalarCount++;
|
|
21
|
+
}
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const w = weights[prim?.verdict];
|
|
25
|
+
if (w != null) {
|
|
26
|
+
witnessSum += w;
|
|
27
|
+
witnessCount++;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const V = witnessCount > 0 ? witnessSum / witnessCount : 0;
|
|
33
|
+
const G = fidelityScalarCount > 0 ? fidelityScalarSum / fidelityScalarCount : 0.5;
|
|
34
|
+
const I = V * G;
|
|
35
|
+
const seconds = wallClockMs / 1000;
|
|
36
|
+
const E = iterations > 0 ? (seconds * 10) / iterations : seconds;
|
|
37
|
+
const A = I * E;
|
|
38
|
+
|
|
39
|
+
return { V, G, I, E, A, seconds, iterations };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function formatReceipt(r) {
|
|
43
|
+
// Unambiguous-decimal format. Parsers can split on space-delimited keys.
|
|
44
|
+
return `Agency: V=${r.V.toFixed(2)} G=${r.G.toFixed(2)} I=${r.I.toFixed(2)} E=${r.E.toFixed(2)} A=${r.A.toFixed(2)} . ${r.seconds.toFixed(2)}s . ${r.iterations} iter`;
|
|
45
|
+
}
|