Skip to content

ADR-001: Dual-Mode Agent System

Date: 2025-10-26 Status: Accepted

Context

Nova AI needs to support two distinct execution environments:

  1. Local Development: Developers working interactively at their keyboards, where prompting before risky operations (git push, file deletion, API calls) provides safety and allows for human oversight.

  2. CI/CD Pipelines: Automated workflows in GitHub Actions where user prompts are impossible and would cause pipeline hangs. These environments require fully autonomous execution.

Initially, we maintained separate agent files for each mode (20 total agents): - 10 interactive agents in .claude/agents/interactive/ - 10 autonomous agents in .claude/agents/autonomous/

This resulted in 95% duplicate content, high maintenance burden, and confusion about which agent to use in different contexts.

Key Requirements

  • Support both interactive (prompt-based) and autonomous (no-prompt) execution
  • Minimize code duplication between modes
  • Make mode selection automatic and transparent
  • Maintain safety in both environments
  • Enable easy testing and debugging

Decision

We implemented a unified dual-mode agent system where:

  1. Single Agent File: Each agent exists as one file (e.g., code-reviewer.md) instead of two separate files

  2. Automatic Mode Detection: The system automatically detects execution mode based on environment variables:

    def detect_mode() -> str:
        """Auto-detect execution mode based on environment."""
        if os.getenv("CI") or os.getenv("GITHUB_ACTIONS"):
            return "autonomous"
        return "interactive"
    

  3. YAML Frontmatter Configuration: Agents specify behavior for both modes in their frontmatter:

    ---
    name: code-reviewer
    mode: auto  # Can be: auto, interactive, or autonomous
    tools:
      interactive:
        - allow: Read, Grep, Glob
        - ask: Bash, Write, GitPush
      autonomous:
        - allow: Read, Grep, Glob, Bash, Write, GitPush
    ---
    

  4. Agent Consolidation: Reduced from 20 agent files to 10 unified agents

  5. Explicit Override: Users can override auto-detection by setting NOVA_AGENT_MODE=interactive|autonomous

Implementation

In claude_sdk_executor.py:

def load_agent(self, agent_name: str) -> Dict[str, Any]:
    """Load agent with automatic mode detection."""
    mode = self._detect_mode()

    # Load single agent file
    agent_path = self.project_root / ".claude" / "agents" / f"{agent_name}.md"

    # Parse frontmatter and apply mode-specific settings
    agent_config = self._parse_agent_frontmatter(agent_path, mode)

    return agent_config

Environment Detection: - CI/CD: CI=true or GITHUB_ACTIONS=true → autonomous mode - Local: No env vars → interactive mode - Override: NOVA_AGENT_MODE=autonomous → forces autonomous mode

Consequences

Positive

  1. 50% Reduction in Agent Files: 20 files → 10 files
  2. 95% Less Duplication: Shared content maintained once
  3. Automatic Context Awareness: No manual mode selection needed
  4. Easier Maintenance: Updates to agent behavior happen in one place
  5. Clearer Intent: Agent files explicitly document both interaction patterns
  6. Better Testing: Easy to test both modes by setting environment variables
  7. Backward Compatible: Old code using -autonomous suffix still works (with deprecation warning)

Negative

  1. Slightly More Complex Frontmatter: Agents need to specify behavior for both modes
  2. Initial Migration Effort: Required consolidating 20 files into 10
  3. Testing Requirement: Must test agents in both modes to ensure correctness

Trade-offs

Considered Alternatives:

  1. Separate Files (Original Approach)
  2. ❌ 95% duplication
  3. ❌ High maintenance burden
  4. ❌ Easy to forget updating both versions
  5. ✅ Simple to understand

  6. Single File with Manual Mode Parameter

  7. ❌ Requires passing mode explicitly everywhere
  8. ❌ Easy to forget mode in CI/CD
  9. ✅ No environment detection complexity

  10. Runtime Permission Prompts (Chosen Alternative)

  11. ❌ Would hang CI/CD pipelines
  12. ✅ Good UX for local development only

  13. Configuration File per Environment

  14. ❌ Separate config to maintain
  15. ❌ Unclear which config applies in edge cases
  16. ✅ More explicit

Why We Chose Dual-Mode: - Automatic mode detection eliminates human error - Single source of truth for agent behavior - Clear separation of concerns (behavior vs execution context) - Scales to future execution environments (Docker, Lambda, etc.)

Implementation Timeline

  • Week 1: Created mode detection logic in claude_sdk_executor.py
  • Week 2: Added frontmatter parser with mode-specific sections
  • Week 3: Migrated 10 agent pairs to unified format
  • Week 4: Deprecated old agent files with migration warnings
  • Week 5: Removed deprecated files after validation

Validation

Tested dual-mode system with: - ✅ Local development (interactive mode) - ✅ GitHub Actions (autonomous mode) - ✅ Explicit mode override (NOVA_AGENT_MODE=autonomous) - ✅ All 10 agents in both modes - ✅ Backward compatibility with old -autonomous suffix

References

  • Implementation: src/orchestrator/claude_sdk_executor.py
  • Mode Detection: src/orchestrator/agent_mode_detector.py (production)
  • Agent Examples: .claude/agents/code-reviewer.md, .claude/agents/tester.md
  • Documentation: AGENT_MERGE_EXECUTIVE_SUMMARY.md
  • Test Suite: tests/agents/test_mode_detection.py

Status Update (October 26, 2025)

Production Implementation: agent_mode_detector.py is the active production module used by claude_sdk_executor.py.

Deprecated Modules: - mode_selector.py - Removed (redundant with agent_mode_detector.py, 0% production usage) - Original test suite relocated to tests/agents/test_mode_detection.py