File size: 4,298 Bytes
77da5ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# lifestack_env.py β€” Environment Reference

`core/lifestack_env.py` β€” The main OpenEnv-compatible RL environment for LifeStack.

---

## Overview

`LifeStackEnv` wraps the full simulation: metric cascades, world events, partial
observability, route execution, milestone tracking, and reward calculation.

Key classes in this file:

| Class | Role |
|---|---|
| `LifeStackAction` | Pydantic action schema (metric_changes, resource_cost, action_type, …) |
| `LifeStackObservation` | Pydantic observation schema (metrics, resources, step, done, reward, metadata) |
| `LifeStackState` | Internal state (current_metrics, budget, task, world_state, hidden_state, …) |
| `PartialObsFilter` | Converts full world state into the agent's partial observation |
| `WorldEngine` | Fires deterministic/probabilistic ExoEvents each step |
| `LifeStackEnv` | The environment itself β€” inherits from OpenEnv `Environment` |

---

## API

### `LifeStackEnv.__init__(seed, task, max_steps=30)`

```python
env = LifeStackEnv()
env = LifeStackEnv(seed=42, max_steps=50)
```

### `LifeStackEnv.reset(...) -> LifeStackObservation`

```python
obs = env.reset(task=my_task, episode_id="ep_001")
```

Parameters:
- `task` β€” a `Task` object (from `core/task.py`). Defaults to `FlightCrisisTask()`.
- `seed` β€” optional int for reproducibility.
- `conflict` β€” legacy `ConflictEvent` for metric disruption on reset.
- `budget` β€” dict with `time`, `money`, `energy` overrides.
- `person` β€” optional `SimPerson` for personality-driven drift.

### `LifeStackEnv.step(action) -> LifeStackObservation`

```python
obs = env.step(LifeStackAction(action_type="execute", target="rebook_premium"))
```

Supported `action_type` values:

| Type | Effect |
|---|---|
| `inspect` | Reveals a hidden-state key into the observation |
| `execute` | Attempts to activate a Route by `target` (route id) |
| `wait` | Passes the step; triggers stress penalty after 4 consecutive waits |
| `rollback` | Reverts metrics/budget to the previous step (one-time per episode) |
| `plan` / `communicate` / `spend` / `delegate` | Apply `metric_changes` and `resource_cost` |

### `LifeStackEnv.render()`

Prints a colour-coded terminal summary of the current state and task progress.

---

## PartialObsFilter

```python
PartialObsFilter.filter(task, revealed_keys) -> dict
```

- Base: `task.visible_world` (always visible).
- Keys in `revealed_keys` that exist in `task.mutable_world` β†’ added as-is.
- Keys in `revealed_keys` that exist in `task.hidden_state` β†’ wrapped as  
  `{"value": <val>, "source": "inspect"}` to signal the agent they came from inspect.

---

## Observation `metadata` fields

```python
obs.metadata = {
    "world_state":     dict,   # partial view after filter
    "goal":            str,
    "active_route":    str | None,
    "milestones":      list[str],
    "events":          list[str],
    "success":         bool,
    "failure":         bool,
    "failure_reason":  str,
    "routes_remaining": int,
    "breakdown":       dict,   # reward component breakdown
    "info":            list[str],  # step-level diagnostic messages
}
```

Key `info` message prefixes:

| Prefix | Meaning |
|---|---|
| `INSPECT_REVEALED:` | Key added to inspected list |
| `INSPECT_REVEALED_HIDDEN:` | Key was in `hidden_state` β€” value included |
| `INSPECT_REDUNDANT:` | Key already revealed, no-op |
| `ROUTE_SUCCESS:` | Route executed and consequences applied |
| `ROUTE_BLOCKED:` | Route was closed by a prior ExoEvent |
| `PRECONDITIONS_FAILED:` | Route preconditions not met |
| `MILESTONE_UNLOCKED:` | A milestone condition was met |
| `EVENT_FIRED:` | An ExoEvent triggered this step |
| `WAIT_CAP_EXCEEDED:` | 4+ consecutive waits β€” stress penalty applied |

---

## End Conditions

| Condition | `done` | `success` | `failure` |
|---|---|---|---|
| `step_count >= max_steps` | βœ… | depends | β€” |
| All `success_conditions` met | βœ… | βœ… | β€” |
| `failure_condition` met | βœ… | β€” | βœ… |
| Any metric hits 0 | βœ… | β€” | βœ… |

---

## Change Log

| Date | Change |
|---|---|
| 2026-04-23 | `PartialObsFilter.filter()` now reads `mutable_world` + `hidden_state` directly from `Task`; removed `world` param; hidden keys wrapped with `source: inspect`; `INSPECT_REVEALED_HIDDEN` info message added |