From 43efb01c51fb8190de40298abc7497d909ff8566 Mon Sep 17 00:00:00 2001 From: Kimi Agent Date: Wed, 18 Mar 2026 21:28:10 -0400 Subject: [PATCH 1/2] fix: remove duplicate agent loader test file (#356) Co-authored-by: Kimi Agent Co-committed-by: Kimi Agent From c7198b1254c91f0f4d9f9c959cb6a4ff06ac2581 Mon Sep 17 00:00:00 2001 From: hermes Date: Wed, 18 Mar 2026 21:36:06 -0400 Subject: [PATCH 2/2] [loop-cycle-152] feat: define canonical presence schema for Workshop (#265) (#359) --- docs/adr/023-workshop-presence-schema.md | 166 +++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 docs/adr/023-workshop-presence-schema.md diff --git a/docs/adr/023-workshop-presence-schema.md b/docs/adr/023-workshop-presence-schema.md new file mode 100644 index 00000000..f26cba1a --- /dev/null +++ b/docs/adr/023-workshop-presence-schema.md @@ -0,0 +1,166 @@ +# 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). + +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) + +```json +{ + "$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 + +```json +{ + "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"). + +## 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)