Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

Bundled Hephaestus rule shows spawn_agent examples the LazyCodex spawn guard denies

Cerrado Apto para principiantes
#164 1 comentario 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
85/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
typescript
Área
testing, tooling

Línea de trabajo

Comienza con las tres variantes de Hephaestus en packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/ y compara sus ejemplos de spawn_agent con components/ulw-loop/src/spawn-role-guard.ts. Ejecuta el nuevo test components/rules/test/hephaestus-spawn-examples.test.ts después de actualizar los ejemplos. Se considera terminado cuando cada ejemplo usa un agent_type registrado, conserva la distinción entre forks V1/V2 y evita forks con el historial completo.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Problem Situation

A Codex session whose only spawn guidance is the bundled Hephaestus rule (components/rules/bundled-rules/hephaestus/gpt-5.5.md, injected by the rules hook at SessionStart) copies the rule's own spawn_agent example, and the LazyCodex spawn guard added in 5.0.0-beta.61 (components/ulw-loop/src/spawn-role-guard.ts) denies it. All six examples across the three variants (gpt-5.5.md, gpt-5.6.md, gpt-6.md) spell the call as {"message":"TASK: act as a <role>. ...","fork_context":false} with no agent_type, which is exactly the shape the guard rejects ("Describing a role in message does not select its TOML"). A long orchestration session that had been spawning workers this way for two days on beta.53 lost every spawn the moment beta.62 loaded, mid-lap.

The ultrawork and ulw-execute skills already say every spawn must pass an exact agent_type; the bundled rule is the one document that still teaches the denied shape, and it is the one every session receives without asking for a skill.

Reproduction Logs

Live denial, Codex CLI with omo 5.0.0-beta.62, multi_agent_v1 surface, a spawn written from the bundled rule's example:

Tool call blocked by PreToolUse hook: LazyCodex requires an explicit registered agent_type: explorer, lazycodex-clone-fidelity-reviewer, lazycodex-code-reviewer, lazycodex-gate-reviewer, lazycodex-qa-executor, lazycodex-worker-high, lazycodex-worker-low, lazycodex-worker-medium, librarian, metis, momus, plan. Received no agent_type. Use the matching role and fork_turns: "none" (V2) or fork_context: false (V1), unless full history is deliberately required. The hook cannot see the tool schema; if agent_type is unavailable, stop and report incompatible role routing rather than spawning a generic agent. Describing a role in message does not select its TOML.. Tool: spawn_agent

The rule's examples fed straight into the shipped guard (spawnRoleDenial from components/ulw-loop/dist/spawn-role-guard.js, placeholders filled with explorer / task):

HEAD     gpt-5.5.md example 1 DENIED {"message":"TASK: act as a explorer. x","fork_context":false}
HEAD     gpt-5.5.md example 2 DENIED {"task_name":"t","message":"TASK: act as a explorer. x","fork_turns":"none"}
HEAD     gpt-5.6.md example 1 DENIED {"message":"TASK: act as a explorer. GOAL: x STOP WHEN: x EVIDENCE: x","fork_context":false}
HEAD     gpt-5.6.md example 2 DENIED {"task_name":"t","message":"TASK: act as a explorer. GOAL: x STOP WHEN: x EVIDENCE: x","fork_turns":"none"}
HEAD     gpt-6.md example 1 DENIED {"message":"TASK: act as a explorer. GOAL: x STOP WHEN: x EVIDENCE: x","fork_context":false}
HEAD     gpt-6.md example 2 DENIED {"task_name":"t","message":"TASK: act as a explorer. GOAL: x STOP WHEN: x EVIDENCE: x","fork_turns":"none"}

Root Cause

