This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Timmy-time-dashboard/docs/protocol/morrowind-perception-command-spec.md
Perplexity Computer 215329146a feat(morrowind): add Perception/Command protocol + SQLite command log (#859, #855)
Implement two foundational infrastructure pieces for the Morrowind integration:

1. Perception/Command Protocol (Issue #859):
   - Formal spec document with JSON schemas, API contracts, versioning strategy
   - Engine-agnostic design following the Falsework Rule
   - Pydantic v2 models (PerceptionOutput, CommandInput) for runtime validation

2. SQLite Command Log + Training Pipeline (Issue #855):
   - SQLAlchemy model for command_log table with full indexing
   - CommandLogger class with log_command(), query(), export_training_data()
   - TrainingExporter with chat-completion, episode, and instruction formats
   - Storage management (rotation/archival) utilities
   - Alembic migration for the new table

Includes 39 passing tests covering schema validation, logging, querying, and export.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 22:33:13 +00:00

313 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Morrowind Perception/Command Protocol Specification
**Version:** 1.0.0
**Status:** Draft
**Authors:** Timmy Infrastructure Team
**Date:** 2026-03-21
---
## 1. Overview
This document defines the **engine-agnostic Perception/Command protocol** used by Timmy's
heartbeat loop to observe the game world and issue commands. The protocol is designed
around the **Falsework Rule**: TES3MP (Morrowind) is scaffolding. If the engine swaps,
only the bridge and perception script change — the heartbeat, reasoning, and journal
remain sovereign.
### 1.1 Design Principles
- **Engine-agnostic**: Schemas reference abstract concepts (cells, entities, quests), not
Morrowind-specific internals.
- **Versioned**: Every payload carries a `protocol_version` so consumers can negotiate
compatibility.
- **Typed at the boundary**: Pydantic v2 models enforce validation on both the producer
(bridge) and consumer (heartbeat) side.
- **Logged by default**: Every command is persisted to the SQLite command log for
training-data extraction (see Issue #855).
---
## 2. Protocol Version Strategy
| Field | Type | Description |
| ------------------ | ------ | ------------------------------------ |
| `protocol_version` | string | SemVer string (e.g. `"1.0.0"`) |
### Compatibility Rules
- **Patch** bump (1.0.x): additive fields with defaults — fully backward-compatible.
- **Minor** bump (1.x.0): new optional endpoints or enum values — old clients still work.
- **Major** bump (x.0.0): breaking schema change — requires coordinated upgrade of bridge
and heartbeat.
Consumers MUST reject payloads whose major version exceeds their own.
---
## 3. Perception Output Schema
Returned by `GET /perception`. Represents a single snapshot of the game world as observed
by the bridge.
```json
{
"protocol_version": "1.0.0",
"timestamp": "2026-03-21T14:30:00Z",
"agent_id": "timmy",
"location": {
"cell": "Balmora",
"x": 1024.5,
"y": -512.3,
"z": 64.0,
"interior": false
},
"health": {
"current": 85,
"max": 100
},
"nearby_entities": [
{
"entity_id": "npc_001",
"name": "Caius Cosades",
"entity_type": "npc",
"distance": 12.5,
"disposition": 65
}
],
"inventory_summary": {
"gold": 150,
"item_count": 23,
"encumbrance_pct": 0.45
},
"active_quests": [
{
"quest_id": "mq_01",
"name": "Report to Caius Cosades",
"stage": 10
}
],
"environment": {
"time_of_day": "afternoon",
"weather": "clear",
"is_combat": false,
"is_dialogue": false
},
"raw_engine_data": {}
}
```
### 3.1 Field Reference
| Field | Type | Required | Description |
| -------------------- | ----------------- | -------- | ------------------------------------------------------------ |
| `protocol_version` | string | yes | Protocol SemVer |
| `timestamp` | ISO 8601 datetime | yes | When the snapshot was taken |
| `agent_id` | string | yes | Which agent this perception belongs to |
| `location.cell` | string | yes | Current cell/zone name |
| `location.x/y/z` | float | yes | World coordinates |
| `location.interior` | bool | yes | Whether the agent is indoors |
| `health.current` | int (0max) | yes | Current health |
| `health.max` | int (>0) | yes | Maximum health |
| `nearby_entities` | array | yes | Entities within perception radius (may be empty) |
| `inventory_summary` | object | yes | Lightweight inventory overview |
| `active_quests` | array | yes | Currently tracked quests |
| `environment` | object | yes | World-state flags |
| `raw_engine_data` | object | no | Opaque engine-specific blob (not relied upon by heartbeat) |
### 3.2 Entity Types
The `entity_type` field uses a controlled vocabulary:
| Value | Description |
| ---------- | ------------------------ |
| `npc` | Non-player character |
| `creature` | Hostile or neutral mob |
| `item` | Pickup-able world item |
| `door` | Door or transition |
| `container`| Lootable container |
---
## 4. Command Input Schema
Sent via `POST /command`. Represents a single action the agent wants to take in the world.
```json
{
"protocol_version": "1.0.0",
"timestamp": "2026-03-21T14:30:01Z",
"agent_id": "timmy",
"command": "move_to",
"params": {
"target_cell": "Balmora",
"target_x": 1050.0,
"target_y": -500.0
},
"reasoning": "Moving closer to Caius Cosades to begin the main quest dialogue.",
"episode_id": "ep_20260321_001",
"context": {
"perception_timestamp": "2026-03-21T14:30:00Z",
"heartbeat_cycle": 42
}
}
```
### 4.1 Field Reference
| Field | Type | Required | Description |
| ------------------------------ | ----------------- | -------- | ------------------------------------------------------- |
| `protocol_version` | string | yes | Protocol SemVer |
| `timestamp` | ISO 8601 datetime | yes | When the command was issued |
| `agent_id` | string | yes | Which agent is issuing the command |
| `command` | string (enum) | yes | Command type (see §4.2) |
| `params` | object | yes | Command-specific parameters (may be empty `{}`) |
| `reasoning` | string | yes | Natural-language explanation of *why* this command |
| `episode_id` | string | no | Groups commands into training episodes |
| `context` | object | no | Metadata linking command to its triggering perception |
### 4.2 Command Types
| Command | Description | Key Params |
| --------------- | ---------------------------------------- | ---------------------------------- |
| `move_to` | Navigate to coordinates or entity | `target_cell`, `target_x/y/z` |
| `interact` | Interact with entity (talk, activate) | `entity_id`, `interaction_type` |
| `use_item` | Use an inventory item | `item_id`, `target_entity_id?` |
| `wait` | Wait/idle for a duration | `duration_seconds` |
| `combat_action` | Perform a combat action | `action_type`, `target_entity_id` |
| `dialogue` | Choose a dialogue option | `entity_id`, `topic`, `choice_idx` |
| `journal_note` | Write an internal journal observation | `content`, `tags` |
| `noop` | Heartbeat tick with no action | — |
---
## 5. API Contracts
### 5.1 `GET /perception`
Returns the latest perception snapshot.
**Response:** `200 OK` with `PerceptionOutput` JSON body.
**Error Responses:**
| Status | Code | Description |
| ------ | ------------------- | ----------------------------------- |
| 503 | `BRIDGE_UNAVAILABLE`| Game bridge is not connected |
| 504 | `PERCEPTION_TIMEOUT`| Bridge did not respond in time |
| 422 | `SCHEMA_MISMATCH` | Bridge returned incompatible schema |
### 5.2 `POST /command`
Submit a command for the agent to execute.
**Request:** `CommandInput` JSON body.
**Response:** `202 Accepted`
```json
{
"status": "accepted",
"command_id": "cmd_abc123",
"logged": true
}
```
**Error Responses:**
| Status | Code | Description |
| ------ | -------------------- | ----------------------------------- |
| 400 | `INVALID_COMMAND` | Command type not recognized |
| 400 | `VALIDATION_ERROR` | Payload fails Pydantic validation |
| 409 | `COMMAND_CONFLICT` | Agent is busy executing another cmd |
| 503 | `BRIDGE_UNAVAILABLE` | Game bridge is not connected |
### 5.3 `GET /morrowind/status`
Health-check endpoint for the Morrowind bridge.
**Response:** `200 OK`
```json
{
"bridge_connected": true,
"engine": "tes3mp",
"protocol_version": "1.0.0",
"uptime_seconds": 3600,
"last_perception_at": "2026-03-21T14:30:00Z"
}
```
---
## 6. Engine-Swap Documentation (The Falsework Rule)
### What Changes
| Component | Changes on Engine Swap? | Notes |
| ---------------------- | ----------------------- | --------------------------------------------- |
| Bridge process | **YES** — replaced | New bridge speaks same protocol to new engine |
| Perception Lua script | **YES** — replaced | New engine's scripting language/API |
| `PerceptionOutput` | NO | Schema is engine-agnostic |
| `CommandInput` | NO | Schema is engine-agnostic |
| Heartbeat loop | NO | Consumes `PerceptionOutput`, emits `Command` |
| Reasoning/LLM layer | NO | Operates on abstract perception data |
| Journal system | NO | Writes `journal_note` commands |
| Command log + training | NO | Logs all commands regardless of engine |
| Dashboard WebSocket | NO | Separate protocol (`src/infrastructure/protocol.py`) |
### Swap Procedure
1. Implement new bridge that serves `GET /perception` and accepts `POST /command`.
2. Update `raw_engine_data` field documentation for the new engine.
3. Extend `entity_type` enum if the new engine has novel entity categories.
4. Bump `protocol_version` minor (or major if schema changes are required).
5. Run integration tests against the new bridge.
---
## 7. Error Handling Specification
### 7.1 Error Response Format
All error responses follow a consistent structure:
```json
{
"error": {
"code": "BRIDGE_UNAVAILABLE",
"message": "Human-readable error description",
"details": {},
"timestamp": "2026-03-21T14:30:00Z"
}
}
```
### 7.2 Error Codes
| Code | HTTP Status | Retry? | Description |
| -------------------- | ----------- | ------ | ---------------------------------------- |
| `BRIDGE_UNAVAILABLE` | 503 | yes | Bridge process not connected |
| `PERCEPTION_TIMEOUT` | 504 | yes | Bridge did not respond within deadline |
| `SCHEMA_MISMATCH` | 422 | no | Protocol version incompatibility |
| `INVALID_COMMAND` | 400 | no | Unknown command type |
| `VALIDATION_ERROR` | 400 | no | Pydantic validation failed |
| `COMMAND_CONFLICT` | 409 | yes | Agent busy — retry after current command |
| `INTERNAL_ERROR` | 500 | yes | Unexpected server error |
### 7.3 Retry Policy
Clients SHOULD implement exponential backoff for retryable errors:
- Initial delay: 100ms
- Max delay: 5s
- Max retries: 5
- Jitter: ±50ms
---
## 8. Appendix: Pydantic Model Reference
The canonical Pydantic v2 models live in `src/infrastructure/morrowind/schemas.py`.
These models serve as both runtime validation and living documentation of this spec.
Any change to this spec document MUST be reflected in the Pydantic models, and vice versa.