moltblock 0.4.0 → 0.6.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.
@@ -67,7 +67,7 @@ Blockchain is optional and used only for anchoring.
67
67
 
68
68
  ## Run (Code Entity MVP)
69
69
 
70
- Requires Node.js 18+, and (for full loop) any OpenAI-compatible API:
70
+ Requires Node.js 22+, and (for full loop) any OpenAI-compatible API:
71
71
  - **OpenAI** — `https://api.openai.com/v1` with `OPENAI_API_KEY`
72
72
  - **Anthropic Claude** — `https://api.anthropic.com/v1` with `ANTHROPIC_API_KEY`
73
73
  - **Google Gemini** — `https://generativelanguage.googleapis.com/v1beta/openai` with `GOOGLE_API_KEY`
@@ -105,6 +105,19 @@ npx moltblock "Implement add(a, b)."
105
105
 
106
106
  ## Configuration
107
107
 
108
+ ### Zero-config (auto-detect)
109
+
110
+ If you have an API key set in your environment, moltblock detects the provider automatically — no config file needed:
111
+
112
+ ```bash
113
+ export OPENAI_API_KEY="sk-..." # auto-detects OpenAI
114
+ npx moltblock "Implement add(a, b)." --json
115
+ ```
116
+
117
+ Override with CLI flags: `--provider google --model gemini-2.0-flash` or `-p zai -m glm-4.7`.
118
+
119
+ Detection priority: `OPENAI_API_KEY` > `GOOGLE_API_KEY` > `MOLTBLOCK_ZAI_API_KEY` > localhost.
120
+
108
121
  ### Quick setup
109
122
 
110
123
  Create a config file at `~/.moltblock/moltblock.json` (user-wide) or `./moltblock.json` (project-specific):
@@ -195,6 +208,87 @@ npm test
195
208
  - **Molt and governance** — `GovernanceConfig` (rate limit, veto); `canMolt()`, `triggerMolt()`, `pause()`, `resume()`, `emergencyShutdown()`; audit log and governance state in `Store`.
196
209
  - **Multi-entity handoff** — `signArtifact()` / `verifyArtifact()`; inbox per entity; `sendArtifact()`, `receiveArtifacts()` for Entity A → Entity B.
197
210
 
