Files
Timmy-time-dashboard/docs/adr/023-workshop-presence-schema.md
Kimi Agent 2209ac82d2
All checks were successful
Tests / lint (push) Successful in 3s
Tests / test (push) Successful in 1m14s
fix: canonically connect the Tower to the Workshop (#392)
Co-authored-by: Kimi Agent <kimi@timmy.local>
Co-committed-by: Kimi Agent <kimi@timmy.local>
2026-03-19 01:38:59 -04:00

5.9 KiB

ADR-023: Workshop Presence Schema

Status: Accepted Date: 2026-03-18 Issue: #265 Epic: #222 (The Workshop)

Context

The Workshop renders Timmy as a living presence in a 3D world. It needs to know what Timmy is doing right now — his working memory, not his full identity or history. This schema defines the contract between Timmy (writer) and the Workshop (reader).

The Tower IS the Workshop

The 3D world renderer lives in the-matrix/ within token-gated-economy, served at /tower by the API server (artifacts/api-server). This is the canonical Workshop scene — not a generic Matrix visualization. All Workshop phase issues (#361, #362, #363) target that codebase. No separate alexanderwhitestone.com scaffold is needed until production deploy.

The workshop-state spec (#360) is consumed by the API server via a file-watch mechanism, bridging Timmy's presence into the 3D scene.

Design principles:

  • Working memory, not long-term memory. Present tense only.
  • Written as side effect of work. Not a separate obligation.
  • Liveness is mandatory. Stale = "not home," shown honestly.
  • Schema is the contract. Keep it minimal and stable.

Decision

File Location

~/.timmy/presence.json

JSON chosen over YAML for predictable parsing by both Python and JavaScript (the Workshop frontend). The Workshop reads this file via the WebSocket bridge (#243) or polls it directly during development.

Schema (v1)

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Timmy Presence State",
  "description": "Working memory surface for the Workshop renderer",
  "type": "object",
  "required": ["version", "liveness", "current_focus"],
  "properties": {
    "version": {
      "type": "integer",
      "const": 1,
      "description": "Schema version for forward compatibility"
    },
    "liveness": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp of last update. If stale (>5min), Timmy is not home."
    },
    "current_focus": {
      "type": "string",
      "description": "One sentence: what Timmy is doing right now. Empty string = idle."
    },
    "active_threads": {
      "type": "array",
      "maxItems": 10,
      "description": "Current work items Timmy is tracking",
      "items": {
        "type": "object",
        "required": ["type", "ref", "status"],
        "properties": {
          "type": {
            "type": "string",
            "enum": ["pr_review", "issue", "conversation", "research", "thinking"]
          },
          "ref": {
            "type": "string",
            "description": "Reference identifier (issue #, PR #, topic name)"
          },
          "status": {
            "type": "string",
            "enum": ["active", "idle", "blocked", "completed"]
          }
        }
      }
    },
    "recent_events": {
      "type": "array",
      "maxItems": 20,
      "description": "Recent events, newest first. Capped at 20.",
      "items": {
        "type": "object",
        "required": ["timestamp", "event"],
        "properties": {
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "event": {
            "type": "string",
            "description": "Brief description of what happened"
          }
        }
      }
    },
    "concerns": {
      "type": "array",
      "maxItems": 5,
      "description": "Things Timmy is uncertain or worried about. Flat list, no severity.",
      "items": {
        "type": "string"
      }
    },
    "mood": {
      "type": "string",
      "enum": ["focused", "exploring", "uncertain", "excited", "tired", "idle"],
      "description": "Emotional texture for the Workshop to render. Optional."
    }
  }
}

Example

{
  "version": 1,
  "liveness": "2026-03-18T21:47:12Z",
  "current_focus": "Reviewing PR #267 — stream adapter for Gitea webhooks",
  "active_threads": [
    {"type": "pr_review", "ref": "#267", "status": "active"},
    {"type": "issue", "ref": "#239", "status": "idle"},
    {"type": "conversation", "ref": "hermes-consultation", "status": "idle"}
  ],
  "recent_events": [
    {"timestamp": "2026-03-18T21:45:00Z", "event": "Completed PR review for #265"},
    {"timestamp": "2026-03-18T21:30:00Z", "event": "Filed issue #268 — flaky test in sensory loop"}
  ],
  "concerns": [
    "WebSocket reconnection logic feels brittle",
    "Not sure the barks system handles uncertainty well yet"
  ],
  "mood": "focused"
}

Design Answers

Question Answer
File format JSON (predictable for JS + Python, no YAML parser needed in browser)
recent_events cap 20 entries max, oldest dropped
concerns severity Flat list, no priority. Keep it simple.
File location ~/.timmy/presence.json — accessible to Workshop via bridge
Staleness threshold 5 minutes without liveness update = "not home"
mood field Optional. Workshop can render visual cues (color, animation)

Consequences

  • Timmy's agent loop must write ~/.timmy/presence.json as a side effect of work. This is a hook at the end of each cycle, not a daemon.
  • The Workshop frontend reads this file and renders accordingly. Stale liveness → dim the wizard, show "away" state.
  • The WebSocket bridge (#243) watches this file and pushes changes to connected Workshop clients.
  • Schema is versioned. Breaking changes increment the version field. Workshop must handle unknown versions gracefully (show raw data or "unknown state").
  • #222 — Workshop epic
  • #243 — WebSocket bridge (transports this state)
  • #239 — Sensory loop (feeds into state)
  • #242 — 3D world (consumes this state for rendering)
  • #246 — Confidence as visible trait (mood field serves this)
  • #360 — Workshop-state spec (consumed by API via file-watch)
  • #361, #362, #363 — Workshop phase issues (target the-matrix/)
  • #372 — The Tower IS the Workshop (canonical connection)