Skip to content

Commit d38fdd7

Browse files
committed
document trace inspection workflow
1 parent 3dedfb5 commit d38fdd7

5 files changed

Lines changed: 293 additions & 7 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ The goal is not photorealism.
172172
The goal is to understand the perception-action loop.
173173

174174
Every example returns a `Trace`, so headless runs can be inspected without
175-
rendering:
175+
rendering. See `docs/trace.md` for the full trace contract.
176176

177177
```python
178178
trace = run(seed=0, render=False)
@@ -200,6 +200,7 @@ This is a lightweight educational bridge toward them.
200200
Bridge direction is documented separately:
201201

202202
- `docs/plan.md`
203+
- `docs/trace.md`
203204
- `docs/ros2_bridge_strategy.md`
204205
- `docs/simulator_integration_strategy.md`
205206

docs/implementation_gap_audit.md

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# Implementation Gap Audit
2+
3+
This audit ranks the most useful next implementation work after the initial
4+
repository push, CI verification, Gymnasium adapter expansion, trace summaries,
5+
and trace replay example.
6+
7+
## Current Stable Surface
8+
9+
- 22 runnable examples
10+
- 59 smoke and regression tests
11+
- CI green on Python 3.10, 3.11, and 3.12
12+
- 21 generated README GIFs with nonblank checks
13+
- Gymnasium-style adapters for `GridWorld2D`, `DynamicObstacleGridWorld`, and
14+
`Tabletop2D`
15+
- `Trace.summary()` and `examples/runtime/26_trace_replay.py`
16+
17+
## Ranked Next Work
18+
19+
| Rank | Candidate | Impact | Effort | Risk | Recommendation |
20+
| --- | --- | --- | --- | --- | --- |
21+
| 1 | `docs/trace.md` | high | low | low | Do next |
22+
| 2 | `BlockedPathWorld` Gymnasium adapter | medium | low-medium | medium | Do after trace docs |
23+
| 3 | `examples/navigation/10_localization_uncertainty_recovery.py` | high | medium-high | medium | Best next major example |
24+
| 4 | `MovingObstacleWorld` continuous-control adapter | medium | medium | medium | Good RL bridge follow-up |
25+
| 5 | `examples/manipulation/08_belief_grasp_selection.py` | high | medium | medium | Good after navigation example |
26+
27+
## 1. Trace Documentation
28+
29+
Why it matters:
30+
31+
- Trace summaries and replay now exist, but their contract is only explained in
32+
scattered README snippets.
33+
- A short dedicated document would make headless inspection discoverable.
34+
- It finishes the remaining Priority 5 documentation task.
35+
36+
Suggested scope:
37+
38+
- Add `docs/trace.md`.
39+
- Explain `observations`, `actions`, `rewards`, and `infos`.
40+
- Explain `info["failure"]` and `Failure`.
41+
- Show `trace.failures()` and `trace.summary()` examples.
42+
- Mention that replay tooling is intentionally small and not a logging
43+
framework.
44+
45+
Acceptance checks:
46+
47+
- `python scripts/run_all_smoke_tests.py --check-gifs`
48+
- Markdown asset checks remain green.
49+
50+
## 2. `BlockedPathWorld` Gymnasium Adapter
51+
52+
Why it matters:
53+
54+
- It is already listed as the next Gymnasium candidate.
55+
- `BlockedPathWorld` has a clean `reset()` / `step()` world boundary.
56+
- The action mapping can reuse `GRID_ACTIONS`.
57+
- The observation is compact: time, robot, goal, known map, dynamic blocker,
58+
last blocked cell.
59+
60+
Implementation note:
61+
62+
- `BlockedPathWorld` currently lives inside
63+
`examples/navigation/09_blocked_path_recovery.py`, not `pir/worlds/`.
64+
- Importing an example module from `pir/adapters` would blur the package
65+
boundary.
66+
- Before adding the adapter, move only `BlockedPathWorld` into `pir/worlds/` or
67+
create a small wrapper module that does not pull in example-agent code.
68+
69+
Suggested tests:
70+
71+
- reset shape
72+
- discrete action decoding
73+
- success termination
74+
- timeout truncation
75+
- recoverable `blocked_path` failure remains nonterminal
76+
- `info["raw_obs"]` preserves the original observation
77+
78+
## 3. `10_localization_uncertainty_recovery.py`
79+
80+
Why it matters:
81+
82+
- It is the top next-tier example in `docs/plan.md`.
83+
- The existing navigation set already has belief-based navigation and active
84+
SLAM, but not a focused recovery loop for ambiguous localization.
85+
- It would teach "I am not sure where I am, so I should take an information
86+
action before acting toward the goal."
87+
88+
Suggested loop:
89+
90+
```text
91+
ambiguous pose belief -> detect high uncertainty -> move to landmark view ->
92+
belief collapses -> resume goal navigation
93+
```
94+
95+
Keep it small:
96+
97+
- reuse the style of `06_belief_based_navigation.py`
98+
- use a fixed grid and a few landmarks
99+
- force an initial ambiguous belief
100+
- make recovery visible through entropy and a `localization_recovery_count`
101+
102+
Required updates:
103+
104+
- `examples/README.md`
105+
- `examples/navigation/README.md`
106+
- `tests/test_examples_smoke.py`
107+
- `docs/status.md`
108+
- optional GIF only if it becomes a major visible example
109+
110+
## 4. Continuous-Control Gymnasium Adapter
111+
112+
Why it matters:
113+
114+
- `MovingObstacleWorld` in `08_interactive_mpc.py` is the clearest continuous
115+
action-space candidate.
116+
- It would demonstrate that optional RL compatibility is not limited to grid
117+
worlds.
118+
119+
Risk:
120+
121+
- `MovingObstacleWorld.step()` returns a tuple, not `StepResult`.
122+
- The observation and action spaces need Gymnasium `Box` spaces.
123+
- Like `BlockedPathWorld`, the world currently lives inside an example module.
124+
125+
Recommendation:
126+
127+
- Do this after the blocked-path adapter, using the same package-boundary
128+
decision.
129+
130+
## 5. Manipulation Belief Grasp Selection
131+
132+
Why it matters:
133+
134+
- The manipulation examples cover retry, visual servoing, IK, search, pushing,
135+
and suction sorting.
136+
- A focused belief-to-grasp-choice example would fill a clear concept gap:
137+
multiple possible object poses leading to different grasp choices and failure
138+
updates.
139+
140+
Suggested loop:
141+
142+
```text
143+
pose belief -> choose grasp with best expected success -> fail -> update belief
144+
-> choose different grasp
145+
```
146+
147+
Recommendation:
148+
149+
- Good next example after navigation localization recovery, unless the project
150+
wants manipulation growth first.
151+
152+
## Recommended Sequence
153+
154+
1. Add `docs/trace.md`.
155+
2. Extract or package `BlockedPathWorld` cleanly, then add its Gymnasium
156+
adapter.
157+
3. Add `examples/navigation/10_localization_uncertainty_recovery.py`.
158+
4. Decide whether the next expansion should be continuous-control RL adapter or
159+
manipulation belief-grasp example.
160+
161+
This order keeps the current surface stable, finishes a mostly complete trace
162+
story, then returns to the plan's highest-value new example work.

docs/plan.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ The repository currently has:
1818
- optional Gymnasium-style adapters for `GridWorld2D`,
1919
`DynamicObstacleGridWorld`, and `Tabletop2D`
2020
- bridge strategy docs for ROS2 and simulators
21+
- trace inspection docs for headless run analysis
2122

2223
The root first-run command must remain:
2324

@@ -184,11 +185,11 @@ Already done:
184185
loop-counter summary fields
185186
- `examples/runtime/26_trace_replay.py`
186187
- smoke test for headless trace replay
188+
- `docs/trace.md`
187189

188190
Tasks:
189191

190-
1. Document how a trace records observations, actions, rewards, and infos.
191-
2. Keep trace tooling small enough that it does not become a logging framework.
192+
1. Keep trace tooling small enough that it does not become a logging framework.
192193

193194
Acceptance criteria:
194195

@@ -252,9 +253,11 @@ Keep these documents distinct:
252253
| `README.md` | first impression, install, GIF gallery, first run |
253254
| `docs/status.md` | current implementation snapshot |
254255
| `docs/plan.md` | execution plan and priorities |
256+
| `docs/trace.md` | trace fields, failures, summaries, and replay |
255257
| `docs/example_roadmap.md` | numbered first 20 examples |
256258
| `docs/learning_paths.md` | learner-facing paths through examples |
257259
| `docs/example_authoring.md` | rules for adding examples |
260+
| `docs/implementation_gap_audit.md` | ranked next implementation candidates |
258261
| `docs/toy_worlds.md` | toy world roles and coverage |
259262
| `docs/ros2_bridge_strategy.md` | optional ROS2 bridge direction |
260263
| `docs/simulator_integration_strategy.md` | optional simulator bridge direction |

docs/status.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ see what exists, what is verified, and what should come next.
1313
- Contributor extra: `pip install -e ".[dev]"`
1414
- CI: Python 3.10, 3.11, 3.12
1515
- Execution plan: `docs/plan.md`
16+
- Trace inspection docs: `docs/trace.md`
1617
- Bridge docs: `docs/ros2_bridge_strategy.md`,
1718
`docs/simulator_integration_strategy.md`
1819
- Optional adapters: `GridWorldGymnasiumAdapter`,
@@ -51,8 +52,8 @@ The regression suite also checks structured `Failure` objects for representative
5152
recoverable and terminal failures.
5253
The adapter suite checks the Gymnasium-style `reset()` / `step()` contract
5354
without making Gymnasium a core dependency.
54-
The trace summary tests check compact reward, success, failure, and loop-counter
55-
extraction from headless runs.
55+
The trace docs and summary tests cover compact reward, success, failure, and
56+
loop-counter extraction from headless runs.
5657
The Markdown asset check verifies local README image links and category GIF
5758
galleries.
5859
GitHub Actions runs the smoke suite and GIF checks on Python 3.10, 3.11, and
@@ -85,8 +86,8 @@ dependency boundaries:
8586
2. expand regression tests for shared contracts as new examples are added
8687
3. extract shared helpers only where three or more examples naturally repeat
8788
4. extend optional Gymnasium compatibility to the next clear toy-world mappings
88-
5. document trace fields and keep replay tooling small enough that it does not
89-
become a logging framework
89+
5. keep trace replay tooling small enough that it does not become a logging
90+
framework
9091

9192
The root first-run experience should remain:
9293

docs/trace.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Trace Inspection
2+
3+
Every runnable example returns a `Trace`. A trace is the smallest shared record
4+
of a closed-loop run: what the robot observed, what action it chose, what reward
5+
it received, and what metadata the world reported.
6+
7+
## Shape
8+
9+
`Trace` stores four aligned lists. Index `i` describes one completed step:
10+
11+
| Field | Meaning |
12+
| --- | --- |
13+
| `observations[i]` | observation after the action was applied |
14+
| `actions[i]` | action chosen by the agent or policy |
15+
| `rewards[i]` | scalar reward returned by the world |
16+
| `infos[i]` | metadata such as success, failure, counters, belief state, or plan state |
17+
18+
Examples append to a trace inside the control loop:
19+
20+
```python
21+
result = env.step(action)
22+
obs, reward, done, info = result.as_tuple()
23+
trace.append(obs, action, reward, info)
24+
```
25+
26+
The trace is intentionally in-memory and lightweight. It is not a logging
27+
framework, file format, telemetry system, or simulator replay API.
28+
29+
## Failures
30+
31+
Failures should be reported through `info["failure"]` as a `Failure` object:
32+
33+
```python
34+
from pir.core.types import Failure
35+
36+
info["failure"] = Failure(
37+
"blocked_path",
38+
"planned path is blocked by a newly observed obstacle",
39+
recoverable=True,
40+
)
41+
```
42+
43+
Use `trace.failures()` to extract structured failures without scanning every
44+
info dictionary manually:
45+
46+
```python
47+
failures = trace.failures()
48+
failure_kinds = [failure.kind for failure in failures]
49+
```
50+
51+
Recoverable failures should usually trigger a different next action: replan,
52+
retry from a new belief, change viewpoint, push before grasping, or update a
53+
world model. Terminal failures such as timeout or collision should use
54+
`recoverable=False`.
55+
56+
## Summary
57+
58+
Use `trace.summary()` for compact headless inspection:
59+
60+
```python
61+
trace = run(seed=0, render=False)
62+
summary = trace.summary()
63+
64+
print(summary.steps)
65+
print(summary.total_reward)
66+
print(summary.success)
67+
print(summary.failure_counts)
68+
print(summary.retry_count)
69+
print(summary.counters)
70+
```
71+
72+
The summary includes:
73+
74+
| Field | Meaning |
75+
| --- | --- |
76+
| `steps` | number of recorded actions |
77+
| `total_reward` | sum of all rewards |
78+
| `success` | whether any step reported `info["success"]` |
79+
| `failure_counts` | count by failure kind |
80+
| `recoverable_failure_count` | number of recoverable failures |
81+
| `terminal_failure_count` | number of nonrecoverable failures |
82+
| `counters` | maximum numeric `*_count` values seen in `infos` |
83+
| `retry_count` | shortcut for `counters.get("retry_count", 0)` |
84+
| `final_info` | final info dictionary |
85+
86+
`counters` is deliberately simple. It picks up fields such as `retry_count`,
87+
`replan_count`, `recovery_count`, `search_count`, or `model_error_count` when an
88+
example reports them.
89+
90+
## Replay
91+
92+
`examples/runtime/26_trace_replay.py` records the smallest runtime loop
93+
headless, then replays the recorded observations, actions, rewards, and infos:
94+
95+
```bash
96+
python examples/runtime/26_trace_replay.py --no-render --max-steps 12
97+
```
98+
99+
The replay example does not rerun the policy or world dynamics. It walks the
100+
recorded trace, tracks cumulative reward, and optionally renders the observation
101+
and action history. This keeps replay useful for teaching without turning the
102+
project into a logging framework.
103+
104+
## Testing
105+
106+
Tests should assert the loop concept directly:
107+
108+
```python
109+
trace = module.run(seed=0, render=False, max_steps=40)
110+
summary = trace.summary()
111+
112+
assert summary.success is True
113+
assert summary.retry_count >= 1
114+
assert "grasp_miss" in summary.failure_counts
115+
```
116+
117+
For failure contracts, prefer `trace.failures()` when the exact step is not the
118+
thing being tested. Prefer direct `trace.infos[-1]` assertions when the final
119+
state is the important behavior.

0 commit comments

Comments
 (0)