211
+ ### New in v0.6
212
+
213
+ - **Pluggable verifier system** — `Verifier` interface so verification isn't limited to vitest. Implement `verify(memory, context)` to plug in any gating strategy.
214
+ - **PolicyVerifier** — Rule-based verifier with ~20 built-in deny rules. Catches destructive commands (`rm -rf`, `DROP TABLE`), sensitive file access (`.ssh/`, `/etc/shadow`), hardcoded secrets, and exfiltration patterns — all without an LLM call.
215
+ - **CodeVerifier** — Adapter wrapping the existing vitest verifier into the pluggable interface.
216
+ - **CompositeVerifier** — Chains multiple verifiers (e.g. policy + code); all must pass. Supports fail-fast and collect-all modes.
217
+ - **Generic Entity** — `Entity` class with pluggable verifier and domain-aware prompts. Use `new Entity({ domain: "general" })` for non-code tasks.
218
+ - **Domain prompts** — Registry mapping domains to role-specific system prompts. Built-in `"code"` and `"general"` domains; register custom domains with `registerDomain()`.
219
+ - **Risk classification** — `classifyRisk(task)` returns `"low"` / `"medium"` / `"high"` with reasons. Pure regex matching, no LLM needed.
220
+ - **Policy rules in config** — Add custom `policy.rules` to `moltblock.json` for project-specific allow/deny rules.
221
+ - **OpenClaw skill** — `skill/SKILL.md` for one-step installation into OpenClaw workspace.
222
+
223
+ ---
224
+
225
+ ## Policy Verifier
226
+
227
+ The `PolicyVerifier` catches dangerous patterns in artifacts and tasks without needing an LLM:
228
+
229
+ ```typescript
230
+ import { PolicyVerifier, WorkingMemory } from "moltblock";
231
+
232
+ const verifier = new PolicyVerifier();
233
+ const memory = new WorkingMemory();
234
+ memory.setFinalCandidate("rm -rf /");
235
+
236
+ const result = await verifier.verify(memory);
237
+ // result.passed === false
238
+ // result.evidence includes "[cmd-rm-rf] Recursive force delete"
239
+ ```
240
+
241
+ Custom rules can be added via constructor or config:
242
+
243
+ ```typescript
244
+ const verifier = new PolicyVerifier([
245
+ {
246
+ id: "allow-tmp-cleanup",
247
+ description: "Allow cleanup in /tmp",
248
+ target: "artifact",
249
+ pattern: "\\/tmp\\/",
250
+ action: "allow",
251
+ category: "destructive-cmd",
252
+ enabled: true,
253
+ },
254
+ ]);
255
+ ```
256
+
257
+ ---
258
+
259
+ ## Risk Classification
260
+
261
+ Classify task risk before deciding whether to verify:
262
+
263
+ ```typescript
264
+ import { classifyRisk } from "moltblock";
265
+
266
+ classifyRisk("write a hello world function");
267
+ // { level: "low", reasons: [] }
268
+
269
+ classifyRisk("sudo rm -rf /home/user");
270
+ // { level: "high", reasons: ["Sudo privilege escalation", "Recursive file deletion (rm -rf)"] }
271
+ ```
272
+
273
+ ---
274
+
275
+ ## Generic Entity
276
+
277
+ For non-code tasks, use the generic `Entity` with domain-aware prompts:
278
+
279
+ ```typescript
280
+ import { Entity, PolicyVerifier, CompositeVerifier, CodeVerifier } from "moltblock";
281
+
282
+ // General-purpose entity (policy verification only)
283
+ const entity = new Entity({ domain: "general" });
284
+
285
+ // Code entity with both policy and vitest verification
286
+ const codeEntity = new Entity({
287
+ domain: "code",
288
+ verifier: new CompositeVerifier([new PolicyVerifier(), new CodeVerifier()]),
289
+ });
290
+ ```
291
+
198
292
  ---
199
293
 
200
294
  ## Roadmap
@@ -202,6 +296,7 @@ npm test
202
296
  - v0.1 — Protocol + architecture
203
297
  - v0.2 — MVP Entity implementation (spec + Code Entity loop + graph, memory, improvement, governance, handoff)
204
298
  - v0.3 — Multi-Entity collaboration (orchestration and tooling)
299
+ - v0.6 — Pluggable verification, policy rules, generic entity, risk classification, OpenClaw skill
205
300
 
206
301
  ---
207
302
 
package/dist/agents.d.ts CHANGED
@@ -7,17 +7,17 @@ import { Store } from "./persistence.js";
7
7
  /**
8
8
  * Generator: task -> draft artifact (code).
9
9
  */
10
- export declare function runGenerator(gateway: LLMGateway, memory: WorkingMemory, store?: Store | null): Promise<void>;
10
+ export declare function runGenerator(gateway: LLMGateway, memory: WorkingMemory, store?: Store | null, domain?: string): Promise<void>;
11
11
  /**
12
12
  * Critic: draft + task -> critique.
13
13
  */
14
- export declare function runCritic(gateway: LLMGateway, memory: WorkingMemory, store?: Store | null): Promise<void>;
14
+ export declare function runCritic(gateway: LLMGateway, memory: WorkingMemory, store?: Store | null, domain?: string): Promise<void>;
15
15
  /**
16
16
  * Judge: task + draft + critique -> final candidate artifact.
17
17
  */
18
- export declare function runJudge(gateway: LLMGateway, memory: WorkingMemory, store?: Store | null): Promise<void>;
18
+ export declare function runJudge(gateway: LLMGateway, memory: WorkingMemory, store?: Store | null, domain?: string): Promise<void>;
19
19
  /**
20
20
  * Run a single role with task and inputs (node_id -> content from predecessors).
21
21
  * Returns the role's output string. Used by the graph runner.
22
22
  */
