Files
cleveractors-core/docs/reference/actor_compiler.md
T
hurui200320 dd6c20df5d
CI / dead_code (pull_request) Successful in 43s
CI / typecheck (pull_request) Successful in 1m13s
CI / unit_tests (pull_request) Successful in 1m11s
CI / lint (pull_request) Successful in 1m16s
CI / security (pull_request) Successful in 1m15s
CI / coverage (pull_request) Successful in 1m16s
CI / build (pull_request) Successful in 26s
CI / typecheck (push) Successful in 1m3s
CI / unit_tests (push) Successful in 1m2s
CI / lint (push) Successful in 1m6s
CI / dead_code (push) Successful in 27s
CI / security (push) Successful in 57s
CI / coverage (push) Successful in 59s
CI / build (push) Successful in 37s
fix(compiler): thread actor-level system_prompt into graph node metadata
actor/compiler.py:_map_node() already threaded provider and model from the
actor-level config into each AGENT node's merged_meta via setdefault, but
system_prompt was never added to the same pattern.

Three targeted additions across two files:

1. _map_node() in actor/compiler.py gains an actor_system_prompt parameter
   and applies merged_meta.setdefault("system_prompt", actor_system_prompt)
   inside the AGENT branch, directly after the existing provider/model calls.
   setdefault semantics ensure per-node config.system_prompt overrides take
   precedence, exactly matching the existing provider/model behaviour.

2. compile_actor() now passes actor_system_prompt=config.system_prompt to
   _map_node() so the actor-level field reaches every AGENT node.

3. Node._execute_agent() in langgraph/nodes.py injects the node's
   metadata["system_prompt"] into the context dict (before state.metadata
   is merged) so host agents can read context.get("system_prompt") without
   needing a direct reference to the raw ActorConfigSchema.

Seven BDD scenarios added in features/compiler_system_prompt.feature:
- Actor-level system_prompt is threaded into each AGENT node metadata.
- Per-node system_prompt takes precedence via setdefault semantics.
- process_message() receives system_prompt in the context dict.
- state.metadata["system_prompt"] overrides compiled default at runtime.
- Non-AGENT nodes (TOOL) do not receive system_prompt in metadata.
- Actor without system_prompt compiles metadata as None.
- Absent system_prompt is excluded from runtime context dict.

Bug fix: changed truthiness check from `if node_system_prompt:` to
`if node_system_prompt is not None:` in _execute_agent() to faithfully
pass empty-string system prompts into the context dict. Added explicit
`str | None` type annotation for node_system_prompt.

docs/reference/actor_compiler.md updated to document system_prompt
threading alongside provider and model in the Node Binding table.

Quality gates: lint, typecheck, unit_tests (14/14), coverage_report,
security_scan, dead_code, complexity all pass.

ISSUES CLOSED: #6
2026-05-22 06:05:16 +00:00

5.1 KiB

Actor Compiler

The actor compiler translates hierarchical YAML-defined GRAPH actors into LangGraph StateGraph node/edge structures that can be executed by the host application.

Module: cleveractors.actor.compiler

Compilation Pipeline

  1. Input validation — The compiler accepts an ActorConfigSchema with type=GRAPH and a populated route field. Non-GRAPH types are rejected with ActorCompilationError.

  2. Reference validation — All node IDs referenced in edges, entry, and exit points are checked against the declared node set.

  3. Intra-graph cycle detection — The route's detect_cycles() method verifies the node graph is acyclic.

  4. Cross-actor subgraph cycle detection — When an optional actor_resolver is provided, the compiler follows SUBGRAPH node references recursively and detects cycles across actor boundaries (e.g. actor A → actor B → actor A).

  5. Node mapping — Each NodeDefinition is mapped to a LangGraph NodeConfig with the appropriate NodeType (AGENT, TOOL, CONDITIONAL, SUBGRAPH).

  6. Edge mapping — Each EdgeDefinition is mapped to a LangGraph Edge. Conditional expressions are preserved in edge.condition.

  7. LSP binding extraction — Per-node lsp_bindings config entries are extracted into LspBinding objects and stored in compilation metadata.

  8. Metadata assembly — The compiler returns a CompiledActor containing the node map, edge list, entry point, and a CompilationMetadata object for diagnostics.

Node Binding

Actor Node Type LangGraph NodeType Notes
agent AGENT LLM invocation node; resolved at runtime via ProviderRegistryPort. Graph-level provider, model, and system_prompt act as per-node defaults (see below).
tool TOOL Tool execution node; reference verified at load time via ToolRegistryPort
conditional CONDITIONAL Routing node
subgraph SUBGRAPH Nested actor reference

AGENT node defaults

Graph-level provider, model, and system_prompt are propagated as fallback defaults into each AGENT node's NodeConfig.metadata via setdefault. Per-node config.provider / config.model / config.system_prompt override them. The system_prompt key is only set in metadata when the actor defines a top-level system_prompt value (including empty string); actors without the field do not carry the key.

At runtime, Node._execute_agent() injects metadata["system_prompt"] (when present) into the context dict passed to agent.process_message(), so host agents can read it without a direct reference to the raw ActorConfigSchema. Per- execution state.metadata["system_prompt"] overrides the compiled default.

LSP bindings are declared per-node in the config.lsp_bindings list:

nodes:
  - id: coder
    type: agent
    name: Code Writer
    description: Writes Python code
    config:
      agent: coder_agent
      lsp_bindings:
        - lsp_server_name: local/pyright
          languages: [python]
          auto_detect: true

Error Modes

Error Class When
Non-GRAPH type ActorCompilationError config.type != GRAPH
Missing route ActorCompilationError config.route is None
Missing node MissingNodeError Edge references unknown node
Invalid entry/exit InvalidEntryExitError Entry/exit node not in graph
Intra-graph cycle SubgraphCycleError Nodes form a cycle
Cross-actor cycle SubgraphCycleError Subgraph refs form a cycle

API Reference

compile_actor(config, *, actor_resolver=None) -> CompiledActor

Compile an ActorConfigSchema into a LangGraph-ready bundle.

from cleveractors.actor.compiler import compile_actor
from cleveractors.actor.schema import ActorConfigSchema

cfg = ActorConfigSchema.model_validate(yaml.safe_load(yaml_text))
compiled = compile_actor(cfg)

Parameters:

  • config — The actor configuration (must be ActorType.GRAPH).
  • actor_resolver — Optional callable (name: str) -> ActorConfigSchema | None for resolving subgraph references during cross-actor cycle detection.

Returns: CompiledActor with nodes, edges, and metadata.

CompiledActor

Field Type Description
name str Actor name
nodes dict[str, NodeConfig] LangGraph node configs
edges list[Edge] LangGraph edges
entry_point str Entry node ID
metadata CompilationMetadata Diagnostic metadata

CompilationMetadata

Field Type Description
node_ids list[str] All node IDs (sorted)
tool_nodes list[str] Tool-type node IDs
lsp_bindings list[LspBinding] Per-node LSP bindings
subgraph_refs dict[str, str] Subgraph node → actor name
entry_node str Entry point node ID
exit_nodes list[str] Exit point node IDs

See Also