Skip to content

fix: make the compound-engineering workflow actually load skills and run the full CE flow - #1696

Merged
gsxdsm merged 7 commits into
mainfrom
gsxdsm/ce-workflow-skill-loading
Jun 21, 2026
Merged

fix: make the compound-engineering workflow actually load skills and run the full CE flow#1696
gsxdsm merged 7 commits into
mainfrom
gsxdsm/ce-workflow-skill-loading

Conversation

@gsxdsm

@gsxdsm gsxdsm commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator

What & why

The builtin:compound-engineering workflow looked wired — each node named a CE skill — but on the graph-node execution path it never actually ran the CE way: the named skill was only injected as prompt text (never loaded), the plugin-injected FUSION_CE_* runtime env never reached step sessions, and fn_spawn_agent was never registered for workflow steps, so skill loading and persona fan-out silently no-op'd. This makes the workflow genuinely load skills and run the full CE flow.

Implements docs/plans/2026-06-20-001-fix-compound-engineering-workflow-skill-loading-plan.md (units U1–U9).

Changes

  • U8 (foundational) — thread the injected CE runtime env (FUSION_CE_SKILLS_DIR / FUSION_CE_AGENTS_DIR) into runGraphCustomNode skill steps via a shared buildInjectedRuntimeEnv helper, and register createSpawnAgentTool for coding-mode skill steps (readonly still strips it).
  • U1 — carry skillName through the WorkflowStep round-trip (type + compiler nodeToStepInputstepInputToNode under the INVERSION CONTRACT); in executeWorkflowStep, merge the bare + namespaced name into requestedSkillNames and pass FUSION_CE_SKILLS_DIR as additionalSkillPaths so the bundled SKILL.md is discovered and selected (mirrors the interactive-session fix in docs/solutions/.../plugin-bundled-skills-not-loading-in-interactive-sessions.md).
  • U2 — engine-injected Fusion workflow-step conventions preamble (await-input sentinel for questions, FUSION_HEADLESS degrade, persona fan-out via systemPromptOverride), so the bundled skills stay byte-for-byte upstream.
  • U3 — explicit unattended opt-in sets FUSION_HEADLESS=1 (default-safe: absent ⇒ board run). Entry-point wiring is deferred (no LFG/pipeline origin marker reaches the executor yet — see Follow-ups).
  • U4 — coding mode on plan / code-review (fan-out) and document (writes docs/solutions).
  • U9 / KTD-6 — path-confined persona read documented in the preamble; verdict-JSON contract required only for gate / skill-less steps.

Code review (autofix) — fixes applied in this PR

A 9-persona review ran; the correctness-critical findings were verified and fixed in fix(review): apply autofix feedback:

  • Graph-path spawn lifecycle (adversarial, verified): the graph path returns from execute() before its outer terminateAllChildren, so U8's new coding-mode children orphaned sessions/worktrees and accumulated the per-parent spawn budget, starving later steps' fan-out. Now terminateAllChildren runs in maybeExecuteWorkflowGraph's finally.
  • Parity test (api-contract + testing): skillName added to the round-trip projections + a skill-step fixture so the INVERSION CONTRACT is actually asserted.
  • Silent skill-load degradation: warn when a step names a skill but FUSION_CE_SKILLS_DIR is unset, instead of failing silent.
  • Dead branch: removed the always-false unattendedRun guard.

Test plan

  • @fusion/core + @fusion/engine typecheck clean.
  • 436 engine tests pass (all workflow-graph-*, executor-core, runtime-env, column-agent, step-session, workflow-step verdict/review/readonly, and the new CE workflow-step tests).
  • New: ce-workflow-step-conventions.test.ts, ce-workflow-step-executor.test.ts; parity + builtin-workflows tests extended.
  • Session layer is mocked in the new tests (asserts engine-owned wiring); a full model-driven board run remains the manual verification step.

Residual Review Findings

