Skip to content

feat: make default workflow domain configurable at runtime#281

Open
mrsimpson wants to merge 4 commits into
mainfrom
refactor/configurable-default-domain
Open

feat: make default workflow domain configurable at runtime#281
mrsimpson wants to merge 4 commits into
mainfrom
refactor/configurable-default-domain

Conversation

@mrsimpson
Copy link
Copy Markdown
Collaborator

@mrsimpson mrsimpson commented May 23, 2026

Summary

Eliminate hardcoded 'code' default domain in WorkflowManager and provide runtime domain switching for long-lived processes (MCP server, OpenCode plugin).

Changes

  • Four-level env var chain: constructor param > WORKFLOW_DOMAINS > DEFAULT_DOMAINS > VIBE_WORKFLOW_DOMAINS > code (default)
  • defaultDomains constructor parameter for programmatic override (library consumers, testing)
  • DEFAULT_ALL_DOMAINS env var for getAllAvailableWorkflows() — replaces incomplete hardcoded list
  • setDomains() method for runtime domain switching with validation against known domains
  • DOMAIN_DESCRIPTIONS constant with meaningful summaries of what each domain is suitable for
  • KNOWN_DOMAIN_NAMES constant — single source of truth for domain names, exported from core
  • load_workflows tool — simple enum parameter, domain descriptions exposed via domain:// resource
  • domain:// resource — exposes all available domains with descriptions for LLM discovery
  • Bug fix: loadPredefinedWorkflows() now clears maps before reloading (prevents workflow accumulation)

Tests

  • 18 new tests: 9 for env var precedence chain, 9 for setDomains()
  • All 401 tests pass (previously failing game-beginner.yaml test now passes)

Architecture Decision