23
- export declare function runRole(role: string, gateway: LLMGateway, task: string, inputs: Record<string, string>, longTermContext?: string, store?: Store | null): Promise<string>;
23
+ export declare function runRole(role: string, gateway: LLMGateway, task: string, inputs: Record<string, string>, longTermContext?: string, store?: Store | null, domain?: string): Promise<string>;
package/dist/agents.js CHANGED
@@ -1,35 +1,49 @@
1
1
  /**
2
2
  * Agents: Generator, Critic, Judge. Each uses LLMGateway and reads/writes WorkingMemory.
3
3
  */
4
+ import { getDomainPrompts } from "./domain-prompts.js";
4
5
  import { getStrategy } from "./persistence.js";
5
6
  // Default prompts; can be overridden by strategy store (recursive improvement)
6
7
  // Note: Prompts updated to produce TypeScript instead of Python
7
8
  const CODE_GENERATOR_SYSTEM = `You are the Generator for a Code Entity. You produce a single TypeScript implementation that satisfies the user's task. Output only valid TypeScript code, no markdown fences or extra commentary. The code will be reviewed by a Critic and then verified by running tests.`;
8
9
  const CODE_CRITIC_SYSTEM = `You are the Critic. Review the draft code for bugs, edge cases, and style. Be concise. List specific issues and suggestions. Do not rewrite the code; only critique.`;
9
10
  const CODE_JUDGE_SYSTEM = `You are the Judge. Given the task, the draft code, and the critique, produce the final single TypeScript implementation. Output only valid TypeScript code, no markdown fences or extra commentary. Incorporate the critic's feedback. The result will be run through vitest.`;
