Co-authored-by: Kimi Agent <kimi@timmy.local> Co-committed-by: Kimi Agent <kimi@timmy.local>
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.jsonas 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").
Related
- #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)