Skip to content

Refactor: Apply template substitution to agent frontmatter and extract swarm defaults to agent files#963

Merged
acreeger merged 6 commits intomainfrom
feat/issue-962
Mar 23, 2026
Merged

Refactor: Apply template substitution to agent frontmatter and extract swarm defaults to agent files#963
acreeger merged 6 commits intomainfrom
feat/issue-962

Conversation

@acreeger
Copy link
Copy Markdown
Collaborator

Fixes #962

Refactor: Apply template substitution to agent frontmatter and extract swarm defaults to agent files

Issue details

Context

Currently in AgentManager.loadAgents(), template variable substitution is applied only to the agent prompt body (after frontmatter parsing). This means frontmatter fields like effort and model cannot use Handlebars expressions. Additionally, swarm-specific defaults for model and effort are maintained as hardcoded maps in SwarmSetupService.ts (defaultSwarmModels, defaultSwarmEfforts), which is a non-obvious place for these defaults to live.

Goal

Make agent defaults declarative by:

  1. Moving template substitution before frontmatter parsing in AgentManager.loadAgents() so frontmatter fields can use Handlebars expressions
  2. Declaring swarm vs non-swarm defaults directly in agent template files using conditionals
  3. Removing the hardcoded defaultSwarmModels and defaultSwarmEfforts maps from SwarmSetupService.ts

Example

Agent templates would declare their own defaults for both modes:

