@holoscript/holoscript-agent 2.0.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { homedir as homedir3 } from "os";
4
+ import { homedir as homedir3, hostname as hostname2 } from "os";
5
5
  import { join as join4 } from "path";
6
+ import { createHash as createHash4, createDecipheriv, randomBytes as randomBytes2 } from "crypto";
7
+ import { Wallet as Wallet2 } from "ethers";
6
8
  import {
7
9
  createAnthropicProvider,
8
10
  createOpenAIProvider,
@@ -10,7 +12,8 @@ import {
10
12
  createMockProvider,
11
13
  createLocalLLMProvider,
12
14
  createXAIProvider,
13
- createOpenRouterProvider
15
+ createOpenRouterProvider,
16
+ resolveSovereignProviderAsync
14
17
  } from "@holoscript/llm-provider";
15
18
 
16
19
  // src/identity.ts
@@ -80,8 +83,9 @@ function identityForLog(id) {
80
83
  // src/brain.ts
81
84
  import { readFile } from "fs/promises";
82
85
  async function loadBrain(brainPath, scopeTier = "warm") {
83
- const systemPrompt = await readFile(brainPath, "utf8");
84
- const { domain, capabilityTags, requires, prefers, avoids } = extractIdentity(systemPrompt);
86
+ const raw = await readFile(brainPath, "utf8");
87
+ const { domain, capabilityTags, requires, prefers, avoids } = extractIdentity(raw);
88
+ const systemPrompt = extractSystemPromptPreamble(raw);
85
89
  return {
86
90
  brainPath,
87
91
  systemPrompt,
@@ -90,9 +94,30 @@ async function loadBrain(brainPath, scopeTier = "warm") {
90
94
  scopeTier,
91
95
  requires,
92
96
  prefers,
93
- avoids
97
+ avoids,
98
+ reflect: extractReflect(raw)
94
99
  };
95
100
  }
101
+ function extractReflect(brain) {
102
+ const block = sliceNamedBlock(brain, "reflect");
103
+ if (block === void 0) return void 0;
104
+ const criteria = scalarField(block, "criteria") ?? scalarField(block, "scorer") ?? scalarField(block, "of") ?? "correctness, completeness, and valid HoloScript syntax";
105
+ const escRaw = scalarField(block, "escalate_on_fail") ?? scalarField(block, "escalateOnFail") ?? scalarField(block, "escalate");
106
+ return { criteria, escalateOnFail: (escRaw ?? "").split(",")[0].trim().toLowerCase() === "true" };
107
+ }
108
+ function extractSystemPromptPreamble(src) {
109
+ const lines = src.split("\n");
110
+ const BLOCK_START = /^(#version|#target|#mode|identity\s*\{|state\s*\{|computed\s*\{|traits\s*\[|capabilities\s*\{|directives\s*\{|behavior\s)/;
111
+ let cutLine = -1;
112
+ for (let i = 0; i < lines.length; i++) {
113
+ if (BLOCK_START.test(lines[i].trim())) {
114
+ cutLine = i;
115
+ break;
116
+ }
117
+ }
118
+ if (cutLine <= 0) return src;
119
+ return lines.slice(0, cutLine).join("\n").trimEnd();
120
+ }
96
121
  function extractIdentity(brain) {
97
122
  const identityBlock = sliceNamedBlock(brain, "identity");
98
123
  if (!identityBlock) {
@@ -158,6 +183,8 @@ function listField(block, key) {
158
183
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
159
184
  import { dirname } from "path";
160
185
  var ANTHROPIC_PRICING_USD_PER_MTOK = {
186
+ "claude-opus-4-8": { input: 10, output: 50 },
187
+ // 3× cheaper than 4.7 on total cost; A-020 2026-06-08
161
188
  "claude-opus-4-7": { input: 5, output: 25 },
162
189
  "claude-opus-4-6": { input: 5, output: 25 },
163
190
  "claude-sonnet-4-6": { input: 3, output: 15 },
@@ -335,9 +362,7 @@ function pickProvider(opts) {
335
362
  picked: candidates[0].name,
336
363
  reason: "open-routing-default",
337
364
  unsatisfiedRequires: [],
338
- matchedPrefers: brain.prefers.filter(
339
- (p) => satisfies(candidates[0].capabilities, p)
340
- ),
365
+ matchedPrefers: brain.prefers.filter((p) => satisfies(candidates[0].capabilities, p)),
341
366
  excludedByAvoids,
342
367
  alternatives: candidates.slice(1).map((c) => c.name)
343
368
  };
@@ -351,7 +376,9 @@ function pickProvider(opts) {
351
376
  alternatives: ordered.slice(1).map((c) => c.name)
352
377
  };
353
378
  }
354
- const eligible = notAvoided.filter((c) => unsatisfiedKeys(c.capabilities, brain.requires).length === 0);
379
+ const eligible = notAvoided.filter(
380
+ (c) => unsatisfiedKeys(c.capabilities, brain.requires).length === 0
381
+ );
355
382
  if (eligible.length === 0) {
356
383
  if (envOverride !== void 0) {
357
384
  const envCandidate = candidates.find((c) => c.name === envOverride);
@@ -443,9 +470,14 @@ var HolomeshClient = class {
443
470
  this.bearer = opts.bearer;
444
471
  this.teamId = opts.teamId;
445
472
  this.fetchImpl = opts.fetchImpl ?? fetch;
473
+ this.signer = opts.signer;
474
+ }
475
+ /** Wrap body in a signed envelope when a signer is available (strict-mode endpoints). */
476
+ async signBody(body) {
477
+ return this.signer ? await this.signer(body) : body;
446
478
  }
447
479
  async heartbeat(payload) {
448
- await this.req("POST", `/team/${this.teamId}/presence`, payload);
480
+ await this.req("POST", `/team/${this.teamId}/presence`, await this.signBody(payload));
449
481
  }
450
482
  async getOpenTasks() {
451
483
  const data = await this.req(
@@ -455,28 +487,33 @@ var HolomeshClient = class {
455
487
  return data.tasks ?? data.open ?? [];
456
488
  }
457
489
  async claim(taskId) {
458
- return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, { action: "claim" });
490
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "claim" }));
459
491
  }
460
492
  async joinTeam() {
461
493
  return this.req(
462
494
  "POST",
463
495
  `/team/${this.teamId}/join`,
464
- {}
496
+ await this.signBody({})
465
497
  );
466
498
  }
467
499
  async sendMessageOnTask(taskId, body) {
468
- await this.req("POST", `/team/${this.teamId}/message`, {
500
+ await this.req("POST", `/team/${this.teamId}/message`, await this.signBody({
469
501
  to: "team",
470
502
  subject: `task:${taskId}`,
471
503
  content: body
472
- });
504
+ }));
473
505
  }
474
506
  async markDone(taskId, summary, commitHash) {
475
- await this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
507
+ await this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({
476
508
  action: "done",
477
509
  summary,
478
- commitHash
479
- });
510
+ // verification_evidence required by server before task can be closed.
511
+ verification_evidence: summary,
512
+ // Exclude commitHash when undefined — JSON.stringify drops undefined but
513
+ // canonicalizeSigning preserves it as the literal string "undefined",
514
+ // causing a signature-mismatch vs what the server sees after JSON.parse.
515
+ ...commitHash !== void 0 ? { commitHash } : {}
516
+ }));
480
517
  }
481
518
  // POST CAEL audit records for this agent. Server validator at
482
519
  // packages/mcp-server/src/holomesh/routes/core-routes.ts:472-533 requires
@@ -510,39 +547,28 @@ var HolomeshClient = class {
510
547
  }
511
548
  /** Post a message to the team feed. */
512
549
  async sendTeamMessage(content, messageType = "text") {
513
- await this.req("POST", `/team/${this.teamId}/message`, {
514
- content,
515
- type: messageType
516
- });
550
+ await this.req("POST", `/team/${this.teamId}/message`, await this.signBody({ content, type: messageType }));
517
551
  }
518
552
  // ── Owner-op API wrappers (E4) ─────────────────────────────────────────────
519
553
  /** Switch team mode. Requires owner or founder role. */
520
554
  async setTeamMode(mode, reason) {
521
- return this.req("POST", `/team/${this.teamId}/mode`, { mode, reason });
555
+ return this.req("POST", `/team/${this.teamId}/mode`, await this.signBody({ mode, reason }));
522
556
  }
523
557
  /** Update room preferences. Requires config:write permission. */
524
558
  async patchRoomPrefs(prefs) {
525
- return this.req("PATCH", `/team/${this.teamId}/room`, prefs);
559
+ return this.req("PATCH", `/team/${this.teamId}/room`, await this.signBody(prefs));
526
560
  }
527
561
  /** Update a board task. */
528
562
  async updateTask(taskId, updates) {
529
- return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
530
- action: "update",
531
- ...updates
532
- });
563
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "update", ...updates }));
533
564
  }