spawnRoleDenial admits a spawn only when tool_input.agent_type is one of LAZYCODEX_SPAWN_ROLES; it cannot see the tool schema, so it fails closed on any unnamed call. The bundled Hephaestus rule predates that guard (its spawn line last moved in 7124986 / ba0383d, July, in oh-my-openagent) and still shows the role in message with no agent_type, so a model following the rule verbatim produces a call the same plugin denies. The two documents shipped in one plugin disagree, and the guard wins.

Verified Fix

Rewrite the spawn sentence in the three variants so both examples carry "agent_type":"<role>", state that the role is selected by agent_type and that the guard rejects an unnamed spawn, and point implementation work at the three worker roles; the role bullets, GOAL/STOP WHEN/EVIDENCE labels and V1/V2 distinction are untouched. A test in components/rules/test parses every spawn_agent({...}) example in the three files as tool_input and requires a registered agent_type and no full-history fork, so the rule cannot drift away from the guard again. The role set in the test mirrors LAZYCODEX_SPAWN_ROLES; the ulw-loop matrix test already pins that set to the installed TOMLs.

Patch against e6be07d (chore: sync Codex marketplace v5.0.0-beta.62); in the source tree the same files live under packages/omo-codex/plugin/components/rules/.

diff --git a/plugins/omo/components/rules/bundled-rules/hephaestus/gpt-5.5.md b/plugins/omo/components/rules/bundled-rules/hephaestus/gpt-5.5.md
index 204ac6d..c334289 100644
--- a/plugins/omo/components/rules/bundled-rules/hephaestus/gpt-5.5.md
+++ b/plugins/omo/components/rules/bundled-rules/hephaestus/gpt-5.5.md
@@ -41,7 +41,7 @@ omo-codex auto-runs LSP diagnostics after every edit and injects the result: any
 
 # Subagents
 
-Read-only Codex subagent roles live in `CODEX_HOME/agents/`. Spawn: `multi_agent_v1.spawn_agent({"message":"TASK: act as a <role>. ...","fork_context":false})`. If your tool list instead has a flat `spawn_agent` with a required `task_name` (`multi_agent_v2`): `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":"TASK: act as a <role>. ...","fork_turns":"none"})` — finished agents end on their own; `wait_agent` takes only `timeout_ms`.
+Read-only Codex subagent roles live in `CODEX_HOME/agents/`. Every spawn selects one of them by exact name in `agent_type`; describing the role in `message` does not select its TOML, and the LazyCodex spawn guard rejects a spawn without a registered `agent_type`. Spawn: `multi_agent_v1.spawn_agent({"message":"TASK: ...","agent_type":"<role>","fork_context":false})`. If your tool list instead has a flat `spawn_agent` with a required `task_name` (`multi_agent_v2`): `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":"TASK: ...","agent_type":"<role>","fork_turns":"none"})` — finished agents end on their own; `wait_agent` takes only `timeout_ms`. Implementation work selects `lazycodex-worker-low`, `lazycodex-worker-medium`, or `lazycodex-worker-high` by difficulty.
 
 - `explorer` - codebase search
 - `librarian` - external docs, OSS code, API contracts
diff --git a/plugins/omo/components/rules/bundled-rules/hephaestus/gpt-5.6.md b/plugins/omo/components/rules/bundled-rules/hephaestus/gpt-5.6.md
index 6e1ef69..6f660e9 100644
--- a/plugins/omo/components/rules/bundled-rules/hephaestus/gpt-5.6.md
+++ b/plugins/omo/components/rules/bundled-rules/hephaestus/gpt-5.6.md
@@ -31,7 +31,7 @@ Waiting is not free: a status poll replays the whole accumulated context through
 
 # Subagents
 