---
name: iloom-issue-implementer
model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}}
effort: {{#if SWARM_MODE}}medium{{else}}high{{/if}}
---

Implementation

1. Move template substitution before frontmatter parsing

In src/lib/AgentManager.ts, apply substituteVariables() to the raw file content before calling parseMarkdownAgent():

let content = await readFile(agentPath, 'utf-8')
if (templateVariables) {
    content = this.templateManager.substituteVariables(content, templateVariables)
}
const parsed = this.parseMarkdownAgent(content, filename)

Remove the later substitution loop (currently lines ~118-124) which becomes redundant.

2. Add conditional defaults to all agent template frontmatter

Update each agent template in templates/agents/ to declare model and effort using {{#if SWARM_MODE}} conditionals, matching the current values in defaultSwarmModels and defaultSwarmEfforts.

3. Remove hardcoded maps from SwarmSetupService

Remove defaultSwarmModels and defaultSwarmEfforts maps. The override chain becomes: user settings > agent frontmatter (which already contains the correct mode-specific defaults) > Claude Code default.

Related

Follow-up from #957 (configurable effort levels) where this pattern was identified during code review.


This PR was created automatically by iloom.

@acreeger
Copy link
Copy Markdown
Collaborator Author

acreeger commented Mar 23, 2026

Combined Analysis & Plan - Issue #962

Executive Summary

Refactor agent template loading so Handlebars expressions work in YAML frontmatter, enabling agents to declare their own swarm-vs-non-swarm defaults for model and effort. This involves: (1) moving template substitution before frontmatter parsing in AgentManager.loadAgents(), (2) adding conditional defaults to agent template frontmatter, and (3) removing the hardcoded defaultSwarmModels/defaultSwarmEfforts maps from SwarmSetupService.ts.

Questions and Key Decisions

Question Answer
Should settings.agents[name].model (non-swarm) override frontmatter swarm defaults when no swarmModel is set? Assumed YES - the issue says "user settings > agent frontmatter". This is a behavior change: previously defaultSwarmModels overrode non-swarm model settings in swarm mode. Now model applies everywhere, swarmModel still overrides in swarm specifically.

HIGH/CRITICAL Risks

  • Behavior change for swarm model resolution: Users who set agents.X.model but NOT agents.X.swarmModel will now see that model used in swarm mode (previously the hardcoded swarm default would override). This is more intuitive but changes existing behavior.

Implementation Overview

High-Level Execution Phases

  1. Move template substitution: Reorder AgentManager.loadAgents() to substitute templates before parsing frontmatter
  2. Update agent templates: Add {{#if SWARM_MODE}} conditionals to model/effort fields in 7 agent templates
  3. Simplify SwarmSetupService: Remove defaultSwarmModels/defaultSwarmEfforts maps and simplify the override loop
  4. Update tests: Adjust AgentManager.test.ts and SwarmSetupService.test.ts for new behavior
  5. Update docs: Update templates/agents/CLAUDE.md and docs/iloom-commands.md

Quick Stats

  • 3 files to modify (AgentManager.ts, SwarmSetupService.ts, SwarmSetupService.test.ts)
  • 7 agent template files to modify (frontmatter conditionals)
  • 2 documentation files to update
  • 0 new files to create
  • Dependencies: None

Complete Analysis & Implementation Details (click to expand)

Research Findings

Problem Space

  • Problem: Agent frontmatter can't use Handlebars expressions because template substitution runs after YAML parsing in AgentManager.loadAgents()
  • Architectural context: Swarm model/effort defaults are hardcoded in SwarmSetupService.ts maps instead of being declarative in agent templates
  • Edge cases: Empty effort field (effort: {{#if SWARM_MODE}}medium{{/if}} in non-swarm mode) produces empty string which isEffortLevel('') correctly rejects at AgentManager.ts:230

Codebase Research

  • Entry point: AgentManager.loadAgents() at src/lib/AgentManager.ts:73-162 - reads files, parses, applies substitution, applies settings
  • Template substitution: Currently at lines 114-124 (after parse), needs to move before parseMarkdownAgent() at line 99
  • YAML parser: MarkdownAgentParser.parse() at src/utils/MarkdownAgentParser.ts:18-53 - splits on --- delimiters, parses key-value YAML
  • Swarm defaults: defaultSwarmModels at SwarmSetupService.ts:69-77, defaultSwarmEfforts at SwarmSetupService.ts:82-90
  • Override loop: SwarmSetupService.ts:93-112 applies user swarmModel/swarmEffort with default map fallback
  • Model validation: parseMarkdownAgent() warns on non-standard models at lines 216-222

Affected Files

Source files:

  • src/lib/AgentManager.ts:73-162 - Reorder template substitution; remove post-parse substitution loop
  • src/lib/SwarmSetupService.ts:65-112 - Remove default maps and simplify override loop

Agent templates (frontmatter changes only):

  • templates/agents/iloom-issue-planner.md:1-6 - model conditional (opus->sonnet in swarm), effort conditional
  • templates/agents/iloom-issue-implementer.md:1-6 - model conditional (opus->sonnet in swarm), effort conditional
  • templates/agents/iloom-issue-enhancer.md:1-6 - model conditional (opus->sonnet in swarm), effort conditional
  • templates/agents/iloom-code-reviewer.md:1-6 - model conditional (opus->sonnet in swarm), effort conditional
  • templates/agents/iloom-issue-analyzer.md:1-6 - effort conditional only (model stays opus)
  • templates/agents/iloom-issue-analyze-and-plan.md:1-6 - effort conditional only (model stays opus)
  • templates/agents/iloom-issue-complexity-evaluator.md:1-6 - effort conditional only (model stays haiku)

Test files:

  • src/lib/AgentManager.test.ts - Add test for template substitution in frontmatter
  • src/lib/SwarmSetupService.test.ts - Update tests that assert defaultSwarmModels/defaultSwarmEfforts behavior

Documentation:

  • templates/agents/CLAUDE.md:52-60 - Update Model Override Rules section
  • docs/iloom-commands.md:1960-1970 - Update swarm model fallback description

Integration Points

  • renderSwarmAgents() calls loadAgents() with SWARM_MODE: true template variables
  • renderSwarmWaveVerifierAgent() also calls loadAgents() with SWARM_MODE
  • Settings model/effort override in loadAgents() (lines 128-158) still runs after frontmatter resolution

Medium Severity Risks

  • Model validation warning for Handlebars expressions: If template substitution fails to run (e.g., no templateVariables passed), raw Handlebars in the model field would trigger model validation warning and the agent would still load with the raw expression as model name. Mitigated: substitution only runs when templateVariables is provided, and callers always provide it in swarm context.

Implementation Plan

Automated Test Cases to Create

Test File: src/lib/AgentManager.test.ts (MODIFY)

Click to expand test structure (12 lines)
describe('template substitution in frontmatter', () => {
  it('should resolve Handlebars expressions in frontmatter model field before parsing', async () => {
    // Setup: agent with model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}}
    // Call loadAgents with templateVariables: { SWARM_MODE: true }
    // Assert: agent model is 'sonnet'
  })

  it('should resolve Handlebars expressions in frontmatter effort field', async () => {
    // Setup: agent with effort: {{#if SWARM_MODE}}medium{{/if}}
    // Call loadAgents with templateVariables: { SWARM_MODE: true }
    // Assert: agent effort is 'medium'
  })

  it('should resolve to non-swarm defaults when SWARM_MODE is falsy', async () => {
    // Setup: agent with model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}}
    // Call loadAgents with templateVariables: {}
    // Assert: agent model is 'opus'
  })

  it('should handle empty effort when SWARM_MODE is falsy', async () => {
    // Setup: agent with effort: {{#if SWARM_MODE}}medium{{/if}}
    // Call loadAgents with templateVariables: {}
    // Assert: agent.effort is undefined
  })
})

Test File: src/lib/SwarmSetupService.test.ts (MODIFY)

Tests to update:

  • 'applies default swarmModel (sonnet) for agents in default map when no swarmModel configured' - This test should now expect the model from loadAgents (which comes from frontmatter swarm conditional), not from hardcoded map
  • 'non-swarm model override does not affect swarm mode when default map covers the agent' - Behavior changes: non-swarm model setting NOW applies in swarm mode when no swarmModel is set. Update assertions.
  • 'applies default swarmModel (opus) for analyzer agent' - Should expect model from loadAgents frontmatter result
  • All defaultSwarmEfforts tests need updating similarly - mock loadAgents to return agents WITH correct effort (since frontmatter now provides it)

Files to Modify

1. src/lib/AgentManager.ts:92-124

Change: Move template substitution before parseMarkdownAgent() and remove the post-parse substitution loop.

// In the for loop at line 92, BEFORE parseMarkdownAgent:
let content = await readFile(agentPath, 'utf-8')

// NEW: Apply template substitution to raw content BEFORE parsing
if (templateVariables) {
  content = this.templateManager.substituteVariables(content, templateVariables)
}

const parsed = this.parseMarkdownAgent(content, filename)
// ... rest of loop

Then remove lines 113-125 (the post-parse substitution loop). The buildReviewTemplateVariables call at line 116 needs to move to BEFORE the for loop, since template variables must be enriched before they're used in substitution.

2. src/lib/SwarmSetupService.ts:65-112

Change: Remove defaultSwarmModels and defaultSwarmEfforts maps (lines 65-90). Simplify the override loop (lines 92-112) to only apply userSwarmModel and userSwarmEffort/userBaseEffort from settings.

// Remove lines 65-90 (both default maps)

// Simplified override loop (only user settings):
for (const [agentName, agentConfig] of Object.entries(agents)) {
  let updated = agentConfig
  const userSwarmModel = settings?.agents?.[agentName]?.swarmModel
  if (userSwarmModel) {
    updated = { ...updated, model: userSwarmModel }
  }

  const userSwarmEffort = settings?.agents?.[agentName]?.swarmEffort
  const userBaseEffort = settings?.agents?.[agentName]?.effort
  if (userSwarmEffort) {
    updated = { ...updated, effort: userSwarmEffort }
  } else if (userBaseEffort) {
    updated = { ...updated, effort: userBaseEffort }
  }
  agents[agentName] = updated
}

3. templates/agents/iloom-issue-planner.md:1-6

Change: Add swarm-conditional model and effort to frontmatter.

---
name: iloom-issue-planner
description: ...
color: blue
model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}}
effort: {{#if SWARM_MODE}}high{{/if}}
---

4. templates/agents/iloom-issue-implementer.md:1-6

Change: Add swarm-conditional model and effort to frontmatter.

model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}}
effort: {{#if SWARM_MODE}}medium{{/if}}

5. templates/agents/iloom-issue-enhancer.md:1-6

Change: Add swarm-conditional model and effort to frontmatter.

model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}}
effort: {{#if SWARM_MODE}}medium{{/if}}

6. templates/agents/iloom-code-reviewer.md:1-6

Change: Add swarm-conditional model and effort to frontmatter.

model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}}
effort: {{#if SWARM_MODE}}medium{{/if}}

7. templates/agents/iloom-issue-analyzer.md:1-6

Change: Add swarm-conditional effort (model stays opus in both modes).

model: opus
effort: {{#if SWARM_MODE}}high{{/if}}

8. templates/agents/iloom-issue-analyze-and-plan.md:1-6

Change: Add swarm-conditional effort (model stays opus in both modes).

model: opus
effort: {{#if SWARM_MODE}}high{{/if}}

9. templates/agents/iloom-issue-complexity-evaluator.md:1-6

Change: Add swarm-conditional effort (model stays haiku in both modes).

model: haiku
effort: {{#if SWARM_MODE}}low{{/if}}

10. templates/agents/CLAUDE.md:52-60

Change: Update Model Override Rules to reflect frontmatter now carries swarm defaults via conditionals.

Replace item 3 ("Swarm model defaults: SwarmSetupService applies swarm-specific defaults") with description of frontmatter conditionals. Update the "Do NOT hardcode model choices" note since we're now using Handlebars conditionals in frontmatter.

11. docs/iloom-commands.md:1960-1970

Change: Update description of swarm model fallback behavior. Remove reference to hardcoded defaults. Explain that when no swarmModel is set, the agent's model setting (or frontmatter swarm-conditional default) is used.

12. src/lib/SwarmSetupService.test.ts

Change: Update tests for new behavior. Mock loadAgents to return agents with swarm-appropriate model/effort values (since frontmatter now provides them). Update assertions that previously relied on defaultSwarmModels/defaultSwarmEfforts.

Key test changes:

  • Tests checking "default swarmModel" now expect values from loadAgents mock (which represents frontmatter output)
  • 'non-swarm model override does not affect swarm mode' test: behavior changes - non-swarm model NOW applies in swarm. Either update assertions or remove test.
  • Effort default tests: loadAgents mock should return agents with effort already set from frontmatter

13. src/lib/AgentManager.test.ts

Change: Add new tests for frontmatter template substitution. Existing tests that mock readFile with static frontmatter continue to work (template substitution with no matching variables is a no-op on static strings).

Detailed Execution Order

NOTE: These steps are executed in a SINGLE implementation run.

  1. Move template substitution in AgentManager.ts

    • Files: /Users/adam/Documents/Projects/iloom-cli/feat-issue-962/src/lib/AgentManager.ts
    • Move buildReviewTemplateVariables call to before the agent loading loop (before line 92). Apply substituteVariables() to raw file content before parseMarkdownAgent(). Remove post-parse substitution loop (lines 113-125). -> Verify: pnpm build succeeds
  2. Update agent template frontmatter

    • Files: All 7 agent templates in /Users/adam/Documents/Projects/iloom-cli/feat-issue-962/templates/agents/
    • Add model: and effort: conditionals per the mapping above. -> Verify: Frontmatter matches expected swarm defaults from the removed maps
  3. Remove default maps from SwarmSetupService.ts

    • Files: /Users/adam/Documents/Projects/iloom-cli/feat-issue-962/src/lib/SwarmSetupService.ts
    • Remove defaultSwarmModels (lines 65-77) and defaultSwarmEfforts (lines 79-90). Simplify the override loop to only use user settings. Remove the defaultSwarmModels[agentName] and defaultSwarmEfforts[agentName] fallback branches. -> Verify: pnpm build succeeds
  4. Update tests

    • Files: /Users/adam/Documents/Projects/iloom-cli/feat-issue-962/src/lib/AgentManager.test.ts, /Users/adam/Documents/Projects/iloom-cli/feat-issue-962/src/lib/SwarmSetupService.test.ts
    • Add frontmatter substitution tests to AgentManager. Update SwarmSetupService tests: adjust loadAgents mocks to return swarm-appropriate values, update assertions for changed behavior. -> Verify: pnpm test passes
  5. Update documentation

    • Files: /Users/adam/Documents/Projects/iloom-cli/feat-issue-962/templates/agents/CLAUDE.md, /Users/adam/Documents/Projects/iloom-cli/feat-issue-962/docs/iloom-commands.md
    • Update model override rules and swarm default descriptions. -> Verify: Documentation is accurate
  6. Final build verification

    • Run pnpm build to ensure everything compiles cleanly

Dependencies and Configuration

None

@acreeger
Copy link
Copy Markdown
Collaborator Author

acreeger commented Mar 23, 2026

Implementation Complete

Summary

Refactored agent template system to apply Handlebars template substitution before frontmatter parsing, enabling conditional expressions in YAML fields like model and effort. Moved swarm-specific defaults from hardcoded maps in SwarmSetupService.ts into declarative conditionals in agent template frontmatter. Added static effort: high to wave-verifier and framework-detector agents.

Changes Made

  • src/lib/AgentManager.ts: Moved template substitution before frontmatter parsing
  • src/lib/SwarmSetupService.ts: Removed defaultSwarmModels/defaultSwarmEfforts maps, simplified override loop
  • templates/agents/ (7 agents): Added {{#if SWARM_MODE}} conditionals for model and/or effort
  • templates/agents/iloom-wave-verifier.md: Added static effort: high
  • templates/agents/iloom-framework-detector.md: Added static effort: high
  • src/lib/AgentManager.test.ts: 5 new tests for frontmatter template substitution
  • src/lib/SwarmSetupService.test.ts: Updated 6 tests for new behavior
  • templates/agents/CLAUDE.md: Updated model/effort override rules documentation
  • docs/iloom-commands.md: Updated swarm model/effort default descriptions

Validation Results

  • ✅ Tests: 5035 passed
  • ✅ Typecheck: Passed
  • ✅ Lint: Passed

@acreeger
Copy link
Copy Markdown
Collaborator Author

acreeger commented Mar 23, 2026

Implementation Complete - Issue #962

Summary

Refactored agent template loading so Handlebars expressions work in YAML frontmatter, enabling agents to declare their own swarm-vs-non-swarm defaults for model and effort. Removed hardcoded defaultSwarmModels/defaultSwarmEfforts maps from SwarmSetupService.ts -- these defaults now live declaratively in agent template files.

Changes Made

  • AgentManager.ts: Moved template substitution before frontmatter parsing; buildReviewTemplateVariables enrichment moved before the agent loading loop
  • 7 agent templates: Added {{#if SWARM_MODE}} conditionals for model and/or effort in frontmatter
  • iloom-wave-verifier.md: Added static effort: high (user request)
  • iloom-framework-detector.md: Added static effort: high (user request)
  • SwarmSetupService.ts: Removed defaultSwarmModels and defaultSwarmEfforts maps; simplified override loop to user settings only
  • Documentation: Updated templates/agents/CLAUDE.md and docs/iloom-commands.md to reflect new frontmatter-based defaults

Validation Results

  • Tests: 5035 passed / 5035 total (1 skipped)
  • Typecheck: Passed
  • Lint: Passed (0 warnings)

Behavior Change

Users who set agents.X.model but NOT agents.X.swarmModel will now see that model used in swarm mode (previously hardcoded swarm defaults would override). This is more intuitive -- swarmModel is the explicit swarm override, model applies everywhere.


Detailed Changes by File (click to expand)

Files Modified

src/lib/AgentManager.ts

Changes: Moved template substitution before parseMarkdownAgent() call. buildReviewTemplateVariables enrichment moved before the for loop. Removed post-parse substitution loop (lines 113-125 in original).

src/lib/SwarmSetupService.ts

Changes: Removed defaultSwarmModels (7 entries) and defaultSwarmEfforts (7 entries) maps. Simplified override loop to only apply user swarmModel/swarmEffort/effort settings. Removed unused ClaudeModel and EffortLevel imports.

templates/agents/iloom-issue-planner.md

Changes: Added model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}} and effort: {{#if SWARM_MODE}}high{{/if}}

templates/agents/iloom-issue-implementer.md

Changes: Added model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}} and effort: {{#if SWARM_MODE}}medium{{/if}}

templates/agents/iloom-issue-enhancer.md

Changes: Added model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}} and effort: {{#if SWARM_MODE}}medium{{/if}}

templates/agents/iloom-code-reviewer.md

Changes: Added model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}} and effort: {{#if SWARM_MODE}}medium{{/if}}

templates/agents/iloom-issue-analyzer.md

Changes: Added effort: {{#if SWARM_MODE}}high{{/if}} (model stays opus in both modes)

templates/agents/iloom-issue-analyze-and-plan.md

Changes: Added effort: {{#if SWARM_MODE}}high{{/if}} (model stays opus in both modes)

templates/agents/iloom-issue-complexity-evaluator.md

Changes: Added effort: {{#if SWARM_MODE}}low{{/if}} (model stays haiku in both modes)

templates/agents/iloom-wave-verifier.md

Changes: Added static effort: high (user request -- no conditional needed)

templates/agents/iloom-framework-detector.md

Changes: Added static effort: high (user request -- no conditional needed)

templates/agents/CLAUDE.md

Changes: Updated YAML frontmatter format section to show Handlebars support. Replaced "Model Override Rules" with "Model and Effort Override Rules" reflecting frontmatter conditionals instead of hardcoded maps.

docs/iloom-commands.md

Changes: Updated phase agent model override docs to describe frontmatter conditionals. Updated swarm effort defaults section to note values come from templates.

Test Coverage Added

src/lib/AgentManager.test.ts

  • 5 new tests in template substitution in frontmatter describe block:
    • Resolves Handlebars in model field (SWARM_MODE=true -> sonnet)
    • Resolves to non-swarm defaults (SWARM_MODE falsy -> opus)
    • Resolves Handlebars in effort field (SWARM_MODE=true -> medium)
    • Handles empty effort when SWARM_MODE is falsy (-> undefined)
    • Does not apply substitution when templateVariables not provided

src/lib/SwarmSetupService.test.ts

  • Updated 5 tests to reflect new behavior:
    • uses model from loadAgents (frontmatter swarm conditional) (was: applies default swarmModel)
    • uses model from loadAgents (frontmatter) for analyzer agent (was: applies default swarmModel opus)
    • user model setting applies in swarm mode when no swarmModel is set (was: non-swarm model override does not affect)
    • uses effort from loadAgents (frontmatter swarm conditionals) (was: applies default swarmEffort)
    • explicit swarmEffort overrides frontmatter effort (was: overrides default effort map)
    • Updated skill wrapper effort test mock to include effort from loadAgents

Dependencies Added

None

acreeger and others added 6 commits March 22, 2026 23:42
…swarm defaults to agent files

- Move Handlebars template substitution before frontmatter parsing in
  AgentManager.loadAgents() so model/effort fields can use conditionals
- Add {{#if SWARM_MODE}} conditionals to 7 agent templates for model/effort
- Remove hardcoded defaultSwarmModels/defaultSwarmEfforts maps from SwarmSetupService
- Add static effort: high to wave-verifier and framework-detector agents
- Fix Object.assign mutation of caller's templateVariables (use local copy)
- Update tests, docs, and agent CLAUDE.md

Fixes #962

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove internal details (env var names, file paths, agent rendering
mechanics, confidence thresholds, subagent_type parameters) from
iloom-commands.md. Keep behavior descriptions, replace mechanisms
with user-visible outcomes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move Handlebars conditional to wrap the full `effort:` key-value line
instead of just the value. Prevents `effort: ` (empty value) in
non-swarm mode. Updated all 7 agent templates and CLAUDE.md example.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
No reason to use low effort on a fast model (haiku). Also wrap entire
effort line in conditionals when there's no else branch to avoid
empty `effort: ` values in non-swarm mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Restore swarmModel explanation and Important note in docs
- Remove implementation details from user-facing docs
- Change complexity-evaluator swarm effort from low to high
- Wrap effort lines in full conditionals (no empty values)
- Add AgentManager integration tests with real template files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@acreeger acreeger marked this pull request as ready for review March 23, 2026 03:43
@acreeger
Copy link
Copy Markdown
Collaborator Author

iloom Session Summary

Key Themes:

  • Template substitution now runs before frontmatter parsing, enabling Handlebars conditionals in YAML fields like model and effort.
  • Swarm-specific defaults moved from hardcoded maps in SwarmSetupService into agent template frontmatter — defaults are now declarative and self-documenting.
  • The override chain is unchanged: user settings always win over frontmatter defaults, in both swarm and non-swarm mode.

Session Details (click to expand)

Key Insights

  • AgentManager.loadAgents() previously applied template substitution after parseMarkdownAgent(), meaning frontmatter fields were always static. Substitution now runs on raw file content before YAML parsing, so model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}} works correctly.
  • defaultSwarmModels and defaultSwarmEfforts in SwarmSetupService were the only place to look up swarm-mode agent defaults — non-obvious, easy to miss when adding new agents. These are now declared in each agent's frontmatter.
  • When a conditional has no else branch (e.g., effort: {{#if SWARM_MODE}}high{{/if}}), it resolves to effort: (empty value) in non-swarm mode, which the parser treats as set-but-empty — not the same as unset. The correct pattern wraps the entire line: {{#if SWARM_MODE}}effort: high{{/if}}, which resolves to a blank line (ignored by the parser).
  • Object.assign(templateVariables, ...) mutated the caller's object. Fixed by spreading into a local enrichedVariables copy. Tests that were asserting mutation needed updating to spy on substituteVariables instead.
  • The apparent "behavior change risk" (users who set model but not swarmModel seeing different swarm behavior) is a non-issue. The frontmatter conditionals produce mode-specific defaults, so the effective override chain is identical to before: user settings > per-mode agent default > Claude Code default.
  • Implementation details (env var names, internal file paths, subagent_type, confidence score thresholds) should not appear in user-facing docs. The dividing line: if it affects how iloom operates from the user's perspective, keep it; if it names internal files or code mechanics users never touch, it belongs in a CLAUDE.md.

Decisions Made

  • Wrap full effort: line in conditional, not just the value. {{#if SWARM_MODE}}effort: high{{/if}} is the correct pattern for effort fields with no non-swarm default, avoiding the empty-value footgun.
  • Complexity evaluator swarm effort changed from low to high. The agent uses haiku (fast, cheap) so there's no reason to constrain reasoning depth — use high effort to get the most out of the model.
  • Integration test added (AgentManager.integration.test.ts). Tests the full pipeline with real template files and a real PromptTemplateManager, verifying model/effort resolution in both swarm and non-swarm mode. This catches any future drift between template content and expected defaults.
  • User-facing explanation of swarmModel behavior kept in docs. The two-paragraph explanation ("swarmModel overrides in swarm mode... costs scale quickly so choices should be explicit") is user-relevant behavior, not an implementation detail — it belongs in iloom-commands.md.

Challenges Resolved

  • Tests asserting mutation broke after the Object.assign fix. Three tests in the "delegates review variables to buildReviewTemplateVariables" suite checked that templateVariables was mutated after loadAgents. After moving to a local copy, these tests needed to spy on manager['templateManager'].substituteVariables and inspect the arguments passed to it rather than checking the original object.
  • "Important" and swarmModel explanation paragraphs were removed along with the actual implementation-detail text. These were user-facing and needed to be restored in their original position (after the config JSON example, before the results table) — not before the example where context is missing.

Lessons Learned

  • When moving an Object.assign to a spread copy, also audit the tests — tests that assert side effects on the original object will silently start failing with undefined rather than a clear type error.
  • The CLAUDE.md in a component directory is the right home for override-chain documentation, internal mechanism descriptions, and Handlebars conventions. iloom-commands.md is strictly for user-visible configuration and behavior.
  • A conditional-only frontmatter line ({{#if X}}key: val{{/if}}) produces a blank line when falsy, which is fine for YAML. A value-only conditional (key: {{#if X}}val{{/if}}) produces key: (empty string) when falsy, which is parsed as a set field — a subtle but important difference.

Generated with 🤖❤️ by iloom.ai

@acreeger acreeger merged commit b5903ff into main Mar 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Refactor: Apply template substitution to agent frontmatter and extract swarm defaults to agent files

1 participant