Files
cleveragents-core/docs/adr/ADR-031-actor-abstraction-definition.md
freemo c2db74ba81
CI / lint (push) Successful in 15s
CI / build (push) Successful in 16s
CI / quality (push) Successful in 20s
CI / security (push) Successful in 35s
CI / typecheck (push) Successful in 42s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m38s
CI / integration_tests (push) Successful in 3m11s
CI / docker (push) Successful in 39s
CI / coverage (push) Successful in 4m58s
CI / benchmark-publish (push) Successful in 17m32s
Docs: Restyled ADR pages
2026-03-10 12:38:35 -04:00

15 KiB

adr_number, title, status_history, tier, authors, superseded_by, related_adrs, acceptance
adr_number title status_history tier authors superseded_by related_adrs acceptance
31 Actor Abstraction Definition
2026-02-17
Proposed
Jeffrey Phillips Freeman
2026-02-17
Accepted
Jeffrey Phillips Freeman
4
Jeffrey Phillips Freeman
null
number title relationship
10 Actor and Agent Architecture Defines the actor implementation model (LangGraph StateGraph), role specialization, and graph construction
number title relationship
11 Tool System Tools serve as atomic execution nodes within actor graphs alongside actor nodes
number title relationship
12 Skill System Skills are the mechanism through which actors acquire tool capabilities
number title relationship
22 LangChain/LangGraph Integration Actors are implemented as LangGraph StateGraph instances; LangGraph is the graph runtime
number title relationship
28 Agent Skills Standard (AgentSkills.io) Agent Skills appear as tool nodes in actor graphs and extend actor knowledge
number title relationship
29 Model Context Protocol (MCP) Adoption MCP tools appear as tool nodes in actor graphs via skill composition
number title relationship
30 Skill Abstraction Definition Skills are the unit of capability assignment to actors; the skill-actor binding model
number title relationship
32 Jinja2 YAML Template Preprocessing Defines how actor YAML files are preprocessed with Jinja2 templates and environment variable interpolation before parsing
number title relationship
27 Language Server Protocol (LSP) Integration Actors acquire language intelligence through LSP servers; the `lsp` config field is the second capability acquisition mechanism alongside skills
votes_for votes_against abstentions
voter comment
Jeffrey Phillips Freeman <Jeffrey.Freeman@CleverThis.com> Unifying agents, workflows, and pipelines under the actor-as-graph abstraction eliminates conceptual fragmentation

Context

CleverAgents needs a single abstraction that covers everything from a simple LLM wrapper answering questions to a complex multi-stage pipeline orchestrating dozens of sub-agents and tool calls. The system must support hierarchical composition (graphs containing graphs), role specialization (strategy, execution, estimation, invariant), and uniform treatment of all conversational participants. Without a formal definition of what an "actor" is, the boundary between actors, agents, graphs, and workflows becomes ambiguous — leading to inconsistent configuration, unclear composition rules, and conceptual fragmentation in both documentation and implementation. This ADR formalizes the actor abstraction as an architectural commitment.

Decision Drivers

  • A single abstraction must cover everything from simple LLM wrappers to complex multi-stage pipelines without requiring separate concepts for "agent," "workflow," and "orchestrator"
  • Actors must support hierarchical composition (graphs containing graphs) with no depth limit for orchestrator-of-orchestrators patterns
  • The boundary between actors (conversational units) and agents (LLM-backed reasoning entities) must be formally defined to preserve precision
  • All conversational participants must maintain a uniform text-in/text-out contract regardless of internal complexity
  • Capability acquisition must be declarative and go exclusively through skills (for tools) and LSP bindings (for language intelligence)

Decision

CleverAgents defines an actor as a YAML-configured conversational unit that generalizes "agent" into "anything conversational." An actor can represent a single LLM node, a composed graph of actors and tool nodes, or any text-in/text-out system. Every custom actor is implemented as a graph (a LangGraph StateGraph), and graphs can compose and contain other actors — enabling hierarchical orchestration without a separate composition model.

Design

Canonical Definition

An actor is:

  1. Anything conversational — the abstraction that generalizes "agent" into the broadest useful category. If it accepts text input and produces text output, it can be an actor.

  2. A YAML-configured graph — every custom actor is technically a LangGraph StateGraph defined via YAML configuration. Even a simple actor wrapping a single LLM is a graph with one node.

  3. Composable and hierarchical — an actor's graph can include nodes that reference other actors by name, and those actors are themselves graphs that may reference further actors. This enables "orchestrator of orchestrators" patterns with no depth limit.

  4. The unit of conversation in CleverAgents — sessions are tied to actors, context is scoped to actors, and all plan phase transitions are driven by actors.

