Implement BackendAdapter interface with four CLI backends: - ClaudeCodeBackend (extracted from AgentRuntime) - CodexBackend (OpenAI Codex CLI) - GeminiBackend (Google Gemini CLI) - OpenCodeBackend (OpenCode CLI) Add BackendRegistry for resolution/creation via AGENT_BACKEND env var. Refactor AgentRuntime to delegate to BackendAdapter instead of hardcoding Claude CLI. Update GatewayConfig with new env vars (AGENT_BACKEND, BACKEND_CLI_PATH, BACKEND_MODEL, BACKEND_MAX_TURNS). Includes 10 property-based test files and unit tests for edge cases.
9.3 KiB
Requirements Document
Introduction
The gateway currently hardcodes Claude Code CLI as its sole agent backend. This feature introduces a pluggable CLI backend system that allows operators to choose between Claude Code CLI, OpenCode CLI, Codex CLI, and Gemini CLI. Each backend has different command-line interfaces, output formats, and session management semantics. The system must abstract these differences behind a unified interface so the rest of the gateway (event processing, session management, Discord integration) remains unchanged.
Glossary
- Gateway: The Discord-to-agent bridge application (Aetheel) that receives prompts and dispatches them to a CLI backend
- CLI_Backend: A pluggable module that knows how to spawn a specific CLI tool, pass prompts and system prompts, parse output, and manage sessions
- Backend_Registry: The component that holds all available CLI_Backend implementations and resolves the active one from configuration
- Agent_Runtime: The existing
AgentRuntimeclass that orchestrates event processing; it will delegate CLI execution to the active CLI_Backend - Backend_Adapter: An interface that each CLI_Backend must implement, defining spawn, parse, and session operations
- Session_ID: An opaque string returned by a CLI backend that allows resuming a prior conversation
- Event_Result: The normalized response object returned by any CLI_Backend after processing a prompt
Requirements
Requirement 1: Backend Adapter Interface
User Story: As a developer, I want a common interface for all CLI backends, so that the gateway can interact with any backend without knowing its implementation details.
Acceptance Criteria
- THE Backend_Adapter SHALL define a method to execute a prompt given a prompt string, a system prompt string, an optional Session_ID, and an optional streaming callback
- THE Backend_Adapter SHALL return an Event_Result containing the response text, an optional Session_ID for continuation, and an error flag
- THE Backend_Adapter SHALL define a method to return the backend name as a string identifier
- THE Backend_Adapter SHALL define a method to validate that the CLI tool is reachable on the system (e.g., binary exists at configured path)
Requirement 2: Claude Code CLI Backend
User Story: As an operator, I want the existing Claude Code CLI integration preserved as a backend, so that current deployments continue working without changes.
Acceptance Criteria
- THE Claude_Code_Backend SHALL implement the Backend_Adapter interface
- THE Claude_Code_Backend SHALL spawn the Claude CLI with
-p,--output-format json,--dangerously-skip-permissions, and--append-system-prompt-fileflags - WHEN a Session_ID is provided, THE Claude_Code_Backend SHALL pass
--resume <Session_ID>to the CLI process - THE Claude_Code_Backend SHALL parse the JSON array output to extract
session_idfromsystem/initobjects andresultfromresultobjects - THE Claude_Code_Backend SHALL pass
--allowedToolsflags for each tool in the configured allowed tools list - THE Claude_Code_Backend SHALL pass
--max-turns 25to the CLI process
Requirement 3: Codex CLI Backend
User Story: As an operator, I want to use OpenAI Codex CLI as a backend, so that I can leverage OpenAI models through the gateway.
Acceptance Criteria
- THE Codex_Backend SHALL implement the Backend_Adapter interface
- THE Codex_Backend SHALL spawn the Codex CLI using
codex execsubcommand for non-interactive execution - THE Codex_Backend SHALL pass
--jsonto receive newline-delimited JSON output - THE Codex_Backend SHALL pass
--dangerously-bypass-approvals-and-sandboxto skip approval prompts - WHEN a working directory is configured, THE Codex_Backend SHALL pass
--cd <path>to set the workspace root - THE Codex_Backend SHALL parse the newline-delimited JSON events to extract the final assistant message as the response text
- WHEN a Session_ID is provided, THE Codex_Backend SHALL use
codex exec resume <Session_ID>to continue a prior session
Requirement 4: Gemini CLI Backend
User Story: As an operator, I want to use Google Gemini CLI as a backend, so that I can leverage Gemini models through the gateway.
Acceptance Criteria
- THE Gemini_Backend SHALL implement the Backend_Adapter interface
- THE Gemini_Backend SHALL spawn the Gemini CLI with the prompt as a positional argument for non-interactive one-shot mode
- THE Gemini_Backend SHALL pass
--output-format jsonto receive structured JSON output - THE Gemini_Backend SHALL pass
--approval-mode yoloto auto-approve tool executions - WHEN a Session_ID is provided, THE Gemini_Backend SHALL pass
--resume <Session_ID>to continue a prior session - THE Gemini_Backend SHALL parse the JSON output to extract the response text
Requirement 5: OpenCode CLI Backend
User Story: As an operator, I want to use OpenCode CLI as a backend, so that I can leverage multiple model providers through OpenCode's provider system.
Acceptance Criteria
- THE OpenCode_Backend SHALL implement the Backend_Adapter interface
- THE OpenCode_Backend SHALL spawn the OpenCode CLI using
opencode runsubcommand for non-interactive execution - THE OpenCode_Backend SHALL pass
--format jsonto receive JSON event output - WHEN a Session_ID is provided, THE OpenCode_Backend SHALL pass
--session <Session_ID> --continueto resume a prior session - WHEN a model is configured, THE OpenCode_Backend SHALL pass
--model <provider/model>to select the model - THE OpenCode_Backend SHALL parse the JSON events to extract the final response text
Requirement 6: Backend Selection via Configuration
User Story: As an operator, I want to select which CLI backend to use through environment variables, so that I can switch backends without code changes.
Acceptance Criteria
- THE Gateway SHALL read an
AGENT_BACKENDenvironment variable to determine which CLI_Backend to activate - THE Gateway SHALL accept values
claude,codex,gemini, andopencodefor theAGENT_BACKENDvariable - WHEN
AGENT_BACKENDis not set, THE Gateway SHALL default toclaudefor backward compatibility - THE Gateway SHALL read a
BACKEND_CLI_PATHenvironment variable to override the default binary path for the selected backend - IF an unrecognized value is provided for
AGENT_BACKEND, THEN THE Gateway SHALL fail at startup with a descriptive error message listing valid options
Requirement 7: Backend-Specific Configuration
User Story: As an operator, I want to pass backend-specific settings through environment variables, so that I can tune each backend's behavior.
Acceptance Criteria
- THE Gateway SHALL read
BACKEND_MODELenvironment variable to pass a model override to the active CLI_Backend - THE Gateway SHALL read
BACKEND_MAX_TURNSenvironment variable to limit the number of agentic turns, defaulting to 25 - WHEN the active backend does not support a configured option, THE Gateway SHALL log a warning and ignore the unsupported option
- THE Gateway SHALL pass the existing
ALLOWED_TOOLSconfiguration to backends that support tool filtering
Requirement 8: Unified Output Parsing
User Story: As a developer, I want each backend to normalize its output into a common format, so that downstream processing (Discord messaging, archiving) works identically regardless of backend.
Acceptance Criteria
- THE Backend_Adapter SHALL return Event_Result with fields:
responseText(string or undefined),sessionId(string or undefined), andisError(boolean) - WHEN a CLI_Backend process exits with a non-zero exit code, THE Backend_Adapter SHALL set
isErrorto true and include the stderr content inresponseText - WHEN a CLI_Backend process exceeds the configured query timeout, THE Backend_Adapter SHALL terminate the process and return an Event_Result with
isErrorset to true andresponseTextset to "Query timed out" - THE Backend_Adapter SHALL support an optional streaming callback that receives partial result text as the CLI process produces output
Requirement 9: Backend Validation at Startup
User Story: As an operator, I want the gateway to verify the selected backend is available at startup, so that I get immediate feedback if the CLI tool is missing or misconfigured.
Acceptance Criteria
- WHEN the Gateway starts, THE Backend_Registry SHALL invoke the active CLI_Backend's validation method
- IF the validation fails, THEN THE Gateway SHALL log an error with the backend name and configured path, and exit with a non-zero exit code
- THE validation method SHALL check that the configured CLI binary path is executable
Requirement 10: Agent Runtime Refactoring
User Story: As a developer, I want the AgentRuntime to delegate CLI execution to the Backend_Adapter, so that the runtime is decoupled from any specific CLI tool.
Acceptance Criteria
- THE Agent_Runtime SHALL accept a Backend_Adapter instance through its constructor instead of directly referencing Claude CLI configuration
- THE Agent_Runtime SHALL call the Backend_Adapter's execute method instead of spawning CLI processes directly
- THE Agent_Runtime SHALL map the Backend_Adapter's Event_Result to the existing EventResult interface used by the rest of the gateway
- WHEN the Backend_Adapter returns a Session_ID, THE Agent_Runtime SHALL store the Session_ID in the Session_Manager for the corresponding channel