Skip to content

AgentGenerator and Build Steps

The generator orchestrates a pipeline of BaseAgentGeneratorBuildStep classes that each perform one discrete stage of a build: writing a source template, compiling it, signing the binary, registering the output artifact. Steps execute sequentially and share state through a common environment SimpleNamespace.

Build steps

Each step subclasses BaseAgentGeneratorBuildStep and implements one method: build(). All lifecycle hooks (on_started, on_running, etc.) are @final on build steps and cannot be overridden. build() is the only customisation point.

from consortium.framework.agents import BaseAgentGeneratorBuildStep
from consortium.framework.signal_exceptions import AgentGeneratorBuildStepRuntimeError


class BuildScript(BaseAgentGeneratorBuildStep):
    name = "Build Script"
    description = "Configure and write the agent Python script."

    async def build(self, parameters: dict) -> None:
        # self.root_directory is a pathlib.Path to this source file's parent
        template_path = self.root_directory / "agent_source" / "agent.py"
        source = template_path.read_text()

        source = source.replace(
            'REMOTE_HOST = "127.0.0.1"',
            f'REMOTE_HOST = {repr(parameters["remote_host"])}',
        ).replace(
            "REMOTE_PORT = 4444",
            f"REMOTE_PORT = {repr(parameters['remote_port'])}",
        )

        if parameters["format"] == "script":
            await self.agent_templates_payload_service.create_payload_file(
                build_parameters=parameters,
                content=source,
                name="agent.py",
            )
        elif parameters["format"] == "oneliner":
            await self.agent_templates_payload_service.create_payload_file(
                build_parameters=parameters,
                content='python -c "' + repr(source) + '"',
                name="agent.txt",
            )
        else:
            raise AgentGeneratorBuildStepRuntimeError(
                f"Unknown format '{parameters['format']}' specified.",
            )

AgentGeneratorBuildStepRuntimeError is the signal exception for recoverable build failures. Raising it transitions the step to ERRORED and propagates the error up to the generator.

self.root_directory

self.root_directory is a pathlib.Path pointing to the directory that contains the build step's source file. Use it to locate sibling files: source templates, signing certificates, embedded scripts:

template = self.root_directory / "agent_source" / "agent.py"
cert = self.root_directory / "signing" / "cert.pem"

The path is resolved from the source file when the step class is defined, so it always points to where the step class was defined, regardless of the working directory at runtime.

self.agent_templates_payload_service

Use self.agent_templates_payload_service to store build artifacts for later retrieval via the REST API:

Method Description
await create_payload_file(build_parameters, content, name) Create a new text file in the payload store; content is a string
await add_payload_file(build_parameters, path, name) Register an existing file from path (a pathlib.Path) into the payload store, moving it by default; pass copy=True to leave the original in place

These methods are coroutines: their disk I/O is offloaded to a worker thread so it does not block the event loop, so they must be awaited from within the step's async def build. Both require build_parameters (the full parameters dict) to tag the artifact with its provenance. Stored artifacts are retrievable via the REST API after the build completes.

Passing state between steps

The environment namespace is shared across all steps within one generator run. Write to it in one step and read from it in the next:

class CompileStep(BaseAgentGeneratorBuildStep):
    name = "Compile"
    async def build(self, parameters):
        # ... compile the binary ...
        self.environment.binary_path = output_path   # set for next step

class SignStep(BaseAgentGeneratorBuildStep):
    name = "Sign"
    async def build(self, parameters):
        binary = self.environment.binary_path        # read from previous step

AgentGenerator

AgentGenerator declares the ordered list of step classes. The on_running() method is @final on BaseAgentGenerator and drives the pipeline automatically; do not override it. Override on_started() to run pre-build validation:

import shutil

from consortium.framework.agents import BaseAgentGenerator
from consortium.framework.signal_exceptions import AgentGeneratorStartError

from .build_steps import BuildScript


class AgentGenerator(BaseAgentGenerator):
    agent_generator_build_steps = [BuildScript]

    async def on_started(self) -> None:
        if (
                self.parameters["format"] == "executable"
                and shutil.which("pyinstaller") is None
        ):
            raise AgentGeneratorStartError(
                "format='executable' requires PyInstaller, but it was not found on PATH."
            )

    async def on_completed(self) -> None:
        self.logger.success(
            "Build complete. {} step(s) executed.",
            len(self.agent_generator_build_steps),
        )

AgentGeneratorStartError transitions the generator back to INITIALIZED before any steps run. Use it to fail fast on missing tools or invalid parameter combinations that would cause all steps to fail.

Generator lifecycle hooks

Hook When it fires Override for
on_started() Before build steps begin Pre-build validation; fail fast with AgentGeneratorStartError
on_running() @final -- do not override Drives the step pipeline automatically
on_completed() After all steps succeed Post-build notifications, cleanup
on_stopped() When stopped before all steps complete Resource cleanup on early halt
on_cancelled() When cancelled externally Resource cleanup on abort
on_errored(error) AgentGeneratorRuntimeError raised Custom error handling
on_fatal(exc, phase) Unhandled exception in any hook Last-resort alerting

What lives on self

BaseAgentGeneratorBuildStep

Attribute Type Description
self.name str Step name (class attribute); unique within the generator
self.parameters dict Generator parameters forwarded from the owning generator
self.environment SimpleNamespace Shared namespace across all steps in one run
self.root_directory pathlib.Path Directory containing this step's source file
self.agent_templates_payload_service AgentTemplatesPayloadsService Storage for build artifacts
self.logger loguru.Logger Step-scoped logger
self.datetime_started datetime \| None Set when the step starts
self.datetime_stopped datetime \| None Set when the step ends
self.time_elapsed_in_seconds float \| None Property: wall-clock duration of the most recent run

BaseAgentGenerator

Attribute Type Description
self.agent_generator_id uuid.UUID Unique identifier for this generator instance
self.name str Display name set at creation time
self.description str Description set at creation time
self.parameters dict Resolved option values from the template
self.agent_generator_build_steps list Step class list declared at class level
self.environment SimpleNamespace Shared mutable namespace across all steps
self.datetime_created datetime Creation timestamp
self.stop_event asyncio.Event Set when stop() is called
self.status Status Lifecycle status
self.logger loguru.Logger Generator-scoped logger
self.services namespace All framework services
self.creating_agent_template BaseAgentTemplate Template that created this generator (class attribute)