What an Actor Can Represent

Scale Example Graph Shape
Single LLM node openai/gpt-4 answering questions One-node graph: LLM agent with skills
Simple agent local/code-reviewer with tools One-node graph: LLM + tool-calling loop
Linear pipeline Analyze -> Implement -> Test Multi-node graph: sequential actor and tool nodes
Branching workflow Triage -> (Security / Performance / Style) -> Merge Multi-node graph: conditional routing to sub-actors
Orchestrator of orchestrators Strategy actor delegates to execution actors, each a graph Nested graphs: top-level actor's nodes are themselves actors with their own graphs
External system wrapper Third-party API with conversational interface One-node graph: adapter translating text-in/text-out to API calls

The Actor-as-Graph Principle

Every custom actor IS a graph. This is not a metaphor — it is the implementation model:

  • A "simple" actor wrapping a single LLM is a graph with one agent node, zero or more tool nodes, and edges connecting them.
  • A "composite" actor orchestrating multiple sub-agents is a graph whose nodes include references to other actors (which are themselves graphs).
  • There is no separate "workflow" or "pipeline" abstraction — actors and graphs are the same thing.

This unification eliminates the need for separate concepts for "agent," "workflow," "pipeline," and "orchestrator." All are actors; they differ only in graph complexity.

Hierarchical Composition

Actors compose through named references in graph node definitions:

Actor: local/review-pipeline (graph)
  nodes:
    - orchestrator  (agent: local/review-controller)
    - security      (agent: local/security-reviewer)    <- itself a graph
    - performance   (agent: local/perf-reviewer)        <- itself a graph
    - style         (agent: local/style-reviewer)       <- itself a graph
    - merge_results (tool: local/merge-reviews)

Each sub-actor (security, performance, style) is independently
defined, registered, and testable. The parent actor references
them by name — it does not inline their definitions.

Key composition rules:

  • Named references only: Actor A references Actor B by its fully-qualified namespaced name (local/security-reviewer), never by inlining B's definition.
  • Load order matters: Referenced actors must be loaded/defined before actors that depend on them.
  • No depth limit: Actor A -> Actor B -> Actor C -> ... is valid and supported.
  • Circular references are prohibited: Detected at registration time.

Actor vs Agent

The actor/agent distinction is intentional and precise:

Actor Agent
Definition Anything conversational — the general abstraction A specialized actor with LLM reasoning, tool-calling, and memory
Scope May be an agent, a composite workflow, a multi-step graph, or an external system wrapper Always an LLM-backed conversational entity
Graph shape Any graph topology Typically a single-node graph with tool-calling loop
Key capability Text-in / text-out Text-in / text-out + tool-calling + planning + role identity
Relationship Superset Subset: every agent IS an actor, not every actor is an agent

An agent is the most common type of actor, but the actor abstraction exists precisely to accommodate non-agent participants in graphs (tool nodes, external system adapters, sub-graph orchestrators).

Actor Roles in the Plan Lifecycle

Actors fill specialized roles within the plan lifecycle:

Role Purpose Required
Strategy actor Produces the initial decision tree during the Strategize phase Yes
Execution actor Carries out decisions during the Execute phase Yes
Estimation actor Provides effort/cost estimates before execution No
Invariant actor Reconciles invariant violations during execution No
Orchestrator actor Top-level actor that a session is bound to Implicit

Each role is filled by an actor reference (namespaced name) in the action configuration. The same actor definition can fill multiple roles across different actions.

Nodes in an Actor's Graph

An actor's graph contains two types of nodes:

  1. Actor nodes (type: agent) — references to other actors by name. These provide intelligence, reasoning, and conversational capability.

  2. Tool nodes (type: tool) — deterministic, non-LLM steps that directly invoke a tool. Tool nodes can reference named registered tools or define anonymous inline tools.

Actors provide intelligence; tools provide capability. Both participate as first-class nodes in the same graph, connected by edges with optional conditional routing.

Capability Acquisition

Actors acquire capabilities through two complementary mechanisms:

Tool Capabilities (via Skills)

  1. An actor's YAML configuration lists skill references in its skills: section.
  2. At activation, the runtime resolves each skill to its flattened tool set.
  3. The merged tool surface is made available for both LLM tool-calling within agent nodes and as named tool nodes in the actor's graph.

