Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
124 commits
Select commit Hold shift + click to select a range
e3400f0
fix: preserve effective codex sandbox denials
apresmoi Sep 13, 2026
43fe48c
fix: validate and bound credential reads from file descriptors
apresmoi Sep 13, 2026
551f596
feat: pin Grok CLI 1.0.34 lean worker contract and single config rend…
apresmoi Sep 17, 2026
18b9d48
feat: refuse Grok broker requests outside the lean tool set and decla…
apresmoi Sep 17, 2026
8dbd8c0
feat: attest Grok 1.0.34 sandbox events, deny lists and read-only wor…
apresmoi Sep 17, 2026
dcfdc8a
fix: register direct Grok MCP endpoints in the Daimon-owned GROK_HOME…
apresmoi Sep 17, 2026
7529ef9
feat: compile the lean Grok worker argv and system prompt into the na…
apresmoi Sep 17, 2026
4bbf224
build: rebuild native engine broker artifacts for the lean Grok worke…
apresmoi Sep 17, 2026
9761d8d
fix: keep the sealed prompt fd open across the Grok worker exec
apresmoi Sep 17, 2026
883111c
build: rebuild native engine broker artifacts with the prompt fd fix
apresmoi Sep 17, 2026
f41774e
fix: pass the Grok provider capability through env_key because 1.0.34…
apresmoi Sep 17, 2026
7f5b62b
build: rebuild native engine broker artifacts with the provider capab…
apresmoi Sep 17, 2026
4bd43d3
docs: document the Grok 1.0.34 lean worker contract and worker home l…
apresmoi Sep 17, 2026
0e09c75
docs: point the worker argv mirror comment at launcherArgv.test.ts
apresmoi Sep 17, 2026
596a87d
test: give Grok direct-session tests a GROK_HOME and unpool proxy rej…
apresmoi Sep 17, 2026
18c2653
fix: escape control characters in the sandbox deny-path regex and for…
apresmoi Sep 17, 2026
838b507
fix: forward the validated Grok request object and refuse unknown top…
apresmoi Sep 17, 2026
5fe9374
fix: send the Grok session-title request to the broker proxy where it…
apresmoi Sep 17, 2026
08ceda6
fix: give Grok workers /dev/null stdin and keep the executable fd out…
apresmoi Sep 17, 2026
cd33c5f
build: rebuild native engine broker artifacts with the worker fd hard…
apresmoi Sep 17, 2026
0878c97
test: cover capability expiry on the proxy token lookups
apresmoi Sep 17, 2026
b1823ec
fix: lock each Grok turn to the ProfileApplied event accepted before …
apresmoi Sep 17, 2026
fdbc7a0
test: cover worker home, hard link and read stability checks in Grok …
apresmoi Sep 17, 2026
a78a993
test: prove the Grok isolation guard gates the first upstream call of…
apresmoi Sep 17, 2026
ad67499
fix: require the opened Grok worker config to be the inode attested b…
apresmoi Sep 17, 2026
f4fe7f3
fix: refuse __proto__ members and extra tool members in Grok broker r…
apresmoi Sep 17, 2026
b55f678
test: prove a Grok worker that fails at exec runs nothing
apresmoi Sep 17, 2026
85bb357
test: drop unused imports from Grok attestation tests
apresmoi Sep 17, 2026
9957160
feat: add engine-neutral wake limit env names with Codex aliases
apresmoi Sep 17, 2026
7e28542
feat: record per-request timestamps, Grok stream usage and turn-keyed…
apresmoi Sep 17, 2026
2bd8ded
feat: seal Grok broker usage in turn record v2, control protocol v2 a…
apresmoi Sep 17, 2026
65d0144
feat: pass wake limits and declared model to the Grok broker from the…
apresmoi Sep 17, 2026
ae8dd17
feat: accept closed-list Grok model and reasoning effort and pin the …
apresmoi Sep 17, 2026
48e9b8b
feat: add the public Grok broker projection
apresmoi Sep 17, 2026
b5be62c
feat: define the Grok slot preflight receipt schema with fixtures and…
apresmoi Sep 17, 2026
55852f3
docs: describe Grok broker limits, sealed accounting, projection and …
apresmoi Sep 17, 2026
50a9325
test: prove Grok per-request rows carry measured proxy intervals, not…
apresmoi Sep 17, 2026
39deffa
fix: count a killed turn's in-flight request in every Grok per-reques…
apresmoi Sep 17, 2026
cb1971f
test: keep Grok engine declaration tests in their own file under the …
apresmoi Sep 17, 2026
a77a6d4
fix: allow one in-flight upstream request per Grok turn and abort it …
apresmoi Sep 17, 2026
8932725
fix: bound per-request Grok usage and charge an estimate when a respo…
apresmoi Sep 17, 2026
9e49e92
fix: seal Grok turn ledger bytes in the turn record and complete an i…
apresmoi Sep 17, 2026
f1f95f0
fix: never re-seal a completed Grok turn when metering after the seal…
apresmoi Sep 17, 2026
c9dbf3c
test: refuse a slot preflight receipt carrying a canary the projectio…
apresmoi Sep 17, 2026
a5fd91b
feat: bind slot preflight receipts to the projected seccomp profile a…
apresmoi Sep 17, 2026
3a1fac8
docs: record the Grok in-flight gate, usage estimates, sealed ledger …
apresmoi Sep 17, 2026
97784b4
fix: treat a Grok turn record as sealed once renamed and report a fai…
apresmoi Sep 17, 2026
3ab4e4a
test: prove concurrent replays of one Grok turn are counted once and …
apresmoi Sep 17, 2026
3174716
fix: charge the estimate when a Grok response's final usage block is …
apresmoi Sep 17, 2026
3dc64e5
feat: add evaluator inference grants as a distinct metered proxy kind…
apresmoi Sep 17, 2026
5834da1
feat: serve inference grant verbs over the control socket and meter g…
apresmoi Sep 17, 2026
90eab61
feat: add the pinned evaluator Grok client config renderer for infere…
apresmoi Sep 17, 2026
8b38727
feat: export the control client grant API for evaluator consumers
apresmoi Sep 17, 2026
6114909
test: prove a grant id equal to a live turn id leaves the turn capabi…
apresmoi Sep 17, 2026
a45a3dc
fix: render max_retries = 0 for the evaluator Grok client so a refuse…
apresmoi Sep 17, 2026
dd175ef
feat: bind the Grok slot preflight receipt to a recycle nonce and slo…
apresmoi Sep 17, 2026
a9e1f68
docs: describe the v2 slot preflight receipt freshness in the engines…
apresmoi Sep 17, 2026
148f3a9
docs: document evaluator inference grants, the captured judge request…
apresmoi Sep 17, 2026
024e102
feat: give each Grok worker a private TMPDIR under its registered home
apresmoi Sep 17, 2026
aa1aef0
feat: refuse Grok turns unless shared temp is closed to workers and t…
apresmoi Sep 17, 2026
faae4cd
build: rebuild native engine broker artifacts with the worker TMPDIR
apresmoi Sep 17, 2026
8d24c50
fix: write tool-output spills group-readable so the agent's own Grok …
apresmoi Sep 17, 2026
45d9e41
docs: document the Grok worker temp and spill provisioning contract
apresmoi Sep 17, 2026
7439edb
fix: attest every registered Grok worker's private temp and close the…
apresmoi Sep 17, 2026
a2ba04b
fix: pin the tool-output spill directory and publish spills by rename…
apresmoi Sep 17, 2026
4aef65c
fix: refuse non-canonical Grok worker registration paths and truncate…
apresmoi Sep 17, 2026
4c761c8
build: rebuild native engine broker artifacts with canonical registra…
apresmoi Sep 17, 2026
4e874ad
docs: record sibling temp attestation, spill directory pinning and ca…
apresmoi Sep 17, 2026
92fb1ca
fix: refuse Grok worker deny paths bubblewrap cannot materialize as t…
apresmoi Sep 17, 2026
8c7e309
fix: let the Grok broker projection mask the wake-acceptance store th…
apresmoi Sep 17, 2026
089c3f8
fix: accept the contracted traverse-only runtime home for brokered Gr…
apresmoi Sep 17, 2026
277ea26
fix: create every runtime-home directory private so a traversable Gro…
apresmoi Sep 17, 2026
bcd2f8e
docs: state the traverse-only Grok runtime home rule and the private …
apresmoi Sep 17, 2026
d485784
fix: name the mounted tools in the agent identity envelope
apresmoi Sep 17, 2026
0c16fc5
build: stage the packaged Linux engine broker on every packing host
apresmoi Sep 17, 2026
1af01fd
fix: refuse broker proxy policy misses non-retryably with a named reason
apresmoi Sep 17, 2026
7b9a92c
fix: keep the Grok session-title sink on its transient refusal shape
apresmoi Sep 17, 2026
1a6ee5d
test: keep the grant-path title sink refusal transient
apresmoi Sep 17, 2026
b54f66f
fix: lead an inbox turn with each delivery's own text and keep the ac…
apresmoi Sep 17, 2026
8496d44
fix: name a failed brokered worker's own reason instead of its exit code
apresmoi Sep 17, 2026
bac2d12
fix: stop naming a healthy turn's own two proxy requests as refusals
apresmoi Sep 17, 2026
081b3ce
test: pin the no-active-turn refusal to its non-retryable shape
apresmoi Sep 17, 2026
37363c3
fix: state the Grok use_tool prefix rule once, from the worker contra…
apresmoi Sep 17, 2026
cb7a745
fix: name the prefixed Grok tool form as the only valid one and add n…
apresmoi Sep 17, 2026
1c74e71
feat: record each brokered request's tool-call names in the per-reque…
apresmoi Sep 17, 2026
d8a67dc
test: split the identity-envelope tests into their own file under the…
apresmoi Sep 17, 2026
964dda3
fix: prefix the inbox prompt's oversized-payload branch for Grok too
apresmoi Sep 17, 2026
23b28e9
fix: forward the MCP session and protocol headers and the GET/DELETE …
apresmoi Sep 17, 2026
8d5c26c
docs: record the broker MCP facade's closed header allowlist and supp…
apresmoi Sep 17, 2026
a43d62c
fix: name the transport send tool the way Grok can call it
apresmoi Sep 17, 2026
9d773ae
test: share one facade across the broker MCP facade tests and cover r…
apresmoi Sep 17, 2026
73cc6b6
fix: name the underlying fault beside the proxy's broker_unavailable …
apresmoi Sep 17, 2026
16c1224
fix: name one level of a broker fault's own cause so a failed provide…
apresmoi Sep 17, 2026
f3d4b2b
fix: escape the flatten ranges and the test's control byte so no sour…
apresmoi Sep 17, 2026
6a4fa63
fix: wake the MCP facade's backpressure await on a hang-up or abort s…
apresmoi Sep 17, 2026
64a30bb
fix: refuse a fenced credential realm as a named non-retryable auth_s…
apresmoi Sep 17, 2026
8ac45f4
docs: keep the session-title sink's own paragraph intact in the runti…
apresmoi Sep 17, 2026
a509277
fix: let a usage decoder fault fall through to the estimate instead o…
apresmoi Sep 17, 2026
20ae57b
fix: decode a failed worker's last words as text and keep both ends o…
apresmoi Sep 17, 2026
3f063c6
fix: keep both ends of the launcher's diagnostic window and scrub cre…
apresmoi Sep 17, 2026
389d7a5
fix: give the brokered worker a blocking stdout pipe so a large write…
apresmoi Sep 18, 2026
4ae129f
test: cover a worker write four times its own pipe buffer in the nati…
apresmoi Sep 18, 2026
cadc637
test: seal the spend the proxy measured when a worker's output never …
apresmoi Sep 18, 2026
734056b
fix: fail a brokered worker's model request fast instead of retrying …
apresmoi Sep 18, 2026
d7e36db
fix: trip the launcher's total-output bound from the post-exit drain too
apresmoi Sep 18, 2026
128dd56
test: take the facade before any mount listens so a contended fixed p…
apresmoi Sep 18, 2026
2ef4589
fix: end a per-wake MCP mount's leftover connections so a finished tu…
apresmoi Sep 18, 2026
25f84ef
fix: end the broker provider proxy's leftover sockets on shutdown ins…
apresmoi Sep 18, 2026
6c33505
test: split the MCP tunnel drain test out of the facade suite
apresmoi Sep 18, 2026
c05306e
feat: seal the brokered worker's in-flight MCP tool calls so a hung c…
apresmoi Sep 18, 2026
1ec76f2
feat: seal a brokered turn's terminal evidence into the broker's ledg…
apresmoi Sep 18, 2026
0fe368a
test: prove a worker still blocked in write past the launcher's outpu…
apresmoi Sep 18, 2026
b096da6
fix: raise the launcher's whole-turn output bound to the control prot…
apresmoi Sep 18, 2026
942df75
feat: observe the brokered worker's standalone MCP GET tunnel
apresmoi Sep 18, 2026
edbbb99
docs: record the MCP GET tunnel observation and what the real CLI doe…
apresmoi Sep 18, 2026
1a5ba8d
docs: name the tunnel timings in the seal line's own bound
apresmoi Sep 18, 2026
130b548
test: split the facade's observation suite and its rig out of the fac…
apresmoi Sep 18, 2026
4f4922a
feat: count and seal the MCP facade's refused requests by reason class
apresmoi Sep 18, 2026
35c9bd5
test: pin the seal row's exact field set for a completed turn
apresmoi Sep 18, 2026
3c7e74f
test: type the sealed completed turn's native result frame
apresmoi Sep 18, 2026
074c75c
fix: assert and correct the mode of existing runtime-home subdirectories
apresmoi Sep 18, 2026
f81c55e
fix: ensure the agent tool-state directory through the runtime-home l…
apresmoi Sep 18, 2026
734372a
fix: derive the MCP capability budget from the compiled worker turn b…
apresmoi Sep 18, 2026
36ce9f0
fix: keep the v2 activity closure query answerable after a control ho…
apresmoi Sep 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ import { PiHarnessAdapter } from "@noopolis/daimon/pi";