10
- function systemPrompt(role, store) {
11
+ function systemPrompt(role, store, domain = "code") {
11
12
  if (store) {
12
13
  const s = getStrategy(store, role);
13
14
  if (s) {
14
15
  return s;
15
16
  }
16
17
  }
17
- const defaults = {
18
- generator: CODE_GENERATOR_SYSTEM,
19
- critic: CODE_CRITIC_SYSTEM,
20
- judge: CODE_JUDGE_SYSTEM,
18
+ // Hard-coded defaults for "code" domain (backward compat)
19
+ if (domain === "code") {
20
+ const defaults = {
21
+ generator: CODE_GENERATOR_SYSTEM,
22
+ critic: CODE_CRITIC_SYSTEM,
23
+ judge: CODE_JUDGE_SYSTEM,
24
+ };
25
+ const d = defaults[role];
26
+ if (d)
27
+ return d;
28
+ }
29
+ // Fall back to domain prompt registry
30
+ const prompts = getDomainPrompts(domain);
31
+ const roleMap = {
32
+ generator: prompts.generator,
33
+ critic: prompts.critic,
34
+ judge: prompts.judge,
21
35
  };
22
- return defaults[role] ?? CODE_GENERATOR_SYSTEM;
36
+ return roleMap[role] ?? prompts.generator;
23
37
  }
24
38
  /**
25
39
  * Generator: task -> draft artifact (code).
26
40
  */
27
- export async function runGenerator(gateway, memory, store = null) {
41
+ export async function runGenerator(gateway, memory, store = null, domain = "code") {
28
42
  let userContent = memory.task;
29
43
  if (memory.longTermContext) {
30
44
  userContent = userContent + "\n\nRelevant verified knowledge:\n" + memory.longTermContext;
31
45
  }
32
- const system = systemPrompt("generator", store);
46
+ const system = systemPrompt("generator", store, domain);
33
47
  const messages = [
34
48
  { role: "system", content: system },
35
49
  { role: "user", content: userContent },
@@ -40,8 +54,8 @@ export async function runGenerator(gateway, memory, store = null) {
40
54
  /**
41
55
  * Critic: draft + task -> critique.
42
56
  */
43
- export async function runCritic(gateway, memory, store = null) {
44
- const system = systemPrompt("critic", store);
57
+ export async function runCritic(gateway, memory, store = null, domain = "code") {
58
+ const system = systemPrompt("critic", store, domain);
45
59
  const messages = [
46
60
  { role: "system", content: system },
47
61
  { role: "user", content: `Task:\n${memory.task}\n\nDraft code:\n${memory.draft}` },
@@ -52,8 +66,8 @@ export async function runCritic(gateway, memory, store = null) {
52
66
  /**
53
67
  * Judge: task + draft + critique -> final candidate artifact.
54
68
  */
55
- export async function runJudge(gateway, memory, store = null) {
56
- const system = systemPrompt("judge", store);
69
+ export async function runJudge(gateway, memory, store = null, domain = "code") {
70
+ const system = systemPrompt("judge", store, domain);
57
71
  const messages = [
58
72
  { role: "system", content: system },
59
73
  {
@@ -68,13 +82,13 @@ export async function runJudge(gateway, memory, store = null) {
68
82
  * Run a single role with task and inputs (node_id -> content from predecessors).
69
83
  * Returns the role's output string. Used by the graph runner.
70
84
  */
71
- export async function runRole(role, gateway, task, inputs, longTermContext = "", store = null) {
85
+ export async function runRole(role, gateway, task, inputs, longTermContext = "", store = null, domain = "code") {
72
86
  let userContent = task;
73
87
  if (longTermContext) {
74
88
  userContent = task + "\n\nRelevant verified knowledge:\n" + longTermContext;
75
89
  }
76
90
  if (role === "generator") {
77
- const system = systemPrompt("generator", store);
91
+ const system = systemPrompt("generator", store, domain);
78
92
  const messages = [
79
93
  { role: "system", content: system },
80
94
  { role: "user", content: userContent },
@@ -87,7 +101,7 @@ export async function runRole(role, gateway, task, inputs, longTermContext = "",
87
101
  if (longTermContext) {
88
102
  content = content + "\n\nRelevant verified knowledge:\n" + longTermContext;
89
103
  }
90
- const system = systemPrompt("critic", store);
104
+ const system = systemPrompt("critic", store, domain);
91
105
  const messages = [
92
106
  { role: "system", content: system },
93
107
  { role: "user", content: content },
@@ -101,7 +115,7 @@ export async function runRole(role, gateway, task, inputs, longTermContext = "",
101
115
  if (longTermContext) {
102
116
  content = content + "\n\nRelevant verified knowledge:\n" + longTermContext;
103
117
  }
104
- const system = systemPrompt("judge", store);
118
+ const system = systemPrompt("judge", store, domain);
105
119
  const messages = [
106
120
  { role: "system", content: system },
107
121
  { role: "user", content: content },
package/dist/cli.js CHANGED
@@ -14,6 +14,8 @@ async function main() {
14
14
  .argument("<task>", "Task description (e.g. 'Implement a function add(a,b) that returns a+b.')")
15
15
  .option("-t, --test <path>", "Path to file containing test code (e.g. vitest test module). If omitted, only syntax check.")
16
16
  .option("--json", "Output result as JSON (draft, critique, final, verification_passed, authoritative_artifact).")
17
+ .option("-p, --provider <name>", "LLM provider (openai, google, zai, local). Auto-detected from env if omitted.")
18
+ .option("-m, --model <name>", "Model for all roles (overrides provider default).")
17
19
  .action(async (task, options) => {
18
20
  // Validate task input
19
21
  const validation = validateTask(task);
@@ -30,7 +32,10 @@ async function main() {
30
32
  if (options.test && fs.existsSync(options.test)) {
31
33
  testCode = fs.readFileSync(options.test, "utf-8");
32
34
  }
33
- const entity = new CodeEntity(defaultCodeEntityBindings());
35
+ const entity = new CodeEntity(defaultCodeEntityBindings({
36
+ provider: options.provider,
37
+ model: options.model,
38
+ }));
34
39
  const memory = await entity.run(task, { testCode });
35
40
  if (options.json) {
36
41
  const out = {
@@ -0,0 +1,13 @@
1
+ /**
2
+ * CodeVerifier: adapter that wraps the existing vitest-based runVerifier into the Verifier interface.
3
+ */
4
+ import type { WorkingMemory } from "./memory.js";
5
+ import type { Verifier, VerificationResult, VerifierContext } from "./verifier-interface.js";
6
+ /**
7
+ * Wraps the existing vitest verifier (runVerifier) into the pluggable Verifier interface.
8
+ * Uses context.testCode for the test file, same as CodeEntity.
9
+ */
10
+ export declare class CodeVerifier implements Verifier {
11
+ readonly name = "CodeVerifier";
12
+ verify(memory: WorkingMemory, context?: VerifierContext): Promise<VerificationResult>;
13
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * CodeVerifier: adapter that wraps the existing vitest-based runVerifier into the Verifier interface.
3
+ */
4
+ import { runVerifier } from "./verifier.js";
5
+ /**
6
+ * Wraps the existing vitest verifier (runVerifier) into the pluggable Verifier interface.
7
+ * Uses context.testCode for the test file, same as CodeEntity.
8
+ */
9
+ export class CodeVerifier {
10
+ name = "CodeVerifier";
11
+ async verify(memory, context) {
12
+ const testCode = context?.testCode;
13
+ // runVerifier mutates memory.verificationPassed / verificationEvidence
14
+ await runVerifier(memory, testCode);
15
+ return {
16
+ passed: memory.verificationPassed,
17
+ evidence: memory.verificationEvidence || (memory.verificationPassed ? "Verification passed." : "Verification failed."),
18
+ verifierName: this.name,
19
+ };
20
+ }
21
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * CompositeVerifier: runs multiple verifiers, all must pass.
3
+ */
4
+ import type { WorkingMemory } from "./memory.js";
5
+ import type { Verifier, VerificationResult, VerifierContext } from "./verifier-interface.js";
6
+ export interface CompositeVerifierOptions {
7
+ /** If true, stop at first failure. Defaults to true. */
8
+ failFast?: boolean;
9
+ }
10
+ /**
11
+ * Runs verifiers sequentially. All must pass for the composite to pass.
12
+ * Fail-fast mode (default) stops at the first failure.
13
+ * Collect-all mode runs every verifier and reports all results.
14
+ */
15
+ export declare class CompositeVerifier implements Verifier {
16
+ readonly name = "CompositeVerifier";
17
+ private verifiers;
18
+ private failFast;
19
+ constructor(verifiers: Verifier[], options?: CompositeVerifierOptions);
20
+ verify(memory: WorkingMemory, context?: VerifierContext): Promise<VerificationResult>;
21
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * CompositeVerifier: runs multiple verifiers, all must pass.
3
+ */
4
+ /**
5
+ * Runs verifiers sequentially. All must pass for the composite to pass.
6
+ * Fail-fast mode (default) stops at the first failure.
7
+ * Collect-all mode runs every verifier and reports all results.
8
+ */
9
+ export class CompositeVerifier {
10
+ name = "CompositeVerifier";
11
+ verifiers;
12
+ failFast;
13
+ constructor(verifiers, options) {
14
+ if (verifiers.length === 0) {
15
+ throw new Error("CompositeVerifier requires at least one verifier");
16
+ }
17
+ this.verifiers = verifiers;
18
+ this.failFast = options?.failFast ?? true;
19
+ }
20
+ async verify(memory, context) {
21
+ const details = [];
22
+ let allPassed = true;
23
+ for (const verifier of this.verifiers) {
24
+ const result = await verifier.verify(memory, context);
25
+ details.push(result);
26
+ if (!result.passed) {
27
+ allPassed = false;
28
+ if (this.failFast)
29
+ break;
30
+ }
31
+ }
32
+ const evidence = details
33
+ .map((d) => `[${d.verifierName}] ${d.passed ? "PASS" : "FAIL"}: ${d.evidence}`)
34
+ .join("\n");
35
+ return {
36
+ passed: allPassed,
37
+ evidence,
38
+ verifierName: this.name,
39
+ details,
40
+ };
41
+ }
42
+ }
package/dist/config.d.ts CHANGED
@@ -19,6 +19,23 @@ export declare const AgentConfigSchema: z.ZodObject<{
19
19
  }, z.core.$strip>>>;
20
20
  }, z.core.$strip>;
21
21
  export type AgentConfig = z.infer<typeof AgentConfigSchema>;
22
+ export declare const PolicyRuleSchema: z.ZodObject<{
23
+ id: z.ZodString;
24
+ description: z.ZodString;
25
+ target: z.ZodEnum<{
26
+ artifact: "artifact";
27
+ task: "task";
28
+ both: "both";
29
+ }>;
30
+ pattern: z.ZodString;
31
+ action: z.ZodEnum<{
32
+ deny: "deny";
33
+ allow: "allow";
34
+ }>;
35
+ category: z.ZodString;
36
+ enabled: z.ZodDefault<z.ZodBoolean>;
37
+ }, z.core.$strip>;
38
+ export type PolicyRuleConfig = z.infer<typeof PolicyRuleSchema>;
22
39
  export declare const MoltblockConfigSchema: z.ZodObject<{
23
40
  agent: z.ZodOptional<z.ZodObject<{
24
41
  bindings: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -28,6 +45,24 @@ export declare const MoltblockConfigSchema: z.ZodObject<{
28
45
  api_key: z.ZodOptional<z.ZodNullable<z.ZodString>>;
29
46
  }, z.core.$strip>>>;
30
47
  }, z.core.$strip>>;
48
+ policy: z.ZodOptional<z.ZodObject<{
49
+ rules: z.ZodOptional<z.ZodArray<z.ZodObject<{
50
+ id: z.ZodString;
51
+ description: z.ZodString;
52
+ target: z.ZodEnum<{
53
+ artifact: "artifact";
54
+ task: "task";
55
+ both: "both";
56
+ }>;
57
+ pattern: z.ZodString;
58
+ action: z.ZodEnum<{
59
+ deny: "deny";
60
+ allow: "allow";
61
+ }>;
62
+ category: z.ZodString;
63
+ enabled: z.ZodDefault<z.ZodBoolean>;
64
+ }, z.core.$strip>>>;
65
+ }, z.core.$strip>>;
31
66
  }, z.core.$strip>;
32
67
  export type MoltblockConfig = z.infer<typeof MoltblockConfigSchema>;
33
68
  export declare const ModelBindingSchema: z.ZodObject<{
@@ -48,8 +83,28 @@ export declare function getConfigSource(): ConfigSource;
48
83
  * Returns null if no file or parse error.
49
84
  */
50
85
  export declare function loadMoltblockConfig(): MoltblockConfig | null;
86
+ /** Overrides for provider/model selection (e.g. from CLI flags). */
87
+ export interface BindingOverrides {
88
+ provider?: string;
89
+ model?: string;
90
+ }
91
+ /**
92
+ * Auto-detect the best available provider from environment variables.
93
+ * Priority: explicit override > OPENAI_API_KEY > GOOGLE_API_KEY > MOLTBLOCK_ZAI_API_KEY/ZAI_API_KEY > local.
94
+ */
95
+ export declare function detectProvider(overrideProvider?: string, overrideModel?: string): {
96
+ backend: string;
97
+ baseUrl: string;
98
+ model: string;
99
+ apiKey: string | null;
100
+ };
51
101
  /**
52
102
  * Model bindings for Code Entity. Load from moltblock.json if present, then env overrides.
53
- * If no JSON, uses env/.env only (backward compatible). API keys from env win over JSON.
103
+ * If no JSON, auto-detects provider from env vars. API keys from env win over JSON.
104
+ */
105
+ export declare function defaultCodeEntityBindings(overrides?: BindingOverrides): Record<string, ModelBinding>;
106
+ /**
107
+ * Load custom policy rules from moltblock config.
108
+ * Returns empty array if no config or no rules defined.
54
109
  */
55
- export declare function defaultCodeEntityBindings(): Record<string, ModelBinding>;
110
+ export declare function loadPolicyRules(): PolicyRuleConfig[];