Non-blocking; tracked here for follow-up:

  • AC-1 (api-contract): WorkflowStep.skillName is not persisted in the workflow_steps table. Moot today — the built-in CE workflow synthesizes its WorkflowStep in-memory from IR and never persists skill-executor rows — but if skill steps ever become user-persistable, add a skill_name column + migration and round-trip it in store.createWorkflowStep / listWorkflowSteps.
  • Maintainability M-02/M-03/M-04/M-05: extract the CE-specific skill-merge/verdict logic from executeWorkflowStep; move FUSION_WORKFLOW_STEP_CONVENTIONS_PREAMBLE into its own module (decouples the test from the 15k-line executor); memoize collectExecutorRuntimeEnv once per graph run (currently per-node); export stripSkillNamespace from skill-resolver.ts instead of inlining the namespace strip.
  • Test gaps: unset-FUSION_CE_SKILLS_DIR no-op; await-input sentinel → awaiting-user-input parking; buildInjectedRuntimeEnv regression with a mock pluginRunner returning real keys; non-gate skill step succeeds without a verdict; spawn-tool input-schema assertion.
  • FNXC comments: a full pass to bring the remaining new comments to the FNXC:Area yyyy-MM-dd-hh:mm convention (applied on the fix comments).
  • R3 (headless) entry-point: wire the LFG/pipeline/disable-model-invocation caller to set unattended before genuinely-unattended runs can degrade honestly. Re-evaluate the accepted coding-mode write-capability posture (Risk-1) before enabling the CE workflow for unattended runs.

🤖 Generated with Claude Code


Open in Stage

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Compound-engineering workflow skills now properly execute and load into the runtime.
    • Fixed environment variable injection and skill discovery for workflow steps.
  • New Features

    • Added support for unattended/headless workflow execution mode.
    • Implemented standardized workflow-step conventions preamble.
    • Enabled persona fan-out capability for coding-mode workflow steps.

gsxdsm and others added 6 commits June 20, 2026 22:47
Add skillName to WorkflowStep and WorkflowStepInput, and round-trip it through
nodeToStepInput / stepInputToNode so a skill-executor node's skill is available
to the step session. Honors the compiler INVERSION CONTRACT (parity test).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…path (U8/U1/U2/U3/U9)

The builtin compound-engineering workflow runs via runGraphCustomNode, which
never loaded the named skill or threaded the plugin-injected runtime env, and
fn_spawn_agent was registered only in the main session. This wires the real seam:

- U8: thread injected FUSION_CE_* env into skill/model graph steps (shared
  buildInjectedRuntimeEnv helper); register createSpawnAgentTool for coding-mode
  skill steps (readonly still strips spawn).
- U1: merge the step's skillName (namespaced + bare) into requestedSkillNames and
  pass FUSION_CE_SKILLS_DIR as additionalSkillPaths so the bundled SKILL.md is
  discovered and selected.
- U2: prepend the Fusion workflow-step conventions preamble (await-input sentinel,
  FUSION_HEADLESS degrade, persona fan-out via systemPromptOverride).
- U3: explicit unattended opt-in sets FUSION_HEADLESS=1 (default-safe board run).
- U9: path-confined persona read documented in the preamble; accepted
  write-capability posture documented at the coding-mode tool registration.
- KTD-6: verdict-JSON contract required only for gate / skill-less steps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…/U7)

plan and code-review need coding so ce-plan/ce-code-review can fan out to their
persona subagents via fn_spawn_agent; document needs coding so ce-compound can
write docs/solutions. Test asserts the tool modes and that skillName is carried
onto the compiled steps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… headless, verdict (U6)

Two engine tests for the new compound-engineering workflow-step wiring:
- conventions: assert the exported preamble carries the await-input sentinel,
  FUSION_HEADLESS degrade, and path-confined persona/systemPromptOverride fan-out.
- executor: drive runGraphCustomNode + executeWorkflowStep and assert skillName is
  carried onto the synthesized step, requestedSkillNames merges bare+namespaced with
  additionalSkillPaths=[FUSION_CE_SKILLS_DIR], fn_spawn_agent present only in coding,
  FUSION_HEADLESS only when unattended, and the verdict-JSON contract is required
  only for gate/skill-less steps (relaxed for non-gate skill steps).

Session layer is mocked (asserts engine-owned wiring, not a model run); a full
model-driven e2e remains a documented residual.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address confirmed code-review findings on the CE workflow-step change:
- Graph-path spawn lifecycle (adversarial A-1/A-2): the graph path returns from
  execute() before its outer finally that calls terminateAllChildren, so U8's new
  coding-mode fn_spawn_agent children orphaned their sessions/worktrees and their
  ids accumulated in the per-parent spawn budget, starving later steps' fan-out.
  Call terminateAllChildren in maybeExecuteWorkflowGraph's finally (mirrors the
  non-graph cleanup).
- INVERSION CONTRACT parity (api-contract AC-2 + testing TF-001): add skillName to
  the workflow-steps-to-ir round-trip projections + a skill-step fixture, so the
  contract the comment claims is actually asserted.
- Silent skill-load degradation (adversarial A-3 / Risk-4): warn when a step names
  a skill but FUSION_CE_SKILLS_DIR is unset, instead of failing silent.