## Organization-runtime contract

For a strict Codex agent in an already prepared runtime, the public
`resolveOrganizationCodexSandboxProjection(config, agentId, { acceptanceStorePath })`
API returns `noopolis.daimon.codex-sandbox-projection.v1`: canonical workspace/home
paths, the resolved executable, and `sandboxArgs` containing the exact permission
profile rendered for production. Append only `--` and a mechanical command when
checking whether that policy can execute in the caller's container.

The resolver opens and verifies caller-owned path identities and executes the
Codex version probe. It does not import/read authentication, create an agent,
accept a wake or invoke a model. The acceptance-store path must be the same one
given to the control host. This API projects command permissions, not the complete
cognition invocation: Codex's `sandbox` subcommand lacks the strict configuration
flags accepted by `exec`. A caller must therefore use a fresh empty `HOME` and
`CODEX_HOME` and a restricted environment for its mechanical probe. Do not infer
successful tool use or completed agent work from this projection alone.

`@noopolis/daimon/runtime` exports a standard JSON Schema for structural
validation plus the strict, side-effect-free semantic
`validateOrganizationRuntimeConfig` / `parseOrganizationRuntimeConfig` API and
Expand Down
32 changes: 32 additions & 0 deletions docs/engines.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,38 @@ the broker owns refresh and stale-credential recovery. The runtime checks broker
readiness before admitting Grok agents and verifies their sandbox policy before
turns. The older credential-lease helper is not the production host path.

