feat: WorldInterface + Heartbeat v2 (#871, #872) #900

Merged
Timmy merged 1 commits from perplexity/Timmy-time-dashboard:feature/world-interface-heartbeat-871-872 into main 2026-03-22 13:44:50 +00:00
Collaborator

Summary

Implements both #871 (WorldInterface abstraction) and #872 (Heartbeat Loop v2) in a single cohesive change.

Issue #871 — Gymnasium-style WorldInterface

  • src/infrastructure/world/interface.py — Abstract WorldInterface base class with observe() → PerceptionOutput, act(CommandInput) → ActionResult, speak(message, target) contract. Optional connect()/disconnect() lifecycle hooks.
  • src/infrastructure/world/types.py — Canonical PerceptionOutput, CommandInput, ActionResult, ActionStatus dataclasses (compatible with PR #864's morrowind/schemas.py types).
  • src/infrastructure/world/registry.pyAdapterRegistry — register adapters by name, instantiate by config. Module-level convenience functions exposed via __init__.py.
  • src/infrastructure/world/adapters/mock.pyMockWorldAdapter — returns configurable canned perception, logs all commands and speech. Inspectable action_log and speech_log for testing.
  • src/infrastructure/world/adapters/tes3mp.pyTES3MPWorldAdapter stub — imports cleanly, raises NotImplementedError on all core methods with guidance comments.

Issue #872 — Heartbeat Loop v2

  • src/loop/heartbeat.pyHeartbeat class that drives the cognitive cycle:
    • With world adapter (embodied mode): observe() → gather → reason → act → world.act() → reflect → broadcast
    • Without adapter (passive mode): Falls back to existing three-phase loop on a timer payload — existing think_once() behaviour is preserved.
    • Configurable interval (30s embodied / 5min passive)
    • CycleRecord dataclass logs each cycle with observation, reasoning summary, action, status, and duration
    • WebSocket broadcast of heartbeat.cycle events with action + reasoning summary
    • on_cycle async callback for external consumers
    • start()/stop() for background loop management
  • src/loop/phase1_gather.py — Enhanced to fold perception metadata from the heartbeat into gathered context when present. No change to existing behaviour when perception is absent.

Tests (63 passing)

  • tests/infrastructure/world/test_interface.py — ABC contract, type construction, subclass requirements
  • tests/infrastructure/world/test_registry.py — Register, get, overwrite warning, type checking
  • tests/infrastructure/world/test_mock_adapter.py — Full observe/act/speak cycle, lifecycle, action logging
  • tests/infrastructure/world/test_tes3mp_adapter.py — Stub instantiation, all methods raise NotImplementedError
  • tests/loop/test_heartbeat.py — Embodied + passive cycles, broadcast, logging, lifecycle, callbacks
  • tests/loop/test_three_phase.py — Existing tests pass (no regressions)

Design Decisions

  • Separate types.py from interface.py: Types can be imported without pulling in the ABC, keeping lightweight consumers decoupled.
  • Heartbeat as new module, not modifying runner.py: run_cycle() remains the primitive; Heartbeat composes it with world interaction. Existing code that calls run_cycle() directly is unaffected.
  • Compatible types rather than cross-branch import: Since PR #864's schemas.py isn't on main yet, we define compatible PerceptionOutput/CommandInput types. Easy to consolidate when #864 merges.

Testing

pytest tests/infrastructure/world/ tests/loop/test_heartbeat.py tests/loop/test_three_phase.py -v
# 63 passed

🤖 Generated with Claude Code

## Summary Implements both **#871** (WorldInterface abstraction) and **#872** (Heartbeat Loop v2) in a single cohesive change. ### Issue #871 — Gymnasium-style WorldInterface - **`src/infrastructure/world/interface.py`** — Abstract `WorldInterface` base class with `observe() → PerceptionOutput`, `act(CommandInput) → ActionResult`, `speak(message, target)` contract. Optional `connect()`/`disconnect()` lifecycle hooks. - **`src/infrastructure/world/types.py`** — Canonical `PerceptionOutput`, `CommandInput`, `ActionResult`, `ActionStatus` dataclasses (compatible with PR #864's morrowind/schemas.py types). - **`src/infrastructure/world/registry.py`** — `AdapterRegistry` — register adapters by name, instantiate by config. Module-level convenience functions exposed via `__init__.py`. - **`src/infrastructure/world/adapters/mock.py`** — `MockWorldAdapter` — returns configurable canned perception, logs all commands and speech. Inspectable `action_log` and `speech_log` for testing. - **`src/infrastructure/world/adapters/tes3mp.py`** — `TES3MPWorldAdapter` stub — imports cleanly, raises `NotImplementedError` on all core methods with guidance comments. ### Issue #872 — Heartbeat Loop v2 - **`src/loop/heartbeat.py`** — `Heartbeat` class that drives the cognitive cycle: - **With world adapter (embodied mode):** `observe() → gather → reason → act → world.act() → reflect → broadcast` - **Without adapter (passive mode):** Falls back to existing three-phase loop on a timer payload — existing `think_once()` behaviour is preserved. - Configurable interval (30s embodied / 5min passive) - `CycleRecord` dataclass logs each cycle with observation, reasoning summary, action, status, and duration - WebSocket broadcast of `heartbeat.cycle` events with action + reasoning summary - `on_cycle` async callback for external consumers - `start()`/`stop()` for background loop management - **`src/loop/phase1_gather.py`** — Enhanced to fold `perception` metadata from the heartbeat into gathered context when present. No change to existing behaviour when perception is absent. ### Tests (63 passing) - `tests/infrastructure/world/test_interface.py` — ABC contract, type construction, subclass requirements - `tests/infrastructure/world/test_registry.py` — Register, get, overwrite warning, type checking - `tests/infrastructure/world/test_mock_adapter.py` — Full observe/act/speak cycle, lifecycle, action logging - `tests/infrastructure/world/test_tes3mp_adapter.py` — Stub instantiation, all methods raise NotImplementedError - `tests/loop/test_heartbeat.py` — Embodied + passive cycles, broadcast, logging, lifecycle, callbacks - `tests/loop/test_three_phase.py` — Existing tests pass (no regressions) ## Design Decisions - **Separate `types.py` from `interface.py`**: Types can be imported without pulling in the ABC, keeping lightweight consumers decoupled. - **Heartbeat as new module, not modifying runner.py**: `run_cycle()` remains the primitive; `Heartbeat` composes it with world interaction. Existing code that calls `run_cycle()` directly is unaffected. - **Compatible types rather than cross-branch import**: Since PR #864's schemas.py isn't on main yet, we define compatible `PerceptionOutput`/`CommandInput` types. Easy to consolidate when #864 merges. ## Testing ```bash pytest tests/infrastructure/world/ tests/loop/test_heartbeat.py tests/loop/test_three_phase.py -v # 63 passed ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
perplexity added 1 commit 2026-03-22 12:00:00 +00:00
feat: WorldInterface abstraction + Heartbeat v2 loop (#871, #872)
Some checks failed
Tests / lint (pull_request) Has been cancelled
Tests / test (pull_request) Has been cancelled
c5a92b6fe1
Add a Gymnasium-style WorldInterface ABC at src/infrastructure/world/
with adapter registry, MockWorldAdapter for testing, and TES3MP stub.

Wire the interface into a new Heartbeat v2 loop (src/loop/heartbeat.py)
that drives observe→reason→act→reflect cycles through whatever world
adapter is connected, with graceful fallback to passive thinking when
no adapter is present. Enrich Phase 1 (Gather) with world observations.

Includes 63 passing tests covering interface contracts, all adapters,
registry, heartbeat cycles (embodied + passive), and WS broadcast.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Timmy merged commit bebbe442b4 into main 2026-03-22 13:44:50 +00:00
Timmy deleted branch feature/world-interface-heartbeat-871-872 2026-03-22 13:44:50 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Rockachopa/Timmy-time-dashboard#900