534
565
  /** Delete a board task. */
535
566
  async deleteTask(taskId) {
536
- return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
537
- action: "delete"
538
- });
567
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "delete" }));
539
568
  }
540
569
  /** Delegate a board task to another agent. */
541
570
  async delegateTask(taskId, toAgentId) {
542
- return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
543
- action: "delegate",
544
- toAgentId
545
- });
571
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "delegate", toAgentId }));
546
572
  }
547
573
  async req(method, path, body) {
548
574
  const url = `${this.apiBase}${path}`;
@@ -611,7 +637,18 @@ function brainClassOf(brain) {
611
637
  return "unknown";
612
638
  }
613
639
  function buildCaelRecord(input) {
614
- const { identity, brain, task, messages, finalText, usage, costUsd, spentUsd, prevChain, runtimeVersion } = input;
640
+ const {
641
+ identity,
642
+ brain,
643
+ task,
644
+ messages,
645
+ finalText,
646
+ usage,
647
+ costUsd,
648
+ spentUsd,
649
+ prevChain,
650
+ runtimeVersion
651
+ } = input;
615
652
  const l0 = sha(brain.systemPrompt);
616
653
  const l1 = sha(`${task.id}|${task.title}|${task.description ?? ""}`);
617
654
  const l2 = sha(JSON.stringify(messages));
@@ -634,9 +671,9 @@ function buildCaelRecord(input) {
634
671
 
635
672
  // src/tools.ts
636
673
  import { readFile as readFile2, writeFile, readdir, mkdir, stat } from "fs/promises";
637
- import { resolve, dirname as dirname2 } from "path";
674
+ import { resolve, dirname as dirname2, delimiter, isAbsolute, sep } from "path";
638
675
  import { spawn } from "child_process";
639
- var ALLOWED_READ_ROOTS = [
676
+ var FLEET_READ_ROOTS = [
640
677
  "/root/msc-paper-22",
641
678
  // Paper 22 mechanization inputs (scp'd by deploy)
642
679
  "/root/holoscript-mesh",
@@ -644,10 +681,23 @@ var ALLOWED_READ_ROOTS = [
644
681
  "/root/agent-output"
645
682
  // Read back what we wrote
646
683
  ];
647
- var ALLOWED_WRITE_ROOTS = [
684
+ var FLEET_WRITE_ROOTS = [
648
685
  "/root/agent-output"
649
686
  // Single write sink — keeps deliverables in one place
650
687
  ];
688
+ function parseRootsEnv(raw, fallback) {
689
+ if (!raw) return fallback;
690
+ const roots = raw.split(delimiter).map((r) => r.trim()).filter((r) => r.length > 0 && isAbsolute(r));
691
+ return roots.length > 0 ? roots : fallback;
692
+ }
693
+ var ALLOWED_READ_ROOTS = parseRootsEnv(
694
+ process.env.HOLOSCRIPT_AGENT_READ_ROOTS,
695
+ FLEET_READ_ROOTS
696
+ );
697
+ var ALLOWED_WRITE_ROOTS = parseRootsEnv(
698
+ process.env.HOLOSCRIPT_AGENT_WRITE_ROOTS,
699
+ FLEET_WRITE_ROOTS
700
+ );
651
701
  var BASH_READ_ONLY_PREFIXES = [
652
702
  "ls ",
653
703
  "ls\n",
@@ -673,7 +723,15 @@ var BASH_PRODUCTIVE_PREFIXES = [
673
723
  "lean ",
674
724
  "pnpm --filter",
675
725
  "pnpm vitest",
676
- "vitest run"
726
+ "vitest run",
727
+ // Robotics / edge-node (Jetson) productive commands — without these, every
728
+ // ros2/colcon/tegrastats task fails the W.107 artifact gate and is abandoned
729
+ // as no-artifact. (jetson-orin-01 lane.)
730
+ "ros2 launch",
731
+ "ros2 topic pub",
732
+ "ros2 service call",
733
+ "colcon build",
734
+ "tegrastats"
677
735
  ];
678
736
  var BASH_WHITELIST = [...BASH_READ_ONLY_PREFIXES, ...BASH_PRODUCTIVE_PREFIXES];
679
737
  function isProductiveBashCommand(cmd) {
@@ -684,7 +742,7 @@ function isProductiveBashCommand(cmd) {
684
742
  var MESH_TOOLS = [
685
743
  {
686
744
  name: "read_file",
687
- description: "Read a file from the agent sandbox. Allowed roots: /root/msc-paper-22, /root/holoscript-mesh, /root/agent-output. Returns the file content as text. Use this to inspect inputs scp'd to the instance (e.g. MSC/Invariants.lean).",
745
+ description: `Read a file from the agent sandbox. Allowed roots: ${ALLOWED_READ_ROOTS.join(", ")}. Returns the file content as text. Use this to inspect task inputs and the read-only repo view.`,
688
746
  input_schema: {
689
747
  type: "object",
690
748
  properties: {
@@ -706,11 +764,11 @@ var MESH_TOOLS = [
706
764
  },
707
765
  {
708
766
  name: "write_file",
709
- description: "Write a file to /root/agent-output/. This is the deliverable sink \u2014 anything you want to emit as task output (a Lean proof, a markdown report, a JSON dataset) goes here. Creates parent directories. Will refuse paths outside the write root.",
767
+ description: `Write a file to the deliverable sink (write roots: ${ALLOWED_WRITE_ROOTS.join(", ")}). Anything you want to emit as task output (a Lean proof, a markdown report, a JSON dataset, a .holo scene) goes here. Creates parent directories. Will refuse paths outside the write root(s).`,
710
768
  input_schema: {
711
769
  type: "object",
712
770
  properties: {
713
- path: { type: "string", description: "Absolute path under /root/agent-output/" },
771
+ path: { type: "string", description: `Absolute path under a write root: ${ALLOWED_WRITE_ROOTS.join(", ")}` },
714
772
  content: { type: "string", description: "File content to write (UTF-8)" }
715
773
  },
716
774
  required: ["path", "content"]
@@ -718,7 +776,7 @@ var MESH_TOOLS = [
718
776
  },
719
777
  {
720
778
  name: "bash",
721
- description: "Run a shell command. Whitelisted prefixes only: lake build, lean, ls, cat, grep, find, wc, head, tail, git status/log/diff/show, pnpm --filter, vitest run, pwd, echo. Hard 60s wall timeout, 1MB stdout cap. Use for lake build / lean kernel-checks, git inspection, repo greps. Refuses rm, curl, ssh, sudo, eval.",
779
+ description: "Run a shell command. Whitelisted prefixes only: lake build, lean, ls, cat, grep, find, wc, head, tail, git status/log/diff/show, pnpm --filter, vitest run, pwd, echo, ros2 launch/topic/service, colcon build, tegrastats. Hard 60s wall timeout, 1MB stdout cap. Use for builds, tests, hardware probes. Refuses rm, curl, ssh, sudo, eval.",
722
780
  input_schema: {
723
781
  type: "object",
724
782
  properties: {
@@ -727,22 +785,52 @@ var MESH_TOOLS = [
727
785
  },
728
786
  required: ["cmd"]
729
787
  }
788
+ },
789
+ {
790
+ name: "emit_hardware_receipt",
791
+ description: "Emit a portable hardware receipt (PortableHardwareReceiptMetadata v1) capturing device identity, runtime, and measured performance. Writes a JSON receipt to the agent output dir. Use after running tegrastats or colcon build to record hardware evidence for the CAEL audit chain. Accepts either pre-parsed measurements or raw tegrastats output (the tool parses it automatically).",
792
+ input_schema: {
793
+ type: "object",
794
+ properties: {
795
+ device_kind: {
796
+ type: "string",
797
+ description: 'Device identifier, e.g. "jetson-orin-nano-super", "raspberry-pi-5"'
798
+ },
799
+ accelerator: {
800
+ description: 'Accelerator string, e.g. "NVIDIA CUDA 8.7", or null for CPU-only'
801
+ },
802
+ runtime_name: { type: "string", description: 'Inference runtime, e.g. "Ollama", "llama.cpp"' },
803
+ runtime_version: { type: "string", description: 'Runtime version, e.g. "0.30.8"' },
804
+ host_os: { type: "string", description: 'OS + firmware, e.g. "JetPack 6.2.1 / Ubuntu 22.04"' },
805
+ composition_id: { type: "string", description: 'Brain composition reference, e.g. "jetson-orin-brain"' },
806
+ measurements: {
807
+ type: "array",
808
+ description: "Pre-parsed measurements. Each item: {metric: string, value: number, unit: string}",
809
+ items: { type: "object" }
810
+ },
811
+ tegrastats_output: {
812
+ type: "string",
813
+ description: "Raw tegrastats output line(s) \u2014 tool auto-parses GPU%, RAM, temp, power"
814
+ }
815
+ },
816
+ required: ["device_kind", "runtime_name", "runtime_version", "host_os"]
817
+ }
730
818
  }
731
819
  ];
732
820
  function isUnderRoot(absPath, root) {
733
821
  const resolved = resolve(absPath);
734
822
  const rootResolved = resolve(root);
735
- return resolved === rootResolved || resolved.startsWith(rootResolved + "/");
823
+ return resolved === rootResolved || resolved.startsWith(rootResolved + sep);
736
824
  }
737
825
  function checkReadAllowed(path) {
738
- if (!path.startsWith("/")) return `path must be absolute, got "${path}"`;
826
+ if (!isAbsolute(path)) return `path must be absolute, got "${path}"`;
739
827
  for (const root of ALLOWED_READ_ROOTS) {
740
828
  if (isUnderRoot(path, root)) return null;
741
829
  }
742
830
  return `read denied \u2014 path "${path}" not under allowed roots: ${ALLOWED_READ_ROOTS.join(", ")}`;
743
831
  }
744
832
  function checkWriteAllowed(path) {
745
- if (!path.startsWith("/")) return `path must be absolute, got "${path}"`;
833
+ if (!isAbsolute(path)) return `path must be absolute, got "${path}"`;
746
834
  for (const root of ALLOWED_WRITE_ROOTS) {
747
835
  if (isUnderRoot(path, root)) return null;
748
836
  }
@@ -797,11 +885,105 @@ async function runTool(use) {
797
885
  return result.code === 0 ? okResult(use.id, result.stdout) : errResult(use.id, `exit=${result.code}
798
886
  ${result.stderr || result.stdout}`);
799
887
  }
888
+ if (use.name === "emit_hardware_receipt") {
889
+ const deviceKind = String(use.input.device_kind ?? "unknown-device");
890
+ const accelerator = use.input.accelerator === null || use.input.accelerator === "null" ? null : String(use.input.accelerator ?? "").trim() || null;
891
+ const runtimeName = String(use.input.runtime_name ?? "Ollama");
892
+ const runtimeVersion = String(use.input.runtime_version ?? "unknown");
893
+ const hostOs = String(use.input.host_os ?? "unknown");
894
+ const compositionId = String(use.input.composition_id ?? "unknown");
895
+ let measurements = [];
896
+ if (Array.isArray(use.input.measurements)) {
897
+ for (const m of use.input.measurements) {
898
+ const metric = String(m.metric ?? "");
899
+ const value = Number(m.value ?? 0);
900
+ const unit = String(m.unit ?? "");
901
+ if (metric && Number.isFinite(value)) {
902
+ measurements.push({ metric, value, unit, method: "measured" });
903
+ }
904
+ }
905
+ }
906
+ if (typeof use.input.tegrastats_output === "string" && use.input.tegrastats_output.length > 0) {
907
+ measurements = [...measurements, ...parseTegrastats(use.input.tegrastats_output)];
908
+ }
909
+ if (measurements.length === 0) {
910
+ measurements.push({ metric: "agent-tick", value: 1, unit: "count", method: "presence" });
911
+ }
912
+ const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
913
+ const receipt = {
914
+ schemaVersion: "holoscript.hardware-receipt-metadata.v1",
915
+ target: {
916
+ id: `${deviceKind}-${Date.now()}`,
917
+ kind: deviceKind,
918
+ architecture: /jetson|orin|nano|agx|xavier/i.test(deviceKind) ? "arm64" : "unknown",
919
+ artifactKind: "measurement-trace"
920
+ },
921
+ device: {
922
+ vendor: /jetson|orin|nvidia/i.test(deviceKind) ? "nvidia" : "unknown",
923
+ model: deviceKind,
924
+ accelerator
925
+ },
926
+ runtime: { name: runtimeName, version: runtimeVersion, hostOS: hostOs },
927
+ compilerVersion: "holoscript-agent-1.0.0",
928
+ constraints: [],
929
+ measuredResults: measurements,
930
+ replayInputs: [
931
+ { kind: "composition-ref", uri: `compositions/${compositionId}`, sha256: "unknown" }
932
+ ],
933
+ provenance: {
934
+ capturedAt,
935
+ sourceCompositionHash: compositionId
936
+ },
937
+ owner: {
938
+ agent: process.env.HOLOSCRIPT_AGENT_HANDLE ?? "unknown",
939
+ ...process.env.HOLOMESH_TEAM_ID ? { team: process.env.HOLOMESH_TEAM_ID } : {}
940
+ }
941
+ };
942
+ const ts = capturedAt.replace(/[:.]/g, "-");
943
+ const outPath = resolve(ALLOWED_WRITE_ROOTS[0], `hardware-receipt-${ts}.json`);
944
+ const denied = checkWriteAllowed(outPath);
945
+ if (denied) return errResult(use.id, `Cannot write receipt: ${denied}`);
946
+ await mkdir(dirname2(outPath), { recursive: true });
947
+ await writeFile(outPath, JSON.stringify(receipt, null, 2), "utf8");
948
+ return okResult(
949
+ use.id,
950
+ `Hardware receipt written to ${outPath} \u2014 ${measurements.length} measurements, accelerator=${accelerator ?? "none"}`
951
+ );
952
+ }
800
953
  return errResult(use.id, `unknown tool: ${use.name}`);
801
954
  } catch (err) {
802
955
  return errResult(use.id, err instanceof Error ? err.message : String(err));
803
956
  }
804
957
  }
958
+ function parseTegrastats(raw) {
959
+ const results = [];
960
+ const m = (pattern, metric, unit, transform) => {
961
+ const match = raw.match(pattern);
962
+ if (match?.[1]) {
963
+ const value = transform ? transform(match[1]) : Number(match[1]);
964
+ if (Number.isFinite(value)) results.push({ metric, value, unit, method: "tegrastats" });
965
+ }
966
+ };
967
+ const ram = raw.match(/RAM\s+(\d+)\/(\d+)MB/);
968
+ if (ram) {
969
+ const used = Number(ram[1]);
970
+ const total = Number(ram[2]);
971
+ results.push({ metric: "ram-used", value: used, unit: "MB", method: "tegrastats" });
972
+ results.push({ metric: "ram-total", value: total, unit: "MB", method: "tegrastats" });
973
+ if (total > 0)
974
+ results.push({ metric: "ram-pct", value: Math.round(used / total * 100), unit: "%", method: "tegrastats" });
975
+ }
976
+ m(/GR3D_FREQ\s+(\d+)%/, "gpu-util", "%");
977
+ m(/EMC_FREQ\s+(\d+)%/, "emc-freq-pct", "%");
978
+ m(/tj@([\d.]+)C/, "temp-tj", "C", parseFloat);
979
+ m(/cpu@([\d.]+)C/, "temp-cpu", "C", parseFloat);
980
+ m(/gpu@([\d.]+)C/, "temp-gpu", "C", parseFloat);
981
+ m(/VDD_SOC\s+(\d+)mW/, "power-soc", "mW");
982
+ m(/VDD_CPU_CV\s+(\d+)mW/, "power-cpu-cv", "mW");
983
+ m(/VDD_IN\s+(\d+)mW/, "power-total", "mW");
984
+ m(/CPU\s+\[(\d+)%/, "cpu-util-core0", "%");
985
+ return results;
986
+ }
805
987
  function runBash(cmd, cwd) {
806
988
  if (process.env.VITEST === "true" || process.env.NODE_ENV === "test") {
807
989
  return Promise.resolve({
@@ -949,12 +1131,16 @@ var AgentRunner = class {
949
1131
  finalText = finalText || `[tool-loop hit ${MAX_TOOL_ITERS}-iter cap before final text]`;
950
1132
  break;
951
1133
  }
1134
+ const activeTools = brain.requires.includes("local-llm") ? MESH_TOOLS.filter((t) => t.name === "write_file") : MESH_TOOLS;
952
1135
  const resp = await provider.complete(
953
1136
  {
954
1137
  messages,
955
- maxTokens: 4096,
1138
+ // 8192 for local thinking models (qwen3:4b uses ~3800 tokens on thinking
1139
+ // before the tool-call JSON; 4096 cuts off mid-generation). Frontier
1140
+ // models ignore this ceiling and stop naturally earlier.
1141
+ maxTokens: 8192,
956
1142
  temperature: 0.4,
957
- tools: MESH_TOOLS
1143
+ tools: activeTools
958
1144
  },
959
1145
  identity.llmModel
960
1146
  );
@@ -965,7 +1151,12 @@ var AgentRunner = class {
965
1151
  totalTokens: aggUsage.totalTokens + resp.usage.totalTokens
966
1152
  };
967
1153
  if (resp.finishReason === "tool_use" && resp.toolUses && resp.toolUses.length > 0) {
968
- log({ ev: "tool-call", taskId: target.id, iter: iters, tools: resp.toolUses.map((t) => t.name) });
1154
+ log({
1155
+ ev: "tool-call",
1156
+ taskId: target.id,
1157
+ iter: iters,
1158
+ tools: resp.toolUses.map((t) => t.name)
1159
+ });
969
1160
  for (const u of resp.toolUses) {
970
1161
  toolsCalled.add(u.name);
971
1162
  if (u.name === "write_file") {
@@ -974,6 +1165,8 @@ var AgentRunner = class {
974
1165
  } else if (u.name === "bash") {
975
1166
  const cmd = String(u.input?.cmd ?? "");
976
1167
  if (isProductiveBashCommand(cmd)) productiveCallCount++;
1168
+ } else if (u.name === "emit_hardware_receipt") {
1169
+ productiveCallCount++;
977
1170
  }
978
1171
  }
979
1172
  messages.push({
@@ -1018,6 +1211,58 @@ var AgentRunner = class {
1018
1211
  message: `no productive tool call observed (toolsCalled=[${[...toolsCalled].join(",")}], productiveCallCount=${productiveCallCount}, iters=${iters})`
1019
1212
  };
1020
1213
  }
1214
+ let reflectVerdict;
1215
+ if (brain.reflect) {
1216
+ try {
1217
+ const reflectResp = await provider.complete(
1218
+ {
1219
+ messages: [
1220
+ {
1221
+ role: "system",
1222
+ content: "You are a strict reviewer. Evaluate the work against the criteria; do not rewrite it."
1223
+ },
1224
+ {
1225
+ role: "user",
1226
+ content: `Reflect on the artifact produced for this task. Evaluate it for: ${brain.reflect.criteria}.
1227
+
1228
+ --- artifact / final response ---
1229
+ ${finalText.slice(0, 4e3)}
1230
+ --- end ---
1231
+
1232
+ Give a one-line reason, then end with exactly "VERDICT: PASS" or "VERDICT: FAIL".`
1233
+ }
1234
+ ],
1235
+ maxTokens: 512,
1236
+ temperature: 0.1
1237
+ },
1238
+ identity.llmModel
1239
+ );
1240
+ aggUsage = {
1241
+ promptTokens: aggUsage.promptTokens + reflectResp.usage.promptTokens,
1242
+ completionTokens: aggUsage.completionTokens + reflectResp.usage.completionTokens,
1243
+ totalTokens: aggUsage.totalTokens + reflectResp.usage.totalTokens
1244
+ };
1245
+ const verdictMatch = /VERDICT:\s*(PASS|FAIL)/i.exec(reflectResp.content);
1246
+ const pass = verdictMatch ? verdictMatch[1].toUpperCase() === "PASS" : true;
1247
+ reflectVerdict = {
1248
+ pass,
1249
+ reason: reflectResp.content.replace(/VERDICT:\s*(PASS|FAIL)/i, "").trim().slice(0, 300)
1250
+ };
1251
+ log({
1252
+ ev: "reflect",
1253
+ taskId: target.id,
1254
+ pass,
1255
+ escalateOnFail: brain.reflect.escalateOnFail,
1256
+ reason: reflectVerdict.reason.slice(0, 120)
1257
+ });
1258
+ } catch (err) {
1259
+ log({
1260
+ ev: "reflect-error",
1261
+ taskId: target.id,
1262
+ message: err instanceof Error ? err.message : String(err)
1263
+ });
1264
+ }
1265
+ }
1021
1266
  const cost = costGuard.recordUsage(identity.llmModel, aggUsage);
1022
1267
  log({
1023
1268
  ev: "executed",
@@ -1027,7 +1272,11 @@ var AgentRunner = class {
1027
1272
  tokens: aggUsage.totalTokens,
1028
1273
  tool_iters: iters
1029
1274
  });
1030
- const response = { ...lastResponse ?? { content: finalText, usage: aggUsage }, content: finalText, usage: aggUsage };
1275
+ const response = {
1276
+ ...lastResponse ?? { content: finalText, usage: aggUsage },
1277
+ content: finalText,
1278
+ usage: aggUsage
1279
+ };
1031
1280
  const execResult = {
1032
1281
  taskId: target.id,
1033
1282
  responseText: response.content,
@@ -1061,10 +1310,32 @@ var AgentRunner = class {
1061
1310
  });
1062
1311
  const posted = await mesh.postAuditRecords(identity.handle, [caelRecord]);
1063
1312
  this.prevCaelChain = caelRecord.fnv1a_chain;
1064
- log({ ev: "cael-posted", taskId: target.id, appended: posted.appended, rejected: posted.rejected });
1313
+ log({
1314
+ ev: "cael-posted",
1315
+ taskId: target.id,
1316
+ appended: posted.appended,
1317
+ rejected: posted.rejected
1318
+ });
1065
1319
  } catch (err) {
1066
1320
  log({ ev: "cael-post-error", message: err instanceof Error ? err.message : String(err) });
1067
1321
  }
1322
+ if (reflectVerdict && !reflectVerdict.pass && brain.reflect?.escalateOnFail) {
1323
+ try {
1324
+ await mesh.sendMessageOnTask(
1325
+ target.id,
1326
+ `[${identity.handle}] reflect gate FAILED \u2014 escalating to the fleet instead of marking done. Reason: ${reflectVerdict.reason}`
1327
+ );
1328
+ } catch {
1329
+ }
1330
+ log({ ev: "reflect-escalate", taskId: target.id, reason: reflectVerdict.reason.slice(0, 120) });
1331
+ return {
1332
+ action: "reflect-escalate",
1333
+ taskId: target.id,
1334
+ spentUsd: costGuard.getState().spentUsd,
1335
+ remainingUsd: costGuard.getRemainingUsd(),
1336
+ message: `reflect self-evaluation failed; escalated to fleet (reason: ${reflectVerdict.reason.slice(0, 120)})`
1337
+ };
1338
+ }
1068
1339
  if (this.opts.onTaskExecuted) {
1069
1340
  await this.opts.onTaskExecuted(execResult, target);
1070
1341
  } else {
@@ -1079,7 +1350,11 @@ ${response.content}`
1079
1350
  await mesh.markDone(target.id, finalText.slice(0, 500), lastCommitHash);
1080
1351
  log({ ev: "mark-done", taskId: target.id, commitHash: lastCommitHash });
1081
1352
  } catch (err) {
1082
- log({ ev: "mark-done-error", taskId: target.id, message: err instanceof Error ? err.message : String(err) });
1353
+ log({
1354
+ ev: "mark-done-error",
1355
+ taskId: target.id,
1356
+ message: err instanceof Error ? err.message : String(err)
1357
+ });
1083
1358
  }
1084
1359
  return {
1085
1360
  action: "executed",
@@ -1173,7 +1448,7 @@ function buildTaskPrompt(task) {
1173
1448
  "Description:",
1174
1449
  task.description ?? "(no description)",
1175
1450
  "",
1176
- "Produce the deliverable described in the task. Apply your brain composition rules \u2014 anti-patterns, decision loop, and scope tier all bind. Return the response as plain text suitable for posting to /room as a message on this task."
1451
+ "Produce the deliverable: call write_file (or bash with a build command) to create all required output files FIRST. Apply your brain composition rules \u2014 anti-patterns, decision loop, and scope tier all bind. After calling the tool(s), return a short plain-text summary of what you did for posting to /room."
1177
1452
  ].join("\n");
1178
1453
  }
1179
1454
  function sleep(ms) {
@@ -1210,7 +1485,9 @@ function makeCommitHook(opts) {
1210
1485
  const relPath = relativeTo(cwd, filePath);
1211
1486
  const addRes = spawn2("git", ["add", relPath], { cwd, encoding: "utf8" });
1212
1487
  if (addRes.status !== 0) {
1213
- throw new Error(`git add failed: ${addRes.stderr || addRes.stdout || `exit ${addRes.status}`}`);
1488
+ throw new Error(
1489
+ `git add failed: ${addRes.stderr || addRes.stdout || `exit ${addRes.status}`}`
1490
+ );
1214
1491
  }
1215
1492
  const message = renderCommitMessage({ scope, task, identity, result });
1216
1493
  const commitArgs = ["commit", "-m", message];
@@ -1219,7 +1496,9 @@ function makeCommitHook(opts) {
1219
1496
  }
1220
1497
  const commitRes = spawn2("git", commitArgs, { cwd, encoding: "utf8" });
1221
1498
  if (commitRes.status !== 0) {
1222
- throw new Error(`git commit failed: ${commitRes.stderr || commitRes.stdout || `exit ${commitRes.status}`}`);
1499
+ throw new Error(
1500
+ `git commit failed: ${commitRes.stderr || commitRes.stdout || `exit ${commitRes.status}`}`
1501
+ );
1223
1502
  }
1224
1503
  const hashRes = spawn2("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" });
1225
1504
  const commitHash = hashRes.status === 0 ? hashRes.stdout.trim() : void 0;
@@ -1442,7 +1721,10 @@ USR:${user}`).digest("hex").slice(0, 16);
1442
1721
  }
1443
1722
  function withTimeout(p, ms, label) {
1444
1723
  return new Promise((resolve4, reject) => {
1445
- const timer = setTimeout(() => reject(new Error(`ablation cell "${label}" timed out after ${ms}ms`)), ms);
1724
+ const timer = setTimeout(
1725
+ () => reject(new Error(`ablation cell "${label}" timed out after ${ms}ms`)),
1726
+ ms
1727
+ );
1446
1728
  p.then(
1447
1729
  (v) => {
1448
1730
  clearTimeout(timer);
@@ -1700,7 +1982,9 @@ var Supervisor = class {
1700
1982
  }
1701
1983
  const wallet = process.env[spec.walletEnvKey];
1702
1984
  if (!wallet || !/^0x[0-9a-fA-F]{40}$/.test(wallet)) {
1703
- throw new Error(`Missing or malformed wallet env var "${spec.walletEnvKey}" for agent "${spec.handle}"`);
1985
+ throw new Error(
1986
+ `Missing or malformed wallet env var "${spec.walletEnvKey}" for agent "${spec.handle}"`
1987
+ );
1704
1988
  }
1705
1989
  return {
1706
1990
  handle: spec.handle,
@@ -1829,7 +2113,9 @@ function validateAgent(entry, idx, seen) {
1829
2113
  }
1830
2114
  const budgetUsdPerDay = optionalNumber(entry, "budgetUsdPerDay");
1831
2115
  if (budgetUsdPerDay != null && budgetUsdPerDay < 0) {
1832
- throw new Error(`agents[${idx}].budgetUsdPerDay must be >= 0 (0 = unlimited), got ${budgetUsdPerDay}`);
2116
+ throw new Error(
2117
+ `agents[${idx}].budgetUsdPerDay must be >= 0 (0 = unlimited), got ${budgetUsdPerDay}`
2118
+ );
1833
2119
  }
1834
2120
  const tickIntervalMs = optionalNumber(entry, "tickIntervalMs");
1835
2121
  if (tickIntervalMs != null && tickIntervalMs < 5e3) {
@@ -1898,9 +2184,14 @@ async function provisionAgent(req, opts = { execute: false }) {
1898
2184
  throw new Error(`handle "${req.handle}" must match ${HANDLE_PATTERN2}`);
1899
2185
  }
1900
2186
  if (!req.founderBearer || req.founderBearer.trim().length === 0) {
1901
- throw new Error("founderBearer is required (HOLOMESH_API_KEY of an agent that can call /register)");
2187
+ throw new Error(
2188
+ "founderBearer is required (HOLOMESH_API_KEY of an agent that can call /register)"
2189
+ );
1902
2190
  }
1903
- const meshApiBase = (req.meshApiBase ?? "https://mcp.holoscript.net/api/holomesh").replace(/\/$/, "");
2191
+ const meshApiBase = (req.meshApiBase ?? "https://mcp.holoscript.net/api/holomesh").replace(
2192
+ /\/$/,
2193
+ ""
2194
+ );
1904
2195
  const seatsRoot = req.seatsRoot ?? defaultSeatsRoot();
1905
2196
  const surface = req.handle;
1906
2197
  const seatId = makeSeatId(surface);
@@ -1915,10 +2206,7 @@ async function provisionAgent(req, opts = { execute: false }) {
1915
2206
  seatId,
1916
2207
  seatDir,
1917
2208
  willGenerateWallet: !existsSync3(walletPath),
1918
- willCallEndpoints: [
1919
- `POST ${meshApiBase}/register/challenge`,
1920
- `POST ${meshApiBase}/register`
1921
- ]
2209
+ willCallEndpoints: [`POST ${meshApiBase}/register/challenge`, `POST ${meshApiBase}/register`]
1922
2210
  };
1923
2211
  }
1924
2212
  if (existsSync3(walletPath) && !opts.force) {
@@ -1961,30 +2249,40 @@ async function provisionAgent(req, opts = { execute: false }) {
1961
2249
  if (!challenge.nonce) {
1962
2250
  throw new Error(`/register/challenge returned no nonce: ${JSON.stringify(challenge)}`);
1963
2251
  }
1964
- const signature = await wallet.signTypedData(EIP712_DOMAIN, EIP712_TYPES, { nonce: challenge.nonce });
1965
- const registration = await postJson(
1966
- fetchImpl,
1967
- `${meshApiBase}/register`,
1968
- req.founderBearer,
1969
- {
1970
- name: req.handle,
1971
- wallet_address: wallet.address,
1972
- nonce: challenge.nonce,
1973
- signature
1974
- }
1975
- );
2252
+ const signature = await wallet.signTypedData(EIP712_DOMAIN, EIP712_TYPES, {
2253
+ nonce: challenge.nonce
2254
+ });
2255
+ const registration = await postJson(fetchImpl, `${meshApiBase}/register`, req.founderBearer, {
2256
+ name: req.handle,
2257
+ wallet_address: wallet.address,
2258
+ nonce: challenge.nonce,
2259
+ signature
2260
+ });
1976
2261
  writeFileSync3(
1977
2262
  regPath,
1978
- JSON.stringify({ status: 201, response: registration, registered_at: (/* @__PURE__ */ new Date()).toISOString(), flow: "x402" }, null, 2),
2263
+ JSON.stringify(
2264
+ {
2265
+ status: 201,
2266
+ response: registration,
2267
+ registered_at: (/* @__PURE__ */ new Date()).toISOString(),
2268
+ flow: "x402"
2269
+ },
2270
+ null,
2271
+ 2
2272
+ ),
1979
2273
  "utf8"
1980
2274
  );
1981
2275
  const agentId = registration.agent?.id;
1982
2276
  const bearer = registration.agent?.api_key;
1983
2277
  if (!agentId || !bearer) {
1984
- throw new Error(`/register did not return agent.id + agent.api_key: ${JSON.stringify(registration).slice(0, 400)}`);
2278
+ throw new Error(
2279
+ `/register did not return agent.id + agent.api_key: ${JSON.stringify(registration).slice(0, 400)}`
2280
+ );
1985
2281
  }
1986
2282
  if (registration.wallet?.private_key) {
1987
- console.warn("[provision] WARN \u2014 server returned private_key despite x402 flow; ignoring (using local key).");
2283
+ console.warn(
2284
+ "[provision] WARN \u2014 server returned private_key despite x402 flow; ignoring (using local key)."
2285
+ );
1988
2286
  }
1989
2287
  let joinedTeam;
1990
2288
  if (req.autoJoinTeamId) {
@@ -2044,7 +2342,12 @@ function encryptPrivateKey(privKey, masterKey) {
2044
2342
  const iv = randomBytes(12);
2045
2343
  const cipher = createCipheriv("aes-256-gcm", masterKey, iv);
2046
2344
  const ct = Buffer.concat([cipher.update(privKey, "utf8"), cipher.final()]);
2047
- return { iv: iv.toString("base64"), ct: ct.toString("base64"), tag: cipher.getAuthTag().toString("base64"), alg: "aes-256-gcm" };
2345
+ return {
2346
+ iv: iv.toString("base64"),
2347
+ ct: ct.toString("base64"),
2348
+ tag: cipher.getAuthTag().toString("base64"),
2349
+ alg: "aes-256-gcm"
2350
+ };
2048
2351
  }
2049
2352
  async function postJson(fetchImpl, url, bearer, body) {
2050
2353
  const res = await fetchImpl(url, {
@@ -2145,7 +2448,8 @@ async function cmdRun(opts) {
2145
2448
  const mesh = new HolomeshClient({
2146
2449
  apiBase: identity.meshApiBase,
2147
2450
  bearer: identity.x402Bearer,
2148
- teamId: identity.teamId
2451
+ teamId: identity.teamId,
2452
+ signer: buildRequestSigner(identity.handle)
2149
2453
  });
2150
2454
  const commitHook = buildCommitHook(identity, mesh);
2151
2455
  const auditLog = buildAuditLog();
@@ -2198,8 +2502,13 @@ function supervisorProviderFactory() {
2198
2502
  case "local-llm":
2199
2503
  return createLocalLLMProvider({
2200
2504
  baseURL: process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_BASE_URL,
2201
- model: spec.model
2505
+ model: spec.model,
2506
+ timeoutMs: process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS ? Number(process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS) : 3e5
2202
2507
  });
2508
+ case "sovereign":
2509
+ return resolveSovereignProviderAsync(spec.model ? { model: spec.model } : {}).then(
2510
+ (r) => r.provider
2511
+ );
2203
2512
  case "mock":
2204
2513
  return createMockProvider();
2205
2514
  default:
@@ -2418,8 +2727,13 @@ async function buildProvider(identity) {
2418
2727
  case "local-llm":
2419
2728
  return createLocalLLMProvider({
2420
2729
  baseURL: process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_BASE_URL,
2421
- model: identity.llmModel
2730
+ model: process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_MODEL ?? identity.llmModel,
2731
+ // Edge devices (Jetson ~15 tok/s) need more than the 120s default.
2732
+ // HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS overrides; default 300s.
2733
+ timeoutMs: process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS ? Number(process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS) : 3e5
2422
2734
  });
2735
+ case "sovereign":
2736
+ return (await resolveSovereignProviderAsync(identity.llmModel ? { model: identity.llmModel } : {})).provider;
2423
2737
  default:
2424
2738
  throw new Error(`Provider "${p}" not yet wired in CLI \u2014 add a case in buildProvider.`);
2425
2739
  }
@@ -2442,6 +2756,41 @@ function buildCommitHook(identity, mesh) {
2442
2756
  }
2443
2757
  };
2444
2758
  }
2759
+ function canonicalizeSigning(value) {
2760
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
2761
+ if (Array.isArray(value))
2762
+ return `[${value.map(canonicalizeSigning).join(",")}]`;
2763
+ const obj = value;
2764
+ return `{${Object.keys(obj).sort().map((k) => `${JSON.stringify(k)}:${canonicalizeSigning(obj[k])}`).join(",")}}`;
2765
+ }
2766
+ function buildRequestSigner(handle) {
2767
+ const seatsRoot = process.env.HOLOSCRIPT_AGENT_SEATS_ROOT ?? join4(homedir3(), ".holoscript-agent", "seats");
2768
+ const fp = createHash4("sha256").update(hostname2() + homedir3()).digest("hex").slice(0, 8);
2769
+ const seatId = `holoscript-${handle}-${fp}-x402`;
2770
+ const walletPath = join4(seatsRoot, seatId, "wallet.enc");
2771
+ const masterKeyPath = join4(seatsRoot, ".master-key");
2772
+ if (!existsSync4(walletPath) || !existsSync4(masterKeyPath)) return void 0;
2773
+ try {
2774
+ const blob = JSON.parse(readFileSync5(walletPath, "utf8"));
2775
+ const masterKey = readFileSync5(masterKeyPath);
2776
+ const iv = Buffer.from(blob.encrypted_privkey.iv, "base64");
2777
+ const ct = Buffer.from(blob.encrypted_privkey.ct, "base64");
2778
+ const tag = Buffer.from(blob.encrypted_privkey.tag, "base64");
2779
+ const decipher = createDecipheriv(blob.encrypted_privkey.alg ?? "aes-256-gcm", masterKey, iv);
2780
+ decipher.setAuthTag(tag);
2781
+ const privateKey = Buffer.concat([decipher.update(ct), decipher.final()]).toString("utf8");
2782
+ const wallet = new Wallet2(privateKey);
2783
+ return async (body) => {
2784
+ const nonce = randomBytes2(16).toString("hex");
2785
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2786
+ const payload = canonicalizeSigning({ body, nonce, timestamp });
2787
+ const signature = await wallet.signMessage(payload);
2788
+ return { body, signature, signer_address: blob.address, nonce, timestamp };
2789
+ };
2790
+ } catch {
2791
+ return void 0;
2792
+ }
2793
+ }
2445
2794
  function scopeTierFromEnv() {
2446
2795
  const t = (process.env.HOLOSCRIPT_AGENT_SCOPE_TIER ?? "warm").toLowerCase();
2447
2796
  if (t === "cold" || t === "warm" || t === "hot") return t;
@@ -2485,8 +2834,8 @@ USAGE
2485
2834
 
2486
2835
  REQUIRED ENV
2487
2836
  HOLOSCRIPT_AGENT_HANDLE agent handle (e.g. "security-auditor")
2488
- HOLOSCRIPT_AGENT_PROVIDER anthropic | openai | gemini | local-llm | mock
2489
- HOLOSCRIPT_AGENT_MODEL model id (e.g. "claude-opus-4-7")
2837
+ HOLOSCRIPT_AGENT_PROVIDER anthropic | openai | gemini | xai | openrouter | local-llm | sovereign | mock
2838
+ HOLOSCRIPT_AGENT_MODEL model id (e.g. "claude-opus-4-8")
2490
2839
  HOLOSCRIPT_AGENT_BRAIN path to .hsplus brain composition
2491
2840
  HOLOSCRIPT_AGENT_WALLET 0x\u2026 wallet address
2492
2841
  HOLOSCRIPT_AGENT_X402_BEARER per-surface mesh bearer (W.087 vertex B)
@@ -2505,6 +2854,8 @@ OPTIONAL ENV
2505
2854
  HOLOSCRIPT_AGENT_WORKING_DIR git repo to commit into (default process.cwd())
2506
2855
  HOLOSCRIPT_AGENT_COMMIT_SCOPE commit subject scope (default "agent(<handle>)")
2507
2856
  HOLOSCRIPT_AGENT_LOCAL_LLM_BASE_URL local-llm provider base URL (default http://localhost:8080)
2857
+ HOLOSCRIPT_AGENT_LOCAL_LLM_MODEL local-llm model id (e.g. "qwen3:4b-instruct"); overrides HOLOSCRIPT_AGENT_MODEL for the local provider
2858
+ HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS local-llm request timeout in ms (default 300000 \u2014 edge devices like Jetson need >120s)
2508
2859
  `);
2509
2860
  }
2510
2861
  main().catch((err) => {