# ADR-014: Autonomous Execution Model

**Status**: Accepted

**Context**: With LangGraph selected as the orchestration framework
(ADR-013), the system needs an explicit execution model that defines how
graph nodes reason (LLM calls), act (tool invocations), and iterate on
failure (retry/repair loops).  The non-LangChain workflow path delegates
all cognition to the Copilot agent running the terminal session — the
agent IS the LLM, so reasoning is implicit.  In the LangGraph-based
path, the framework manages orchestration and routing, but nodes need a
well-defined contract for invoking LLMs with appropriate context,
executing side effects through existing `agentic_devtools` Python
functions, and handling failures with retry logic.

## Options Considered

Four options were evaluated:

### Option A: Direct LLM + Function Calls

Each node calls the LLM directly with tool definitions; the LLM decides
which tools to invoke.  The node manages the tool-calling loop until the
LLM signals completion.

- ✅ Simple mental model — one LLM call per node
- ❌ Couples nodes to a specific LLM provider's function-calling format
- ❌ Testing requires mocking provider-specific APIs
- ❌ No clear separation between reasoning and acting

### Option B: Subprocess Agent Delegation

Nodes spawn a coding agent (e.g., Copilot CLI, aider) as a subprocess,
passing a structured prompt and collecting results.

- ✅ Leverages existing agent capabilities for implementation work
- ❌ Subprocess overhead and non-deterministic output
- ❌ Difficult to unit test — requires running real agents or complex mocks
- ❌ No structured output guarantee

### Option C: Hybrid Model (Selected)

Analysis/decision nodes use direct LLM reasoning with structured output.
Implementation nodes may delegate to specialized coding agents.  All
interactions go through protocol-based abstractions.

- ✅ Best fit for both analysis (PR review, planning) and implementation
- ✅ Provider-agnostic via `ReasoningProvider` protocol
- ✅ Fully testable with mocked dependencies — no live LLM calls needed
- ✅ Per-node model selection enables cost control
- ✅ Composable retry with context accumulation
- ✅ Zero coupling to LangChain, OpenAI, or Anthropic libraries

### Option D: LangChain Agent Executor

Use LangChain's built-in `AgentExecutor` or `create_tool_calling_agent`
within nodes.

- ✅ Battle-tested agent loop implementation
- ❌ Deep coupling to LangChain's tool and message abstractions
- ❌ Makes the `execution/` module depend on `langchain-core`
- ❌ Harder to test without LangChain's test utilities
- ❌ Limits future flexibility to swap providers

**Decision**: Use **Option C — Hybrid Model** with factory/closure
dependency injection.

The execution model is implemented as the
`agentic_devtools/orchestration/execution/` sub-package with these
contracts:

1. **`ReasoningProvider` protocol** — provider-agnostic LLM invocation
   accepting prompt, optional tool definitions, output schema, and model
   identifier.  Returns `ReasoningResponse[T]`.

2. **`ToolRegistry` protocol** — sole execution boundary for tool lookup
   and invocation.  Nodes MUST NOT import tool implementations directly;
   they call `registry.invoke(tool_name, **kwargs)` instead.

3. **`TraceEmitter` protocol** — minimal observability sink.  Emit
   failures never propagate back into node execution.

4. **`ExecutionContext` dataclass** — immutable container bundling the
   three protocols plus JSON-safe configuration.

5. **Factory pattern** — node factories accept `ExecutionContext` and
   return `Callable[[ExecutionState], NodeUpdateAlias]`, preserving
   LangGraph's expected node signature while enabling DI.

6. **`with_retry()` utility** — composable retry wrapper with context
   accumulation so downstream reasoning attempts learn from prior
   failures.

**Consequences**:

- ✅ Nodes are fully unit-testable with mocked LLM and tool dependencies
- ✅ No runtime dependency on `langchain`, `openai`, or `anthropic` in the
  `execution/` package — verified by `assert_import_isolation()`
- ✅ Per-node model selection supports cost control (cheap models for
  simple tasks, expensive models for complex reasoning)
- ✅ Trace events provide debuggability for every LLM call and tool
  invocation
- ✅ Retry with context accumulation enables self-correcting nodes
- ⚠️ Protocol-based DI requires implementers to follow structural
  subtyping conventions
- ⚠️ The factory pattern adds one level of indirection compared to plain
  node functions
- ⚠️ Real `ReasoningProvider` implementations (sibling subtask #1878) are
  needed before nodes can do live LLM work
