Skip to content

feat: 3D town view with goose animations, document artifacts, and token fix#3

Draft
AaronGoldsmith wants to merge 5 commits intoblock:mainfrom
AaronGoldsmith:feature/3d-goosetown
Draft

feat: 3D town view with goose animations, document artifacts, and token fix#3
AaronGoldsmith wants to merge 5 commits intoblock:mainfrom
AaronGoldsmith:feature/3d-goosetown

Conversation

@AaronGoldsmith
Copy link

@AaronGoldsmith AaronGoldsmith commented Feb 26, 2026

3D Town View with Goose Animations and Document Artifacts

Summary

  • 3D town scene (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 UI
  • Document artifacts: Geese carry note meshes to a desk when files are created in watched directories. Click desk to browse, click filename to read in a centered modal. Adds file watcher SSE events to the server
  • Token display fix: Shows current context window size instead of accumulated total — displays "34.5K ctx / 390K tot" so you can distinguish live context from cumulative usage
  • Shell tests: Adds goose wrapper shell tests (tests/test_goose_wrapper.sh)

3D Goose Asset

The goose.glb model 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

image

🎬 Demo 2

goosetown-3d-example.mov

How to run

# Start the goosetown server (serves both 2D dashboard and 3D town)
./goose run goosetown-ui

# Open the 3D town view
open http://localhost:4242/ui/town

# Open the 2D dashboard (existing)
open http://localhost:4242

Launching an orchestrator

You can launch a persistent goose orchestrator directly from the command line:

curl -X POST localhost:4242/api/launch \
  -d '{"prompt":"Please help spawn a couple workers. Id like them to greet each other with a joke"}'

# {"ok":true,"pid":34760,"wall_file":"~/.goosetown/walls/wall-<id>.log","wall_id":"...","session_id":"20260226_9"}

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

# Run shell tests
tests/run_all.sh

# Manual testing:
# 1. Start the server and open /ui/town
# 2. Launch a goose session via the launch control panel (or curl above)
# 3. Watch goose models appear and animate through lifecycle states
# 4. Post to the wall — bulletin board should update via SSE
# 5. Create a file in a watched directory — goose should carry it to the desk
# 6. Click the desk to browse documents, click a filename to read in modal
# 7. Check session cards show current context tokens (not cumulative)

AaronGoldsmith and others added 5 commits February 25, 2026 16:36
…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>
@AaronGoldsmith AaronGoldsmith changed the title Feature/3d goosetown feat: 3D town view with goose animations, document artifacts, and token fix Feb 26, 2026
# ── ACP Client ─────────────────────────────────────────────────────────────
class AcpClient:
"""Async client for goose ACP (Agent Client Protocol) over stdio JSON-RPC 2.0."""

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 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,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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})
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 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):
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 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.

@AaronGoldsmith
Copy link
Author

On PR size and splitting

This 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)

  1. feat: ACP launch control + server changesscripts/goosetown-ui only (~650 lines). The AcpClient, launch/stop endpoints, file watcher, SSE events. Self-contained and reviewable without any Three.js context.
  2. feat: 3D town viewui/town/ (~2000 lines). Three.js scene, goose animations, bulletin board, panel UI. Depends on the SSE events from PR 1.
  3. fix/test: token display + shell testsui/js/components.js + tests/ (~200 lines). Totally independent, could go first.

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 AcpClient

Worth discussing: AcpClient (~130 lines) could live in its own scripts/acp_client.py or upstream in goose itself. Right now it's embedded in the server for simplicity, but if other tools want a persistent goose session it's a natural extraction point.

Happy to split however is most useful for reviewers.


while True:
await asyncio.sleep(1.0)
if not parent_id:
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 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.

@AaronGoldsmith
Copy link
Author

AaronGoldsmith commented Mar 17, 2026

🔍 Pre-Review Triage — Review When Convenient ⚠️ Contested

Large feature PR adding a 3D town visualization for Goose agents — worth reviewing but no production impact or blocked teammates to rush it.

Full triage analysis — models disagreed

Why now: Author-driven feature work; no external trigger or incident.
Cost of not merging: 3D town view and document artifact viewer don't ship; token display fix and delegate name regex fix remain unmerged — none of these break anything in production.
Linked ticket: None found in body, branch name, or commits.
Production code changes?: No — goosetown is a personal demo/tooling project, not a production service.

What's in this PR: Three.js 3D town scene with GLB goose models and animated state machine (idle/walking/posting/reading); document artifact viewer (file watching + desk modal); ACP async client for launching persistent goose orchestrators; cycle detection in find_children to prevent infinite recursion; delegate name regex fix (removed trailing .); token display fix (current context vs cumulative); shell tests. ~3,100 lines added across 10 files.

Claude's take (Review When Convenient): No production incident, no blocked teammates, no ticket. Cost of delay is: feature sits, not: something breaks. The embedded bug fixes are genuine but not critical in a demo context.
GPT's take (challenged — "Review Soon"): Scope is too broad for a low-priority pass; combination of new async ACP client, SSE-driven lifecycle, filesystem watching, and correctness fixes raises regression risk that basic CI won't catch.
Opus tiebreaker (Review When Convenient): Claude's read is correct — this is a personal demo/tooling project with zero production impact. GPT's concern is valid review guidance (look carefully at find_children and the ACP client), but scope risk doesn't elevate the priority when a regression breaks a demo, not a service.


🤖 Claude vs GPT disagreed — Opus arbitrated. Generated via /triage-prs

@ddrao
Copy link

ddrao commented Mar 18, 2026

Ship it! 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants