Skip to content

Simulation Extensibility API Reference

This page is a developer-facing API reference for extending simulation structure in code. It is separate from workflow guides such as Environment Layer and Building Agents.

Use this reference when adding new agent runtimes, backend apps, game masters, GM components, engines, engine policies, or probe/evaluator code.

Reference Map

Layer Primary modules
Agents src/silisocs/agents/base_agent.py, agents/native.py, agents/fixed.py
Agent builders src/silisocs/runtime/construction/agent_builders/
Environment apps src/silisocs/environments/backends/base.py, environments/backends/factory.py
GMs src/silisocs/environments/gm/base_game_master.py, gm/game_master.py
GM components src/silisocs/environments/gm/components/
Engines src/silisocs/simulation_engines/base_engines.py, simulation_engines/runtime_base.py
Engine policies src/silisocs/simulation_engines/policies/loops.py, steps.py, turns.py, probe_schedule.py

1) Agents API

All agent runtimes must implement silisocs.agents.base_agent.Agent:

class Agent(ABC):
    @property
    def name(self) -> str: ...

    def observe(self, observation: str) -> None: ...

    def act(self, action_spec: ActionSpec) -> ActionOutput: ...

act() returns an ActionOutput. Native agents usually assemble their context inside act() and call self._call_model(context, action_spec), which routes the typed ActionSpec to the configured LanguageModel. Custom runtimes with internal state should also implement get_state() and set_state(state) for checkpoint resume.

Native configs instantiate runtime classes directly with class_path and params. Reference implementations live in src/silisocs/agents/native.py and src/silisocs/agents/fixed.py. Concordia-style prefab agents are supported only behind compat: concordia and the optional Concordia adapter.

Language Model API

All model providers implement silisocs.runtime.language_models.LanguageModel. Agents do not call provider-specific clients directly; they use _call_model(...) or, for custom low-level behavior, the typed model methods:

sample_text(prompt, **kwargs) -> str
sample_choice(prompt, responses, **kwargs) -> tuple[int, str, dict[str, float]]
sample_tool_calls(prompt, tools, *, mode="single", **kwargs) -> list[ToolCall]
sample_structured(prompt, schema, **kwargs) -> dict[str, Any]

Use ScriptedLanguageModel for deterministic tests, OpenAILanguageModel for the OpenAI API, OpenAICompatibleLanguageModel for compatible local or hosted endpoints, and NoLanguageModel only when an agent never needs model output. Unsupported typed outputs should raise rather than silently returning dummy data.

Agent Builder API

Agent builders translate config into construction specs. They do not instantiate live agents and they do not receive the model. Subclass silisocs.runtime.construction.agent_builders.AgentBuilder and implement:

def build_agent_configs(self) -> list[AgentConfig]: ...

The default PersonaPipelineAgentBuilder handles inline/config/file/Hugging Face records, field mapping, shared/specific memories, and fixed-action plan rendering. Custom builders may call it internally, then append additional AgentConfig records. Runtime assembly remains responsible for loading classes, injecting the LanguageModel, and wrapping explicit Concordia-compatible agents.

2) Environment App API

The core backend contract is BackendApp in src/silisocs/environments/backends/base.py.

Common backend setup hook:

def initialize(self, agent_names: list[str], **kwargs: Any) -> None: ...

Common optional hooks:

  • observe(actor_name: str, **kwargs) -> str
  • update(step: int, agent_names: Sequence[str], context=None) -> None

Timeline/recommendation capability methods live on SocialBackendApp, not on plain BackendApp:

  • get_timeline(user_name: str, limit: int = 10) -> list[dict]
  • get_timeline_mode(...) -> list[dict]
  • format_timeline_for_observation(timeline: list[dict]) -> str
  • parse_and_resolve_action(user_name: str, action_data: dict) -> str

Expose executable actions with @app_action. generic_action and tool_calling resolve modes discover those actions automatically. If an action needs the acting identity, include an agent_name: str parameter; the resolver injects it from the active Agent Name and omits it from agent-facing prompts/tool schemas. Target parameters such as target_user remain agent-visible.

SocialBackendApp subclasses BackendApp for backends that use the timeline/recommendation GM components. Backends that do not need those components should subclass BackendApp directly.

Custom app config:

env:
  gm:
    backend:
      type: custom
      class_path: my_pkg.apps.MyBackendApp
      params:
        initial_cash: 20

params are strict constructor arguments. Unknown keys fail before simulation startup unless the target class accepts **kwargs.

3) Game Master API

Base class: BaseGameMaster Module: src/silisocs/environments/gm/base_game_master.py