The broker worker is pinned to Grok CLI 1.0.34 and runs lean: a fixed Daimon
system prompt, six tools (`run_terminal_command`, `read_file`, `grep`,
`list_dir`, and the MCP meta-tools `search_tool`/`use_tool`), no bundled
skills, workflows, plan mode, subagents, memory or web search, and a declared
model and reasoning effort from a closed list (default `grok-4.6` at `low`).
The broker proxy refuses any request outside that shape before it spends.

Each broker registration (`service.json` v2) declares its model and effort,
its usage ledger, and turn limits `{maxRequests, maxTokens, timeoutMs}`. A wake
may only lower them (`DAIMON_ENGINE_WAKE_TIMEOUT_MS`,
`DAIMON_ENGINE_WAKE_TOKEN_CEILING`; the `DAIMON_CODEX_WAKE_*` names are
aliases). The proxy refuses request `maxRequests + 1` and any request after the
deadline with HTTP 429 before upstream, and stops admitting requests once the
upstream-reported running total (cached input included) reaches `maxTokens`, so
a turn overshoots its token ceiling by at most one request. A tripped limit
kills the worker. The broker seals every terminal turn with its usage, request
count, declared model and limit reason, and writes one usage row (keyed by
`turn`) plus per-request rows for completed and failed turns alike; a replayed
turn is never metered twice. `resolveOrganizationGrokBrokerProjection` exposes
a slot's full declared shape, and `noopolis.daimon.grok-slot-preflight.v2`
receipts bind a slot's denied-path canaries to that projection's digest and to
one recycle (the caller's nonce and the slot's increasing generation).