There is no mechanism for an actor to gain tool capabilities outside the skill system.

Language Intelligence (via LSP)

  1. An actor's YAML configuration declares LSP server bindings in its lsp: section — explicit server names, language-based resolution, or auto-discovery from project resources.
  2. At activation, the runtime resolves each binding to registered LSP servers from the LSP Registry and starts the required language server processes.
  3. Language intelligence (diagnostics, type information, completions, symbol references, definition lookups) is exposed to the actor both as tools (via LSPToolAdapter, analogous to MCPToolAdapter) and as ACMS context enrichment (auto-injected diagnostics and type annotations in code context windows).

This dual acquisition model — tools through skills, language intelligence through LSP — gives actors the same software development capabilities that IDEs give human developers, without coupling the actor system to any IDE or presentation layer. See ADR-027 for full details.

Constraints

  • Every custom actor must be defined in a YAML configuration file with a namespaced name.
  • Every custom actor is implemented as a LangGraph StateGraph — there is no "non-graph" actor type.
  • Actors must only reference other actors and tools by fully-qualified namespaced name.
  • Circular actor references are prohibited and must be detected at registration time.
  • Actor capability acquisition must go through the skill system — direct tool references outside skills are not permitted for capability assignment.
  • All actors must maintain the text-in/text-out conversational contract, regardless of internal complexity.

Consequences

Positive

  • A single abstraction covers all scales from simple LLM wrappers to complex multi-stage pipelines, eliminating conceptual fragmentation.
  • The actor-as-graph principle means there is one composition model, one configuration format, and one runtime execution model for all conversational participants.
  • Hierarchical composition enables reuse — sub-actors are independently defined, tested, and composed into larger workflows.
  • The actor/agent distinction preserves precision (agents have specific capabilities) while the actor generalization ensures nothing conversational is excluded.
  • New types of conversational participants (e.g., external system adapters) fit naturally into the model without new abstractions.

Negative

  • Even trivially simple LLM wrappers are technically graphs, adding conceptual overhead for users who just want "an LLM that answers questions."
  • Named reference composition requires all sub-actors to be independently registered, increasing configuration surface for deep hierarchies.
  • The text-in/text-out constraint excludes non-conversational processing (e.g., pure data transformation) from the actor model, requiring tool nodes instead.

Risks

  • Users may conflate "actor" and "agent" despite the formal distinction, leading to configuration errors (e.g., treating a composite workflow as if it were a single LLM agent).
  • Deeply nested actor hierarchies could become difficult to debug when errors propagate through multiple graph layers.
  • The "everything is a graph" principle could discourage simpler mental models that are sufficient for basic use cases.

Alternatives Considered

Separate abstractions for agents, workflows, and orchestrators — Define distinct types (Agent, Workflow, Pipeline, Orchestrator) with different configuration formats and composition rules. Increases conceptual load, fragments the configuration model, and requires mapping between types when composing. The actor-as-graph unification is strictly more powerful and simpler. Rejected.

Agent-only model (no actor generalization) — All conversational participants are "agents." This conflates LLM-backed reasoning with graph orchestration and makes it unclear how to model non-LLM nodes or external system wrappers. The actor/agent distinction preserves precision. Rejected.

Code-defined actors (no YAML) — Define actors in Python instead of YAML. Provides maximum flexibility but sacrifices declarative configuration, CLI management (agents actor add/show/list), and the ability for non-developers to compose workflows. YAML configuration with code escape hatches (inline Python in tool nodes) is the correct balance. Rejected as the primary model.

Flat actor model (no hierarchical composition) — Actors cannot reference other actors; all graph nodes must be tool nodes or direct LLM nodes. This eliminates the "orchestrator of orchestrators" pattern, prevents sub-graph reuse, and forces users to flatten complex workflows into single monolithic graphs. Rejected.

Compliance

  • Actor-as-graph tests: Verify that all custom actor configurations (including single-LLM actors) produce valid LangGraph StateGraph instances.
  • Composition tests: Verify that actor graphs correctly resolve named actor references, detect circular references, and enforce load order.
  • Role binding tests: Validate that actions correctly bind strategy, execution, estimation, and invariant roles to registered actors.
  • Capability binding tests: Confirm that actor activation resolves skill references and produces the correct merged tool surface.
  • Conversational contract tests: Verify that all actors (simple and composite) accept text input and produce text output regardless of internal graph complexity.