- Dead branch (maintainability M-01): drop the always-false unattendedRun guard;
  keep the delete + extension-point comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@gsxdsm, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 12 minutes and 51 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2ea2d7c3-c79b-4c93-9978-6a01069bed86

📥 Commits

Reviewing files that changed from the base of the PR and between c0f330e and 7235b25.

📒 Files selected for processing (3)
  • packages/core/src/__tests__/workflow-steps-to-ir.test.ts
  • packages/engine/src/__tests__/ce-workflow-step-executor.test.ts
  • packages/engine/src/executor.ts
📝 Walkthrough

Walkthrough

The PR fixes the builtin:compound-engineering workflow so it actually loads named CE skills at runtime. It adds skillName to WorkflowStep/WorkflowStepInput and rounds it through the IR compiler, exports FUSION_WORKFLOW_STEP_CONVENTIONS_PREAMBLE, centralizes plugin runtime env injection, adds explicit unattended/FUSION_HEADLESS signaling, gates fn_spawn_agent to coding mode, and conditions the verdict JSON contract on step type.

Changes

CE Workflow Skill Loading Fix

Layer / File(s) Summary
WorkflowStep skillName contract and IR round-trip
packages/core/src/types.ts, packages/core/src/workflow-compiler.ts, packages/core/src/workflow-steps-to-ir.ts
Adds optional skillName to WorkflowStep and WorkflowStepInput, propagates it in nodeToStepInput, and preserves it in the inverse stepInputToNode path, completing the compiler-visible round-trip.
FUSION_WORKFLOW_STEP_CONVENTIONS_PREAMBLE and buildInjectedRuntimeEnv
packages/engine/src/executor.ts, packages/engine/src/agent-runtime.ts
Exports the conventions preamble constant (await-input sentinel, headless degrade-to-assumption, persona fan-out rules). Adds buildInjectedRuntimeEnv() helper and updates legacy execute() to use it. Adds additionalSkillPaths to AgentRuntimeOptions.
Graph node execution: skillName threading, unattended tracking, preamble injection
packages/engine/src/executor.ts
Adds graphUnattendedRuns set; clears it at run start and in finally cleanup. runGraphCustomNode prepends the conventions preamble for skill nodes, threads skillName into the synthesized WorkflowStep, derives unattended from the set, and calls executeWorkflowStep with it. Child agents are terminated earlier in finally.
executeWorkflowStep: headless, skill loading, spawn-tool gating, verdict contract
packages/engine/src/executor.ts
Gains stepOptions.unattended; sets FUSION_HEADLESS=1 only when true. Loads named CE skills via additionalSkillPaths from FUSION_CE_SKILLS_DIR. Exposes fn_spawn_agent in coding mode only. Conditions the trailing verdict JSON on merge-gate steps; relaxes it for non-gate skill steps.
builtin-workflows.ts comments and core IR/compiler tests
packages/core/src/builtin-workflows.ts, packages/core/src/__tests__/builtin-workflows.test.ts, packages/core/src/__tests__/workflow-steps-to-ir.test.ts
Adds explanatory comments on ce-plan, ce-code-review, and ce-compound toolMode: "coding" configs. New test asserts skillName propagation through compilation. Round-trip tests extend projections to include skillName and add a WS-6 skill-executor fixture.
CE step conventions and dual-form skill resolution tests
packages/engine/src/__tests__/ce-workflow-step-conventions.test.ts
Asserts FUSION_WORKFLOW_STEP_CONVENTIONS_PREAMBLE contains all required contract tokens. Validates U1 dual-form resolution: bare name resolves, namespaced-only does not, both together resolve exactly once, and removing the install dir prevents resolution.
CE workflow-step executor integration tests
packages/engine/src/__tests__/ce-workflow-step-executor.test.ts
Adds captureSession mock harness and helpers; exercises runGraphCustomNode (skill vs. non-skill), FUSION_HEADLESS gating, skill-merge/additionalSkillPaths wiring, fn_spawn_agent registration gating, and system prompt verdict/format contract for GATE vs. NON-GATE skill steps.
Plan document and changeset
docs/plans/2026-06-20-001-fix-compound-engineering-workflow-skill-loading-plan.md, .changeset/fix-ce-workflow-skill-loading.md
Implementation plan (U1–U9, requirements, KTDs, scope/risks, verification strategy) and the patch changeset entry.

Sequence Diagram(s)