Domain descriptions are exposed as a resource (domain://) rather than embedded in the tool parameter. This keeps the tool schema lean and reduces LLM context usage. The LLM can query the resource to discover domains before calling load_workflows.

Affected Files

  • packages/core/src/workflow-manager.ts — core changes
  • packages/core/test/unit/workflow-domains-precedence.test.ts — 9 new tests
  • packages/core/test/unit/workflow-domain-switching.test.ts — 9 new tests (new file)
  • packages/core/test/unit/workflow-domain-filtering.test.ts — updated for code-only default
  • packages/mcp-server/src/server-config.ts — register load_workflows tool + domain:// resource
  • packages/mcp-server/src/tool-handlers/load-workflows.ts — simplified handler (new file)
  • packages/mcp-server/src/tool-handlers/index.ts — register handler
  • packages/opencode-plugin/src/plugin.ts — register load_workflows tool
  • packages/opencode-plugin/src/tool-handlers/load-workflows.ts — simplified handler (new file)

Backward Compatibility

  • VIBE_WORKFLOW_DOMAINS still works as lowest-priority env var
  • Adding options?: WorkflowManagerOptions to constructor is backward compatible
  • Default remains code — out-of-the-box behavior unchanged

mrsimpson added 3 commits May 23, 2026 20:31
Eliminate hardcoded 'code' defaults in WorkflowManager through:

- Four-level env var chain: constructor param > WORKFLOW_DOMAINS >
  DEFAULT_DOMAINS > VIBE_WORKFLOW_DOMAINS > empty Set (all workflows)
- defaultDomains constructor parameter for programmatic override
- DEFAULT_ALL_DOMAINS env var for getAllAvailableWorkflows()
- setDomains() method for runtime domain switching with validation
- DOMAIN_DESCRIPTIONS constant with meaningful domain summaries
- load_workflows tool in MCP server and OpenCode plugin for LLM
  to dynamically load domains without restarting the process

Fix pre-existing bug: loadPredefinedWorkflows() now clears maps
before reloading, preventing workflow accumulation across domain
switches.

Add 18 new tests: 9 for env var precedence chain, 9 for setDomains().
Update existing domain filtering test for new empty Set default.
Simplify load_workflows tool parameter to use domain name enums
instead of embedding full descriptions. This keeps the tool schema
lean and reduces LLM context usage.

- load_workflows(domains: string[]) now uses a simple enum
- New domain-info:// resource exposes full domain descriptions
- OpenCode plugin uses enum with inline domain list in description
- Domain descriptions kept in DOMAIN_DESCRIPTIONS constant (core)
@mrsimpson
Copy link
Copy Markdown
Collaborator Author

@claude Review once

@mrsimpson
Copy link
Copy Markdown
Collaborator Author

PR Review

Great overall design — the four-level precedence chain, setDomains(), and load_workflows tool are all clean and well-tested. A few things to address:

🔴 Must fix: Default fallback should remain code, not empty Set

The final fallback in parseEnabledDomains() was changed from new Set(['code']) to new Set(). This changes the default behavior — users who set no config now get all domains loaded instead of just code.

The goal of this PR is to make the default configurable and switchable, not to change what the default is. code should remain the out-of-the-box default.

Fix:

// packages/core/src/workflow-manager.ts
// Change this:
logger.debug('No domain configuration found, loading all workflows');
return new Set();

// To this:
logger.debug('No domain configuration found, using default: code');
return new Set(['code']);

This also likely fixes the CI failure — the game-beginner.yaml validation test now runs because children domain loads by default (empty Set = all domains). Restoring Set(['code']) would exclude children from the default load and likely make that test pass again.

The affected tests in workflow-domain-filtering.test.ts and workflow-domains-precedence.test.ts that assert "all workflows load by default" should be reverted to assert code-only default.


⚠️ Should fix: getActiveWorkflow() dead code guard

The guard in setDomains() can never trigger since getActiveWorkflow() always returns null:

const activeWorkflow = this.getActiveWorkflow(); // always null
if (activeWorkflow && ...) { // dead branch — never executes

This silently promises safety that isn't delivered. Please either remove the guard and add a // TODO comment, or document clearly that the guard is inactive:

// NOTE: Active workflow conflict detection is not yet implemented.
// getActiveWorkflow() always returns null until ConversationManager integration is complete.
// Callers can switch domains freely regardless of active workflow state.

⚠️ Should fix: setDomains('') behavior should be documented

setDomains('') produces an empty Set which loads all workflows — but callers might expect it to reset to the default (code). This is especially surprising after restoring Set(['code']) as the default. Please document this in the JSDoc:

/**
 * @param domains - Comma-separated string or array of domain names.
 *   Pass an empty string or empty array to load ALL workflows (no domain filtering).
 *   To reset to the default domain, pass 'code' explicitly.
 */

💡 Nice to have: Use constructor param in getAllAvailableWorkflows() to avoid env var mutation

The current implementation temporarily mutates process.env['WORKFLOW_DOMAINS'], which is not safe under concurrent calls. Since defaultDomains constructor param was added in this PR, use it instead:

public getAllAvailableWorkflows(): WorkflowInfo[] {
  const allDomains = process.env['DEFAULT_ALL_DOMAINS'] ||
    'code,architecture,office,sdd,sdd-crowd,skilled,children';
  const tempManager = new WorkflowManager({ defaultDomains: allDomains });
  return tempManager.getAvailableWorkflows();
}

No env var mutation, no try/finally needed, safe for concurrent use.


💡 Nice to have: Deduplicate KNOWN_DOMAINS list

opencode-plugin/src/tool-handlers/load-workflows.ts duplicates the domain list from DOMAIN_DESCRIPTIONS in core. If a new domain is added to core, the plugin must be manually updated. Consider exporting a KNOWN_DOMAIN_NAMES constant from core and importing it in the plugin.


Summary: The architecture and test coverage are solid. The one must-fix is the default fallback — one line change that likely heals the CI failure too. Everything else is polish.

- Restore 'code' as default fallback in parseEnabledDomains() (was
  accidentally changed to empty Set which loads all domains)
- Remove dead getActiveWorkflow() guard — always returned null,
  replaced with clear documentation that conflict detection is not
  yet implemented
- Document setDomains('') behavior: empty loads all workflows,
  pass 'code' to reset to default
- Use constructor param in getAllAvailableWorkflows() instead of
  env var mutation — safe for concurrent use
- Export KNOWN_DOMAIN_NAMES from core — single source of truth for
  domain names, imported by MCP server and OpenCode plugin
- Revert tests to assert 'code'-only default

This also fixes the CI failure: game-beginner.yaml validation test
now passes because 'children' domain no longer loads by default.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant