Immutable Quest Engineering account-cell tool broker

Quest Engineering is a control plane for orchestrating persistent AI-agent work. This repository contains the authoritative Elixir control plane foundation and its first dependency-free semantic tactic compiler.
Quest Engineering Client
↓
Quest Engineering Control Plane (this repository)
↓
Quest Engineering Worker (separate Bun/TypeScript component)
↓
HarnessRegistry (semantic AgentHarness adapters)
├─ provider-neutral execution handles (headless is valid)
└─ optional InteractiveAgentHarness → TerminalSessionBackend (Herdr)
The control plane hosts Worker Protocol v18 and workers/bun is the filesystem authority. SBX is the accepted per-Run execution boundary for the additive Pi/Antigravity/Claude profile; each harness executes in the Run-owned environment with private Git and lineage-scoped native state. Claude Agent SDK is a headless, streamed-process harness with no Herdr dependency; see docs/claude-agent-sdk.md. Phase 5 acceptance and its independent teardown/evidence-hygiene dimensions are recorded in docs/phase-5-closure.md. Live session identity, local Herdr attachment, and operational HumanAttention are documented in docs/live-execution-sessions.md. Product Workspaces are logical and path-free; long-lived Workers discover explicitly authorized source repositories and provision one durable isolated Git worktree per Run before model execution. Workspace bindings reconcile independently when a Worker connects: a binding to a Product Workspace that no longer exists is retired without taking the Worker offline or deleting retained worktrees, dispatch state, or harness-native sessions. After successful Runtime completion, the Worker safely finalizes and pushes Git changes while Phoenix creates and centrally reconciles one same-repository GitHub Pull Request. Only an exact human-merged PR completes the Quest; publishing retry, explicit Run Again, and safe worktree cleanup remain separate operations. The Svelte/Pixi/Tauri client presents this truthful lifecycle as a dense Mini Medieval fantasy management town with deterministic Members, work orders, delivery markers, and accessible DOM management windows.
Planning Classes, immutable Markdown execution artifacts, exact-subject verdicts, and independent plan/code remediation gates are documented in docs/spec-driven-development.md.
Phase-A provider-neutral accounts and durable account-owned, Run-independent execution environments are documented in docs/provider-accounts.md. Their physical availability is explicitly separate from authentication and inference readiness.
quest_engineering_core — dependency-free Elixir boundary containing reusable Product Tactic authoring and pure expansion into semantic Step/Sequence/Parallel/Until tactics, deterministic compilation, and a pure runtime.quest_engineering_server — Phoenix/Ecto infrastructure boundary with Product Tactic persistence, immutable resolved launch snapshots, transactional runtime persistence, and the generation-fenced WebSocket Worker Protocol v18.Dependencies point inward only:
quest_engineering_server
↓
quest_engineering_core
quest_engineering_core must never depend on the server, Phoenix, Ecto, PostgreSQL, HTTP, process/filesystem APIs, Herdr, Pi, or Bun.
Versions are declared in .tool-versions:
Install Erlang and Elixir with your preferred version manager. With asdf:
asdf install
Start PostgreSQL:
docker compose up -d postgres
Install dependencies, create the development database, and run migrations:
mix deps.get
mix ecto.create
mix ecto.migrate
# Or perform all three steps at once:
mix setup
Additive migrations create Product definitions, logical Workspaces, durable binding attempts, Worker source bindings, Run workspace assignments, one-per-Run Deliveries, Quest completion metadata, runtime persistence, dispatches, and reconciliation records. See apps/quest_engineering_server/README.md and workers/bun/README.md for v4 provisioning, fencing, delivery, and restart recovery.
Run the umbrella tests. The test alias creates and migrates the test database automatically:
mix test
Prove the core compiles and tests independently:
cd apps/quest_engineering_core
mix test
cd ../..
Run the API server:
mix phx.server
The health endpoint is available at <http://localhost:4000/api/v1/health>. Workers connect to /worker/websocket with the configured static Worker token.
Run deterministic code-quality checks (format, warnings-as-errors compilation, unused dependency check, ExUnit, and Credo):
mix check
Run Dialyzer separately because initial PLT construction is comparatively slow:
mix dialyzer
Generate test coverage with ExCoveralls:
mix coveralls.htmlhooks/register.ts 528 lines1import type {
2 EngineInterface,
3 Next,
4 PluginRegisterInput,
5 Register,
6 TurnStepInput,
7} from "claude-code";
8
9const PROTOCOL = "qe_claude_account_cell_v1";
10const BRIDGE = "/opt/qe/claude/qe-account-cell-bridge";
11const MAX_BRIDGE_OUTPUT = 65_536;
12const MAX_OPERATION_INPUT = 1_048_576;
13
14const PREFIX = "mcp__qe-account-cell__";
15const TOOLS = {
16 read: `${PREFIX}qe_workspace_read`,
17 list: `${PREFIX}qe_workspace_list`,
18 write: `${PREFIX}qe_workspace_write`,
19 edit: `${PREFIX}qe_workspace_edit`,
20 search: `${PREFIX}qe_workspace_search`,
21 shell: `${PREFIX}qe_run_shell`,
22 complete: `${PREFIX}qe_complete_step`,
23 attention: `${PREFIX}qe_request_human_assistance_and_wait`,
24} as const;
25const ALLOWED_TOOLS = new Set<string>(Object.values(TOOLS));
26
27type JsonObject = Record<string, unknown>;
28
29function object(value: unknown, label: string): JsonObject {
30 if (typeof value !== "object" || value === null || Array.isArray(value))
31 throw new Error(`${label} must be an object.`);
32 return value as JsonObject;
33}
34
35function boundedString(value: unknown, label: string, maximum: number): string {
36 if (typeof value !== "string" || value.length === 0 || value.length > maximum)
37 throw new Error(`${label} is invalid.`);
38 return value;
39}
40
41function optionalInteger(
42 value: unknown,
43 label: string,
44 minimum: number,
45 maximum: number,
46): number | undefined {
47 if (value === undefined) return undefined;
48 if (
49 typeof value !== "number" ||
50 !Number.isSafeInteger(value) ||
51 value < minimum ||
52 value > maximum
53 )
54 throw new Error(`${label} is invalid.`);
55 return value;
56}
57
58function workspacePath(value: unknown, label: string): string {
59 const path = boundedString(value, label, 4_096);
60 if (
61 path.startsWith("/") ||
62 path.includes("\\") ||
63 path.includes("\0") ||
64 path.split("/").some((segment) => segment === "..")
65 )
66 throw new Error(`${label} must remain relative to the Run workspace.`);
67 return path;
68}
69
70function boundedJson(value: unknown, label: string, maximum: number): string {
71 const encoded = JSON.stringify(value);
72 if (encoded === undefined || encoded.length > maximum)
73 throw new Error(`${label} is invalid or too large.`);
74 return encoded;
75}
76
77function parseBridgeOutput(text: string): JsonObject {
78 if (text.length > MAX_BRIDGE_OUTPUT)
79 throw new Error("QE broker output exceeded the spike limit.");
80 return object(JSON.parse(text), "QE broker output");
81}
82
83function parseEffort(value: string): NonNullable<TurnStepInput["effort"]> {
84 switch (value) {
85 case "low":
86 case "medium":
87 case "high":
88 case "xhigh":
89 case "max":
90 return value;
91 default:
92 throw new Error("QE_CLAUDE_EFFORT is invalid.");
93 }
94}
95
96async function runBridge(
97 $: EngineInterface,
98 operation: JsonObject,
99): Promise<JsonObject> {
100 const input = boundedJson(
101 operation,
102 "QE broker operation",
103 MAX_OPERATION_INPUT,
104 );
105 const execution = await $.process.run([BRIDGE, "bound-operation"], {
106 stdin: input,
107 timeoutMs: 600_000,
108 });
109 if (
110 execution.exitCode !== 0 ||
111 execution.isStdoutTruncated ||
112 execution.isStderrTruncated
113 )
114 throw new Error(`QE broker failed with exit code ${execution.exitCode}.`);
115 return parseBridgeOutput(execution.stdout);
116}
117
118async function waitForHuman(
119 $: EngineInterface,
120 operation: JsonObject,
121): Promise<JsonObject> {
122 const input = boundedJson(
123 operation,
124 "QE HumanAttention operation",
125 MAX_OPERATION_INPUT,
126 );
127 const stream = $.process.spawn({
128 argv: [BRIDGE, "bound-human-attention-wait"],
129 input,
130 });
131 let stdout = "";
132 let stderr = "";
133 let item = await stream.next();
134 while (item.done !== true) {
135 if (item.value.stream === "stdout") stdout += item.value.text;
136 else stderr += item.value.text;
137 if (stdout.length + stderr.length > MAX_BRIDGE_OUTPUT) {
138 await stream.return({ code: null, signal: "SIGTERM" });
139 throw new Error("QE HumanAttention output exceeded the spike limit.");
140 }
141 item = await stream.next();
142 }
143 if (item.value.code !== 0 || item.value.signal !== null)
144 throw new Error("QE HumanAttention bridge did not exit successfully.");
145 return parseBridgeOutput(stdout);
146}
147
148async function checkPlugin(
149 _$: EngineInterface,
150 event: PluginRegisterInput,
151 next: Next<"plugin.register">,
152) {
153 if (event.tier === "user")
154 return {
155 refuse:
156 "QE account-cell policy permits only immutable organization and built-in Mods.",
157 } as const;
158 return next(event);
159}
160
161function brokerOperation(tool: string, event: JsonObject): JsonObject {
162 switch (tool) {
163 case TOOLS.read:
164 return {
165 type: "workspace_read",
166 path: workspacePath(event.path, "path"),
167 ...(optionalInteger(event.offset, "offset", 0, 10_000_000) === undefined
168 ? {}
169 : { offset: event.offset }),
170 ...(optionalInteger(event.limit, "limit", 1, 100_000) === undefined
171 ? {}
172 : { limit: event.limit }),
173 };
174 case TOOLS.list:
175 return {
176 type: "workspace_list",
177 path: workspacePath(event.path ?? ".", "path"),
178 };
179 case TOOLS.write:
180 return {
181 type: "workspace_write",
182 path: workspacePath(event.path, "path"),
183 content: boundedString(event.content, "content", 262_144),
184 };
185 case TOOLS.edit:
186 return {
187 type: "workspace_edit",
188 path: workspacePath(event.path, "path"),
189 oldText: boundedString(event.oldText, "oldText", 262_144),
190 newText:
191 typeof event.newText === "string" && event.newText.length <= 262_144
192 ? event.newText
193 : (() => {
194 throw new Error("newText is invalid.");
195 })(),
196 expectedReplacements:
197 optionalInteger(
198 event.expectedReplacements,
199 "expectedReplacements",
200 1,
201 1_000,
202 ) ?? 1,
203 };
204 case TOOLS.search:
205 return {
206 type: "workspace_search",
207 query: boundedString(event.query, "query", 4_096),
208 path: workspacePath(event.path ?? ".", "path"),
209 ...(optionalInteger(event.maxResults, "maxResults", 1, 1_000) ===
210 undefined
211 ? {}
212 : { maxResults: event.maxResults }),
213 };
214 case TOOLS.shell:
215 return {
216 type: "run_shell",
217 command: boundedString(event.command, "command", 32_768),
218 timeoutMs:
219 optionalInteger(event.timeoutMs, "timeoutMs", 100, 30_000) ?? 30_000,
220 };
221 case TOOLS.complete:
222 return {
223 type: "complete_step",
224 outputs: object(event.outputs, "outputs"),
225 };
226 default:
227 throw new Error("The tool is not a synchronous QE broker operation.");
228 }
229}
230
231export const register: Register = (on) => {
232 let ready = false;
233 let expectedModel = "";
234 let expectedEffort: NonNullable<TurnStepInput["effort"]> | "" = "";
235 let nativeSessionId = "";
236
237 on("plugin.register", checkPlugin).catch(async (_$, event, next) => {
238 if (event.tier === "user")
239 return {
240 refuse: "QE failed closed while checking an unapproved Mod.",
241 };
242 return next(event);
243 });
244
245 on("session.start", async ($, event, next) => {
246 ready = false;
247 const protocol = await $.env.get("QE_ACCOUNT_CELL_MOD_PROTOCOL");
248 expectedModel = (await $.env.get("QE_CLAUDE_MODEL")) ?? "";
249 expectedEffort = parseEffort((await $.env.get("QE_CLAUDE_EFFORT")) ?? "");
250 nativeSessionId = await $.session.id();
251 if (
252 protocol !== PROTOCOL ||
253 expectedModel.length === 0 ||
254 nativeSessionId.length === 0
255 )
256 throw new Error("QE account-cell Mod environment is incomplete.");
257
258 await $.tool.register({
259 name: "qe_workspace_read",
260 description:
261 "Read a UTF-8 file from the pre-bound Quest Engineering Run workspace. Paths are relative to that workspace; this tool cannot address the Claude account cell.",
262 inputSchema: {
263 type: "object",
264 properties: {
265 path: { type: "string", minLength: 1, maxLength: 4_096 },
266 offset: { type: "integer", minimum: 0, maximum: 10_000_000 },
267 limit: { type: "integer", minimum: 1, maximum: 100_000 },
268 },
269 required: ["path"],
270 additionalProperties: false,
271 },
272 });
273 await $.tool.register({
274 name: "qe_workspace_list",
275 description:
276 "List a directory in the pre-bound Quest Engineering Run workspace. The account-cell filesystem is not in this tool's namespace.",
277 inputSchema: {
278 type: "object",
279 properties: {
280 path: { type: "string", minLength: 1, maxLength: 4_096 },
281 },
282 additionalProperties: false,
283 },
284 });
285 await $.tool.register({
286 name: "qe_workspace_write",
287 description:
288 "Write a UTF-8 file in the pre-bound Quest Engineering Run workspace.",
289 inputSchema: {
290 type: "object",
291 properties: {
292 path: { type: "string", minLength: 1, maxLength: 4_096 },
293 content: { type: "string", maxLength: 262_144 },
294 },
295 required: ["path", "content"],
296 additionalProperties: false,
297 },
298 });
299 await $.tool.register({
300 name: "qe_workspace_edit",
301 description:
302 "Replace exact text in a file in the pre-bound Quest Engineering Run workspace.",
303 inputSchema: {
304 type: "object",
305 properties: {
306 path: { type: "string", minLength: 1, maxLength: 4_096 },
307 oldText: { type: "string", minLength: 1, maxLength: 262_144 },
308 newText: { type: "string", maxLength: 262_144 },
309 expectedReplacements: {
310 type: "integer",
311 minimum: 1,
312 maximum: 1_000,
313 },
314 },
315 required: ["path", "oldText", "newText"],
316 additionalProperties: false,
317 },
318 });
319 await $.tool.register({
320 name: "qe_workspace_search",
321 description:
322 "Search only the pre-bound Quest Engineering Run workspace and return bounded matches.",
323 inputSchema: {
324 type: "object",
325 properties: {
326 query: { type: "string", minLength: 1, maxLength: 4_096 },
327 path: { type: "string", minLength: 1, maxLength: 4_096 },
328 maxResults: { type: "integer", minimum: 1, maximum: 1_000 },
329 },
330 required: ["query"],
331 additionalProperties: false,
332 },
333 });
334 await $.tool.register({
335 name: "qe_run_shell",
336 description:
337 "Execute a command only inside the pre-bound credential-free Quest Engineering Run SBX. No command executes in the Claude account cell.",
338 inputSchema: {
339 type: "object",
340 properties: {
341 command: { type: "string", minLength: 1, maxLength: 32_768 },
342 timeoutMs: { type: "integer", minimum: 100, maximum: 30_000 },
343 },
344 required: ["command"],
345 additionalProperties: false,
346 },
347 });
348 await $.tool.register({
349 name: "qe_complete_step",
350 description:
351 "Submit the declared structured outputs to the pre-bound Quest Engineering completion authority.",
352 inputSchema: {
353 type: "object",
354 properties: { outputs: { type: "object" } },
355 required: ["outputs"],
356 additionalProperties: false,
357 },
358 });
359 await $.tool.register({
360 name: "qe_request_human_assistance_and_wait",
361 description:
362 "Create a durable Quest Engineering HumanAttention request and wait for its exact response without opening a local account-cell prompt.",
363 inputSchema: {
364 type: "object",
365 properties: {
366 category: {
367 type: "string",
368 enum: [
369 "needs_input",
370 "needs_permission",
371 "needs_authentication",
372 "needs_confirmation",
373 "blocked_external",
374 "interactive_prompt",
375 "unknown_interactive_block",
376 ],
377 },
378 message: { type: "string", minLength: 1, maxLength: 16_384 },
379 interaction: {
380 type: "string",
381 enum: [
382 "confirmation",
383 "text",
384 "choice",
385 "multiline_response",
386 "conversational_intervention",
387 ],
388 },
389 responseSchema: { type: "object" },
390 },
391 required: ["category", "message"],
392 additionalProperties: false,
393 },
394 });
395 ready = true;
396 return next(event);
397 });
398
399 on("command.run", () => ({
400 text: "QE account-cell policy refused this local command.",
401 })).catch(() => ({
402 text: "QE account-cell command policy failed closed.",
403 }));
404
405 on("agent.offer", () => ({ isOffered: false })).catch(() => ({
406 isOffered: false,
407 }));
408
409 on("agent.spawn", () => ({
410 deny: "QE account-cell policy disables Claude subagents.",
411 })).catch(() => ({
412 deny: "QE account-cell subagent policy failed closed.",
413 }));
414
415 on("tool.call", async ($, event) => {
416 if (!ready) return { deny: "QE account-cell Mod is not ready." };
417 if (event.agentId)
418 return { deny: "QE account-cell policy denies subagent tool calls." };
419 if (!ALLOWED_TOOLS.has(event.tool))
420 return {
421 deny: `QE account-cell policy did not authorize ${event.tool}.`,
422 };
423
424 const input = object(event, "tool input");
425 if (event.tool === TOOLS.attention) {
426 const category = boundedString(input.category, "category", 64);
427 const message = boundedString(input.message, "message", 16_384);
428 const interaction =
429 input.interaction === undefined
430 ? undefined
431 : boundedString(input.interaction, "interaction", 64);
432 const responseSchema =
433 input.responseSchema === undefined
434 ? undefined
435 : object(input.responseSchema, "responseSchema");
436 if (responseSchema !== undefined)
437 boundedJson(responseSchema, "responseSchema", 16_384);
438 const result = await waitForHuman($, {
439 type: "request_human_assistance_and_wait",
440 category,
441 message,
442 ...(interaction === undefined ? {} : { interaction }),
443 ...(responseSchema === undefined ? {} : { responseSchema }),
444 });
445 return { result: JSON.stringify(result) };
446 }
447
448 const result = await runBridge($, brokerOperation(event.tool, input));
449 return { result: JSON.stringify(result) };
450 }).catch((_$, event, next) => ({
451 deny: `QE account-cell tool mediation failed closed for ${event.tool}: ${next.error.kind}.`,
452 }));
453
454 on("tool.check", async (_$, event, next) => {
455 if (!ready || event.agentId || !ALLOWED_TOOLS.has(event.tool))
456 return {
457 decision: "deny",
458 reason: "QE account-cell exact tool policy denied this call.",
459 };
460 const lower = await next(event);
461 if (lower.decision === "deny") return lower;
462 if (event.ceiling === "ask" || event.ceiling === "deny")
463 return {
464 decision: event.ceiling,
465 reason: "QE preserved the managed policy ceiling.",
466 };
467 return {
468 decision: "allow",
469 reason: "QE allowed an exact pre-bound broker tool.",
470 };
471 }).catch(() => ({
472 decision: "deny",
473 reason: "QE account-cell permission mediation failed closed.",
474 }));
475
476 on("prompt.submit", async (_$, event, next) => {
477 if (!ready)
478 return {
479 drop: "QE account-cell Mod is not ready; refusing the prompt.",
480 };
481 return next({
482 ...event,
483 context: [
484 ...(event.context ?? []),
485 "Quest Engineering account-cell contract: every filesystem, search, and shell operation must use the QE broker tools. No local account-cell coding tool exists. Never claim completion without calling qe_complete_step with exactly the declared output keys. Use qe_request_human_assistance_and_wait for human input.",
486 ],
487 });
488 }).catch(() => ({
489 drop: "QE account-cell prompt mediation failed closed.",
490 }));
491
492 on("turn.step", async function* (_$, event, next) {
493 if (
494 !ready ||
495 event.agentId !== undefined ||
496 event.model !== expectedModel ||
497 event.effort !== expectedEffort
498 )
499 return {
500 turnId: event.turnId,
501 index: event.index,
502 answer: "",
503 toolUses: [],
504 stopReason: "refusal",
505 usage: null,
506 };
507 const chunks = [];
508 const downstream = next(event);
509 let item = await downstream.next();
510 while (item.done !== true) {
511 chunks.push(item.value);
512 item = await downstream.next();
513 }
514 if ((item.value.serverToolUses?.length ?? 0) > 0)
515 return {
516 turnId: event.turnId,
517 index: event.index,
518 answer: "",
519 toolUses: [],
520 serverToolUses: item.value.serverToolUses,
521 stopReason: "refusal",
522 usage: item.value.usage,
523 };
524 for (const chunk of chunks) yield chunk;
525 return item.value;
526 });
527};
528