sequenceDiagram
  participant Graph as maybeExecuteWorkflowGraph
  participant Runner as runGraphCustomNode
  participant Step as executeWorkflowStep
  participant Session as createFnAgent (AgentSession)

  Graph->>Graph: clear graphUnattendedRuns[task.id]
  Graph->>Runner: dispatch skill node
  Runner->>Runner: prepend FUSION_WORKFLOW_STEP_CONVENTIONS_PREAMBLE to prompt
  Runner->>Runner: set WorkflowStep.skillName from node config
  Runner->>Runner: buildInjectedRuntimeEnv() → taskEnv
  Runner->>Runner: read graphUnattendedRuns → unattended flag
  Runner->>Step: executeWorkflowStep(step, task, { unattended })
  Step->>Step: unattended=true → FUSION_HEADLESS=1
  Step->>Step: resolve FUSION_CE_SKILLS_DIR → additionalSkillPaths
  Step->>Step: coding mode → register fn_spawn_agent
  Step->>Step: gate step? → inject verdictBlock JSON contract
  Step->>Session: createFnAgent({ tools, skillNames, additionalSkillPaths, env })
  Session-->>Step: prompt() → message events
  Step-->>Runner: result
  Graph->>Graph: finally — terminateAllChildren, clear graphUnattendedRuns
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Runfusion/Fusion#1672: Directly related — both PRs modify the compound-engineering workflow execution path, wiring fn_spawn_agent subpersona fan-out, await-input sentinel handling, and CE skill/subagent execution end-to-end.

Poem

🐇 Hop hop, the skills were lost in the void,
Named in the prompt but never deployed!
Now skillName threads through compiler and step,
FUSION_HEADLESS knows when to rest,
The preamble guards each persona's domain —
CE workflow runs end-to-end again! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: making the compound-engineering workflow load and execute skills end-to-end via the full CE flow, which is the main purpose of this multi-unit implementation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gsxdsm/ce-workflow-skill-loading

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ghost

ghost commented Jun 21, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
docs/plans/2026-06-20-001-fix-compound-engineering-workflow-skill-loading-plan.md (2)

56-58: 💤 Low value

Optional style improvement: Consider rephrasing "it is exactly the" for conciseness.

In the KTD-1 section, the phrase "this is the one missing hand-off, and it is exactly the 'workflow execution loads skills' requirement" could be tightened to avoid the slightly redundant "exactly" (see LanguageTool style note ~57). Example: "…and it represents the 'workflow execution loads skills' requirement" or simply remove "exactly".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs/plans/2026-06-20-001-fix-compound-engineering-workflow-skill-loading-plan.md`
around lines 56 - 58, In the KTD-1 section of the plan document, the phrase
"this is the one missing hand-off, and it is exactly the 'workflow execution
loads skills' requirement" contains redundant phrasing with the word "exactly".
Rephrase this to improve conciseness by either replacing "exactly" with a more
precise verb like "represents" (changing "it is exactly the" to "it represents
the"), or remove "exactly" entirely to streamline the sentence while maintaining
its meaning.

Source: Linters/SAST tools


196-196: 💤 Low value

Optional style suggestion: Consider "brief" instead of "short".

In the U4 description, "Add a short comment on each" could read "Add a brief comment on each" for stronger, more precise wording per LanguageTool suggestion (~196).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@docs/plans/2026-06-20-001-fix-compound-engineering-workflow-skill-loading-plan.md`
at line 196, In the U4 description section where it states "Add a short comment
on each", change the word "short" to "brief" for more precise and stronger
wording that better conveys the intent to keep the comments concise while being
clear and complete.

Source: Linters/SAST tools

packages/engine/src/__tests__/ce-workflow-step-executor.test.ts (1)

226-234: ⚡ Quick win

Test should assert FUSION_WORKFLOW_STEP=1 for the no-stepOptions case.

The test description (line 197) states "always sets FUSION_WORKFLOW_STEP", but the third scenario (no stepOptions) only asserts that FUSION_HEADLESS is undefined. For consistency with the other two cases and to verify the "always sets" claim, add an assertion for FUSION_WORKFLOW_STEP=1.

✅ Add missing assertion
   expect(cap.last?.taskEnv?.FUSION_HEADLESS).toBeUndefined();
