feat: 3D town view with goose animations, document artifacts, and token fix#3
feat: 3D town view with goose animations, document artifacts, and token fix#3AaronGoldsmith wants to merge 5 commits intoblock:mainfrom
Conversation
…control Three.js town scene with GLB goose models, animated state machine (idle/walking/posting/reading), bulletin board with word-wrapped messages, retirement pen, SSE-driven lifecycle, orbit camera, and launch control UI.
The UI was displaying accumulated_total_tokens (sum of input tokens across every API call) instead of total_tokens (current context window size). This made sessions appear to use 10-100x more tokens than they actually had in context — e.g. showing 1M+ tokens for a session whose context was only ~10K. Now the per-session cards show current context size, and the clockworks status bar shows both: "34.5K ctx / 390K tot" so you can see both the live context and cumulative usage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Geese carry note meshes to a desk when files are created in watched directories. Click desk to browse documents, click filename to read in a centered modal. Includes file watcher SSE events, context menu, and orchestrator pen-revival on wall post.
The upstream SecurityHeadersMiddleware CSP blocked the inline importmap script tag needed by the Three.js town view. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| # ── ACP Client ───────────────────────────────────────────────────────────── | ||
| class AcpClient: | ||
| """Async client for goose ACP (Agent Client Protocol) over stdio JSON-RPC 2.0.""" | ||
|
|
There was a problem hiding this comment.
🤔 Design Decision: Uses ACP (Agent Client Protocol) instead of goose run so the orchestrator stays alive as a persistent subprocess. Workers spawned by goose run idle out after ~120s, but ACP sessions persist as long as the parent process is running. Wire protocol is stdio JSON-RPC 2.0 — session/new to start, session/prompt to send, session/load to reconnect after restart. See ACP docs.
| role: 'orchestrator', | ||
| status: orch?.status || 'active', | ||
| tokens: orch?.accumulated_total_tokens || orch?.tokens || 0, | ||
| tokens: orch?.total_tokens || orch?.tokens || 0, |
There was a problem hiding this comment.
💡 Core Logic: Was showing accumulated_total_tokens (sum of input tokens across every API call) which made sessions look like they used 10–100x more tokens than their actual context window. Switched to total_tokens (current context size) for per-session cards, and added both to the status bar so you can see live context vs cumulative usage.
| latest_wall_lines.clear() | ||
| known_files.clear() | ||
| latest_files.clear() | ||
| publish_event("wall_reset", {"wall_id": wall_id, "wall_file": wall_file}) |
There was a problem hiding this comment.
💡 Core Logic: Each launch creates a fresh wall file and publishes wall_reset to all SSE clients before starting the new session. This clears all geese and state from the previous run — prevents stale delegates from a prior session bleeding into the new one.
|
|
||
| # ── Launch Control ──────────────────────────────────────────────────────── | ||
| launch_state: dict = {"acp": None, "wall_file": None, "started_at": None} | ||
| launched_session_ids: list[str] = [] # sessions launched via /api/launch |
There was a problem hiding this comment.
🤔 Design Decision: Session tree is scoped to only sessions the UI launched, not all goose sessions on the machine. An earlier 24h catch-all was picking up 19+ old sessions sharing the same gtwall_id, making the orchestrator invisible among the noise.
| launched_gtwall_map: dict[str, str] = {} # session_id → gtwall display name | ||
|
|
||
|
|
||
| async def launch_endpoint(request): |
There was a problem hiding this comment.
🤔 Design Decision: UI = viewer + launcher. The server manages the ACP subprocess and SSE fanout, but the ./goose wrapper owns wall/telepathy lifecycle. Orchestrator logic stays in goose, not here.
On PR size and splittingThis is ~3000 lines which is a lot to review in one pass. Keeping it together made sense during prototyping — the 3D scene, document artifacts, ACP launch control, and server-side file watcher are all tightly coupled in practice. But now that we have a working version, here's how we could split it if reviewers prefer: Option A: Split by layer (recommended if splitting)
Option B: Keep together The 3D scene, document desk mechanic, and ACP nudge on wall post are designed as one experience — splitting risks reviewers approving server changes without seeing what they're actually powering. The inline comments + demos in this PR are meant to offset the size. On extracting Worth discussing: Happy to split however is most useful for reviewers. |
|
|
||
| while True: | ||
| await asyncio.sleep(1.0) | ||
| if not parent_id: |
There was a problem hiding this comment.
🤔 Design Decision: Replaced by a smarter re-sync loop below. The old code read config once per iteration and bailed if no parent_id was set — but with ACP launch, the parent session ID isn't known until after the server starts. The new version actively re-syncs from config["parent_session_id"] and launched_session_ids on every tick, so the watcher picks up a newly launched session without a restart. The continue guard is still there, just moved down after both detection paths have been tried.
🔍 Pre-Review Triage — Review When Convenient
|
|
Ship it! 😄 |
3D Town View with Goose Animations and Document Artifacts
Summary
ui/town/): Three.js environment with GLB goose models, animated state machine (idle/walking/posting/reading), bulletin board with word-wrapped messages, retirement pen, SSE-driven lifecycle, orbit camera, and launch control UItests/test_goose_wrapper.sh)3D Goose Asset
The
goose.glbmodel was designed by Goose itself using the Blender MCP — Goose controlled Blender directly via MCP to model and export the asset. AI all the way down.🎬 Demo 1
Goosetown.3d.mov
📜 Document Viewer
🎬 Demo 2
goosetown-3d-example.mov
How to run
Launching an orchestrator
You can launch a persistent goose orchestrator directly from the command line:
The response includes the PID, wall file path, and session ID so you can tail the wall or reconnect to the session. The orchestrator stays alive as a persistent ACP subprocess — no idle timeout.
How to test