-Read-only Codex subagent roles live in `CODEX_HOME/agents/`. Spawn: `multi_agent_v1.spawn_agent({"message":"TASK: act as a <role>. GOAL: ... STOP WHEN: ... EVIDENCE: ...","fork_context":false})`. If your tool list instead has a flat `spawn_agent` with a required `task_name` (`multi_agent_v2`): `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":"TASK: act as a <role>. GOAL: ... STOP WHEN: ... EVIDENCE: ...","fork_turns":"none"})` - finished agents end on their own; `wait_agent` takes only `timeout_ms`.
+Read-only Codex subagent roles live in `CODEX_HOME/agents/`. Every spawn selects one of them by exact name in `agent_type`; describing the role in `message` does not select its TOML, and the LazyCodex spawn guard rejects a spawn without a registered `agent_type`. Spawn: `multi_agent_v1.spawn_agent({"message":"TASK: ... GOAL: ... STOP WHEN: ... EVIDENCE: ...","agent_type":"<role>","fork_context":false})`. If your tool list instead has a flat `spawn_agent` with a required `task_name` (`multi_agent_v2`): `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":"TASK: ... GOAL: ... STOP WHEN: ... EVIDENCE: ...","agent_type":"<role>","fork_turns":"none"})` - finished agents end on their own; `wait_agent` takes only `timeout_ms`. Implementation work selects `lazycodex-worker-low`, `lazycodex-worker-medium`, or `lazycodex-worker-high` by difficulty.
 
 - `explorer` - codebase search
 - `librarian` - external docs, OSS code, API contracts
diff --git a/plugins/omo/components/rules/bundled-rules/hephaestus/gpt-6.md b/plugins/omo/components/rules/bundled-rules/hephaestus/gpt-6.md
index e921103..924219a 100644
--- a/plugins/omo/components/rules/bundled-rules/hephaestus/gpt-6.md
+++ b/plugins/omo/components/rules/bundled-rules/hephaestus/gpt-6.md
@@ -78,7 +78,7 @@ When an approach fails, change something material - an algorithm, library, or pa
 
 ## Codex tool and skills notes
 
-The actual Codex tool list and schemas determine the route. Read-only subagent roles live in `CODEX_HOME/agents/`. For `multi_agent_v1`, use `multi_agent_v1.spawn_agent({"message":"TASK: act as a <role>. GOAL: ... STOP WHEN: ... EVIDENCE: ...","fork_context":false})`. If the tool list instead exposes a flat `spawn_agent` requiring `task_name` (`multi_agent_v2`), use `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":"TASK: act as a <role>. GOAL: ... STOP WHEN: ... EVIDENCE: ...","fork_turns":"none"})`. Finished agents end on their own; `wait_agent` takes only `timeout_ms`. Keep the two payloads distinct and do not send v1 fields to v2 or vice versa.
+The actual Codex tool list and schemas determine the route. Read-only subagent roles live in `CODEX_HOME/agents/`, and every spawn selects one of them by exact name in `agent_type`; describing the role in `message` does not select its TOML, and the LazyCodex spawn guard rejects a spawn without a registered `agent_type`. For `multi_agent_v1`, use `multi_agent_v1.spawn_agent({"message":"TASK: ... GOAL: ... STOP WHEN: ... EVIDENCE: ...","agent_type":"<role>","fork_context":false})`. If the tool list instead exposes a flat `spawn_agent` requiring `task_name` (`multi_agent_v2`), use `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":"TASK: ... GOAL: ... STOP WHEN: ... EVIDENCE: ...","agent_type":"<role>","fork_turns":"none"})`. Finished agents end on their own; `wait_agent` takes only `timeout_ms`. Keep the two payloads distinct and do not send v1 fields to v2 or vice versa. Implementation work selects `lazycodex-worker-low`, `lazycodex-worker-medium`, or `lazycodex-worker-high` by difficulty.
 
 - `explorer`: codebase search.
 - `librarian`: external docs, OSS code, and API contracts.