+  expect(cap.last?.taskEnv?.FUSION_WORKFLOW_STEP).toBe("1");
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/engine/src/__tests__/ce-workflow-step-executor.test.ts` around lines
226 - 234, The test case for the no-stepOptions scenario (which calls
executeWorkflowStep with undefined stepOptions) is missing an assertion to
verify that FUSION_WORKFLOW_STEP is set to 1. Add an assertion after the
existing expect(cap.last?.taskEnv?.FUSION_HEADLESS).toBeUndefined() line to
check that cap.last?.taskEnv?.FUSION_WORKFLOW_STEP equals "1", which will align
this test case with the other two scenarios and verify the documented behavior
that FUSION_WORKFLOW_STEP is always set.
packages/engine/src/__tests__/ce-workflow-step-conventions.test.ts (1)

105-152: ⚡ Quick win

Consider mocking loadSkills to avoid file I/O and narrow the test seam.

Per coding guidelines, test files should prefer narrow seams and in-memory fakes over real file I/O. This test validates resolution logic (resolveSessionSkills + createSkillsOverrideFromSelection), not discovery. Mocking loadSkills to return fake skills would:

  • Focus the test on the system under test (resolution)
  • Eliminate file I/O overhead
  • Align with the "focused unit coverage" scope stated in the file comment
♻️ Example refactor to use mocked loadSkills
+import { vi } from "vitest";
+
+// Mock loadSkills at the top level
+vi.mock("`@earendil-works/pi-coding-agent`", () => ({
+  loadSkills: vi.fn(),
+}));
+
+import { loadSkills } from "`@earendil-works/pi-coding-agent`";
+const mockedLoadSkills = vi.mocked(loadSkills);

 describe("U1: dual-form (namespaced + bare) CE skill resolution", () => {
-  let tmp: string;
   let projectRootDir: string;
-  let agentDir: string;
-  let installRoot: string;

-  function materialize(id: string): void {
-    const dir = join(installRoot, id);
-    mkdirSync(dir, { recursive: true });
-    writeFileSync(
-      join(dir, "SKILL.md"),
-      `---\nname: ${id}\ndescription: ${id} pipeline stage\n---\n\n# ${id}\n`,
-    );
-  }
-
   function resolveFor(requestedSkillNames: string[]): string[] {
-    const discovered = loadSkills({
-      cwd: projectRootDir,
-      agentDir,
-      skillPaths: [installRoot],
-      includeDefaults: false,
-    });
+    // Mock returns fake discovered skills
+    const discovered = { skills: [{ name: "ce-work", filePath: "/fake/ce-work/SKILL.md" }], diagnostics: [] };
     const selection = resolveSessionSkills({
       projectRootDir,
       requestedSkillNames,
       sessionPurpose: "executor",
     });
     // ... rest unchanged
   }

   beforeEach(() => {
-    tmp = mkdtempSync(join(tmpdir(), "ce-conv-"));
-    projectRootDir = join(tmp, "project");
-    agentDir = join(tmp, "agent");
-    installRoot = join(tmp, ".fusion-ce-skills");
-    mkdirSync(projectRootDir, { recursive: true });
-    mkdirSync(agentDir, { recursive: true });
-    materialize("ce-work");
+    projectRootDir = "/fake/project";
+    mockedLoadSkills.mockReturnValue({ skills: [{ name: "ce-work", filePath: "/fake/ce-work/SKILL.md" }], diagnostics: [] });
   });

   afterEach(() => {
-    rmSync(tmp, { recursive: true, force: true });
+    vi.clearAllMocks();
   });

Then update the "without install dir" test to mock an empty skills array instead of repointing discovery.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/engine/src/__tests__/ce-workflow-step-conventions.test.ts` around
lines 105 - 152, The test is performing unnecessary file I/O through the
`materialize` function and `loadSkills` to validate resolution logic that should
be tested in isolation. Mock the `loadSkills` function to return predetermined
fake skills instead of creating actual files and directories. Update the
`resolveFor` function to use the mocked `loadSkills` that returns known skills
directly, removing the need for the `materialize` helper and related file system
setup in `beforeEach`. This will narrow the test focus to only the resolution
logic in `resolveSessionSkills` and `createSkillsOverrideFromSelection` without
the overhead of real file I/O.

Source: Coding guidelines

packages/engine/src/executor.ts (1)

981-995: ⚡ Quick win

Add FNXC headings to the new requirement comments.

Several new blocks document durable workflow behavior but omit the repo’s dated FNXC heading format. Add a short FNXC:<Area> 2026-06-20-hh:mm: heading to these requirement comments, matching the style already used at Line 4301 and Line 12726.

As per coding guidelines, “Add FNXC_LOG comments (format: FNXC:Area-of-product yyyy-MM-dd-hh:mm:)”; based on learnings, use the established FNXC: heading rather than a literal FNXC_LOG token.

Also applies to: 4055-4060, 4183-4196, 6059-6065, 6272-6377, 12533-12756, 15425-15434

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/engine/src/executor.ts` around lines 981 - 995, The multi-line
comment block documenting the U2/KTD-2 and U9/KTD-7 workflow conventions and
persona fan-out instruction is missing the FNXC dated heading format used
elsewhere in the codebase. Add a short FNXC heading at the beginning of this
comment block (before the existing "U2 / KTD-2" text) using the format
FNXC:<Area> yyyy-MM-dd-hh:mm: to match the style established at other locations
like Line 4301 and Line 12726. Apply this same fix to all other requirement
comment blocks mentioned in the review (at line ranges 4055-4060, 4183-4196,
6059-6065, 6272-6377, 12533-12756, and 15425-15434).

