Spec: ObservationBuilder¶
Formal contract for all observation builder implementations.
Identity¶
| Property | Value |
|---|---|
| Abstract base | ObservationBuilder |
| File | src/ppage/observations/base.py |
| Registry | observation_registry.py |
| Extensible by | All contributors |
Required Interface¶
class MyObservation(ObservationBuilder):
def __init__(self, **params):
super().__init__(**params)
# extract what you need from params
def build(self, env) -> Dict[str, dict]:
# must return one entry per agent in env.agents
...
def encode(self, obs, env) -> np.ndarray:
# required (abstract) — encodes one agent's observation dict into a
# flat numpy array for the function-approximation baselines
...
build(env) Contract¶
Preconditions¶
env.agentsis non-emptyenv.reset()has been called (agent positions are valid)
Postconditions¶
- Returns a dict with exactly one key per agent name in
env.agents - Each value is a dict (structure is builder-specific but must be consistent across calls)
envstate is unchanged — no attribute may be written- Agent state is unchanged —
agent._agent_locationmust not be written
Determinism requirement¶
Given the same env state (positions, captured set), build(env) must return the same output. No stochastic elements, no calls to random, no time-dependent values.
What build() May Access¶
| Attribute | Allowed? |
|---|---|
env.agents (read) |
✅ |
env._obstacle_location (read) |
✅ |
env.size (read) |
✅ |
env._captured_agents (read) |
✅ |
agent._agent_location (read) |
✅ |
agent.agent_type (read) |
✅ |
agent.agent_name (read) |
✅ |
env._default_observations() (call) |
✅ (DefaultObservation only) |
env._obstacle_location (write) |
❌ |
agent._agent_location (write) |
❌ |
env.reward_fn (call) |
❌ |
env.step() |
❌ |
Registration¶
Every builder must be registered before it can be used via config:
# In observation_registry.py — add to _OBSERVATION_REGISTRY:
"my_obs": MyObservation,
# Or at runtime (e.g. in a custom script):
from ppage.registry.observation_registry import register_observation
register_observation("my_obs", MyObservation)
The registry key must match what you put in observations.yaml:
Output Structure Requirements¶
The output structure is builder-specific but must be:
1. Consistent across calls (same keys every step for the same builder)
2. Serializable to a hashable tuple by IQL._encode_state() (no non-serializable objects)
3. Dict-of-dicts at the top level: {agent_name: {field: value, ...}}
Recommended minimum structure:
{
"agent_name": {
"local": np.ndarray, # agent's own position [x, y]
# ... builder-specific fields
}
}
Checklist for New Builders¶
Before submitting a new observation builder:
- [ ] Inherits from
ObservationBuilder - [ ]
build(env)covers all agents inenv.agents - [ ]
encode(obs, env)implemented (abstract — the class cannot be instantiated without it); returns a flat numpy array - [ ] No writes to env or agent state inside
build() - [ ]
__init__uses only kwargs (no positional args beyondself) - [ ] Registered in
observation_registry.pywith correct key (no typos) - [ ] Exported from
observations/__init__.py - [ ] Added an entry to concepts/observations.md