Evaluators (Paideia judges and the optimizer, organization uid only) borrow the
same credential through inference grants: `request_inference_grant` over the
control socket returns a ten-minute token for one declared model and effort,
which the evaluator's Grok CLI presents to the provider proxy through
`env_key` in a config rendered by `renderGrokInferenceClientConfig`. Grant
requests must carry no tools, are metered like a turn, and are written only to
the broker's separate `inferenceLedgerPath` (`kind: "inference"` rows), never
to a subject usage ledger or the wake fuse.

AGY uses OS-native secure storage through one private D-Bus and Secret Service
realm. Enroll it once with:

Expand Down
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@
"@earendil-works/pi-coding-agent": "^0.79.10",
"@modelcontextprotocol/sdk": "^1.29.0",
"@noopolis/mneme": "^0.1.1",
"ajv": "^8.17.1"
"ajv": "^8.17.1",
"zod": "^4.4.3"
},
"devDependencies": {
"@types/node": "^24.12.4",
Expand Down
24 changes: 14 additions & 10 deletions scripts/liveGrokBrokerSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import { readChild } from "../src/pi/cliChildOutput.ts";
import { terminateChild, trackCliChild } from "../src/pi/cliProcess.ts";
import { decodeGrokHeadlessTurn } from "../src/pi/grokHeadlessResult.ts";
import { readGrokBrokerCredential } from "../src/runtime/grokBrokerCredentialReader.ts";
import { DEFAULT_GROK_BROKER_TURN_LIMITS } from "../src/runtime/engineBrokerTurnAccounting.ts";
import { startGrokBrokerProxy } from "../src/runtime/grokBrokerProxy.ts";
import { renderGrokBrokerWorkerConfig } from "../src/runtime/grokBrokerWorkerConfig.ts";
import { GrokBrokerTurnMeter } from "../src/runtime/grokBrokerTurnMeter.ts";
import { DEFAULT_GROK_BROKER_MODEL_POLICY } from "../src/runtime/grokBrokerModelPolicy.ts";
import { GROK_BROKER_PROVIDER_CAPABILITY_ENV, renderGrokBrokerWorkerArgs, renderGrokBrokerWorkerConfigWith } from "../src/runtime/grokBrokerWorkerConfig.ts";

// Explicit live auth/transport check, not the Linux native worker/isolation E2E.
// Read the operator credential only in this process; never stage or rotate it.
Expand Down Expand Up @@ -37,20 +40,21 @@ try {
const capability = proxy.capabilities.issue("local-auth-probe", turnId);
// This local transport probe deliberately does not attest a native worker.
proxy.registerIsolationGuard(turnId, async () => undefined);
const helper = path.join(home, "auth-helper");
await writeFile(helper, `#!/bin/sh\nprintf '{"access_token":"${capability}","expires_in":600}\\n'\n`, { mode: 0o700 });
// The proxy forwards nothing unmetered; the probe runs under the default v1 limits.
proxy.registerTurn(turnId, { policy: DEFAULT_GROK_BROKER_MODEL_POLICY, meter: new GrokBrokerTurnMeter(DEFAULT_GROK_BROKER_TURN_LIMITS) });
// No MCP tools are needed for this exact-reply authentication probe.
await writeFile(path.join(home, "config.toml"), renderGrokBrokerWorkerConfig(helper, proxy.port).split("[mcp_servers.daimon]")[0]);
await writeFile(path.join(home, "config.toml"), renderGrokBrokerWorkerConfigWith(DEFAULT_GROK_BROKER_MODEL_POLICY, { proxyPort: proxy.port, mcpUrl: "http://127.0.0.1:43124/mcp" }).split("[mcp_servers.daimon]")[0]);
const prompt = path.join(home, "prompt.txt");
await writeFile(prompt, `Reply exactly ${sentinel}. Do not use tools.`);
stage = `model turn ${round}`;
const child = trackCliChild(spawn("grok", [
"--sandbox", "strict", "--prompt-file", prompt, "--no-memory", "--no-subagents",
"--disable-web-search", "--max-turns", "1", "--permission-mode", "dontAsk",
"--model", "daimon-broker-grok", "--output-format", "streaming-messages-json",
], {
// The proxy only forwards the lean worker request shape (pinned client
// version, exact tool set, declared effort), so the probe uses the same
// argv as the native launcher with the built-in strict profile.
const args = [...renderGrokBrokerWorkerArgs(prompt, home)].map((value) => value === "daimon-strict" ? "strict" : value);
args[args.indexOf("--max-turns") + 1] = "1";
const child = trackCliChild(spawn("grok", args, {
cwd: home, detached: process.platform !== "win32",
env: { PATH: process.env.PATH, HOME: home, GROK_HOME: home, LANG: "C", LC_ALL: "C", TZ: "UTC" },
env: { PATH: process.env.PATH, HOME: home, GROK_HOME: home, LANG: "C", LC_ALL: "C", TZ: "UTC", [GROK_BROKER_PROVIDER_CAPABILITY_ENV]: capability },
stdio: ["ignore", "pipe", "pipe"],
}));
let output: string;
Expand Down
34 changes: 33 additions & 1 deletion scripts/scriptSourcePolicy.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
import { mkdir, mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import test from "node:test";
Expand Down Expand Up @@ -37,3 +37,35 @@ test("script source policy detects a maintained JavaScript regression", async ()
await rm(root, { force: true, recursive: true });
}
});

// Raw control bytes make review tooling classify a source file as binary and skip it.
// Escape them (`\u0000`) instead; tab, newline and carriage return are the only exceptions.
const RAW_CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u;
const textSourcesWithRawControlBytes = async (roots: string[]): Promise<string[]> => {
const results: string[] = [];
const walk = async (directory: string): Promise<void> => {
for (const entry of await readdir(directory, { withFileTypes: true })) {
const entryPath = path.join(directory, entry.name);
if (entry.isDirectory()) { if (entry.name !== "artifacts" && entry.name !== "node_modules") await walk(entryPath); continue; }
if (!/\.(?:ts|mts|mjs|js|json|jsonl|md|c|h|inc|toml|sh|yml|yaml)$/u.test(entry.name)) continue;
if (RAW_CONTROL.test(await readFile(entryPath, "latin1"))) results.push(entryPath);
}
};
await Promise.all(roots.map(walk));
return results.sort();
};

test("maintained text sources contain no raw control bytes", async () => {
assert.deepEqual(await textSourcesWithRawControlBytes(["src", "scripts", "docs"]), []);
});

test("raw control byte policy detects a regression", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "daimon-control-policy-"));
try {
await writeFile(path.join(root, "regex.ts"), `export const r = /[${String.fromCharCode(0)}-${String.fromCharCode(0x1f)}]/u;\n`);
await writeFile(path.join(root, "clean.ts"), "export const r = /[\\u0000-\\u001f]/u;\n");
assert.deepEqual(await textSourcesWithRawControlBytes([root]), [path.join(root, "regex.ts")]);
} finally {
await rm(root, { force: true, recursive: true });
}
});
95 changes: 95 additions & 0 deletions src/contracts/grokWorkerContract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* Fixed operating contract every broker-launched Grok worker receives through
* `--system-prompt-override`.
*
* The override replaces Grok's ~12k-token coding-agent system prompt and stops
* cwd `AGENTS.md` injection; the agent's identity and instructions still arrive
* in the prompt file. It is compiled into the native launcher byte-for-byte
* (`DBL_GROK_SYSTEM_PROMPT`), pinned by sha256 in the runtime contract
* manifest, and must stay ASCII without quotes or backslashes so the C literal
* needs no escaping.
*
* Daimon's per-wake tools reach Grok as deferred MCP tools named
* `daimon__<tool>` (server `[mcp_servers.daimon]`). Naming them lets `use_tool`
* run directly and saves one `search_tool` round trip per tool (P0: 3 → 2
* requests).
*/

/**
* The atoms of that route, and its single definition.
*
* Both texts a Grok worker receives are rendered from them: the pinned system
* prompt below, and the caller's identity envelope
* ({@link grokMountedToolNamingRule}, used by `src/runtime/engineDispatcher.ts`).
* They were worded independently once, and the envelope told the model to call
* the tools by their bare names - which Grok 1.0.34 refuses outright, before
* any HTTP: `'moltnet_read' is not a valid MCP tool name. Tool names must be
* qualified as \`server__tool\`` (local rig, real CLI, real rendered config).
* There is exactly one valid spelling, so two independently worded naming rules
* are one rule too many; this is the single definition both render from.
*/
export const DAIMON_GROK_MCP_SERVER = "daimon" as const;
/** Grok's own name for an MCP tool of that server: exactly what `tool_name` must carry. */
export const DAIMON_GROK_TOOL_PREFIX = `${DAIMON_GROK_MCP_SERVER}__` as const;
export const grokDaimonToolName = (tool: string): string => `${DAIMON_GROK_TOOL_PREFIX}${tool}`;
/** Grok's two MCP meta-tools, and the argument that names a tool for the first. */
export const GROK_MCP_INVOKE_TOOL = "use_tool" as const;
export const GROK_MCP_SEARCH_TOOL = "search_tool" as const;
export const GROK_MCP_TOOL_NAME_ARGUMENT = "tool_name" as const;
/** Illustrative Daimon tools for the system prompt, which cannot know a wake's real mount. */
const DAIMON_GROK_EXAMPLE_TOOLS = Object.freeze(["moltnet_read", "moltnet_send", "memory_search", "memory_register"] as const);

export const DAIMON_GROK_SYSTEM_PROMPT = [
"You are a headless Daimon agent; no human is present.",
"Your identity, instructions and wake event are in the user prompt.",
`Daimon tools are MCP tools on server ${DAIMON_GROK_MCP_SERVER}: call a known one directly with ${GROK_MCP_INVOKE_TOOL} (${GROK_MCP_TOOL_NAME_ARGUMENT} ${DAIMON_GROK_EXAMPLE_TOOLS.map(grokDaimonToolName).join(", ")}, or another ${DAIMON_GROK_TOOL_PREFIX} name you were given); use ${GROK_MCP_SEARCH_TOOL} only for a name you do not know.`,
"If a tool result says output was saved to a file, read that path with read_file.",
"If a tool fails, do not retry it in a loop: stop and report the failure.",
"Your final answer is a private note to the runtime: one line, or empty."
].join(" ");

/**
* The same route, stated once for the caller's identity envelope, where a
* wake's real mounted tools are known.
*
* It contributes exactly what the pinned prompt cannot know - the wake's real
* mounted names - and the one rule that makes them callable. It asserts rather
* than corrects: one bare catalogue, one prefix rule, one example, and the
* prefixed form named as the *only* valid form, because that is the CLI's own
* verdict on a bare name rather than a preference.
*
* What it deliberately leaves out is as load bearing. It never claims the
* agent's own instructions spell a tool wrongly, never offers a shell or CLI
* route, never repeats the catalogue in prefixed form - and never restates the
* `search_tool` rule. A Grok worker already reads two authoritative sentences
* about `search_tool`: the pinned prompt's ("only for a name you do not know")
* and Grok's own injected notice, which says the model MUST call it before any
* MCP tool. Observed on the rig: that contradiction is not enforced, and
* `use_tool` works with no prior `search_tool`. A third wording would only add
* a voice, so this sentence stays out of that argument entirely.
*/
export const grokMountedToolNamingRule = (mountedToolNames: readonly string[]): string => {
const example = grokDaimonToolName(mountedToolNames[0] ?? DAIMON_GROK_EXAMPLE_TOOLS[0]);
return `Your mounted tools are exactly: ${mountedToolNames.join(", ")}. `
+ `On this engine each is an MCP tool on server ${DAIMON_GROK_MCP_SERVER}, and its only valid tool name is `
+ `${DAIMON_GROK_TOOL_PREFIX}<name>: invoke it with ${GROK_MCP_INVOKE_TOOL}, ${GROK_MCP_TOOL_NAME_ARGUMENT} = ${example}. `
+ "A bare name is not a valid MCP tool name and reaches nothing.";
};

/** Closed declared-model vocabulary; defaults are `grok-4.6` at `low`. */
export const GROK_BROKER_MODELS = Object.freeze(["grok-4.6", "grok-4.5", "grok-build"] as const);
export const GROK_BROKER_REASONING_EFFORTS = Object.freeze(["low", "medium", "high"] as const);

/** `--tools` input ids. These are NOT the model-visible names (see below). */
export const GROK_WORKER_TOOL_IDS = Object.freeze(["run_terminal_cmd", "read_file", "grep", "list_dir", "search_tool", "use_tool"] as const);

/**
* The exact tool names a lean worker request body must carry.
*
* Grok 1.0.34 fails open on an unmappable `--tools` entry and ships all 19
* tools, so the proxy compares every upstream body against this set.
*/
export const GROK_WORKER_VISIBLE_TOOLS = Object.freeze(["grep", "list_dir", "read_file", "run_terminal_command", "search_tool", "use_tool"] as const);

/** `--max-turns` backstop compiled into the launcher; per-wake ceilings belong to the broker. */
export const GROK_WORKER_MAX_TURNS = 48 as const;
8 changes: 7 additions & 1 deletion src/contracts/organizationRuntimeContract.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { GROK_BROKER_MODELS, GROK_BROKER_REASONING_EFFORTS } from "./grokWorkerContract.js";

/** The data-only organization-runtime constants shared by product code and artifacts. */
export const ORGANIZATION_RUNTIME_VERSION = "noopolis.daimon.organization-runtime.v1" as const;
export const ORGANIZATION_RUNTIME_V2_VERSION = "noopolis.daimon.organization-runtime.v2" as const;
Expand Down Expand Up @@ -74,7 +76,11 @@ export const ORGANIZATION_RUNTIME_CONFIG_SCHEMA = {
codexSandbox: { type: "object", additionalProperties: false, required: ["mode", "networkAccess", "webSearch"], properties: {
mode: { const: "workspace-write" }, networkAccess: { const: false }, webSearch: { const: "disabled" }
} }
} },
}, allOf: [
// grok: a declared model is the closed broker pair, both or neither; never a Codex sandbox.
{ if: { properties: { kind: { const: "grok" } } }, then: { properties: { model: { enum: GROK_BROKER_MODELS }, reasoningEffort: { enum: GROK_BROKER_REASONING_EFFORTS }, codexSandbox: false }, dependentRequired: { model: ["reasoningEffort"], reasoningEffort: ["model"] } } },
{ if: { properties: { kind: { const: "agy" } } }, then: { properties: { model: false, reasoningEffort: false, codexSandbox: false } } }
] },
...PRODUCTION_TOOL_PROPERTIES
}
} }
Expand Down
Loading
Loading