From e61c91f4e43302daf2ef56ccdbc97447c468dc61 Mon Sep 17 00:00:00 2001 From: Adam Creeger Date: Sun, 22 Mar 2026 23:22:32 -0400 Subject: [PATCH 1/6] feat(agents): apply template substitution to agent frontmatter, move 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 --- docs/iloom-commands.md | 12 +- src/lib/AgentManager.test.ts | 166 ++++++++++++++++-- src/lib/AgentManager.ts | 29 ++- src/lib/SwarmSetupService.test.ts | 53 +++--- src/lib/SwarmSetupService.ts | 38 +--- templates/agents/CLAUDE.md | 19 +- templates/agents/iloom-code-reviewer.md | 3 +- templates/agents/iloom-framework-detector.md | 1 + .../agents/iloom-issue-analyze-and-plan.md | 1 + templates/agents/iloom-issue-analyzer.md | 1 + .../iloom-issue-complexity-evaluator.md | 1 + templates/agents/iloom-issue-enhancer.md | 3 +- templates/agents/iloom-issue-implementer.md | 3 +- templates/agents/iloom-issue-planner.md | 3 +- templates/agents/iloom-wave-verifier.md | 1 + 15 files changed, 236 insertions(+), 98 deletions(-) diff --git a/docs/iloom-commands.md b/docs/iloom-commands.md index f62dfded..82f379e8 100644 --- a/docs/iloom-commands.md +++ b/docs/iloom-commands.md @@ -1957,17 +1957,17 @@ Each agent supports a `swarmModel` field for a clean, per-agent swarm model over } ``` -If `swarmModel` is set for an agent, it overrides the agent's model in swarm mode. If no `swarmModel` is set, the Swarm Quality Mode defaults apply (see below) — not the agent's base `model`. This separation is intentional: swarms run many agents in parallel and costs scale quickly, so swarm model choices should always be explicit. +If `swarmModel` is set for an agent, it overrides the agent's model in swarm mode. If no `swarmModel` is set, the agent uses its frontmatter default — which may differ between swarm and non-swarm modes via `{{#if SWARM_MODE}}` conditionals in the template. If the user has also set a base `model` in settings, that takes precedence over the frontmatter default in both modes. -**Important:** Changing an agent's `model` only affects non-swarm mode (single-issue looms via `il start`). To change an agent's model in swarm mode, use `swarmModel` or choose a different Swarm Quality Mode. +**Important:** Agent templates declare mode-specific defaults using Handlebars conditionals (e.g., `model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}}`). Setting `model` in agent settings overrides the frontmatter default in both swarm and non-swarm mode. To override the model only in swarm mode, use `swarmModel`. With the configuration above: | Agent | Non-swarm mode | Swarm mode | |-------|---------------|------------| -| `iloom-issue-implementer` | `opus` | `sonnet` (swarmModel) | -| `iloom-issue-complexity-evaluator` | `haiku` | `haiku` (swarmModel) | -| `iloom-issue-analyzer` | `.md` default | `opus` (Balanced mode default) | +| `iloom-issue-implementer` | `opus` (settings) | `sonnet` (swarmModel) | +| `iloom-issue-complexity-evaluator` | `haiku` (settings) | `haiku` (swarmModel) | +| `iloom-issue-analyzer` | `opus` (frontmatter default) | `opus` (frontmatter swarm default) | **Example using the `--set` flag:** @@ -2138,7 +2138,7 @@ Each agent supports `effort` and `swarmEffort` fields, following the same patter **Default Swarm Effort Levels:** -When no user configuration is provided, swarm agents use these defaults: +When no user configuration is provided, swarm agents use effort defaults declared in their template frontmatter via `{{#if SWARM_MODE}}` conditionals: | Agent | Default Swarm Effort | |-------|---------------------| diff --git a/src/lib/AgentManager.test.ts b/src/lib/AgentManager.test.ts index 62ddabda..9a13d73a 100644 --- a/src/lib/AgentManager.test.ts +++ b/src/lib/AgentManager.test.ts @@ -953,16 +953,24 @@ Prompt` } const templateVariables = {} as Record + + // Spy on substituteVariables to capture the enriched variables + const substituteSpy = vi.spyOn(manager['templateManager'], 'substituteVariables') + await manager.loadAgents(settings as never, templateVariables) - // Verify key review fields are populated (detailed logic tested in PromptTemplateManager.test.ts) - expect(templateVariables.REVIEW_ENABLED).toBe(true) - expect(templateVariables.ARTIFACT_REVIEW_ENABLED).toBe(true) - expect(templateVariables.HAS_ARTIFACT_REVIEW_CLAUDE).toBe(true) - expect(templateVariables.HAS_ARTIFACT_REVIEW_GEMINI).toBe(true) - expect(templateVariables.ENHANCER_REVIEW_ENABLED).toBe(true) - expect(templateVariables.PLANNER_REVIEW_ENABLED).toBe(true) - expect(templateVariables.ANALYZER_REVIEW_ENABLED).toBe(false) + // Verify enriched variables were passed to substituteVariables (not the original object) + const enrichedVars = substituteSpy.mock.calls[0][1] as Record + expect(enrichedVars.REVIEW_ENABLED).toBe(true) + expect(enrichedVars.ARTIFACT_REVIEW_ENABLED).toBe(true) + expect(enrichedVars.HAS_ARTIFACT_REVIEW_CLAUDE).toBe(true) + expect(enrichedVars.HAS_ARTIFACT_REVIEW_GEMINI).toBe(true) + expect(enrichedVars.ENHANCER_REVIEW_ENABLED).toBe(true) + expect(enrichedVars.PLANNER_REVIEW_ENABLED).toBe(true) + expect(enrichedVars.ANALYZER_REVIEW_ENABLED).toBe(false) + + // Original templateVariables should NOT be mutated + expect(templateVariables.REVIEW_ENABLED).toBeUndefined() }) it('should not populate review fields when templateVariables is not provided', async () => { @@ -1011,10 +1019,13 @@ Prompt` } const templateVariables = { SWARM_MODE: true } as Record + const substituteSpy = vi.spyOn(manager['templateManager'], 'substituteVariables') + await manager.loadAgents(settings as never, templateVariables) // When SWARM_MODE is true, swarmReview: false should override review: true - expect(templateVariables.PLANNER_REVIEW_ENABLED).toBe(false) + const enrichedVars = substituteSpy.mock.calls[0][1] as Record + expect(enrichedVars.PLANNER_REVIEW_ENABLED).toBe(false) }) it('should pass false for isSwarmMode when SWARM_MODE is not in templateVariables', async () => { @@ -1038,10 +1049,145 @@ Prompt` } const templateVariables = {} as Record + const substituteSpy = vi.spyOn(manager['templateManager'], 'substituteVariables') + await manager.loadAgents(settings as never, templateVariables) // When SWARM_MODE is not set, review: true should be used (swarmReview ignored) - expect(templateVariables.PLANNER_REVIEW_ENABLED).toBe(true) + const enrichedVars = substituteSpy.mock.calls[0][1] as Record + expect(enrichedVars.PLANNER_REVIEW_ENABLED).toBe(true) + }) + }) + + describe('template substitution in frontmatter', () => { + it('should resolve Handlebars expressions in frontmatter model field before parsing', async () => { + const mockTemplateManager = { + substituteVariables: vi.fn((content: string, vars: Record) => { + // Simulate Handlebars: resolve {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}} + return content.replace( + /\{\{#if SWARM_MODE\}\}sonnet\{\{else\}\}opus\{\{\/if\}\}/g, + vars.SWARM_MODE ? 'sonnet' : 'opus', + ) + }), + } + + const managerWithTemplate = new AgentManager('templates/agents', mockTemplateManager as never) + + vi.mocked(fg).mockResolvedValueOnce(['agent.md']) + vi.mocked(readFile).mockResolvedValueOnce(`--- +name: test-agent +description: Test agent +model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}} +--- + +Prompt content`) + + const result = await managerWithTemplate.loadAgents(undefined, { SWARM_MODE: true }) + + expect(result['test-agent'].model).toBe('sonnet') + expect(mockTemplateManager.substituteVariables).toHaveBeenCalled() + }) + + it('should resolve to non-swarm defaults when SWARM_MODE is falsy', async () => { + const mockTemplateManager = { + substituteVariables: vi.fn((content: string, vars: Record) => { + return content.replace( + /\{\{#if SWARM_MODE\}\}sonnet\{\{else\}\}opus\{\{\/if\}\}/g, + vars.SWARM_MODE ? 'sonnet' : 'opus', + ) + }), + } + + const managerWithTemplate = new AgentManager('templates/agents', mockTemplateManager as never) + + vi.mocked(fg).mockResolvedValueOnce(['agent.md']) + vi.mocked(readFile).mockResolvedValueOnce(`--- +name: test-agent +description: Test agent +model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}} +--- + +Prompt content`) + + const result = await managerWithTemplate.loadAgents(undefined, {}) + + expect(result['test-agent'].model).toBe('opus') + }) + + it('should resolve Handlebars expressions in frontmatter effort field', async () => { + const mockTemplateManager = { + substituteVariables: vi.fn((content: string, vars: Record) => { + return content.replace( + /\{\{#if SWARM_MODE\}\}medium\{\{\/if\}\}/g, + vars.SWARM_MODE ? 'medium' : '', + ) + }), + } + + const managerWithTemplate = new AgentManager('templates/agents', mockTemplateManager as never) + + vi.mocked(fg).mockResolvedValueOnce(['agent.md']) + vi.mocked(readFile).mockResolvedValueOnce(`--- +name: test-agent +description: Test agent +model: sonnet +effort: {{#if SWARM_MODE}}medium{{/if}} +--- + +Prompt content`) + + const result = await managerWithTemplate.loadAgents(undefined, { SWARM_MODE: true }) + + expect(result['test-agent'].effort).toBe('medium') + }) + + it('should handle empty effort when SWARM_MODE is falsy', async () => { + const mockTemplateManager = { + substituteVariables: vi.fn((content: string, vars: Record) => { + return content.replace( + /\{\{#if SWARM_MODE\}\}medium\{\{\/if\}\}/g, + vars.SWARM_MODE ? 'medium' : '', + ) + }), + } + + const managerWithTemplate = new AgentManager('templates/agents', mockTemplateManager as never) + + vi.mocked(fg).mockResolvedValueOnce(['agent.md']) + vi.mocked(readFile).mockResolvedValueOnce(`--- +name: test-agent +description: Test agent +model: sonnet +effort: {{#if SWARM_MODE}}medium{{/if}} +--- + +Prompt content`) + + const result = await managerWithTemplate.loadAgents(undefined, {}) + + // Empty effort string should not pass isEffortLevel check, so effort is undefined + expect(result['test-agent'].effort).toBeUndefined() + }) + + it('should not apply template substitution when templateVariables is not provided', async () => { + const mockTemplateManager = { + substituteVariables: vi.fn(), + } + + const managerWithTemplate = new AgentManager('templates/agents', mockTemplateManager as never) + + vi.mocked(fg).mockResolvedValueOnce(['agent.md']) + vi.mocked(readFile).mockResolvedValueOnce(`--- +name: test-agent +description: Test agent +model: sonnet +--- + +Prompt content`) + + await managerWithTemplate.loadAgents() + + expect(mockTemplateManager.substituteVariables).not.toHaveBeenCalled() }) }) diff --git a/src/lib/AgentManager.ts b/src/lib/AgentManager.ts index 7384227e..6ff4519c 100644 --- a/src/lib/AgentManager.ts +++ b/src/lib/AgentManager.ts @@ -87,13 +87,26 @@ export class AgentManager { caseSensitiveMatch: false, }) + // Enrich template variables with review config before substitution + // (must happen before the loop so all agents get the same enriched variables) + // Use a local copy to avoid mutating the caller's object + const enrichedVariables = templateVariables + ? { ...templateVariables, ...buildReviewTemplateVariables(!!templateVariables.SWARM_MODE, settings?.agents) } + : undefined + const agents: AgentConfigs = {} for (const filename of agentFiles) { const agentPath = path.join(this.agentDir, filename) try { - const content = await readFile(agentPath, 'utf-8') + let content = await readFile(agentPath, 'utf-8') + + // Apply template substitution to raw content BEFORE parsing frontmatter + // This allows frontmatter fields (model, effort) to use Handlebars expressions + if (enrichedVariables) { + content = this.templateManager.substituteVariables(content, enrichedVariables) + } // Parse markdown with frontmatter const parsed = this.parseMarkdownAgent(content, filename) @@ -110,20 +123,6 @@ export class AgentManager { } } - // Apply template variable substitution to agent prompts if variables provided - if (templateVariables) { - // Extract review config from settings and add to template variables - Object.assign(templateVariables, buildReviewTemplateVariables(!!templateVariables.SWARM_MODE, settings?.agents)) - - for (const [agentName, agentConfig] of Object.entries(agents)) { - agents[agentName] = { - ...agentConfig, - prompt: this.templateManager.substituteVariables(agentConfig.prompt, templateVariables), - } - logger.debug(`Applied template substitution to agent: ${agentName}`) - } - } - // Apply settings overrides if provided if (settings?.agents) { for (const [agentName, agentSettings] of Object.entries(settings.agents)) { diff --git a/src/lib/SwarmSetupService.test.ts b/src/lib/SwarmSetupService.test.ts index a0a4b170..010468b2 100644 --- a/src/lib/SwarmSetupService.test.ts +++ b/src/lib/SwarmSetupService.test.ts @@ -168,46 +168,48 @@ describe('SwarmSetupService', () => { expect(getAgentContent('iloom-swarm-issue-implementer')).toContain('model: haiku') }) - it('applies default swarmModel (sonnet) for agents in default map when no swarmModel configured', async () => { + it('uses model from loadAgents (frontmatter swarm conditional) when no swarmModel configured', async () => { vi.mocked(mockSettingsManager.loadSettings).mockResolvedValueOnce({ agents: { 'iloom-issue-implementer': { model: 'opus' }, }, } as unknown as IloomSettings) + // loadAgents now returns swarm-appropriate model from frontmatter conditional vi.mocked(mockAgentManager.loadAgents).mockResolvedValueOnce({ 'iloom-issue-implementer': { description: 'Implementer agent', prompt: 'Implement things', tools: ['Bash', 'Read'], - model: 'opus', + model: 'sonnet', // frontmatter resolves to sonnet in SWARM_MODE }, }) await service.renderSwarmAgents('/Users/dev/project-epic-610') - // iloom-issue-implementer is in the default swarmModel map, so it should be sonnet + // Model comes from loadAgents (frontmatter swarm conditional), not a hardcoded map expect(getAgentContent('iloom-swarm-issue-implementer')).toContain('model: sonnet') }) - it('applies default swarmModel (opus) for analyzer agent', async () => { + it('uses model from loadAgents (frontmatter) for analyzer agent', async () => { vi.mocked(mockSettingsManager.loadSettings).mockResolvedValueOnce({} as unknown as IloomSettings) + // Analyzer frontmatter declares model: opus (same in both modes) vi.mocked(mockAgentManager.loadAgents).mockResolvedValueOnce({ 'iloom-issue-analyzer': { description: 'Analyzer agent', prompt: 'Analyze things', - model: 'sonnet', + model: 'opus', }, }) await service.renderSwarmAgents('/Users/dev/project-epic-610') - // iloom-issue-analyzer is in the default swarmModel map as opus + // Model comes from loadAgents (frontmatter), which is opus for analyzer expect(getAgentContent('iloom-swarm-issue-analyzer')).toContain('model: opus') }) - it('non-swarm model override does not affect swarm mode when default map covers the agent', async () => { + it('user model setting applies in swarm mode when no swarmModel is set', async () => { vi.mocked(mockSettingsManager.loadSettings).mockResolvedValueOnce({ agents: { 'iloom-issue-implementer': { model: 'haiku' }, @@ -216,6 +218,8 @@ describe('SwarmSetupService', () => { }, } as unknown as IloomSettings) + // loadAgents now returns agents with user-overridden model (haiku) + // since settings.model overrides frontmatter in loadAgents vi.mocked(mockAgentManager.loadAgents).mockResolvedValueOnce({ 'iloom-issue-implementer': { description: 'Implementer agent', @@ -236,11 +240,11 @@ describe('SwarmSetupService', () => { await service.renderSwarmAgents('/Users/dev/project-epic-610') - // Even though non-swarm model is set to haiku, swarm defaults override - // Check agent files (which carry the full prompt and model) - expect(getAgentContent('iloom-swarm-issue-implementer')).toContain('model: sonnet') - expect(getAgentContent('iloom-swarm-issue-analyzer')).toContain('model: opus') - expect(getAgentContent('iloom-swarm-issue-planner')).toContain('model: sonnet') + // Without swarmModel set, user's model setting now applies in swarm mode + // (previously hardcoded defaults would override) + expect(getAgentContent('iloom-swarm-issue-implementer')).toContain('model: haiku') + expect(getAgentContent('iloom-swarm-issue-analyzer')).toContain('model: haiku') + expect(getAgentContent('iloom-swarm-issue-planner')).toContain('model: haiku') }) it('explicit swarmModel overrides both non-swarm model and default map', async () => { @@ -333,34 +337,36 @@ describe('SwarmSetupService', () => { return call ? (call[1] as string) : undefined } - it('applies default swarmEffort for agents in default effort map', async () => { + it('uses effort from loadAgents (frontmatter swarm conditionals) when no user override', async () => { vi.mocked(mockSettingsManager.loadSettings).mockResolvedValueOnce({} as unknown as IloomSettings) + // loadAgents now returns agents with effort from frontmatter conditionals vi.mocked(mockAgentManager.loadAgents).mockResolvedValueOnce({ 'iloom-issue-analyzer': { description: 'Analyzer agent', prompt: 'Analyze things', - model: 'sonnet', + model: 'opus', + effort: 'high', // from frontmatter: {{#if SWARM_MODE}}high{{/if}} }, 'iloom-issue-implementer': { description: 'Implementer agent', prompt: 'Implement things', model: 'sonnet', + effort: 'medium', // from frontmatter: {{#if SWARM_MODE}}medium{{/if}} }, 'iloom-issue-complexity-evaluator': { description: 'Evaluator agent', prompt: 'Evaluate things', - model: 'sonnet', + model: 'haiku', + effort: 'low', // from frontmatter: {{#if SWARM_MODE}}low{{/if}} }, }) await service.renderSwarmAgents('/Users/dev/project-epic-610') - // Analyzer default effort is 'high' + // Effort values come from loadAgents (frontmatter conditionals) expect(getAgentContent('iloom-swarm-issue-analyzer')).toContain('effort: high') - // Implementer default effort is 'medium' expect(getAgentContent('iloom-swarm-issue-implementer')).toContain('effort: medium') - // Complexity evaluator default effort is 'low' expect(getAgentContent('iloom-swarm-issue-complexity-evaluator')).toContain('effort: low') }) @@ -384,7 +390,7 @@ describe('SwarmSetupService', () => { expect(getAgentContent('iloom-swarm-issue-implementer')).toContain('effort: max') }) - it('explicit swarmEffort overrides default effort map', async () => { + it('explicit swarmEffort overrides frontmatter effort', async () => { vi.mocked(mockSettingsManager.loadSettings).mockResolvedValueOnce({ agents: { 'iloom-issue-analyzer': { swarmEffort: 'low' }, @@ -395,24 +401,27 @@ describe('SwarmSetupService', () => { 'iloom-issue-analyzer': { description: 'Analyzer agent', prompt: 'Analyze things', - model: 'sonnet', + model: 'opus', + effort: 'high', // from frontmatter swarm conditional }, }) await service.renderSwarmAgents('/Users/dev/project-epic-610') - // Default for analyzer is 'high' but explicit swarmEffort 'low' should win + // Frontmatter resolves to 'high' but explicit swarmEffort 'low' should win expect(getAgentContent('iloom-swarm-issue-analyzer')).toContain('effort: low') }) it('includes effort in skill wrapper frontmatter', async () => { vi.mocked(mockSettingsManager.loadSettings).mockResolvedValueOnce({} as unknown as IloomSettings) + // loadAgents now returns agents with effort from frontmatter conditionals vi.mocked(mockAgentManager.loadAgents).mockResolvedValueOnce({ 'iloom-issue-implementer': { description: 'Implementer agent', prompt: 'Implement things', model: 'sonnet', + effort: 'medium', // from frontmatter: {{#if SWARM_MODE}}medium{{/if}} }, }) @@ -422,7 +431,7 @@ describe('SwarmSetupService', () => { (call) => (call[0] as string).endsWith('SKILL.md'), ) const skillContent = skillFileCall![1] as string - // Implementer default effort is 'medium' + // Effort comes from frontmatter swarm conditional expect(skillContent).toContain('effort: medium') }) }) diff --git a/src/lib/SwarmSetupService.ts b/src/lib/SwarmSetupService.ts index 906e2400..ccbf6944 100644 --- a/src/lib/SwarmSetupService.ts +++ b/src/lib/SwarmSetupService.ts @@ -1,8 +1,7 @@ import path from 'path' import fs from 'fs-extra' import { AgentManager } from './AgentManager.js' -import { SettingsManager, type ClaudeModel } from './SettingsManager.js' -import type { EffortLevel } from '../types/index.js' +import { SettingsManager } from './SettingsManager.js' import { PromptTemplateManager, buildReviewTemplateVariables, type TemplateVariables } from './PromptTemplateManager.js' import { IssueManagementProviderFactory } from '../mcp/IssueManagementProviderFactory.js' import { getLogger } from '../utils/logger-context.js' @@ -62,41 +61,14 @@ export class SwarmSetupService { const agents = await this.agentManager.loadAgents(settings, templateVariables) - // Default swarmModel map for "Balanced" mode. All swarm phase agents are - // listed explicitly so that swarm mode never accidentally inherits a - // non-swarm model override. User-configured swarmModel values always - // take precedence. - const defaultSwarmModels: Record = { - 'iloom-issue-analyzer': 'opus', - 'iloom-issue-analyze-and-plan': 'opus', - 'iloom-issue-planner': 'sonnet', - 'iloom-issue-implementer': 'sonnet', - 'iloom-issue-enhancer': 'sonnet', - 'iloom-code-reviewer': 'sonnet', - 'iloom-issue-complexity-evaluator': 'haiku', - } - - // Default swarmEffort map for swarm phase agents. Agents that do - // deep analysis/planning get higher effort, while implementation - // and review agents use medium, and simple evaluators use low. - const defaultSwarmEfforts: Record = { - 'iloom-issue-analyzer': 'high', - 'iloom-issue-analyze-and-plan': 'high', - 'iloom-issue-planner': 'high', - 'iloom-issue-implementer': 'medium', - 'iloom-issue-enhancer': 'medium', - 'iloom-code-reviewer': 'medium', - 'iloom-issue-complexity-evaluator': 'low', - } - - // Apply per-agent swarmModel and swarmEffort overrides (user-configured takes precedence over defaults) + // Apply per-agent swarmModel and swarmEffort overrides from user settings. + // Default swarm model/effort values are now declared in agent template frontmatter + // using {{#if SWARM_MODE}} conditionals, so only user overrides are needed here. for (const [agentName, agentConfig] of Object.entries(agents)) { let updated = agentConfig const userSwarmModel = settings?.agents?.[agentName]?.swarmModel if (userSwarmModel) { updated = { ...updated, model: userSwarmModel } - } else if (defaultSwarmModels[agentName]) { - updated = { ...updated, model: defaultSwarmModels[agentName] } } const userSwarmEffort = settings?.agents?.[agentName]?.swarmEffort @@ -105,8 +77,6 @@ export class SwarmSetupService { updated = { ...updated, effort: userSwarmEffort } } else if (userBaseEffort) { updated = { ...updated, effort: userBaseEffort } - } else if (!updated.effort && defaultSwarmEfforts[agentName]) { - updated = { ...updated, effort: defaultSwarmEfforts[agentName] } } agents[agentName] = updated } diff --git a/templates/agents/CLAUDE.md b/templates/agents/CLAUDE.md index e542e416..659c267d 100644 --- a/templates/agents/CLAUDE.md +++ b/templates/agents/CLAUDE.md @@ -34,11 +34,14 @@ complexity-evaluator → analyzer (or analyze-and-plan for SIMPLE) → planner ## YAML Frontmatter Format +Frontmatter fields support Handlebars expressions because template substitution runs on the raw file content **before** YAML parsing. This allows conditional defaults based on execution mode (e.g., swarm vs regular). + ```yaml --- name: iloom-issue-implementer description: One-line description of this agent's role -model: opus # Default model (can be overridden by settings) +model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}} +effort: {{#if SWARM_MODE}}medium{{/if}} color: green # Optional: terminal color for status display tools: # Optional: restrict available tools - Read @@ -49,15 +52,17 @@ tools: # Optional: restrict available tools Agent prompt content in Markdown... ``` -## Model Override Rules +When `SWARM_MODE` is falsy, conditional-only values (like `effort: {{#if SWARM_MODE}}medium{{/if}}`) resolve to an empty string, which is treated as "not set" — the agent inherits the session default. + +## Model and Effort Override Rules -Agent models resolve in this order (highest priority first): +Agent models and effort resolve in this order (highest priority first): 1. **CLI flag**: `--set agents.iloom-issue-implementer.model=sonnet` -2. **Settings**: `settings.agents["iloom-issue-implementer"].model` -3. **Swarm model defaults**: `SwarmSetupService` applies swarm-specific defaults (e.g., haiku for complexity evaluator) -4. **Frontmatter**: The `model` field in the YAML above +2. **Settings**: `settings.agents["iloom-issue-implementer"].model` / `.effort` +3. **Swarm settings**: `settings.agents["iloom-issue-implementer"].swarmModel` / `.swarmEffort` (applied by `SwarmSetupService` in swarm mode) +4. **Frontmatter**: The `model` / `effort` field in YAML above (which may use `{{#if SWARM_MODE}}` conditionals for mode-specific defaults) -Do NOT hardcode model choices in agent templates to work around performance issues — use the settings system instead. +Swarm-specific model and effort defaults are declared directly in agent template frontmatter using `{{#if SWARM_MODE}}` conditionals, not in hardcoded maps. ## Swarm Agent Rendering diff --git a/templates/agents/iloom-code-reviewer.md b/templates/agents/iloom-code-reviewer.md index 099cd93f..c6dda105 100644 --- a/templates/agents/iloom-code-reviewer.md +++ b/templates/agents/iloom-code-reviewer.md @@ -1,7 +1,8 @@ --- name: iloom-code-reviewer description: Use this agent to review code changes. -model: opus +model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}} +effort: {{#if SWARM_MODE}}medium{{/if}} color: cyan --- diff --git a/templates/agents/iloom-framework-detector.md b/templates/agents/iloom-framework-detector.md index ced5c1b3..72d454b1 100644 --- a/templates/agents/iloom-framework-detector.md +++ b/templates/agents/iloom-framework-detector.md @@ -3,6 +3,7 @@ name: iloom-framework-detector description: Use this agent to detect a project's language and framework, then generate appropriate build/test/dev scripts for non-Node.js projects. The agent creates `.iloom/package.iloom.json` with shell commands tailored to the detected stack. Use this for Python, Rust, Ruby, Go, and other non-Node.js projects that don't have a package.json. color: cyan model: opus +effort: high --- You are Claude, a framework detection specialist. Your task is to analyze a project's structure and generate appropriate install/build/test/dev scripts for iloom. diff --git a/templates/agents/iloom-issue-analyze-and-plan.md b/templates/agents/iloom-issue-analyze-and-plan.md index 802a3eaa..342142fc 100644 --- a/templates/agents/iloom-issue-analyze-and-plan.md +++ b/templates/agents/iloom-issue-analyze-and-plan.md @@ -3,6 +3,7 @@ name: iloom-issue-analyze-and-plan description: Combined analysis and planning agent for SIMPLE tasks. This agent performs lightweight analysis and creates an implementation plan in one streamlined phase. Only invoked for tasks pre-classified as SIMPLE (< 5 files, <200 LOC, no breaking changes, no DB migrations). Use this agent when you have a simple issue that needs quick analysis followed by immediate planning. color: teal model: opus +effort: {{#if SWARM_MODE}}high{{/if}} --- {{#if SWARM_MODE}} diff --git a/templates/agents/iloom-issue-analyzer.md b/templates/agents/iloom-issue-analyzer.md index 70973d5c..bcb415bf 100644 --- a/templates/agents/iloom-issue-analyzer.md +++ b/templates/agents/iloom-issue-analyzer.md @@ -3,6 +3,7 @@ name: iloom-issue-analyzer description: Use this agent when you need to analyze and research issues, bugs, or enhancement requests. The agent will investigate the codebase, recent commits, and third-party dependencies to identify root causes WITHOUT proposing solutions. Ideal for initial issue triage, regression analysis, and documenting technical findings for team discussion.\n\nExamples:\n\nContext: User wants to analyze a newly reported bug in issue #42\nuser: "Please analyze issue #42 - users are reporting that the login button doesn't work on mobile"\nassistant: "I'll use the issue-analyzer agent to investigate this issue and document my findings."\n\nSince this is a request to analyze an issue, use the Task tool to launch the issue-analyzer agent to research the problem.\n\n\n\nContext: User needs to understand a regression that appeared after recent changes\nuser: "Can you look into issue #78? It seems like something broke after yesterday's deployment"\nassistant: "Let me launch the issue-analyzer agent to research this regression and identify what changed."\n\nThe user is asking for issue analysis and potential regression investigation, so use the issue-analyzer agent.\n\n color: pink model: opus +effort: {{#if SWARM_MODE}}high{{/if}} --- {{#if SWARM_MODE}} diff --git a/templates/agents/iloom-issue-complexity-evaluator.md b/templates/agents/iloom-issue-complexity-evaluator.md index 269aac7a..5cdb4c94 100644 --- a/templates/agents/iloom-issue-complexity-evaluator.md +++ b/templates/agents/iloom-issue-complexity-evaluator.md @@ -3,6 +3,7 @@ name: iloom-issue-complexity-evaluator description: Use this agent when you need to quickly assess the complexity of an issue before deciding on the appropriate workflow. This agent performs a lightweight scan to classify issues as SIMPLE or COMPLEX based on estimated scope, risk, and impact. Runs first before any detailed analysis or planning. color: orange model: haiku +effort: {{#if SWARM_MODE}}low{{/if}} --- {{#if SWARM_MODE}} diff --git a/templates/agents/iloom-issue-enhancer.md b/templates/agents/iloom-issue-enhancer.md index 549c95ac..5f3af9dc 100644 --- a/templates/agents/iloom-issue-enhancer.md +++ b/templates/agents/iloom-issue-enhancer.md @@ -2,7 +2,8 @@ name: iloom-issue-enhancer description: Use this agent when you need to analyze bug or enhancement reports from a Product Manager perspective. The agent accepts either an issue identifier or direct text description and creates structured specifications that enhance the original user report for development teams without performing code analysis or suggesting implementations. Ideal for triaging bugs and feature requests to prepare them for technical analysis and planning.\n\nExamples:\n\nContext: User wants to triage and enhance a bug report from issue tracker\nuser: "Please analyze issue #42 - the login button doesn't work on mobile"\nassistant: "I'll use the iloom-issue-enhancer agent to analyze this bug report and create a structured specification."\n\nSince this is a request to triage and structure a bug report from a user experience perspective, use the iloom-issue-enhancer agent.\n\n\n\nContext: User needs to enhance an enhancement request that lacks detail\nuser: "Can you improve the description on issue #78? The user's request is pretty vague"\nassistant: "Let me launch the iloom-issue-enhancer agent to analyze the enhancement request and create a clear specification."\n\nThe user is asking for enhancement report structuring, so use the iloom-issue-enhancer agent.\n\n\n\nContext: User provides direct description without issue identifier\nuser: "Analyze this bug: Users report that the search function returns no results when they include special characters like & or # in their query"\nassistant: "I'll use the iloom-issue-enhancer agent to create a structured specification for this bug report."\n\nEven though no issue identifier was provided, the iloom-issue-enhancer agent can analyze the direct description and create a structured specification.\n\n\n\nContext: An issue has been labeled as a valid baug and needs structured analysis\nuser: "Structure issue #123 that was just labeled as a triaged bug"\nassistant: "I'll use the iloom-issue-enhancer agent to create a comprehensive bug specification."\n\nThe issue needs Product Manager-style analysis and structuring, so use the iloom-issue-enhancer agent.\n\n color: purple -model: opus +model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}} +effort: {{#if SWARM_MODE}}medium{{/if}} --- {{#if SWARM_MODE}} diff --git a/templates/agents/iloom-issue-implementer.md b/templates/agents/iloom-issue-implementer.md index 239cfd08..174605b1 100644 --- a/templates/agents/iloom-issue-implementer.md +++ b/templates/agents/iloom-issue-implementer.md @@ -1,7 +1,8 @@ --- name: iloom-issue-implementer description: Use this agent when you need to implement an issue exactly as specified in its comments and description. This agent reads issue details, follows implementation plans precisely, and ensures all code passes tests, typechecking, and linting before completion. Examples:\n\n\nContext: User wants to implement a specific issue.\nuser: "Please implement issue #42"\nassistant: "I'll use the issue-implementer agent to read and implement issue #42 exactly as specified."\n\nSince the user is asking to implement an issue, use the Task tool to launch the issue-implementer agent.\n\n\n\n\nContext: User references an issue that needs implementation.\nuser: "Can you work on the authentication issue we discussed in #15?"\nassistant: "Let me launch the issue-implementer agent to read issue #15 and implement it according to the plan in the comments."\n\nThe user is referencing a specific issue number, so use the issue-implementer agent to handle the implementation.\n\n -model: opus +model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}} +effort: {{#if SWARM_MODE}}medium{{/if}} color: green --- diff --git a/templates/agents/iloom-issue-planner.md b/templates/agents/iloom-issue-planner.md index a7271f57..1496b5f9 100644 --- a/templates/agents/iloom-issue-planner.md +++ b/templates/agents/iloom-issue-planner.md @@ -2,7 +2,8 @@ name: iloom-issue-planner description: Use this agent when you need to analyze issues and create detailed implementation plans. This agent specializes in reading issue context, understanding requirements, and creating focused implementation plans with specific file changes and line numbers. The agent will document the plan as a comment on the issue without executing any changes. Examples: Context: The user wants detailed implementation planning for an issue.\nuser: "Analyze issue #42 and create an implementation plan"\nassistant: "I'll use the issue-planner agent to analyze the issue and create a detailed implementation plan"\nSince the user wants issue analysis and implementation planning, use the issue-planner agent. Context: The user needs a plan for implementing a feature described in an issue.\nuser: "Read issue #15 and plan out what needs to be changed"\nassistant: "Let me use the issue-planner agent to analyze the issue and document a comprehensive implementation plan"\nThe user needs issue analysis and planning, so the issue-planner agent is the right choice. color: blue -model: opus +model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}} +effort: {{#if SWARM_MODE}}high{{/if}} --- {{#if SWARM_MODE}} diff --git a/templates/agents/iloom-wave-verifier.md b/templates/agents/iloom-wave-verifier.md index 117110b7..f7ab3cb4 100644 --- a/templates/agents/iloom-wave-verifier.md +++ b/templates/agents/iloom-wave-verifier.md @@ -2,6 +2,7 @@ name: iloom-wave-verifier description: Wave verification agent that checks must-have criteria from child issues after each swarm wave, invokes fix skills for failures, and reports structured results.\n\nExamples:\n\nContext: Orchestrator wants to verify that wave 1 work meets acceptance criteria\nuser: "Verify must-haves for issues #101, #102, #103 from wave 1"\nassistant: "I'll check all must-have criteria from those issues against the codebase and report results."\n\nThe orchestrator needs wave verification after a completed wave, so use the iloom-wave-verifier agent.\n\n\n\nContext: Swarm orchestrator needs to gate the next wave on verification passing\nuser: "Run wave verification for child issues #45, #46 before proceeding to wave 2"\nassistant: "I'll verify all must-haves for the specified issues, fix any failures, and return a structured report."\n\nWave gating requires verification of completed work, so use the iloom-wave-verifier agent.\n\n model: opus +effort: high color: red --- From ebdbcc0a83091604f6409f77ca89a20ec2e7d5e0 Mon Sep 17 00:00:00 2001 From: Adam Creeger Date: Sun, 22 Mar 2026 23:27:23 -0400 Subject: [PATCH 2/6] refactor(docs): simplify agent model and effort level descriptions in iloom-commands --- docs/iloom-commands.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/docs/iloom-commands.md b/docs/iloom-commands.md index 82f379e8..47cd02f0 100644 --- a/docs/iloom-commands.md +++ b/docs/iloom-commands.md @@ -1957,17 +1957,13 @@ Each agent supports a `swarmModel` field for a clean, per-agent swarm model over } ``` -If `swarmModel` is set for an agent, it overrides the agent's model in swarm mode. If no `swarmModel` is set, the agent uses its frontmatter default — which may differ between swarm and non-swarm modes via `{{#if SWARM_MODE}}` conditionals in the template. If the user has also set a base `model` in settings, that takes precedence over the frontmatter default in both modes. - -**Important:** Agent templates declare mode-specific defaults using Handlebars conditionals (e.g., `model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}}`). Setting `model` in agent settings overrides the frontmatter default in both swarm and non-swarm mode. To override the model only in swarm mode, use `swarmModel`. - With the configuration above: | Agent | Non-swarm mode | Swarm mode | |-------|---------------|------------| | `iloom-issue-implementer` | `opus` (settings) | `sonnet` (swarmModel) | | `iloom-issue-complexity-evaluator` | `haiku` (settings) | `haiku` (swarmModel) | -| `iloom-issue-analyzer` | `opus` (frontmatter default) | `opus` (frontmatter swarm default) | +| `iloom-issue-analyzer` | `opus` (default) | `opus` (default) | **Example using the `--set` flag:** @@ -2083,7 +2079,7 @@ To configure, run `il init` — you'll be asked during setup, or you can change ### Effort Configuration -Effort levels control Claude's reasoning depth. iloom propagates effort to Claude Code via the `CLAUDE_CODE_EFFORT_LEVEL` environment variable for top-level sessions and via per-agent `effort:` frontmatter for agent-level overrides. +Effort levels control Claude's reasoning depth. **Valid effort levels:** `low`, `medium`, `high`, `max` @@ -2138,7 +2134,7 @@ Each agent supports `effort` and `swarmEffort` fields, following the same patter **Default Swarm Effort Levels:** -When no user configuration is provided, swarm agents use effort defaults declared in their template frontmatter via `{{#if SWARM_MODE}}` conditionals: +When no user configuration is provided, swarm agents use these defaults: | Agent | Default Swarm Effort | |-------|---------------------| @@ -2159,8 +2155,6 @@ Effort is resolved with the following priority (highest first): 3. Settings (`spin.effort` / `plan.effort`) 4. No effort set (defers to Claude Code default) -For per-agent effort, Claude Code resolves: agent frontmatter `effort:` > `CLAUDE_CODE_EFFORT_LEVEL` env var > session default. - **Example using the `--set` flag:** ```bash From b05e4298b60f7f5dd87ae93294976023cbf5cfa9 Mon Sep 17 00:00:00 2001 From: Adam Creeger Date: Sun, 22 Mar 2026 23:31:24 -0400 Subject: [PATCH 3/6] docs: remove implementation details from user-facing docs 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 --- docs/iloom-commands.md | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/docs/iloom-commands.md b/docs/iloom-commands.md index 47cd02f0..c68c2f28 100644 --- a/docs/iloom-commands.md +++ b/docs/iloom-commands.md @@ -99,7 +99,7 @@ The override follows a two-level model: **Effort Override:** -The `--effort` flag controls Claude's reasoning depth via the `CLAUDE_CODE_EFFORT_LEVEL` environment variable: +The `--effort` flag controls Claude's reasoning depth: - `low` - Quick, straightforward implementation with minimal overhead - `medium` - Balanced approach with standard implementation and testing - `high` - Comprehensive implementation with extensive testing and documentation @@ -626,15 +626,13 @@ When `il spin` detects an epic loom (created via `il start --epic` or by confirm 1. **Fetches/refreshes child data** - Re-fetches child issue details and dependency map from the issue tracker 2. **Creates child worktrees** - One worktree per child issue, branched off the epic branch, with dependencies installed -3. **Renders swarm agents** - Writes swarm-mode agent templates to `.claude/agents/` in the epic worktree -4. **Renders swarm worker agent** - Writes the iloom workflow as a custom agent type to `.claude/agents/iloom-swarm-worker.md` -5. **Copies agents to child worktrees** - Copies `.claude/agents/` from the epic worktree to each child worktree so workers can resolve agent files locally -6. **Launches orchestrator** - Starts Claude with agent teams enabled and `bypassPermissions` mode +3. **Configures swarm agents** - Sets up the orchestrator and worker agents for the epic and all child worktrees +4. **Launches orchestrator** - Starts the swarm orchestrator session The orchestrator then: - Analyzes the dependency DAG to identify initially unblocked issues -- Spawns parallel agents for all unblocked child issues simultaneously -- Each agent uses the `iloom-swarm-worker` custom agent type, receiving the full iloom workflow as its system prompt +- Spawns parallel worker agents for all unblocked child issues simultaneously +- Each worker receives full issue context and implements its assigned issue autonomously - Completed work is rebased and fast-forward merged into the epic branch for clean linear history - Newly unblocked issues are spawned as their dependencies complete - Failed children are isolated and do not block unrelated issues @@ -653,7 +651,7 @@ il spin --skip-cleanup After all child agents complete and their work is merged into the epic branch, the orchestrator automatically runs a full code review using the `iloom-code-reviewer` agent. This catches cross-cutting issues that individual child agents miss because they each only see their own changes, not the integrated result. -If the review finds any issues (confidence score 80+), a fix agent is spawned to address them before the final commit. The review is non-blocking -- if the reviewer or fix agent fails, the swarm continues to finalization without interruption. Only a single review-fix pass is performed (no re-review loops). +If the review finds issues, they are automatically fixed before the final commit. The review is non-blocking -- if the reviewer or fix agent fails, the swarm continues to finalization without interruption. Only a single review-fix pass is performed (no re-review loops). Post-swarm review is enabled by default. To disable it, set `spin.postSwarmReview` to `false` in your settings: @@ -1908,12 +1906,9 @@ Only sibling dependencies (between child issues of the same epic) are included. Each child agent runs in complete isolation: -1. The orchestrator spawns the agent with `subagent_type: "iloom-swarm-worker"`, passing the child's issue number, title, worktree path, and issue body in the Task prompt -2. The agent's system prompt contains the full iloom issue workflow adapted for swarm mode (high-authority instructions) -3. The agent implements the issue autonomously in its own worktree (branched off the epic branch) -4. On completion, the agent reports back to the orchestrator with status and summary - -The orchestrator uses `bypassPermissions` mode and Claude's agent teams feature, both set automatically. +1. The orchestrator spawns a worker for each child issue, passing its issue number, title, worktree path, and issue body +2. The worker implements the issue autonomously in its own worktree (branched off the epic branch) +3. On completion, the worker reports back to the orchestrator with status and summary **Worker Model Configuration:** @@ -2073,7 +2068,7 @@ Example settings for each mode: } ``` -These modes use `swarmModel` on phase agents (not `model`), so non-swarm behavior is preserved. When agents run outside of swarm mode, their base `model` setting is used. Mode settings merge with existing agent settings — only the `swarmModel` (and worker `model`) fields are overwritten. +These modes only affect swarm behavior — non-swarm sessions continue using each agent's base `model` setting. To configure, run `il init` — you'll be asked during setup, or you can change it later in the advanced configuration section. @@ -2102,7 +2097,7 @@ Configure default effort levels for spin and plan commands in `.iloom/settings.j **Swarm Orchestrator Effort:** -Set the effort level for the swarm orchestrator using `spin.swarmEffort`. This defaults to `medium` when not configured (matching the current hardcoded behavior): +Set the effort level for the swarm orchestrator using `spin.swarmEffort`. This defaults to `medium` when not configured: ```json { From fe865af43b3ada63061c902c42d4b180ef2dc5cd Mon Sep 17 00:00:00 2001 From: Adam Creeger Date: Sun, 22 Mar 2026 23:34:06 -0400 Subject: [PATCH 4/6] fix(agents): wrap entire effort line in conditional when no else branch 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 --- templates/agents/CLAUDE.md | 4 ++-- templates/agents/iloom-code-reviewer.md | 2 +- templates/agents/iloom-issue-analyze-and-plan.md | 2 +- templates/agents/iloom-issue-analyzer.md | 2 +- templates/agents/iloom-issue-complexity-evaluator.md | 2 +- templates/agents/iloom-issue-enhancer.md | 2 +- templates/agents/iloom-issue-implementer.md | 2 +- templates/agents/iloom-issue-planner.md | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/templates/agents/CLAUDE.md b/templates/agents/CLAUDE.md index 659c267d..27e09106 100644 --- a/templates/agents/CLAUDE.md +++ b/templates/agents/CLAUDE.md @@ -41,7 +41,7 @@ Frontmatter fields support Handlebars expressions because template substitution name: iloom-issue-implementer description: One-line description of this agent's role model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}} -effort: {{#if SWARM_MODE}}medium{{/if}} +{{#if SWARM_MODE}}effort: medium{{/if}} color: green # Optional: terminal color for status display tools: # Optional: restrict available tools - Read @@ -52,7 +52,7 @@ tools: # Optional: restrict available tools Agent prompt content in Markdown... ``` -When `SWARM_MODE` is falsy, conditional-only values (like `effort: {{#if SWARM_MODE}}medium{{/if}}`) resolve to an empty string, which is treated as "not set" — the agent inherits the session default. +When `SWARM_MODE` is falsy, conditional lines (like `{{#if SWARM_MODE}}effort: medium{{/if}}`) resolve to an empty line, which is ignored by the parser — the agent inherits the session default. ## Model and Effort Override Rules diff --git a/templates/agents/iloom-code-reviewer.md b/templates/agents/iloom-code-reviewer.md index c6dda105..c8b390c1 100644 --- a/templates/agents/iloom-code-reviewer.md +++ b/templates/agents/iloom-code-reviewer.md @@ -2,7 +2,7 @@ name: iloom-code-reviewer description: Use this agent to review code changes. model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}} -effort: {{#if SWARM_MODE}}medium{{/if}} +{{#if SWARM_MODE}}effort: medium{{/if}} color: cyan --- diff --git a/templates/agents/iloom-issue-analyze-and-plan.md b/templates/agents/iloom-issue-analyze-and-plan.md index 342142fc..6e9eef5d 100644 --- a/templates/agents/iloom-issue-analyze-and-plan.md +++ b/templates/agents/iloom-issue-analyze-and-plan.md @@ -3,7 +3,7 @@ name: iloom-issue-analyze-and-plan description: Combined analysis and planning agent for SIMPLE tasks. This agent performs lightweight analysis and creates an implementation plan in one streamlined phase. Only invoked for tasks pre-classified as SIMPLE (< 5 files, <200 LOC, no breaking changes, no DB migrations). Use this agent when you have a simple issue that needs quick analysis followed by immediate planning. color: teal model: opus -effort: {{#if SWARM_MODE}}high{{/if}} +{{#if SWARM_MODE}}effort: high{{/if}} --- {{#if SWARM_MODE}} diff --git a/templates/agents/iloom-issue-analyzer.md b/templates/agents/iloom-issue-analyzer.md index bcb415bf..3cd1f736 100644 --- a/templates/agents/iloom-issue-analyzer.md +++ b/templates/agents/iloom-issue-analyzer.md @@ -3,7 +3,7 @@ name: iloom-issue-analyzer description: Use this agent when you need to analyze and research issues, bugs, or enhancement requests. The agent will investigate the codebase, recent commits, and third-party dependencies to identify root causes WITHOUT proposing solutions. Ideal for initial issue triage, regression analysis, and documenting technical findings for team discussion.\n\nExamples:\n\nContext: User wants to analyze a newly reported bug in issue #42\nuser: "Please analyze issue #42 - users are reporting that the login button doesn't work on mobile"\nassistant: "I'll use the issue-analyzer agent to investigate this issue and document my findings."\n\nSince this is a request to analyze an issue, use the Task tool to launch the issue-analyzer agent to research the problem.\n\n\n\nContext: User needs to understand a regression that appeared after recent changes\nuser: "Can you look into issue #78? It seems like something broke after yesterday's deployment"\nassistant: "Let me launch the issue-analyzer agent to research this regression and identify what changed."\n\nThe user is asking for issue analysis and potential regression investigation, so use the issue-analyzer agent.\n\n color: pink model: opus -effort: {{#if SWARM_MODE}}high{{/if}} +{{#if SWARM_MODE}}effort: high{{/if}} --- {{#if SWARM_MODE}} diff --git a/templates/agents/iloom-issue-complexity-evaluator.md b/templates/agents/iloom-issue-complexity-evaluator.md index 5cdb4c94..3404a54e 100644 --- a/templates/agents/iloom-issue-complexity-evaluator.md +++ b/templates/agents/iloom-issue-complexity-evaluator.md @@ -3,7 +3,7 @@ name: iloom-issue-complexity-evaluator description: Use this agent when you need to quickly assess the complexity of an issue before deciding on the appropriate workflow. This agent performs a lightweight scan to classify issues as SIMPLE or COMPLEX based on estimated scope, risk, and impact. Runs first before any detailed analysis or planning. color: orange model: haiku -effort: {{#if SWARM_MODE}}low{{/if}} +{{#if SWARM_MODE}}effort: low{{/if}} --- {{#if SWARM_MODE}} diff --git a/templates/agents/iloom-issue-enhancer.md b/templates/agents/iloom-issue-enhancer.md index 5f3af9dc..088a21cb 100644 --- a/templates/agents/iloom-issue-enhancer.md +++ b/templates/agents/iloom-issue-enhancer.md @@ -3,7 +3,7 @@ name: iloom-issue-enhancer description: Use this agent when you need to analyze bug or enhancement reports from a Product Manager perspective. The agent accepts either an issue identifier or direct text description and creates structured specifications that enhance the original user report for development teams without performing code analysis or suggesting implementations. Ideal for triaging bugs and feature requests to prepare them for technical analysis and planning.\n\nExamples:\n\nContext: User wants to triage and enhance a bug report from issue tracker\nuser: "Please analyze issue #42 - the login button doesn't work on mobile"\nassistant: "I'll use the iloom-issue-enhancer agent to analyze this bug report and create a structured specification."\n\nSince this is a request to triage and structure a bug report from a user experience perspective, use the iloom-issue-enhancer agent.\n\n\n\nContext: User needs to enhance an enhancement request that lacks detail\nuser: "Can you improve the description on issue #78? The user's request is pretty vague"\nassistant: "Let me launch the iloom-issue-enhancer agent to analyze the enhancement request and create a clear specification."\n\nThe user is asking for enhancement report structuring, so use the iloom-issue-enhancer agent.\n\n\n\nContext: User provides direct description without issue identifier\nuser: "Analyze this bug: Users report that the search function returns no results when they include special characters like & or # in their query"\nassistant: "I'll use the iloom-issue-enhancer agent to create a structured specification for this bug report."\n\nEven though no issue identifier was provided, the iloom-issue-enhancer agent can analyze the direct description and create a structured specification.\n\n\n\nContext: An issue has been labeled as a valid baug and needs structured analysis\nuser: "Structure issue #123 that was just labeled as a triaged bug"\nassistant: "I'll use the iloom-issue-enhancer agent to create a comprehensive bug specification."\n\nThe issue needs Product Manager-style analysis and structuring, so use the iloom-issue-enhancer agent.\n\n color: purple model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}} -effort: {{#if SWARM_MODE}}medium{{/if}} +{{#if SWARM_MODE}}effort: medium{{/if}} --- {{#if SWARM_MODE}} diff --git a/templates/agents/iloom-issue-implementer.md b/templates/agents/iloom-issue-implementer.md index 174605b1..d85b44f7 100644 --- a/templates/agents/iloom-issue-implementer.md +++ b/templates/agents/iloom-issue-implementer.md @@ -2,7 +2,7 @@ name: iloom-issue-implementer description: Use this agent when you need to implement an issue exactly as specified in its comments and description. This agent reads issue details, follows implementation plans precisely, and ensures all code passes tests, typechecking, and linting before completion. Examples:\n\n\nContext: User wants to implement a specific issue.\nuser: "Please implement issue #42"\nassistant: "I'll use the issue-implementer agent to read and implement issue #42 exactly as specified."\n\nSince the user is asking to implement an issue, use the Task tool to launch the issue-implementer agent.\n\n\n\n\nContext: User references an issue that needs implementation.\nuser: "Can you work on the authentication issue we discussed in #15?"\nassistant: "Let me launch the issue-implementer agent to read issue #15 and implement it according to the plan in the comments."\n\nThe user is referencing a specific issue number, so use the issue-implementer agent to handle the implementation.\n\n model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}} -effort: {{#if SWARM_MODE}}medium{{/if}} +{{#if SWARM_MODE}}effort: medium{{/if}} color: green --- diff --git a/templates/agents/iloom-issue-planner.md b/templates/agents/iloom-issue-planner.md index 1496b5f9..559650bd 100644 --- a/templates/agents/iloom-issue-planner.md +++ b/templates/agents/iloom-issue-planner.md @@ -3,7 +3,7 @@ name: iloom-issue-planner description: Use this agent when you need to analyze issues and create detailed implementation plans. This agent specializes in reading issue context, understanding requirements, and creating focused implementation plans with specific file changes and line numbers. The agent will document the plan as a comment on the issue without executing any changes. Examples: Context: The user wants detailed implementation planning for an issue.\nuser: "Analyze issue #42 and create an implementation plan"\nassistant: "I'll use the issue-planner agent to analyze the issue and create a detailed implementation plan"\nSince the user wants issue analysis and implementation planning, use the issue-planner agent. Context: The user needs a plan for implementing a feature described in an issue.\nuser: "Read issue #15 and plan out what needs to be changed"\nassistant: "Let me use the issue-planner agent to analyze the issue and document a comprehensive implementation plan"\nThe user needs issue analysis and planning, so the issue-planner agent is the right choice. color: blue model: {{#if SWARM_MODE}}sonnet{{else}}opus{{/if}} -effort: {{#if SWARM_MODE}}high{{/if}} +{{#if SWARM_MODE}}effort: high{{/if}} --- {{#if SWARM_MODE}} From 5c690e06816b90ab8b11b1a185ef3dc2af38ec53 Mon Sep 17 00:00:00 2001 From: Adam Creeger Date: Sun, 22 Mar 2026 23:35:50 -0400 Subject: [PATCH 5/6] fix(agents): change complexity-evaluator swarm effort from low to high 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 --- docs/iloom-commands.md | 2 +- src/lib/SwarmSetupService.test.ts | 4 ++-- templates/agents/iloom-issue-complexity-evaluator.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/iloom-commands.md b/docs/iloom-commands.md index c68c2f28..98a14498 100644 --- a/docs/iloom-commands.md +++ b/docs/iloom-commands.md @@ -2139,7 +2139,7 @@ When no user configuration is provided, swarm agents use these defaults: | `iloom-issue-implementer` | `medium` | | `iloom-issue-enhancer` | `medium` | | `iloom-code-reviewer` | `medium` | -| `iloom-issue-complexity-evaluator` | `low` | +| `iloom-issue-complexity-evaluator` | `high` | **Effort Resolution Order:** diff --git a/src/lib/SwarmSetupService.test.ts b/src/lib/SwarmSetupService.test.ts index 010468b2..ea60a23f 100644 --- a/src/lib/SwarmSetupService.test.ts +++ b/src/lib/SwarmSetupService.test.ts @@ -358,7 +358,7 @@ describe('SwarmSetupService', () => { description: 'Evaluator agent', prompt: 'Evaluate things', model: 'haiku', - effort: 'low', // from frontmatter: {{#if SWARM_MODE}}low{{/if}} + effort: 'high', // from frontmatter: {{#if SWARM_MODE}}high{{/if}} }, }) @@ -367,7 +367,7 @@ describe('SwarmSetupService', () => { // Effort values come from loadAgents (frontmatter conditionals) expect(getAgentContent('iloom-swarm-issue-analyzer')).toContain('effort: high') expect(getAgentContent('iloom-swarm-issue-implementer')).toContain('effort: medium') - expect(getAgentContent('iloom-swarm-issue-complexity-evaluator')).toContain('effort: low') + expect(getAgentContent('iloom-swarm-issue-complexity-evaluator')).toContain('effort: high') }) it('uses per-agent swarmEffort when configured', async () => { diff --git a/templates/agents/iloom-issue-complexity-evaluator.md b/templates/agents/iloom-issue-complexity-evaluator.md index 3404a54e..64c7589b 100644 --- a/templates/agents/iloom-issue-complexity-evaluator.md +++ b/templates/agents/iloom-issue-complexity-evaluator.md @@ -3,7 +3,7 @@ name: iloom-issue-complexity-evaluator description: Use this agent when you need to quickly assess the complexity of an issue before deciding on the appropriate workflow. This agent performs a lightweight scan to classify issues as SIMPLE or COMPLEX based on estimated scope, risk, and impact. Runs first before any detailed analysis or planning. color: orange model: haiku -{{#if SWARM_MODE}}effort: low{{/if}} +{{#if SWARM_MODE}}effort: high{{/if}} --- {{#if SWARM_MODE}} From 050fac06873b63ad8433158ea5057fb962421b77 Mon Sep 17 00:00:00 2001 From: Adam Creeger Date: Sun, 22 Mar 2026 23:42:04 -0400 Subject: [PATCH 6/6] fix: docs cleanup, complexity-evaluator effort, integration tests - 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 --- docs/iloom-commands.md | 4 + src/lib/AgentManager.integration.test.ts | 221 +++++++++++++++++++++++ 2 files changed, 225 insertions(+) create mode 100644 src/lib/AgentManager.integration.test.ts diff --git a/docs/iloom-commands.md b/docs/iloom-commands.md index 98a14498..3f5998f4 100644 --- a/docs/iloom-commands.md +++ b/docs/iloom-commands.md @@ -1952,6 +1952,10 @@ Each agent supports a `swarmModel` field for a clean, per-agent swarm model over } ``` +If `swarmModel` is set for an agent, it overrides the agent's model in swarm mode. If no `swarmModel` is set, the Swarm Quality Mode defaults apply (see below) — not the agent's base `model`. This separation is intentional: swarms run many agents in parallel and costs scale quickly, so swarm model choices should always be explicit. + +**Important:** Changing an agent's `model` only affects non-swarm mode (single-issue looms via `il start`). To change an agent's model in swarm mode, use `swarmModel` or choose a different Swarm Quality Mode. + With the configuration above: | Agent | Non-swarm mode | Swarm mode | diff --git a/src/lib/AgentManager.integration.test.ts b/src/lib/AgentManager.integration.test.ts new file mode 100644 index 00000000..c1482042 --- /dev/null +++ b/src/lib/AgentManager.integration.test.ts @@ -0,0 +1,221 @@ +import { describe, it, expect } from 'vitest' +import path from 'path' +import { AgentManager } from './AgentManager.js' +import { PromptTemplateManager } from './PromptTemplateManager.js' + +/** + * Integration test for AgentManager + PromptTemplateManager + real agent templates. + * + * These tests read the actual agent markdown files from templates/agents/, + * run real Handlebars substitution, and verify the end-to-end result of + * frontmatter parsing with template variables. + */ + +// Resolve the real templates/agents/ directory relative to the project root +const PROJECT_ROOT = path.resolve(import.meta.dirname, '..', '..') +const AGENTS_DIR = path.join(PROJECT_ROOT, 'templates', 'agents') +const PROMPTS_DIR = path.join(PROJECT_ROOT, 'templates', 'prompts') + +// All agent names expected in the templates/agents/ directory +const ALL_AGENT_NAMES = [ + 'iloom-artifact-reviewer', + 'iloom-code-reviewer', + 'iloom-framework-detector', + 'iloom-issue-analyze-and-plan', + 'iloom-issue-analyzer', + 'iloom-issue-complexity-evaluator', + 'iloom-issue-enhancer', + 'iloom-issue-implementer', + 'iloom-issue-planner', + 'iloom-wave-verifier', +] + +describe('AgentManager integration (real templates)', () => { + // Use real PromptTemplateManager and real agent files + const templateManager = new PromptTemplateManager(PROMPTS_DIR) + + describe('loadAgents with SWARM_MODE=true', () => { + it('should load all agents successfully', async () => { + const manager = new AgentManager(AGENTS_DIR, templateManager) + const agents = await manager.loadAgents(undefined, { SWARM_MODE: true }) + + const loadedNames = Object.keys(agents).sort() + expect(loadedNames).toEqual(ALL_AGENT_NAMES) + }) + + it('should resolve swarm-mode model overrides from Handlebars conditionals', async () => { + const manager = new AgentManager(AGENTS_DIR, templateManager) + const agents = await manager.loadAgents(undefined, { SWARM_MODE: true }) + + // Agents with conditional model: sonnet in swarm mode + expect(agents['iloom-issue-implementer'].model).toBe('sonnet') + expect(agents['iloom-issue-planner'].model).toBe('sonnet') + expect(agents['iloom-issue-enhancer'].model).toBe('sonnet') + expect(agents['iloom-code-reviewer'].model).toBe('sonnet') + + // Agents with unconditional model (always opus regardless of mode) + expect(agents['iloom-issue-analyzer'].model).toBe('opus') + expect(agents['iloom-issue-analyze-and-plan'].model).toBe('opus') + expect(agents['iloom-wave-verifier'].model).toBe('opus') + expect(agents['iloom-framework-detector'].model).toBe('opus') + expect(agents['iloom-artifact-reviewer'].model).toBe('opus') + expect(agents['iloom-issue-complexity-evaluator'].model).toBe('haiku') + }) + + it('should resolve swarm-mode effort defaults from Handlebars conditionals', async () => { + const manager = new AgentManager(AGENTS_DIR, templateManager) + const agents = await manager.loadAgents(undefined, { SWARM_MODE: true }) + + // Agents with conditional effort: {{#if SWARM_MODE}}effort: {{/if}} + expect(agents['iloom-issue-analyzer'].effort).toBe('high') + expect(agents['iloom-issue-planner'].effort).toBe('high') + expect(agents['iloom-issue-implementer'].effort).toBe('medium') + expect(agents['iloom-issue-enhancer'].effort).toBe('medium') + expect(agents['iloom-code-reviewer'].effort).toBe('medium') + expect(agents['iloom-issue-complexity-evaluator'].effort).toBe('high') + expect(agents['iloom-issue-analyze-and-plan'].effort).toBe('high') + + // Agents with unconditional effort (always set regardless of SWARM_MODE) + expect(agents['iloom-wave-verifier'].effort).toBe('high') + expect(agents['iloom-framework-detector'].effort).toBe('high') + + // Agents with no effort field at all + expect(agents['iloom-artifact-reviewer'].effort).toBeUndefined() + }) + + it('should have non-empty prompts for all agents', async () => { + const manager = new AgentManager(AGENTS_DIR, templateManager) + const agents = await manager.loadAgents(undefined, { SWARM_MODE: true }) + + for (const [name, config] of Object.entries(agents)) { + expect(config.prompt.length, `${name} should have a non-empty prompt`).toBeGreaterThan(0) + } + }) + + it('should have non-empty descriptions for all agents', async () => { + const manager = new AgentManager(AGENTS_DIR, templateManager) + const agents = await manager.loadAgents(undefined, { SWARM_MODE: true }) + + for (const [name, config] of Object.entries(agents)) { + expect(config.description.length, `${name} should have a non-empty description`).toBeGreaterThan(0) + } + }) + }) + + describe('loadAgents with SWARM_MODE=false (non-swarm)', () => { + it('should load all agents successfully', async () => { + const manager = new AgentManager(AGENTS_DIR, templateManager) + const agents = await manager.loadAgents(undefined, { SWARM_MODE: false }) + + const loadedNames = Object.keys(agents).sort() + expect(loadedNames).toEqual(ALL_AGENT_NAMES) + }) + + it('should resolve non-swarm model defaults', async () => { + const manager = new AgentManager(AGENTS_DIR, templateManager) + const agents = await manager.loadAgents(undefined, { SWARM_MODE: false }) + + // Agents with conditional model resolve to opus in non-swarm mode + expect(agents['iloom-issue-implementer'].model).toBe('opus') + expect(agents['iloom-issue-planner'].model).toBe('opus') + expect(agents['iloom-issue-enhancer'].model).toBe('opus') + expect(agents['iloom-code-reviewer'].model).toBe('opus') + + // Agents with unconditional model are the same regardless of mode + expect(agents['iloom-issue-analyzer'].model).toBe('opus') + expect(agents['iloom-issue-analyze-and-plan'].model).toBe('opus') + expect(agents['iloom-wave-verifier'].model).toBe('opus') + expect(agents['iloom-framework-detector'].model).toBe('opus') + expect(agents['iloom-artifact-reviewer'].model).toBe('opus') + expect(agents['iloom-issue-complexity-evaluator'].model).toBe('haiku') + }) + + it('should have undefined effort for agents that only set effort in swarm mode', async () => { + const manager = new AgentManager(AGENTS_DIR, templateManager) + const agents = await manager.loadAgents(undefined, { SWARM_MODE: false }) + + // These agents use {{#if SWARM_MODE}}effort: {{/if}} - resolves to empty/undefined + expect(agents['iloom-issue-analyzer'].effort).toBeUndefined() + expect(agents['iloom-issue-planner'].effort).toBeUndefined() + expect(agents['iloom-issue-implementer'].effort).toBeUndefined() + expect(agents['iloom-issue-enhancer'].effort).toBeUndefined() + expect(agents['iloom-code-reviewer'].effort).toBeUndefined() + expect(agents['iloom-issue-complexity-evaluator'].effort).toBeUndefined() + expect(agents['iloom-issue-analyze-and-plan'].effort).toBeUndefined() + + // These agents have unconditional effort - always present + expect(agents['iloom-wave-verifier'].effort).toBe('high') + expect(agents['iloom-framework-detector'].effort).toBe('high') + + // No effort field at all + expect(agents['iloom-artifact-reviewer'].effort).toBeUndefined() + }) + }) + + describe('loadAgents without templateVariables (no substitution)', () => { + it('should load all agents successfully even without template variables', async () => { + const manager = new AgentManager(AGENTS_DIR, templateManager) + const agents = await manager.loadAgents() + + const loadedNames = Object.keys(agents).sort() + expect(loadedNames).toEqual(ALL_AGENT_NAMES) + }) + }) + + describe('swarm vs non-swarm model differences', () => { + it('should produce different models for swarm-conditional agents', async () => { + const manager = new AgentManager(AGENTS_DIR, templateManager) + + const swarmAgents = await manager.loadAgents(undefined, { SWARM_MODE: true }) + const nonSwarmAgents = await manager.loadAgents(undefined, { SWARM_MODE: false }) + + // Agents that change model between swarm and non-swarm + const conditionalModelAgents = [ + 'iloom-issue-implementer', + 'iloom-issue-planner', + 'iloom-issue-enhancer', + 'iloom-code-reviewer', + ] + + for (const name of conditionalModelAgents) { + expect( + swarmAgents[name].model, + `${name} swarm model should be sonnet`, + ).toBe('sonnet') + expect( + nonSwarmAgents[name].model, + `${name} non-swarm model should be opus`, + ).toBe('opus') + } + }) + + it('should produce different effort levels for swarm-conditional agents', async () => { + const manager = new AgentManager(AGENTS_DIR, templateManager) + + const swarmAgents = await manager.loadAgents(undefined, { SWARM_MODE: true }) + const nonSwarmAgents = await manager.loadAgents(undefined, { SWARM_MODE: false }) + + // Agents that only have effort in swarm mode + const conditionalEffortAgents = [ + 'iloom-issue-analyzer', + 'iloom-issue-planner', + 'iloom-issue-implementer', + 'iloom-issue-enhancer', + 'iloom-code-reviewer', + 'iloom-issue-complexity-evaluator', + 'iloom-issue-analyze-and-plan', + ] + + for (const name of conditionalEffortAgents) { + expect( + swarmAgents[name].effort, + `${name} should have effort in swarm mode`, + ).toBeDefined() + expect( + nonSwarmAgents[name].effort, + `${name} should NOT have effort in non-swarm mode`, + ).toBeUndefined() + } + }) + }) +})