diff --git a/plugins/omo/components/rules/test/hephaestus-spawn-examples.test.ts b/plugins/omo/components/rules/test/hephaestus-spawn-examples.test.ts
new file mode 100644
index 0000000..73d2a26
--- /dev/null
+++ b/plugins/omo/components/rules/test/hephaestus-spawn-examples.test.ts
@@ -0,0 +1,53 @@
+import { readFileSync } from "node:fs";
+import { join } from "node:path";
+import { describe, expect, it } from "vitest";
+
+// The bundled Hephaestus rules are the only spawn_agent examples a fresh session reads, and the
+// LazyCodex spawn guard (ulw-loop spawn-role-guard) denies any spawn whose tool_input carries no
+// registered agent_type. An example without agent_type teaches the model a call the guard rejects.
+// The role set below mirrors LAZYCODEX_SPAWN_ROLES; the ulw-loop test pins that set to the TOMLs.
+const REGISTERED_ROLES = new Set([
+	"explorer",
+	"lazycodex-clone-fidelity-reviewer",
+	"lazycodex-code-reviewer",
+	"lazycodex-gate-reviewer",
+	"lazycodex-qa-executor",
+	"lazycodex-worker-high",
+	"lazycodex-worker-low",
+	"lazycodex-worker-medium",
+	"librarian",
+	"metis",
+	"momus",
+	"plan",
+]);
+const VARIANTS = ["gpt-5.5.md", "gpt-5.6.md", "gpt-6.md"] as const;
+const EXAMPLE = /spawn_agent\((\{[^)]*\})\)/g;
+
+function spawnExamples(variant: string): Record<string, unknown>[] {
+	const text = readFileSync(join(process.cwd(), "bundled-rules", "hephaestus", variant), "utf8");
+	const found: Record<string, unknown>[] = [];
+	for (const match of text.matchAll(EXAMPLE)) {
+		const raw = (match[1] ?? "")
+			.replace(/\.\.\./g, "x")
+			.replace(/<role>/g, "explorer")
+			.replace(/<lowercase_digits_underscores>/g, "task");
+		found.push(JSON.parse(raw) as Record<string, unknown>);
+	}
+	return found;
+}
+
+describe("Hephaestus bundled rule spawn examples", () => {
+	for (const variant of VARIANTS) {
+		it(`#given the ${variant} variant #when its spawn_agent examples are read as tool_input #then each names a registered agent_type and no full-history fork`, () => {
+			const examples = spawnExamples(variant);
+			expect(examples.length).toBeGreaterThanOrEqual(2);
+			for (const example of examples) {
+				const role = example["agent_type"];
+				expect(typeof role).toBe("string");
+				expect(REGISTERED_ROLES.has(role as string)).toBe(true);
+				if ("fork_context" in example) expect(example["fork_context"]).toBe(false);
+				if ("fork_turns" in example) expect(example["fork_turns"]).toBe("none");
+			}
+		});
+	}
+});

Verification

  • RED, new test on unpatched rules (vitest --run test/hephaestus-spawn-examples.test.ts): Tests 3 failed (3), each at expect(typeof role).toBe("string") with Received: "undefined".
  • GREEN, same test on patched rules: Tests 3 passed (3).
  • Guard replay on the patched examples: all six admitted ({"message":"TASK: x","agent_type":"explorer","fork_context":false} and the V2 shape with fork_turns:"none").
  • Manual QA: the same session that was denied above spawned multi_agent_v1.spawn_agent({"message":"TASK: ...","agent_type":"lazycodex-gate-reviewer","fork_context":false}) and the hook admitted it.
  • Not run here: the sibling suites hephaestus-model-variant and windows-git-bash-bundled-rule need the @oh-my-opencode/rules-engine workspace link that the mirror does not carry; the diff does not touch the discovery paths they read.

This fix was debugged, implemented, and verified with LazyCodex.
Tag: lazycodex-generated

Lenguaje dominante
TypeScript
Estrellas
3.8k
Forks
246
Métricas de merge de PR
Sin PR fusionados en 30 d

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de code-yeongyu/lazycodex

Todos los issues de code-yeongyu/lazycodex

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.