Skip to content

symfonic.capabilities.delegation.compiler

compiler

The child compiler: declarations in, a roster and an ownership list out.

This is the half of delegation that used to run inside the parent agent's constructor, interleaved with twenty other concerns. Pulling it out changes three things:

  • Deciding and constructing are separate. The compiler resolves inheritance, sanitises the config, fixes the palette, and emits a :class:~.values.ChildBuildPlan; a builder port constructs. Every rule above is therefore testable without an agent, a provider, or a model.
  • The set is validated before anything is built. A duplicate name used to surface after the first child had already been constructed — with a checkpointer pool nobody would ever close, because the parent's constructor never returned to record it as owned.
  • Ownership is recorded, not inferred. A child built here must be released by whoever built it; a child handed over must not be touched. The compiler is the only place that knows which is which, so it says so once.

ChildCompiler

ChildCompiler(*, builder: Any, inheritance: Any)

Resolves declarations into children, via ports it does not implement.

Parameters:

Name Type Description Default
builder Any

Constructs a child from a plan. The one port that names a concrete agent type, which is why it lives at the composition root.

required
inheritance Any

Derives a child config from the parent's. A port because the shipped primitive is a classmethod on the facade's config class, and reaching for it directly would drag the whole configuration module into a capability.

required
Source code in src/symfonic/capabilities/delegation/compiler.py
def __init__(self, *, builder: Any, inheritance: Any) -> None:
    self._builder = builder
    self._inheritance = inheritance

compile

compile(declarations: Iterable[Any], *, parent_config: Any, parent_provider: Any = None, shared: dict[str, Any] | None = None) -> CompiledChildren

Resolve declarations into roster entries and owned children.

Parameters:

Name Type Description Default
parent_config Any

Inherited by every spec child that declares no config of its own.

required
parent_provider Any

Inherited by every spec child that declares no provider. Sharing one instance is safe — providers are stateless — and building a second would double the connection pools for no behavioural difference.

None
shared dict[str, Any] | None

Wiring handed to every built child, verbatim. The parent's memory orchestrator goes here: a child built with its own would resolve a different store than the parent, which silently breaks any child whose prompt reads a memory-backed block.

None

Raises:

Type Description
DuplicateChildError

If two declarations claim one name. Checked across the whole list first, so a failure constructs nothing.

TypeError

If a declaration is neither form.

Source code in src/symfonic/capabilities/delegation/compiler.py
def compile(
    self,
    declarations: Iterable[Any],
    *,
    parent_config: Any,
    parent_provider: Any = None,
    shared: dict[str, Any] | None = None,
) -> CompiledChildren:
    """Resolve ``declarations`` into roster entries and owned children.

    Args:
        parent_config: Inherited by every spec child that declares no
            config of its own.
        parent_provider: Inherited by every spec child that declares no
            provider. Sharing one instance is safe — providers are
            stateless — and building a second would double the connection
            pools for no behavioural difference.
        shared: Wiring handed to every built child, verbatim. The parent's
            memory orchestrator goes here: a child built with its own would
            resolve a different store than the parent, which silently
            breaks any child whose prompt reads a memory-backed block.

    Raises:
        DuplicateChildError: If two declarations claim one name. Checked
            across the whole list first, so a failure constructs nothing.
        TypeError: If a declaration is neither form.
    """
    declared = tuple(declarations)
    self._refuse_duplicates(declared)
    entries: list[RosterEntry] = []
    owned: list[Any] = []
    for declaration in declared:
        entry = self._resolve(
            declaration,
            parent_config=parent_config,
            parent_provider=parent_provider,
            shared=shared or {},
        )
        entries.append(entry)
        if entry.owned:
            owned.append(entry.runner)
    return CompiledChildren(entries=tuple(entries), owned=tuple(owned))

CompiledChildren dataclass

CompiledChildren(entries: tuple[RosterEntry, ...] = (), owned: tuple[Any, ...] = ())

What compiling a declaration list produced.

Attributes:

Name Type Description
entries tuple[RosterEntry, ...]

Roster entries in declaration order.

owned tuple[Any, ...]

The children this compilation constructed, and only those. The list a lifecycle closes.