Environment Layer: Engine, Game Master, and Backends¶
For API-level extension contracts (classes, method signatures, and factory hooks), see Simulation Extensibility API.
The environment layer is composed of three parts:
- Engine: step loop, concurrency, probe timing, and action execution orchestration.
- Game Master (GM): who acts next, what agents observe, and how action text is resolved.
- Environment Backend: domain state and executable actions
(
@app_actionmethods).
This page focuses on end-user and developer configurability for Engine/GM/backends.
Design Goals¶
- Strong defaults that run out of the box.
- YAML-first component selection for common customization.
- Class-path extension for advanced custom components.
- Predictable
sim.action_modeworkflows (custom,generic) with explicitsim.tool_calling.mode.
Current Runtime Model¶
- Engine remains policy-oriented (loop and scheduling concerns).
- GM follows a native component-routing style with direct typed methods.
- Backend actions are exposed as callable tools through
@app_actionmethods. - Specialized agent classes can define distinct runtime behavior (for example, fixed-action agents) while still using the same GM resolve components.
Flow controls use two independent settings:
env.gm.class_path: silisocs.environments.gm.game_master.MultiFlowGameMaster: enables GM-side component routing.sim.engine.step.built_in: flow: enables engine-side flow scheduling.
Canonical Structure¶
Canonical modules:
src/silisocs/environments/gm/game_master.py: primary component-GM runtime.src/silisocs/environments/gm/components/: native slot components.src/silisocs/simulation_engines/base_engines.py: primary runtime engine. Import from theenvironments/gm/andsimulation_engines/packages.
GM Component Slots (YAML)¶
Configure GM behavior from env.gm.components:
env:
gm:
components:
next_acting:
built_in: activity_markov
class_path: null
params: {}
observe:
built_in: timeline_every_turn
class_path: null
params: {}
resolve:
built_in: parsed_action
class_path: null
params: {}
Configured params are strict constructor arguments. Unknown keys fail before
the simulation starts unless the target class accepts **kwargs. Observe
components that explicitly accept observation_params may use params as a
forwarded observation-settings bag.
Built-in Next-Acting Components¶
activity_markov: role-conditioned active/inactive transitions (baseline behavior).activity_probability: independent per-step activation using role or global probabilities.all_agents: all agents are active each step.fixed_order: one active agent in cyclic order.
Built-in Observe Components¶
app_observation: callBackendApp.observe(...).timeline_every_turn: fetch timeline whenever observation is requested.episode_only: return episode-only observations (for fixed/pre-scripted flows).
timeline_every_turn is social-media-specific and lives under
silisocs.environments.gm.components.social_media. app_observation and
episode_only are generic baselines for backends that do not use timeline components.
Built-in Resolve Components¶
parsed_action: parseACTION TYPE / TARGET ID / CONTENT / REASONINGoutput.generic_action: parseACTION: <name>andparam: valuelines.tool_calling: use model tool-calling directly over backend action schemas.
Fixed-action directive handling:
- Fixed-action agents should emit standard action text directly (for example,
the existing
ACTION TYPE / TARGET ID / CONTENT / REASONINGformat), so no resolve-component modification is required. - GM observe components can branch by agent flow type and return specialized
observations (for example
EPISODE: <n>).
Initialization Components¶
Engine startup runs agent initialization, Game Master initialization, then
simulation initialization. Backend setup is owned by each Game Master through
its components.initialize slot. Seed content is simulation initialization and
is posted later through normal resolve_action(...) calls.
Built-in Game Master initialize components:
social_media: create users, wire follow/subreddit graphs, and bind social action metadata.app_initialize: callBackendApp.initialize(...).none: skip backend setup.
Built-in Recommendation Components¶
app_update: callBackendApp.update(...)for backend-owned world ticks.social_recommendation: update recommendation state through the backend capability methods.disabled/none: explicit no-op update component.
Use app_update when world state should advance before actor selection.
Use disabled/none when no per-step update is needed. Components such as
social_recommendation may call backend-specific capability methods instead of
the generic update hook.
Tool-Calling Resolve Mode¶
tool_calling is a full resolve pathway, not a parser branch.
It uses backend tools generated from @app_action and can include both:
- YAML action guidance (
env.gm.components.action_prompt.params.action_prompt) - auto-generated backend action catalog
This supports both styles:
- custom YAML guidance and action constraints
- automatic action API discovery from backend code
Quick Customization Recipes¶
1. Switch to tool-calling without Python changes¶
2. Keep defaults but override only Game Master initialization¶
env:
gm:
components:
initialize:
class_path: my_world.gm.CustomInitializer
params:
graph_strategy: role_weighted
3. Force all agents active each step¶
Writing Custom GM Components¶
Each component can be swapped via class_path. Component constructors should
use explicit keyword parameters. The factory injects common runtime values
such as backend, model, agent_names, sim_roles, and agent_flow_tags
when the constructor accepts them.
- Initialize component: subclass
InitializeComponentand implementinitialize(*, agents, gm_context, context) -> None. - Next-acting component: subclass
NextActingComponentand implementacting_agent_names() -> list[str]. - Action-prompt component: subclass
ActionPromptComponentand implementaction_prompt(agent_name) -> ActionSpec. - Observe component: subclass
ObservationComponentand implementmake_observation(agent_name) -> str. - Resolve component: subclass
ResolveComponentand implementresolve_action(agent_name, action) -> str. - Update component: subclass
UpdateComponentand implementupdate(*, step, agents, context=None) -> None.
Stateful components may override get_state() and set_state(state) for
checkpoint resume. Keep backend domain state in the backend; component state is
for component-local cursors, caches, or policy state.
Use built-ins under src/silisocs/environments/gm/components/ as templates.
Why native component inheritance?¶
Pros:
- Components fit directly into native GM routing through explicit slot methods.
- You get consistent checkpoint state behavior (
get_state,set_state). - Easier interoperability with other Silisocs components.
Tradeoffs:
- Slightly more boilerplate than plain strategy objects.
- You need to follow the component contracts carefully.
This is not a meaningful runtime-overhead concern; the main tradeoff is API discipline and code structure.
Building a Full Custom GM¶
YAML slot switching is ideal for most use cases, but you can still replace the entire GM runtime.
- Implement a native GM class exposing
initialize,update,acting_agents,action_prompt,make_observation, andresolve_action. - Point config to the GM
class_pathand constructorparams.
Typical configuration path:
env.gm.class_path
This allows full control over custom inputs/outputs, component graph wiring, and orchestration logic beyond slot-level swaps.
Pre-Built GM Defaults¶
The baseline game master is pre-wired with default components. Users do not need slot-level selection for normal runs.
For incremental customization, override only one component slot in YAML and keep other slots at baseline defaults.
Engine Extensibility Layout¶
Engine extensibility lives under:
src/silisocs/simulation_engines/base_engines.pysrc/silisocs/simulation_engines/runtime_base.pysrc/silisocs/simulation_engines/policies/loops.pysrc/silisocs/simulation_engines/policies/steps.pysrc/silisocs/simulation_engines/policies/turns.pysrc/silisocs/simulation_engines/policies/probe_schedule.pysrc/silisocs/simulation_engines/policies/factory.py
Configure engine turn policy from sim.engine and probe timing from eval:
sim:
engine:
step:
built_in: flow
params:
flow_order: [fixed_pre, default]
agent_to_flow: {}
turn_policy:
built_in: single_action # single_action | fixed_count | open_ended
class_path: null
params:
observe_before_act: first # first | always | never
eval:
probes:
schedule:
built_in: step_schedule # step_schedule | fixed_interval | disabled
class_path: null
params: {}
Built-in turn policies:
single_action: one observe/act/resolve cycle per active agent.fixed_count: exactlycountcycles per active agent.open_ended: keep acting untilfinished_action_signalormax_actionsis reached.
Built-in probe schedule policies:
step_schedule: defer to probe orchestrator schedule.fixed_interval: trigger onstart_step+ everyevery_n_steps.disabled: never run probe phase.
Policy params are strict constructor arguments, matching the GM component
contract.
Engine remains separate from GM component routing by design.
Flow routing notes:
flow_orderdefines execution buckets per episode.- Agents in each flow bucket still execute in parallel.
- Flow buckets execute sequentially, enabling deterministic pre/post phases for specialized agent classes without sacrificing per-bucket parallelism.
- Assign classes to buckets using
persona_pipeline.classes.<name>.flow_tag. - Add one-off overrides with
sim.engine.step.params.agent_to_flowwhen a specific agent should move to a different phase. - Use
env.gm.components.observe.params.episode_observation_flowsfor flows that should receive episode-index observations instead of timeline content.
Shared-Flow GM Contract¶
MultiFlowGameMaster is an advanced GM class for routing multiple
component instances by flow. It uses the same runtime contracts as the baseline
GM:
- Backend apps are created through the backend factory with the configured
env.gm.backend.class_pathandenv.gm.backend.params. - Action events are written through the standard action event logger.
- Game Master initialization receives agent names, simulation roles, and component-owned initializer params through the configured initializer component.
- Open-ended turn policies expose
FINISHEDas an enabled backend action.
This keeps simple and shared-flow runs interchangeable from the backend and artifact perspective. Custom shared-flow components should specialize routing, not bypass backend initialization or logging.
Recommended Boundary¶
- Put loop, step, and turn scheduling in Engine policies.
- Put next actor, observe, resolve, update, and initializer behavior in GM components.
- Put backend action semantics in backend
@app_actionmethods.
For new environment domains, subclass BackendApp, expose actions with
@app_action, provide observe(...), and use app_observation. Add
update(...) plus app_update when the world needs a per-step tick. Use
SocialBackendApp only when the backend needs the timeline, feed, parsed
social action, or recommendation capability surface.
Action Prompt Pipeline Implementation¶
For detailed configuration and behavior of action prompts, see docs/configuration.md under Action Prompt Additions Configuration and How Action Prompts Are Constructed.
Implementation Modules¶
The action prompt pipeline is implemented across three layers:
- Runner-time compilation (
src/silisocs/runtime/prompts/action_prompts.py): compile_action_prompt()applies prompt additions such as action-count guidance and output-style handling.-
Generic prompts are compiled during GM construction from the backend action catalog.
-
Game master prompt assembly (
src/silisocs/environments/gm/game_master.py): ComponentGameMaster.action_prompt()returns typedActionSpec- If
enable_tool_calling=True: returnsOutputType.TOOL_CALLSwith schemas inextra_args["tools"] -
Keeps base prompt text unchanged
-
Agent act layer (
src/silisocs/agents/base_agent.pyandsrc/silisocs/agents/native.py):- Native agents build context in
act(...) Agent._call_model(context, action_spec)routes typedActionSpec.output_typeandextra_argsto the language model
- Native agents build context in
Key Architectural Property¶
Output format stripping: When tool_calling.mode != none, the [OUTPUT STYLE] section is automatically stripped from the final prompt. Tool-calling uses JSON format (determined by LLM, not by text instruction). This is enforced in compile_action_prompt() at runner time, not downstream.
Testing¶
Integration tests validate the complete prompt pipeline in tests/test_prompt_pipeline_integration.py.