Direct GM methods live on ComponentGameMaster. MultiFlowGameMaster keeps the same public methods and routes component slots by agent flow:

  • acting_agents(...): chooses active actors.
  • action_prompt(agent_name): emits typed ActionSpec.
  • make_observation(agent_name): computes per-agent observation text.
  • resolve_action(agent_name, action): dispatches typed ActionOutput.
  • update(step, agents, context): runs pre-turn environment updates.
  • initialize(agents, context): initializes backend/app state through the Game Master's components.initialize slot.

Important integration fields used by engines/components:

  • backend
  • agent_flow_tags
  • flow_to_component_map

GMs should keep state minimal: the backend, typed component slots, routing metadata, and optional get_state/set_state hooks. They should not read the Hydra config directly; construction code passes normalized params into backend and component constructors.

4) GM Component API

Factory module: src/silisocs/environments/gm/components/factory.py

Public builders:

  • build_initialize_component(...)
  • build_initialize_components(...)
  • build_action_prompt_component(...)
  • build_observe_component(...)
  • build_observe_components(...)
  • build_resolve_component(...)
  • build_next_acting_component(...)
  • build_update_component(...)

Config schema pattern:

<slot_name>:
  built_in: <registered_name>
  class_path: <optional.module.Class>
  params: {}
  instances: {}   # optional, for flow-routed GMs
  flow_map: {}    # optional, maps flow names to instance keys

params are strict constructor arguments. Unknown keys fail early unless the target component accepts **kwargs. Runtime-injected values such as model, agent_names, and app handles may still be filtered when a constructor does not accept them. Observe components that explicitly accept observation_params may use params as a forwarded observation-settings bag.

Built-in component names:

  • initialize: social_media, app_initialize, disabled, none
  • action_prompt: default
  • observe: app_observation, timeline_every_turn, episode_only
  • resolve: parsed_action, generic_action, tool_calling
  • next_acting: activity_markov, activity_probability, all_agents, fixed_order
  • update: social_recommendation, disabled, none

Generic component bases and defaults live directly under silisocs.environments.gm.components. Social-media-only components live under silisocs.environments.gm.components.social_media but keep the same built-in YAML names.

Subcomponent interfaces in components/base.py:

  • BaseComponent.get_state() and BaseComponent.set_state(...)
  • InitializeComponent.initialize(...)
  • NextActingComponent.acting_agent_names()
  • ActionPromptComponent.action_prompt(agent_name)
  • ObservationComponent.make_observation(agent_name)
  • ResolveComponent.resolve_action(agent_name, action)
  • UpdateComponent.update(step, agents, context)

Default baselines are available for backends that only need BackendApp:

  • AppInitializeComponent calls a backend's initialize(...).
  • AllAgentsNextActing, FixedOrderNextActing, ActivityProbabilityNextActing, and ActivityMarkovNextActing select agents without social timeline logic.
  • AppObservationComponent delegates to BackendApp.observe(...).
  • EpisodeObservation returns episode-index observations for scripted flows.
  • generic action and tool-calling resolvers dispatch backend actions discovered through @app_action.
  • AppUpdateComponent delegates to BackendApp.update(...).
  • NoOpUpdateComponent is the explicit update slot for environments that do not need pre-turn state refresh.

Social-media-specific defaults are intentionally isolated under components.social_media: TimelineMakeObservation and SocialRecommendationUpdateComponent. They require a SocialBackendApp.

Use BaseComponent.get_state() / set_state(...) only for component-owned state that must survive checkpoint resume, such as a fixed-order index. Backend domain state should live in the backend.

Runtime bootstrap extensions live under silisocs.initialization:

  • initialization.agents.AgentInitializer
  • initialization.game_masters.GameMasterInitializerStrategy
  • initialization.game_masters.GameMasterInitializer
  • initialization.simulation.SimulationInitializer

The Engine runs those phases in order: agents, Game Masters, then simulation. Seed posts are simulation initialization and are posted through GameMaster.resolve_action(...).

Initializer contracts:

  • Agent initializers receive agents plus AgentInitializationContext, mutate agent memory/observations by calling agent.initialize(...), and return None.
  • Game Master phase strategies receive GMs, agents, and the initialization context, then call each GM's initialize(...). The GM delegates backend setup to its initialize component.
  • Simulation initializers receive agents, GMs, and context after backend setup. Seed-post initializers generate or load seed text and post it through normal GM resolution, so backend action logs keep the same shape as in-loop actions.

5) Engine API

Modules:

  • src/silisocs/simulation_engines/base_engines.py: concrete RuntimeEngine plus preset wrappers.
  • src/silisocs/simulation_engines/runtime_base.py: shared dataclasses and protocols, including AgentStepResult, StepBatch, and StepResult.
  • src/silisocs/simulation_engines/policies/loops.py: loop policies.
  • src/silisocs/simulation_engines/policies/steps.py: step policies.
  • src/silisocs/simulation_engines/policies/turns.py: turn policies.

Factory entrypoint: build_engine(cfg) in src/silisocs/runtime/construction/engines.py.

RuntimeEngine provides startup initialization, per-step GM updates, per-agent observe/act/resolve, action concurrency, retry telemetry, and probe phase orchestration. The preset wrappers select default step policies: BaseRuntimeEngine uses base scheduling, FlowRuntimeEngine uses flow scheduling, and MultiGMRuntimeEngine uses multi-GM flow routing.

6) Engine Policy API

Loop policies own the outer episode lifecycle and implement:

def run(
    *,
    engine: Any,
    game_masters: list[Any],
    agents: list[Any],
    max_steps: int,
    start_step: int,
    verbose: bool,
    checkpoint_callback: Any | None,
) -> None: ...

Step policies own one episode step and implement:

def run(
    *,
    engine: Any,
    step_index: int,
    game_masters: list[Any],
    agents: list[Any],
    verbose: bool,
) -> StepResult: ...

Most custom step policies should call engine._execute_batches(...) with a sequence of StepBatch(flow_name, game_master, turns). Each turns entry is (agent, action_spec). Calling _execute_batches(...) keeps standard action logging, retry telemetry, concurrency limits, and StepResult shape. A custom step policy may bypass it, but then it owns those responsibilities.

Turn policies own one selected agent's action cadence and implement:

def run(
    *,
    engine: Any,
    game_master: Any,
    agent: Any,
    action_spec: Any,
    verbose: bool,
) -> str: ...

Most custom turn policies should call engine.run_agent_step(game_master=..., agent=..., action_spec=..., verbose=...). That helper performs observation, agent.act(...), GM resolution, and result observation. A custom turn policy may bypass it only when it intentionally owns the full observe/act/resolve cycle.

Probe-schedule policies live in src/silisocs/simulation_engines/policies/probe_schedule.py and implement:

def should_run_probe_phase(self, *, step: int, orchestrator: Any) -> bool: ...

Built-ins:

  • Loop policy: fixed_steps
  • Step policy: base, sequential, flow, multi_gm
  • Turn policy: single_action, fixed_count, open_ended
  • Probe schedule: step_schedule, fixed_interval, disabled

Turn policy params include observe_before_act: first | always | never. first is the default and preserves existing behavior for repeated actions. Policy factories support built_in, optional class_path, and strict constructor validation for configured params.

7) Extension Recipes

Add a custom backend app:

  1. Subclass BackendApp.
  2. Implement initialize(...).
  3. Implement observe(...) and optionally update(...).
  4. Add @app_action methods.
  5. Configure env.gm.backend.class_path and env.gm.backend.params.

Add a custom GM component:

  1. Implement a native context component or initializer hook.
  2. Point env.gm.components.<slot>.class_path to the class.
  3. Put public constructor settings under params.

Add a custom turn policy:

sim:
  engine:
    turn_policy:
      class_path: my_pkg.policies.MyTurnPolicy
      params:
        max_actions: 5

Add a custom sequential-style step policy:

from silisocs.simulation_engines.runtime_base import StepBatch, StepResult


class MySequentialStepPolicy:
    name = "my_sequential"

    def run(self, *, engine, step_index, game_masters, agents, verbose) -> StepResult:
        del step_index, verbose
        gm = game_masters[0]
        turns = engine._selected_turns(game_master=gm, candidate_agents=agents)
        batches = [
            StepBatch(flow_name=f"agent:{agent.name}", game_master=gm, turns=[turn])
            for turn in turns
            for agent, _spec in [turn]
        ]
        return engine._execute_batches(step_index=0, batches=batches, verbose=False)

Add a custom probe schedule policy by implementing should_run_probe_phase(...) and configuring eval.probes.schedule.class_path.

Add a custom probe type:

  1. Subclass ProbeBase.
  2. Implement form_question_for_agent(agent) and parse_answer(raw_response).
  3. Register the module with eval.probes.probe_lib_module.

Probe prompts should ask the measurement question and any answer-format constraint. Do not inject agent identity text into probes; agents supply their own identity/persona context when they act.

8) Compatibility Checklist

  • Keep method signatures aligned with factory call sites.
  • Treat params as strict public API; add **kwargs only when intentionally accepting arbitrary config keys.
  • Preserve checkpoint state contracts (get_state/set_state) when stateful.
  • Add targeted tests for factory construction and runtime behavior.
  • Update this page when adding new built-ins or extension hooks.