Skip to content

Resources

Resources is a type-keyed dependency injection container for world-level shared state. It holds configuration, services, and context that processors need but that is not entity data.

A Resource is a capability available while executing a tick. Correctness must not depend on its process-local lifetime. It should be read-only, reconstructible, or safely idempotent.

The container supplies capabilities; it does not make their effects durable. Work whose external outcome must survive process loss belongs behind the post-commit Activity boundary.

class Resources:
    def insert(self, resource: T) -> None: ...
    def get(self, resource_type: type[T]) -> T | None: ...
    def require(self, resource_type: type[T]) -> T: ...
    def remove(self, resource_type: type[T]) -> T | None: ...
    def contains(self, resource_type: type[T]) -> bool: ...
    def items(self) -> ItemsView[type, object]: ...

How It Works

Resources are the shared state layer of the ECS. Where Components hold per-entity data in DataFrame columns, Resources hold per-world singletons that live outside the columnar storage path entirely.

The container is a dict[type, object] -- each resource is keyed by its concrete Python type. This means there is exactly one instance of any given type at a time. Inserting a second instance of the same type overwrites the first.

Every AsyncWorld owns a .resources instance. During tick execution, AsyncSystem.execute() passes it into each processor's process() method as a keyword argument.

Every per-table compute task in that world receives the same container and the same stored objects. A resource mutation can therefore be visible to another archetype during the same tick. Per-table DataFrames are separate; resources are not isolated. Stateful resources must expose concurrency-safe operations or protect compound mutations with synchronization such as an asyncio.Lock. Do not rely on task scheduling to order cross-archetype resource access.

Runtime users usually stage resources when creating a handle:

world = runtime.world("sim", resources=[SimConfig(gravity=9.8)])

Post-activation resource attachment is the exact AddResource operation. Trusted runtime entry uses apply; authenticated API entry, when exposed, uses apply_as and requires operator/admin permission.

runtime.world(..., resources=[SimConfig(...)])
       |
AsyncSystem.execute(resources=world.resources)
       |
processor.process(df, resources=resources, tick=tick)

API

insert

Store a resource, keyed by its type. This is the core AsyncWorld API; runtime/API callers should use staged resources or gated resource attachment.

world.resources.insert(SimConfig(gravity=9.8))

Calling insert() with a second instance of the same type replaces the first.

get

Retrieve a resource by type, returning None if absent:

client = resources.get(PhysicsClient)
if client:
    await client.sync()

require

Retrieve a resource by type, raising KeyError if absent:

config = resources.require(SimConfig)

Use require() when the processor cannot function without the resource. Use get() when the resource is optional.

remove

Remove and return a resource, or None if not present:

old_config = resources.remove(SimConfig)

contains

Check whether a resource type is registered. Also supports the in operator:

resources.contains(SimConfig)   # True
SimConfig in resources          # True

What Goes in Resources

Resources are not entity data. They are the scaffolding around it:

Category Examples
Environment parameters SimConfig(gravity=9.8), PhysicsConfig(...)
Shared clients PhysicsClient, LabelingConfig
Simulation context SamplingConfig, budget trackers

In RL terms: MDP parameters, hyperparameters, shared infrastructure.

Resource or Activity?

Use a Resource when a processor needs tick-time access to configuration, a read-only service, a reconstructible client, or an operation that is safely idempotent at the real retry boundary.

Use an Activity when one committed tick authorizes work whose result must be recovered and observed by a later committed tick. Sandboxed agent execution, Git publication, whole hosted episodes, and hardware actions are examples.

A persistent NATS, inference, or simulator client may still be a useful executor optimization. Its host owns readiness and teardown. The durable workflow may not depend on that Python object surviving, and provider lifetime does not replace Activity identity, result durability, or reconciliation.

The AsyncResources/WorldHost prototype is frozen as evidence while the Activity migration is implemented. The core Resources bag and its supported API remain unchanged.

Usage in Processors

Processors receive resources as a keyword argument in process():

class PhysicsProcessor(AsyncProcessor):
    components = (Position, Velocity)
    priority = 5

    async def process(self, df: DataFrame, resources: Resources = None, **kwargs) -> DataFrame:
        config = resources.require(SimConfig) if resources else SimConfig()
        return df.with_column(
            "velocity__vy",
            col("velocity__vy") - config.gravity,
        )

Workflow clients in processors

The framework does not inject CommandScheduler, CommandDispatcher, or RuntimeResources into world resources. Doing so would cross the application boundary from inside tick execution and can create re-entrant lifecycle locks. Processors transform their DataFrame and may use application-supplied, concurrency-safe domain clients only when their use satisfies the Resource boundary above. Schedule durably coordinated work from committed intent through the owning application's Activity workflow, outside the processor call.

World Forking

When a world is forked, resources are shared by default. The source and fork point at the same Resources instance, so mutations to a stateful resource are visible to both worlds.

fork = await world.fork()
# fork uses the same resource instances as the source by default

For isolated resources, create a fork through a workflow that explicitly attaches replacement resource instances after fork. That API is outside the v1 lifecycle contract.

Source Reference

Resources: packages/archetype-ecs/src/archetype/core/resources.py