Sources: Coding guidelines, Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/__tests__/workflow-steps-to-ir.test.ts`:
- Around line 94-105: The step helper function in the WS-6 test case is dropping
the skillName property, which means the round-trip assertion through
stepInputToNode and nodeToStepInput is not actually validating that skillName is
preserved. Modify the step helper or the round-trip assertion logic to ensure
that the skillName field is retained and checked during the conversion process
so that the test can properly verify the compiler inversion contract for the
skillName property.

In `@packages/engine/src/executor.ts`:
- Around line 12693-12700: The stepEnv object spreads environment variables from
taskEnv or process.env, which can inherit an existing FUSION_HEADLESS variable
even when unattended is false. When constructing stepEnv, you need to explicitly
remove the FUSION_HEADLESS key from the inherited environment unless the
unattended flag is explicitly true. Modify the stepEnv object construction to
conditionally delete FUSION_HEADLESS from the spread parent environment when
unattended is false, or use a conditional spread pattern that excludes this key
in non-unattended scenarios.
- Around line 12757-12765: The issue is that codingCustomTools is empty in
readonly mode, so filterCustomToolsForReadonly never sees the spawn agent tool
created by createSpawnAgentTool and cannot add it to the denied list. Fix this
by always creating codingCustomTools with the spawn agent tool (regardless of
toolMode value), then pass it to filterCustomToolsForReadonly to properly
identify and log denied tools. This ensures the denial logging for
fn_spawn_agent works correctly in readonly mode instead of staying empty.

---

Nitpick comments:
In
`@docs/plans/2026-06-20-001-fix-compound-engineering-workflow-skill-loading-plan.md`:
- Around line 56-58: In the KTD-1 section of the plan document, the phrase "this
is the one missing hand-off, and it is exactly the 'workflow execution loads
skills' requirement" contains redundant phrasing with the word "exactly".
Rephrase this to improve conciseness by either replacing "exactly" with a more
precise verb like "represents" (changing "it is exactly the" to "it represents
the"), or remove "exactly" entirely to streamline the sentence while maintaining
its meaning.
- Line 196: In the U4 description section where it states "Add a short comment
on each", change the word "short" to "brief" for more precise and stronger
wording that better conveys the intent to keep the comments concise while being
clear and complete.

In `@packages/engine/src/__tests__/ce-workflow-step-conventions.test.ts`:
- Around line 105-152: The test is performing unnecessary file I/O through the
`materialize` function and `loadSkills` to validate resolution logic that should
be tested in isolation. Mock the `loadSkills` function to return predetermined
fake skills instead of creating actual files and directories. Update the
`resolveFor` function to use the mocked `loadSkills` that returns known skills
directly, removing the need for the `materialize` helper and related file system
setup in `beforeEach`. This will narrow the test focus to only the resolution
logic in `resolveSessionSkills` and `createSkillsOverrideFromSelection` without
the overhead of real file I/O.

In `@packages/engine/src/__tests__/ce-workflow-step-executor.test.ts`:
- Around line 226-234: The test case for the no-stepOptions scenario (which
calls executeWorkflowStep with undefined stepOptions) is missing an assertion to
verify that FUSION_WORKFLOW_STEP is set to 1. Add an assertion after the
existing expect(cap.last?.taskEnv?.FUSION_HEADLESS).toBeUndefined() line to
check that cap.last?.taskEnv?.FUSION_WORKFLOW_STEP equals "1", which will align
this test case with the other two scenarios and verify the documented behavior
that FUSION_WORKFLOW_STEP is always set.

In `@packages/engine/src/executor.ts`:
- Around line 981-995: The multi-line comment block documenting the U2/KTD-2 and
U9/KTD-7 workflow conventions and persona fan-out instruction is missing the
FNXC dated heading format used elsewhere in the codebase. Add a short FNXC
heading at the beginning of this comment block (before the existing "U2 / KTD-2"
text) using the format FNXC:<Area> yyyy-MM-dd-hh:mm: to match the style
established at other locations like Line 4301 and Line 12726. Apply this same
fix to all other requirement comment blocks mentioned in the review (at line
ranges 4055-4060, 4183-4196, 6059-6065, 6272-6377, 12533-12756, and
15425-15434).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cca04a8e-36d2-47e6-a187-35575c7e8556

📥 Commits

Reviewing files that changed from the base of the PR and between 2c1cff8 and c0f330e.

📒 Files selected for processing (12)
  • .changeset/fix-ce-workflow-skill-loading.md
  • docs/plans/2026-06-20-001-fix-compound-engineering-workflow-skill-loading-plan.md
  • packages/core/src/__tests__/builtin-workflows.test.ts
  • packages/core/src/__tests__/workflow-steps-to-ir.test.ts
  • packages/core/src/builtin-workflows.ts
  • packages/core/src/types.ts
  • packages/core/src/workflow-compiler.ts
  • packages/core/src/workflow-steps-to-ir.ts
  • packages/engine/src/__tests__/ce-workflow-step-conventions.test.ts
  • packages/engine/src/__tests__/ce-workflow-step-executor.test.ts
  • packages/engine/src/agent-runtime.ts
  • packages/engine/src/executor.ts

Comment thread packages/core/src/__tests__/workflow-steps-to-ir.test.ts
Comment thread packages/engine/src/executor.ts
Comment thread packages/engine/src/executor.ts
@greptile-apps

greptile-apps Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the builtin:compound-engineering workflow so it actually loads CE skills and runs the full compound-engineering flow. Previously, skill names were injected only as prompt text while the session never discovered or loaded the skill, the plugin-injected FUSION_CE_* runtime env never reached graph-node step sessions, and fn_spawn_agent was never registered for workflow steps.

  • U8 (foundational): extracts buildInjectedRuntimeEnv into a shared helper called from both the legacy path and runGraphCustomNode, and registers createSpawnAgentTool for coding-mode skill steps; adds terminateAllChildren to maybeExecuteWorkflowGraph's finally to clean up graph-spawned children that would otherwise be orphaned.
  • U1/U2: carries skillName through the WorkflowStep round-trip and WorkflowStepInput, merges both the bare and namespaced skill name into requestedSkillNames in executeWorkflowStep, and prepends FUSION_WORKFLOW_STEP_CONVENTIONS_PREAMBLE to the skill step prompt.
  • U3/U4: wires an explicit unattended opt-in that sets FUSION_HEADLESS=1 (with a delete-on-board-run guard against inherited env pollution), and bumps plan/code-review/document nodes to toolMode: \"coding\" so they can spawn subagents and write output.
  • KTD-6/U9: relaxes the trailing-verdict JSON contract for non-gate skill steps and documents the path-confinement requirement for persona-def reads via systemPromptOverride.

Confidence Score: 5/5

Safe to merge — the change fixes a silent no-op in the compound-engineering workflow path and is well-guarded by new tests.

The core logic changes are well-contained: the FUSION_HEADLESS stripping guard prevents inherited-env leakage, terminateAllChildren placement in the graph finally correctly closes the spawn-lifecycle gap, the skill-loading path mirrors a proven interactive-session fix, and the verdict-contract relaxation is gated precisely on the isSkillStep && !isGate condition. All three new test files cover the failure modes the PR was designed to fix. No functional regressions to existing workflow-step, reviewer, or column-agent paths are apparent.

No files require special attention — the executor.ts changes are the most complex but are thoroughly exercised by the new test suite.

Important Files Changed

Filename Overview
packages/engine/src/executor.ts Core of the fix: adds buildInjectedRuntimeEnv helper, wires skill loading + spawn tool into executeWorkflowStep, adds terminateAllChildren to maybeExecuteWorkflowGraph's finally, and implements KTD-6 verdict-contract relaxation. Extensive and well-tested.
packages/engine/src/tests/ce-workflow-step-executor.test.ts New executor integration tests covering skillName threading, spawn gating, FUSION_HEADLESS invariants (including inherited-env stripping), and the verdict-contract conditional.
packages/engine/src/tests/ce-workflow-step-conventions.test.ts Unit tests for FUSION_WORKFLOW_STEP_CONVENTIONS_PREAMBLE and dual-form skill resolution, documenting that the namespaced form alone does not match the resolver's bareSkillName.
packages/engine/src/agent-runtime.ts Adds additionalSkillPaths to AgentRuntimeOptions; properly forwarded through createResolvedAgentSession → pi.ts where it is consumed.
packages/core/src/types.ts Adds optional skillName field to WorkflowStep and WorkflowStepInput with clear documentation.
packages/core/src/workflow-compiler.ts Carries skillName through nodeToStepInput; updates INVERSION CONTRACT comment; paired with workflow-steps-to-ir.ts.
packages/core/src/workflow-steps-to-ir.ts Adds skillName round-trip in stepInputToNode, maintaining the INVERSION CONTRACT. Parity test extended accordingly.
packages/core/src/builtin-workflows.ts Adds toolMode: coding to plan, code-review, and document nodes with comments explaining the accepted write-capability posture.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant GR as maybeExecuteWorkflowGraph
    participant RGN as runGraphCustomNode
    participant BIRE as buildInjectedRuntimeEnv
    participant EWS as executeWorkflowStep
    participant Session as createResolvedAgentSession

    GR->>RGN: skill node (cfg.skillName)
    RGN->>BIRE: taskId, worktreePath, branch
    BIRE-->>RGN: nodeEnv (FUSION_CE_SKILLS_DIR, FUSION_CE_AGENTS_DIR, PATH)
    RGN->>RGN: prepend FUSION_WORKFLOW_STEP_CONVENTIONS_PREAMBLE
    RGN->>RGN: "stepSkillName = cfg.skillName (U1)"
    RGN->>EWS: "step{skillName}, nodeEnv, {unattended}"
    EWS->>EWS: "stepEnv = {...nodeEnv, FUSION_WORKFLOW_STEP:1}"
    EWS->>EWS: "unattended? set FUSION_HEADLESS=1 : delete FUSION_HEADLESS"
    EWS->>EWS: merge bare+namespaced into requestedSkillNames (U1)
    EWS->>EWS: "codingCustomTools = [createSpawnAgentTool] if coding (U8b)"
    EWS->>Session: skillSelection, additionalSkillPaths, customTools, taskEnv
    Session-->>EWS: session with CE skill loaded
    GR->>GR: finally: terminateAllChildren(task.id)
    GR->>GR: finally: clear graphUnattendedRuns, graphRouting, etc.
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant GR as maybeExecuteWorkflowGraph
    participant RGN as runGraphCustomNode
    participant BIRE as buildInjectedRuntimeEnv
    participant EWS as executeWorkflowStep
    participant Session as createResolvedAgentSession

    GR->>RGN: skill node (cfg.skillName)
    RGN->>BIRE: taskId, worktreePath, branch
    BIRE-->>RGN: nodeEnv (FUSION_CE_SKILLS_DIR, FUSION_CE_AGENTS_DIR, PATH)
    RGN->>RGN: prepend FUSION_WORKFLOW_STEP_CONVENTIONS_PREAMBLE
    RGN->>RGN: "stepSkillName = cfg.skillName (U1)"
    RGN->>EWS: "step{skillName}, nodeEnv, {unattended}"
    EWS->>EWS: "stepEnv = {...nodeEnv, FUSION_WORKFLOW_STEP:1}"
    EWS->>EWS: "unattended? set FUSION_HEADLESS=1 : delete FUSION_HEADLESS"
    EWS->>EWS: merge bare+namespaced into requestedSkillNames (U1)
    EWS->>EWS: "codingCustomTools = [createSpawnAgentTool] if coding (U8b)"
    EWS->>Session: skillSelection, additionalSkillPaths, customTools, taskEnv
    Session-->>EWS: session with CE skill loaded
    GR->>GR: finally: terminateAllChildren(task.id)
    GR->>GR: finally: clear graphUnattendedRuns, graphRouting, etc.
Loading

Reviews (2): Last reviewed commit: "Address PR review feedback (#1696)" | Re-trigger Greptile

- step() test helper now carries skillName, so the WS-6 round-trip fixture
  actually exercises the INVERSION CONTRACT for skillName (was silently dropped).
- executeWorkflowStep now strips an inherited FUSION_HEADLESS on board runs
  (unattended=false), preserving the U3 default-safe invariant — a board step
  nested under a headless-env parent could otherwise skip user questions.
  Added a regression test for the inherited-env strip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gsxdsm
gsxdsm merged commit eafe6f7 into main Jun 21, 2026
6 checks passed
@gsxdsm
gsxdsm deleted the gsxdsm/ce-workflow-skill-loading branch June 21, 2026 09:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant