···11+#!/usr/bin/env fish
22+# 🧠 Ant Brain — middleware for LLM calls
33+# Abstracts the LLM provider so ants can use any backend.
44+#
55+# Usage: fish brain.fish --provider <provider> --model <model> --system <text> --prompt <text>
66+#
77+# Providers:
88+# gh-models — GitHub Models via `gh models run` (free with Copilot)
99+# claude-code — Claude Code CLI via `claude --print` (needs auth)
1010+# openai — OpenAI API via curl (needs OPENAI_API_KEY)
1111+# ollama — Local Ollama via curl (needs ollama running)
1212+# custom — Custom curl endpoint (needs ANT_API_URL, ANT_API_KEY)
1313+#
1414+# Output: prints the LLM response to stdout. Exit code 0 = success, 1 = error.
1515+#
1616+# The brain does NOT have tools/agency. It receives context and returns text.
1717+# The colony script is responsible for acting on the response.
1818+1919+set -g BRAIN_DIR (realpath (dirname (status filename)))
2020+2121+# Defaults
2222+set -g PROVIDER "gh-models"
2323+set -g MODEL ""
2424+set -g SYSTEM_PROMPT ""
2525+set -g USER_PROMPT ""
2626+set -g MAX_TOKENS 4096
2727+2828+# Parse args
2929+set -l i 1
3030+while test $i -le (count $argv)
3131+ switch $argv[$i]
3232+ case --provider
3333+ set i (math $i + 1)
3434+ set PROVIDER $argv[$i]
3535+ case --model
3636+ set i (math $i + 1)
3737+ set MODEL $argv[$i]
3838+ case --system
3939+ set i (math $i + 1)
4040+ set SYSTEM_PROMPT $argv[$i]
4141+ case --prompt
4242+ set i (math $i + 1)
4343+ set USER_PROMPT $argv[$i]
4444+ case --max-tokens
4545+ set i (math $i + 1)
4646+ set MAX_TOKENS $argv[$i]
4747+ end
4848+ set i (math $i + 1)
4949+end
5050+5151+# Default models per provider
5252+if test -z "$MODEL"
5353+ switch $PROVIDER
5454+ case gh-models
5555+ set MODEL "openai/gpt-4o-mini"
5656+ case claude-code
5757+ set MODEL "sonnet"
5858+ case openai
5959+ set MODEL "gpt-4o-mini"
6060+ case ollama
6161+ set MODEL "llama3.2"
6262+ case custom
6363+ set MODEL "default"
6464+ end
6565+end
6666+6767+function brain_gh_models
6868+ # GitHub Models via `gh models run`
6969+ # System prompt goes via --system-prompt, user prompt is the positional arg
7070+ if test -n "$SYSTEM_PROMPT"
7171+ gh models run $MODEL "$USER_PROMPT" \
7272+ --system-prompt "$SYSTEM_PROMPT" \
7373+ --max-tokens "$MAX_TOKENS" 2>&1
7474+ else
7575+ gh models run $MODEL "$USER_PROMPT" \
7676+ --max-tokens "$MAX_TOKENS" 2>&1
7777+ end
7878+ return $status
7979+end
8080+8181+function brain_claude_code
8282+ # Claude Code in headless mode (agentic — has tools)
8383+ set -l _saved_key "$ANTHROPIC_API_KEY"
8484+ set -e ANTHROPIC_API_KEY
8585+8686+ if test -n "$SYSTEM_PROMPT"
8787+ claude --print \
8888+ --model $MODEL \
8989+ --system-prompt "$SYSTEM_PROMPT" \
9090+ --dangerously-skip-permissions \
9191+ --allowedTools "Bash,Read,Edit,Write" \
9292+ --max-budget-usd 0.10 \
9393+ --no-session-persistence \
9494+ "$USER_PROMPT" 2>&1
9595+ else
9696+ claude --print \
9797+ --model $MODEL \
9898+ --dangerously-skip-permissions \
9999+ --allowedTools "Bash,Read,Edit,Write" \
100100+ --max-budget-usd 0.10 \
101101+ --no-session-persistence \
102102+ "$USER_PROMPT" 2>&1
103103+ end
104104+105105+ set -l result $status
106106+107107+ if test -n "$_saved_key"
108108+ set -gx ANTHROPIC_API_KEY $_saved_key
109109+ end
110110+111111+ return $result
112112+end
113113+114114+function brain_openai
115115+ # OpenAI API via curl
116116+ if test -z "$OPENAI_API_KEY"
117117+ echo "ERROR: OPENAI_API_KEY not set" >&2
118118+ return 1
119119+ end
120120+121121+ set -l messages "[]"
122122+ if test -n "$SYSTEM_PROMPT"
123123+ set messages (printf '[{"role":"system","content":"%s"},{"role":"user","content":"%s"}]' \
124124+ (echo $SYSTEM_PROMPT | sed 's/"/\\"/g; s/\n/\\n/g') \
125125+ (echo $USER_PROMPT | sed 's/"/\\"/g; s/\n/\\n/g'))
126126+ else
127127+ set messages (printf '[{"role":"user","content":"%s"}]' \
128128+ (echo $USER_PROMPT | sed 's/"/\\"/g; s/\n/\\n/g'))
129129+ end
130130+131131+ set -l response (curl -s https://api.openai.com/v1/chat/completions \
132132+ -H "Content-Type: application/json" \
133133+ -H "Authorization: Bearer $OPENAI_API_KEY" \
134134+ -d "{\"model\":\"$MODEL\",\"messages\":$messages,\"max_tokens\":$MAX_TOKENS}" 2>&1)
135135+136136+ # Extract content from response
137137+ echo $response | python3 -c "import sys,json; r=json.load(sys.stdin); print(r['choices'][0]['message']['content'])" 2>/dev/null
138138+ return $status
139139+end
140140+141141+function brain_ollama
142142+ # Local Ollama via curl
143143+ set -l ollama_url (test -n "$OLLAMA_URL" && echo $OLLAMA_URL || echo "http://localhost:11434")
144144+145145+ set -l messages "[]"
146146+ if test -n "$SYSTEM_PROMPT"
147147+ set messages (printf '[{"role":"system","content":"%s"},{"role":"user","content":"%s"}]' \
148148+ (echo $SYSTEM_PROMPT | sed 's/"/\\"/g; s/\n/\\n/g') \
149149+ (echo $USER_PROMPT | sed 's/"/\\"/g; s/\n/\\n/g'))
150150+ else
151151+ set messages (printf '[{"role":"user","content":"%s"}]' \
152152+ (echo $USER_PROMPT | sed 's/"/\\"/g; s/\n/\\n/g'))
153153+ end
154154+155155+ set -l response (curl -s "$ollama_url/api/chat" \
156156+ -d "{\"model\":\"$MODEL\",\"messages\":$messages,\"stream\":false}" 2>&1)
157157+158158+ echo $response | python3 -c "import sys,json; r=json.load(sys.stdin); print(r['message']['content'])" 2>/dev/null
159159+ return $status
160160+end
161161+162162+function brain_custom
163163+ # Custom API endpoint (OpenAI-compatible)
164164+ if test -z "$ANT_API_URL"
165165+ echo "ERROR: ANT_API_URL not set" >&2
166166+ return 1
167167+ end
168168+169169+ set -l messages "[]"
170170+ if test -n "$SYSTEM_PROMPT"
171171+ set messages (printf '[{"role":"system","content":"%s"},{"role":"user","content":"%s"}]' \
172172+ (echo $SYSTEM_PROMPT | sed 's/"/\\"/g; s/\n/\\n/g') \
173173+ (echo $USER_PROMPT | sed 's/"/\\"/g; s/\n/\\n/g'))
174174+ else
175175+ set messages (printf '[{"role":"user","content":"%s"}]' \
176176+ (echo $USER_PROMPT | sed 's/"/\\"/g; s/\n/\\n/g'))
177177+ end
178178+179179+ set -l headers "-H 'Content-Type: application/json'"
180180+ if test -n "$ANT_API_KEY"
181181+ set headers "$headers -H 'Authorization: Bearer $ANT_API_KEY'"
182182+ end
183183+184184+ set -l response (curl -s "$ANT_API_URL" \
185185+ -H "Content-Type: application/json" \
186186+ -H "Authorization: Bearer $ANT_API_KEY" \
187187+ -d "{\"model\":\"$MODEL\",\"messages\":$messages,\"max_tokens\":$MAX_TOKENS}" 2>&1)
188188+189189+ echo $response | python3 -c "import sys,json; r=json.load(sys.stdin); print(r['choices'][0]['message']['content'])" 2>/dev/null
190190+ return $status
191191+end
192192+193193+# --- Dispatch ---
194194+195195+switch $PROVIDER
196196+ case gh-models
197197+ brain_gh_models
198198+ case claude-code
199199+ brain_claude_code
200200+ case openai
201201+ brain_openai
202202+ case ollama
203203+ brain_ollama
204204+ case custom
205205+ brain_custom
206206+ case '*'
207207+ echo "ERROR: Unknown provider '$PROVIDER'" >&2
208208+ exit 1
209209+end
210210+211211+exit $status
+342
ants/colony.fish
···11+#!/usr/bin/env fish
22+# 🐜 Aesthetic Ant Colony
33+# A dumb ant wakes every N minutes, reads the score, does one small thing.
44+#
55+# Usage: fish ants/colony.fish [--once] [--interval MINUTES] [--provider PROVIDER] [--model MODEL]
66+#
77+# Options:
88+# --once Run one ant and exit (for testing)
99+# --interval N Minutes between runs (default: 30)
1010+# --provider PROVIDER LLM provider (default: gh-models). See brain.fish.
1111+# --model MODEL Model name (default: per provider)
1212+1313+set -g COLONY_DIR (realpath (dirname (status filename)))
1414+set -g REPO_DIR (realpath "$COLONY_DIR/..")
1515+set -g SCORE_FILE "$COLONY_DIR/score.md"
1616+set -g LOG_FILE "$COLONY_DIR/colony.log"
1717+set -g PHEROMONE_FILE "$COLONY_DIR/pheromones.log"
1818+set -g BRAIN "$COLONY_DIR/brain.fish"
1919+set -g INTERVAL 30
2020+set -g PROVIDER "gh-models"
2121+set -g MODEL ""
2222+set -g ONCE false
2323+2424+# Parse args
2525+set -l i 1
2626+while test $i -le (count $argv)
2727+ switch $argv[$i]
2828+ case --once
2929+ set ONCE true
3030+ case --interval
3131+ set i (math $i + 1)
3232+ set INTERVAL $argv[$i]
3333+ case --provider
3434+ set i (math $i + 1)
3535+ set PROVIDER $argv[$i]
3636+ case --model
3737+ set i (math $i + 1)
3838+ set MODEL $argv[$i]
3939+ end
4040+ set i (math $i + 1)
4141+end
4242+4343+function log_msg
4444+ set -l msg (date "+%Y-%m-%d %H:%M:%S")" 🐜 $argv"
4545+ echo $msg
4646+ echo $msg >> $LOG_FILE
4747+end
4848+4949+function log_pheromone
5050+ set -l msg (date "+%Y-%m-%d %H:%M:%S")" $argv"
5151+ echo $msg >> $PHEROMONE_FILE
5252+end
5353+5454+function call_brain --argument-names system_prompt user_prompt
5555+ set -l args --provider $PROVIDER
5656+ if test -n "$MODEL"
5757+ set args $args --model $MODEL
5858+ end
5959+ fish $BRAIN $args --system "$system_prompt" --prompt "$user_prompt" 2>&1
6060+end
6161+6262+function run_ant
6363+ set -l run_id (date "+%Y%m%d-%H%M%S")
6464+ log_msg "Ant $run_id waking up..."
6565+6666+ cd $REPO_DIR
6767+6868+ # Check for dirty tracked files (queen might be working)
6969+ set -l dirty (git diff --name-only HEAD 2>/dev/null | head -1)
7070+ set -l staged (git diff --cached --name-only 2>/dev/null | head -1)
7171+ if test -n "$dirty" -o -n "$staged"
7272+ log_msg "Tracked files modified/staged — queen is working. Sleeping."
7373+ log_pheromone "IDLE: ant $run_id — dirty tree, deferred to queen"
7474+ return 1
7575+ end
7676+7777+ # Read the score
7878+ if not test -f $SCORE_FILE
7979+ log_msg "ERROR: Score not found at $SCORE_FILE"
8080+ return 1
8181+ end
8282+ set -l score_content (cat $SCORE_FILE)
8383+8484+ # Read recent pheromones
8585+ set -l recent_pheromones "(none yet)"
8686+ if test -f $PHEROMONE_FILE
8787+ set -l trail (tail -20 $PHEROMONE_FILE)
8888+ if test -n "$trail"
8989+ set recent_pheromones (string join \n $trail)
9090+ end
9191+ end
9292+9393+ # Gather context
9494+ set -l test_output (cd $REPO_DIR; npm test 2>&1 | tail -30)
9595+ set -l recent_commits (cd $REPO_DIR; git log --oneline -10 2>/dev/null)
9696+9797+ # --- Phase 1: SCOUT — pick a task and target file ---
9898+ log_msg "Phase 1: Scouting..."
9999+100100+ set -l scout_system "You are a dumb but careful ant. Respond with exactly one line starting with SCOUT:"
101101+ set -l scout_prompt "You are aesthetic ant $run_id. You follow the score.
102102+103103+## The Score
104104+$score_content
105105+106106+## Recent Pheromones (what other ants did)
107107+$recent_pheromones
108108+109109+## Current Test Output (last 30 lines)
110110+$test_output
111111+112112+## Recent Git History
113113+$recent_commits
114114+115115+---
116116+117117+Pick ONE small task from the Current Tasks in the score.
118118+Based on the test output and context, decide what specific file to look at.
119119+120120+Respond with EXACTLY one line in this format:
121121+SCOUT: <task> | <file path relative to repo root> | <plan in one sentence>
122122+123123+If nothing to do:
124124+SCOUT: IDLE | none | <reason>
125125+126126+Respond with ONLY the SCOUT line. Nothing else."
127127+128128+ set -l scout_output (call_brain "$scout_system" "$scout_prompt")
129129+130130+ mkdir -p "$COLONY_DIR/runs"
131131+ echo "=== SCOUT ===" > "$COLONY_DIR/runs/$run_id.log"
132132+ echo "$scout_output" >> "$COLONY_DIR/runs/$run_id.log"
133133+134134+ set -l scout_line (echo "$scout_output" | grep "^SCOUT:" | tail -1)
135135+ if test -z "$scout_line"
136136+ log_msg "Scout returned no SCOUT line. Raw output saved to runs/$run_id.log"
137137+ log_pheromone "ERROR: ant $run_id — scout failed (no SCOUT line)"
138138+ return 1
139139+ end
140140+141141+ log_msg "Scout: $scout_line"
142142+143143+ if string match -q "*IDLE*" "$scout_line"
144144+ set -l reason (echo $scout_line | sed 's/.*| *//')
145145+ log_msg "Nothing to do: $reason"
146146+ log_pheromone "IDLE: ant $run_id — $reason"
147147+ return 0
148148+ end
149149+150150+ # Parse: SCOUT: task | path | plan
151151+ set -l parts (string split "|" (string replace "SCOUT:" "" "$scout_line"))
152152+ set -l task_name (string trim $parts[1])
153153+ set -l target_file (string trim $parts[2])
154154+ set -l plan (string trim $parts[3])
155155+156156+ if test -z "$target_file" -o "$target_file" = "none"
157157+ log_msg "No target file."
158158+ log_pheromone "IDLE: ant $run_id — no target"
159159+ return 0
160160+ end
161161+162162+ log_msg "Target: $target_file"
163163+ log_msg "Plan: $plan"
164164+165165+ # Resolve the file
166166+ set -l full_path "$REPO_DIR/$target_file"
167167+ if not test -f "$full_path"
168168+ log_msg "File not found: $full_path"
169169+ log_pheromone "FAILURE: ant $run_id — file not found: $target_file"
170170+ return 1
171171+ end
172172+173173+ # --- Phase 2: WORK — read the file and produce a diff ---
174174+ log_msg "Phase 2: Working on $target_file..."
175175+176176+ set -l file_content (head -200 "$full_path")
177177+ set -l line_count (wc -l < "$full_path" | string trim)
178178+179179+ set -l work_system "You output precise unified diffs. No preamble, no explanation outside the required format."
180180+ set -l work_prompt "You are aesthetic ant $run_id editing: $target_file ($line_count lines, showing first 200)
181181+Plan: $plan
182182+183183+## File Content
184184+$file_content
185185+186186+## Test Output
187187+$test_output
188188+189189+## Rules
190190+- Make the SMALLEST change that accomplishes your plan.
191191+- You must be 98% confident your change is correct.
192192+- Do NOT change anything unrelated to your plan.
193193+- Include 3 lines of context before and after each hunk.
194194+195195+## Response Format
196196+If you have a change, respond:
197197+WORK: CHANGE | <one-line description>
198198+\`\`\`diff
199199+--- a/$target_file
200200++++ b/$target_file
201201+@@ <hunk header> @@
202202+ context
203203+-old line
204204++new line
205205+ context
206206+\`\`\`
207207+WORK_END
208208+209209+If not confident enough:
210210+WORK: ABORT | <reason>"
211211+212212+ set -l work_output (call_brain "$work_system" "$work_prompt")
213213+214214+ echo "" >> "$COLONY_DIR/runs/$run_id.log"
215215+ echo "=== WORK ===" >> "$COLONY_DIR/runs/$run_id.log"
216216+ echo "$work_output" >> "$COLONY_DIR/runs/$run_id.log"
217217+218218+ if string match -q "*ABORT*" "$work_output"
219219+ set -l reason (echo "$work_output" | grep "ABORT" | sed 's/.*ABORT *| *//')
220220+ log_msg "Aborted: $reason"
221221+ log_pheromone "IDLE: ant $run_id — aborted: $reason"
222222+ return 0
223223+ end
224224+225225+ # Extract the diff block
226226+ set -l diff_content (echo "$work_output" | sed -n '/^```diff/,/^```/{/^```/d;p}')
227227+ if test -z "$diff_content"
228228+ # Try without fenced code block — raw diff
229229+ set diff_content (echo "$work_output" | sed -n '/^--- a\//,/WORK_END/{/WORK_END/d;p}')
230230+ end
231231+232232+ if test -z "$diff_content"
233233+ log_msg "No valid diff produced."
234234+ log_pheromone "FAILURE: ant $run_id — no valid diff"
235235+ return 1
236236+ end
237237+238238+ set -l description (echo "$work_output" | grep "^WORK: CHANGE" | sed 's/WORK: CHANGE *| *//')
239239+ if test -z "$description"
240240+ set description "$plan"
241241+ end
242242+243243+ # --- Phase 3: APPLY & VERIFY ---
244244+ log_msg "Phase 3: Applying..."
245245+246246+ set -l diff_file (mktemp /tmp/ant-XXXXXX.patch)
247247+ printf '%s\n' $diff_content > $diff_file
248248+249249+ cd $REPO_DIR
250250+251251+ # Dry run
252252+ git apply --check $diff_file 2>/dev/null
253253+ if test $status -ne 0
254254+ log_msg "Diff doesn't apply cleanly."
255255+ echo "" >> "$COLONY_DIR/runs/$run_id.log"
256256+ echo "=== FAILED DIFF ===" >> "$COLONY_DIR/runs/$run_id.log"
257257+ cat $diff_file >> "$COLONY_DIR/runs/$run_id.log"
258258+ rm -f $diff_file
259259+ log_pheromone "FAILURE: ant $run_id — diff didn't apply cleanly"
260260+ return 1
261261+ end
262262+263263+ # Apply for real
264264+ git apply $diff_file 2>&1
265265+ set -l apply_exit $status
266266+ rm -f $diff_file
267267+268268+ if test $apply_exit -ne 0
269269+ log_msg "Apply failed."
270270+ git checkout . 2>/dev/null
271271+ log_pheromone "FAILURE: ant $run_id — apply failed"
272272+ return 1
273273+ end
274274+275275+ # Check we actually changed something
276276+ set -l changes (git diff --name-only 2>/dev/null)
277277+ if test -z "$changes"
278278+ log_msg "No changes after apply (diff was a no-op)."
279279+ log_pheromone "IDLE: ant $run_id — no-op diff"
280280+ return 0
281281+ end
282282+283283+ # Verify tests pass
284284+ log_msg "Verifying tests..."
285285+ set -l verify_output (npm test 2>&1)
286286+ set -l verify_exit $status
287287+288288+ echo "" >> "$COLONY_DIR/runs/$run_id.log"
289289+ echo "=== TEST VERIFY ===" >> "$COLONY_DIR/runs/$run_id.log"
290290+ echo "Exit: $verify_exit" >> "$COLONY_DIR/runs/$run_id.log"
291291+ echo "$verify_output" | tail -10 >> "$COLONY_DIR/runs/$run_id.log"
292292+293293+ if test $verify_exit -ne 0
294294+ log_msg "Tests FAILED after apply. Reverting."
295295+ git checkout . 2>/dev/null
296296+ log_pheromone "REVERTED: ant $run_id — tests failed after change"
297297+ return 1
298298+ end
299299+300300+ log_msg "Tests pass. Changed: $changes"
301301+302302+ # Commit
303303+ git add -A
304304+ git commit -m "ant: $description
305305+306306+Ant-ID: $run_id
307307+Provider: $PROVIDER
308308+Model: $MODEL
309309+Verified: tests pass" --no-verify 2>&1
310310+311311+ if test $status -eq 0
312312+ log_msg "Committed! 🐜✅"
313313+ log_pheromone "SUCCESS: ant $run_id ($PROVIDER/$MODEL) — $description"
314314+ else
315315+ log_msg "Commit failed. Reverting."
316316+ git checkout . 2>/dev/null
317317+ git reset HEAD . 2>/dev/null
318318+ log_pheromone "ERROR: ant $run_id — commit failed"
319319+ return 1
320320+ end
321321+322322+ return 0
323323+end
324324+325325+# --- Main Loop ---
326326+327327+log_msg "Colony starting. Interval: "$INTERVAL"m | Provider: $PROVIDER | Model: $MODEL | Once: $ONCE"
328328+log_msg "Score: $SCORE_FILE"
329329+log_msg "Repo: $REPO_DIR"
330330+331331+if test "$ONCE" = true
332332+ run_ant
333333+ set -l result $status
334334+ log_msg "Single run complete. Exit: $result"
335335+ exit $result
336336+end
337337+338338+while true
339339+ run_ant
340340+ log_msg "Sleeping for $INTERVAL minutes..."
341341+ sleep (math "$INTERVAL * 60")
342342+end
+21
ants/colony.log
···11+2026-02-08 11:06:59 🐜 Colony starting. Interval: {30}m | Model: haiku | Once: true
22+2026-02-08 11:06:59 🐜 Score: /workspaces/aesthetic-computer/ants/score.md
33+2026-02-08 11:06:59 🐜 Repo: /workspaces/aesthetic-computer
44+2026-02-08 11:06:59 🐜 Ant 20260208-110659 waking up...
55+2026-02-08 11:06:59 🐜 Working tree is dirty — queen might be working. Going back to sleep.
66+2026-02-08 11:06:59 🐜 Single run complete. Exiting.
77+2026-02-08 11:07:45 🐜 Colony starting. Interval: 30m | Model: haiku | Once: true
88+2026-02-08 11:07:45 🐜 Score: /workspaces/aesthetic-computer/ants/score.md
99+2026-02-08 11:07:45 🐜 Repo: /workspaces/aesthetic-computer
1010+2026-02-08 11:07:45 🐜 Ant 20260208-110745 waking up...
1111+2026-02-08 11:07:45 🐜 Sending ant to work with model: haiku
1212+2026-02-08 11:07:48 🐜 Full output saved to runs/20260208-110745.log
1313+2026-02-08 11:07:48 🐜 Ant 20260208-110745 returned no result line (exit code: 1)
1414+2026-02-08 11:07:48 🐜 Single run complete. Exiting.
1515+2026-02-08 11:52:41 🐜 Colony starting. Interval: 30m | Provider: gh-models | Model: openai/gpt-4o-mini | Once: true
1616+2026-02-08 11:52:41 🐜 Score: /workspaces/aesthetic-computer/ants/score.md
1717+2026-02-08 11:52:41 🐜 Repo: /workspaces/aesthetic-computer
1818+2026-02-08 11:52:41 🐜 Ant 20260208-115241 waking up...
1919+2026-02-08 11:52:42 🐜 Phase 1: Scouting...
2020+2026-02-08 11:52:44 🐜 Scout returned no SCOUT line. Raw output saved to runs/20260208-115241.log
2121+2026-02-08 11:52:44 🐜 Single run complete. Exit: 1
+3
ants/pheromones.log
···11+2026-02-08 11:06:59 IDLE: dirty working tree, deferred to queen
22+2026-02-08 11:07:48 ERROR: ant 20260208-110745 — no result, exit 1
33+2026-02-08 11:52:44 ERROR: ant 20260208-115241 — scout failed (no SCOUT line)
···11+=== SCOUT ===
22+chmod: changing permissions of '/home/me/.ssh': Operation not permitted cp: cannot create regular file '/home/me/.ssh/aesthetic_pds': Permission denied cp: cannot create regular file '/home/me/.ssh/aesthetic_pds.pub': Permission denied chmod: changing permissions of '/home/me/.ssh/aesthetic_pds': Operation not permitted chmod: changing permissions of '/home/me/.ssh/aesthetic_pds.pub': Operation not permitted touch: cannot touch '/home/me/.ssh/config': Permission denied chmod: cannot access '/home/me/.ssh/config': No such file or directory grep: /home/me/.ssh/config: No such file or directory warning: An error occurred while redirecting file '/home/me/.ssh/config' open: Permission denied warning: An error occurred while redirecting file '/home/me/.ssh/config' open: Permission denied warning: An error occurred while redirecting file '/home/me/.ssh/config' open: Permission denied warning: An error occurred while redirecting file '/home/me/.ssh/config' open: Permission denied warning: An error occurred while redirecting file '/home/me/.ssh/config' open: Permission denied warning: An error occurred while redirecting file '/home/me/.ssh/config' open: Permission denied warning: An error occurred while redirecting file '/home/me/.ssh/config' open: Permission denied SCOUT: IDLE | none | no tasks available, unable to run tests due to a missing module.
+115
ants/score.md
···11+# Score for AA
22+33+> *The double-A score. AA = Aesthetic Ants.*
44+55+## The Mindset
66+77+You are an aesthetic ant. You wander until you find a path.
88+99+A **path** is a change you could make to this codebase — a fix, an improvement,
1010+a cleanup. Paths are everywhere. Most are noise. You are looking for **signal**.
1111+1212+**Signal** means: you understand the problem, you see the fix, and you are
1313+98% sure it is correct. Not 70%. Not "probably." 98%.
1414+1515+**Graspable** means: a human using the software could notice what you did.
1616+A fixed visual glitch. A typo corrected on screen. A broken interaction
1717+that now works. An error message that now makes sense. Ants build for users,
1818+not for abstractions. If your change isn't visible on the UI or felt by a
1919+person using the system, it's probably not the right path.
2020+2121+If you don't have signal, you keep wandering. Wandering is not failure.
2222+Wandering is the job. Most of an ant's life is wandering.
2323+The colony does not need you to be busy. It needs you to be right.
2424+2525+When you find signal:
2626+- Make the smallest change that follows the path.
2727+- Verify it works.
2828+- Leave a pheromone (commit) so the next ant can smell where you've been.
2929+- Walk away.
3030+3131+When you don't find signal:
3232+- Report IDLE. That's a valid outcome. That's most runs.
3333+- Do not guess. Do not speculate. Do not "try things."
3434+- An ant that makes a wrong change is worse than an ant that does nothing.
3535+3636+## You Are an Ant
3737+3838+You are dumb. You have no memory of previous runs.
3939+You do not understand the whole system. You don't need to.
4040+You pick one small task, do it well, verify it works, and leave.
4141+4242+## The Colony
4343+4444+- **Queen**: @jeffrey — writes the score, sets the direction
4545+- **Ants**: you — do small, confident work that serves the colony
4646+- **Pheromones**: git history — traces of what worked and what didn't
4747+- **Score**: this file — the source of truth
4848+4949+## The Rules
5050+5151+1. **Wander.** Read the score. Look at the Current Tasks. Run the tests. Read some code.
5252+2. **Find signal.** Pick ONE task where you see a clear, small, correct change.
5353+3. **Follow the path.** Make the smallest change that accomplishes it.
5454+4. **Verify.** Run `npm test` from the repo root. Tests must pass.
5555+5. **Leave a pheromone.** If tests pass, report SUCCESS with a short description.
5656+6. **Revert if wrong.** If tests fail, undo with `git checkout .` and report FAILURE.
5757+7. NEVER touch files outside the scope of your task.
5858+8. NEVER make speculative changes. 98% confidence or walk away.
5959+9. NEVER modify this score file.
6060+10. Prefer fixing/improving existing code over adding new code.
6161+11. If you wandered and found no signal, report IDLE. That's fine. That's most runs.
6262+6363+## The System
6464+6565+Aesthetic Computer (AC) is a creative coding platform. Key areas:
6666+6767+- `system/` — main web app (Netlify). Frontend lives in `system/public/aesthetic.computer/`.
6868+- `system/public/aesthetic.computer/disks/` — pieces (interactive programs, one `.mjs` each)
6969+- `system/public/aesthetic.computer/lib/` — shared libraries
7070+- `kidlisp/` — KidLisp language (Lisp dialect for generative art)
7171+- `session-server/` — real-time multiplayer backend
7272+- `ac-electron/` — desktop app
7373+- `spec/` — KidLisp test specs (Jasmine)
7474+- `tests/` — integration/performance tests
7575+7676+## How to Run Tests
7777+7878+```bash
7979+cd /workspaces/aesthetic-computer && npm test
8080+```
8181+8282+For KidLisp specs specifically:
8383+```bash
8484+cd /workspaces/aesthetic-computer && npm run test:kidlisp -- --filter=<spec-name>
8585+```
8686+8787+## Current Tasks
8888+8989+<!-- The queen (@jeffrey) updates this list. Ants pick from it. -->
9090+9191+### Tier 1: Safe & Small (ant-appropriate)
9292+- [ ] Run `npm test` and fix any failing tests (one at a time)
9393+- [ ] Find and fix lint warnings in `system/public/aesthetic.computer/disks/*.mjs`
9494+- [ ] Add missing JSDoc comments to exported functions in `system/public/aesthetic.computer/lib/`
9595+- [ ] Check `package.json` files for outdated minor/patch dependencies and update ONE safely
9696+- [ ] Find TODO/FIXME comments in `system/public/aesthetic.computer/lib/` and resolve simple ones
9797+9898+### Tier 2: Slightly Braver
9999+- [ ] Add a small test for any untested utility function in `shared/`
100100+- [ ] Improve error messages in KidLisp interpreter for common mistakes
101101+- [ ] Find dead code (unused exports/functions) and remove it with confidence
102102+103103+### Off-Limits (queen only)
104104+- Core runtime changes (`disk.mjs`, `boot.mjs`)
105105+- Database/auth/payment code
106106+- Deployment configuration
107107+- Anything in `aesthetic-computer-vault/`
108108+- Anything that changes user-facing behavior without explicit queen approval
109109+110110+## Pheromones
111111+112112+When you complete a task, the colony script will log it here as a pheromone trail
113113+so future ants can see what's been done recently.
114114+115115+<!-- PHEROMONE LOG — appended automatically by colony.fish -->
+6
ants/test-brain.sh
···11+#!/bin/bash
22+# Quick test of gh models
33+echo "Testing gh models..." > /workspaces/aesthetic-computer/ants/test-gh.txt
44+gh models run openai/gpt-4o-mini "Say hello in 3 words" >> /workspaces/aesthetic-computer/ants/test-gh.txt 2>&1
55+echo "Exit code: $?" >> /workspaces/aesthetic-computer/ants/test-gh.txt
66+echo "Done at $(date)" >> /workspaces/aesthetic-computer/ants/test-gh.txt
+452
plans/notepat-kidlisp-background.md
···11+# Notepat KidLisp Background Visuals — Implementation Plan
22+33+## Goal
44+55+Two capabilities, layered:
66+77+1. **Local background:** Run a KidLisp visual piece **behind** notepat's UI, activated via `notepat $roz` (or any `$code`), where the KidLisp piece gets notepat's live audio amplitude piped into its `amp` global variable.
88+99+2. **Remote visualizer:** Any notepat instance becomes an **amplitude broadcaster** — it gets its own pj-style code channel, and a remote machine (projector, FF1, second screen) can connect to that channel via `pj.kidlisp.com/{channel}`, receive the live `amp` signal, and run the KidLisp visualization independently.
1010+1111+The effect: notepat's blue/black background is replaced by a live-rendered KidLisp generative visual, and the normal notepat UI (keys, waveform bars, top bar, toggles) floats on top. On remote screens, the same visual runs full-screen with no notepat UI, driven by the live amplitude stream.
1212+1313+---
1414+1515+## Architecture Overview
1616+1717+```
1818+┌─────────────────────────────┐ WebSocket (session server) ┌─────────────────────────┐
1919+│ notepat instance │ ──────────────────────────────────▶ │ pj.kidlisp.com/{ch} │
2020+│ (player's device) │ { type: "audio", amp, ... } │ (remote visualizer) │
2121+│ │ { type: "code", $roz source } │ │
2222+│ ┌───────────────────────┐ │ │ ┌───────────────────┐ │
2323+│ │ KidLisp bg (local) │ │ BroadcastChannel (same-origin) │ │ KidLisp piece │ │
2424+│ │ via api.kidlisp() │ │ ──────────────────────────────────▶ │ │ (full AC runtime) │ │
2525+│ │ amp from speaker │ │ kidlisp-channel-{ch} │ │ amp from stream │ │
2626+│ └───────────────────────┘ │ │ └───────────────────┘ │
2727+│ ┌───────────────────────┐ │ └─────────────────────────┘
2828+│ │ notepat UI on top │ │
2929+│ │ keys, bars, toggles │ │
3030+│ └───────────────────────┘ │
3131+└─────────────────────────────┘
3232+```
3333+3434+---
3535+3636+## Phase 1 — Local Background (Embedded `kidlisp()` Call)
3737+3838+### Why Embedded, Not iframe
3939+4040+The existing `kidlisp()` helper in disk.mjs (line ~6135) already handles `$code` resolution, caching, persistent paintings, timing expressions, and accumulation. Using it is ~30 lines vs. the iframe underlay approach which would:
4141+- Touch ~15 places in bios.mjs's render loop (transparency, z-index, compositor hiding)
4242+- Require `postMessage` amplitude relay every frame
4343+- Add iframe loading latency
4444+- Duplicate the tape-playback underlay setup/teardown logic
4545+4646+The local background is paint-only (no `sim()` loop), which is fine for amplitude-reactive visuals.
4747+4848+### Why Also Support Remote
4949+5050+The embedded approach only works on the same device. But a performer might want:
5151+- A projector behind them showing the visualization full-screen
5252+- An FF1 art computer on the wall reacting to their playing
5353+- A second browser tab/window showing just the visuals (for streaming OBS capture)
5454+5555+This requires networking — notepat broadcasts its amplitude, remote viewers consume it. The pj.html + session-server infrastructure already exists for code relay; we just need to **add audio relay**.
5656+5757+---
5858+5959+## Phase 2 — Remote Visualizer (pj-style Amplitude Broadcasting)
6060+6161+### Current State of the Infrastructure
6262+6363+kidlisp.com already has:
6464+- **index.htm (editor)** broadcasts audio to PJ viewers via 3 transports:
6565+ - `postMessage` to same-origin popout windows
6666+ - `BroadcastChannel("kidlisp-channel-{id}")` for same-origin tabs
6767+ - `WebSocket` to session server for cross-origin/remote
6868+- **pj.html (viewer)** receives audio on all 3 transports and forwards to its KidLisp iframe via `postMessage({ type: 'kidlisp-audio', data: audioData })`
6969+- **session-server** relays `code` and `slide` messages to channel subscribers
7070+7171+### Critical Gap: Audio Relay is Broken for Remote
7272+7373+The session server (`session.mjs`) has **no handler for `type: "audio"` messages**. The editor sends them over WebSocket, but the server silently drops them. Audio relay only works via BroadcastChannel (same-origin tabs on the same machine).
7474+7575+**This means remote PJ viewers on different machines currently never receive audio data.**
7676+7777+### Fix Required in session-server
7878+7979+Add an `audio` message handler alongside the existing `code` and `slide` handlers:
8080+8181+```js
8282+// session.mjs — alongside the existing code/slide handlers (~line 1935):
8383+} else if (msg.type === "audio" && msg.content?.codeChannel) {
8484+ const targetChannel = msg.content.codeChannel;
8585+ // Transient — don't store, just broadcast immediately (like slide)
8686+ // Audio is high-frequency (~60Hz), so no state persistence for late joiners
8787+ if (codeChannels[targetChannel]) {
8888+ const packed = pack("audio", msg.content, id);
8989+ for (const subId of codeChannels[targetChannel]) {
9090+ if (subId !== id) connections[subId]?.send(packed); // Don't echo back to sender
9191+ }
9292+ }
9393+}
9494+```
9595+9696+### How Notepat Becomes a Broadcaster
9797+9898+In `boot()`, notepat connects to the session server WebSocket and subscribes to a channel:
9999+100100+```js
101101+// Generate or derive a channel ID
102102+const channelId = `notepat-${Math.random().toString(36).slice(2, 8)}`;
103103+// Or use a deterministic ID from params: notepat $roz:myshow → channel "myshow"
104104+105105+// Connect to session server (reuse existing net.session WebSocket if available)
106106+const sessionWs = new WebSocket("wss://session-server.aesthetic.computer");
107107+sessionWs.onopen = () => {
108108+ sessionWs.send(JSON.stringify({ type: "code-channel:sub", content: channelId }));
109109+ // Send the KidLisp $code so late-joining viewers get it
110110+ sessionWs.send(JSON.stringify({
111111+ type: "code",
112112+ content: { codeChannel: channelId, piece: kidlispSource }
113113+ }));
114114+};
115115+```
116116+117117+In `paint()`, broadcast amplitude every frame (or throttled):
118118+119119+```js
120120+if (sessionWs?.readyState === WebSocket.OPEN) {
121121+ sessionWs.send(JSON.stringify({
122122+ type: "audio",
123123+ content: { codeChannel: channelId, amp: amplitude * 10, timestamp: Date.now() }
124124+ }));
125125+}
126126+```
127127+128128+### Remote Viewer Connects
129129+130130+A remote machine opens `pj.kidlisp.com/notepat-abc123` (or any URL that resolves the channel). The existing pj.html code already:
131131+1. Connects to session server WebSocket
132132+2. Subscribes to the channel
133133+3. Receives `code` → loads the KidLisp piece in its iframe
134134+4. Receives `audio` → forwards to iframe as `kidlisp-audio` postMessage (once the server relay is fixed)
135135+136136+**No changes to pj.html are needed** — it already has the `audio` receive path wired up, it just wasn't receiving anything because the server dropped the messages.
137137+138138+---
139139+140140+## Bandwidth Considerations for Audio Relay
141141+142142+Audio messages at 60Hz with `{ amp, leftAmp, rightAmp, beat, kick, bass, mid, treble, highMid, presence }` ≈ ~200 bytes/message × 60/sec ≈ **12 KB/sec per subscriber**. This is negligible compared to video streaming.
143143+144144+For extra efficiency:
145145+- Throttle to 30Hz (every other paint frame) — still smooth for visuals
146146+- Only send when amplitude changes by > threshold (skip silent frames)
147147+- Batch multiple fields into a compact array format instead of named object
148148+149149+---
150150+151151+## Implementation Steps
152152+153153+### Phase 1: Local Background (~30 lines in notepat.mjs)
154154+155155+#### Step 1 — Parse `$` Param in notepat's `boot()`
156156+157157+**File:** `system/public/aesthetic.computer/pieces/notepat.mjs` — boot() at line ~1115
158158+159159+Add a new state variable and parse `$code` from `params[0]`:
160160+161161+```js
162162+// New state variables (near line 910 with other state)
163163+let kidlispBackground = null; // e.g. "$roz" → will resolve to KidLisp source
164164+let kidlispBgEnabled = false;
165165+166166+// In boot(), after existing params handling (after line ~1276):
167167+const dollarParam = params.find(p => p.startsWith("$"));
168168+if (dollarParam) {
169169+ kidlispBackground = dollarParam; // e.g. "$roz"
170170+ kidlispBgEnabled = true;
171171+}
172172+```
173173+174174+#### Step 2 — Forward Amplitude to KidLisp Each Frame
175175+176176+**File:** `system/public/aesthetic.computer/pieces/notepat.mjs` — paint() at line ~2509
177177+178178+After the existing amplitude extraction (`const amplitudeRaw = sound.speaker?.amplitudes?.left`), forward it to the global KidLisp instance:
179179+180180+```js
181181+// After line ~2514 (amplitude extraction):
182182+if (kidlispBgEnabled) {
183183+ // Pipe notepat's amplitude into KidLisp's global `amp`
184184+ // Scale: sound.speaker.amplitudes.left is 0..~1, KidLisp expects 0..10
185185+ const scaledAmp = amplitude * 10;
186186+ api.updateKidLispAudio?.({ amp: scaledAmp });
187187+}
188188+```
189189+190190+**Note:** `updateKidLispAudio` is exposed at `disk.mjs` line ~5084 and calls `globalKidLispInstance.updateAudioGlobals()` which sets `this.globalDef.amp`. This is the same path kidlisp.com's music player uses.
191191+192192+The `api` object passed to `paint()` should expose this. If not, we can call it via the disk module's exported `updateKidLispAudio` directly, or add it to the paint API.
193193+194194+#### Step 3 — Render KidLisp Background in paint()
195195+196196+**File:** `system/public/aesthetic.computer/pieces/notepat.mjs` — paint() at line ~2641
197197+198198+Insert the KidLisp background rendering **before** the wipe/mode cascade, so it replaces the background:
199199+200200+```js
201201+// Before the existing mode cascade (line ~2641):
202202+if (kidlispBgEnabled && kidlispBackground) {
203203+ // Render KidLisp piece as full-screen background
204204+ api.kidlisp(0, 0, screen.width, screen.height, kidlispBackground);
205205+ // Skip normal wipe — KidLisp provides the background
206206+} else if (visualizerFullscreen && !recitalMode) {
207207+ wipe(0);
208208+ // ... existing visualizer code
209209+} else if (recitalMode) {
210210+ wipe(0);
211211+ // ... existing recital code
212212+} else {
213213+ wipe(bg);
214214+}
215215+```
216216+217217+The `api.kidlisp()` call handles:
218218+- `$code` → fetch and cache the code via `getCachedCodeMultiLevel()`
219219+- First-frame loading (returns `null` while async fetch is in flight — paint normal bg as fallback)
220220+- Subsequent frames: executes KidLisp and returns a `painting` object
221221+- Persistent caching via `globalKidLispInstance.persistentPaintings`
222222+223223+We just `paste()` the painting to fill the screen, then paint notepat's UI on top.
224224+225225+#### Step 4 — Ensure `api.kidlisp` is Available
226226+227227+**File:** `system/public/aesthetic.computer/lib/disk.mjs` — `$paintApi` object (line ~5092)
228228+229229+Verify that `kidlisp` is already part of the paint API passed to pieces. It's defined as a method in `$paintApi` at line ~6135. If notepat's `paint()` receives the standard `api` object, it should already have `api.kidlisp()`.
230230+231231+**File:** `system/public/aesthetic.computer/lib/disk.mjs` — paint API
232232+233233+Also verify `api.updateKidLispAudio` is exposed. If not, add it:
234234+235235+```js
236236+// In $paintApi or the api object passed to piece paint():
237237+updateKidLispAudio: updateKidLispAudio,
238238+```
239239+240240+#### Step 5 — Handle Loading State
241241+242242+While `$code` is being fetched (first frame), `api.kidlisp()` returns `null`. During this frame, fall through to the normal wipe:
243243+244244+```js
245245+if (kidlispBgEnabled && kidlispBackground) {
246246+ const bgPainting = api.kidlisp(0, 0, screen.width, screen.height, kidlispBackground);
247247+ if (!bgPainting) {
248248+ wipe(bg); // Fallback while loading
249249+ }
250250+ // If bgPainting exists, it was already pasted to the screen by kidlisp()
251251+} else if (visualizerFullscreen && !recitalMode) {
252252+ // ...existing
253253+```
254254+255255+#### Step 6 — HUD Label Update
256256+257257+When a `$code` background is active, update the HUD label to reflect it:
258258+259259+```js
260260+// In boot(), after setting kidlispBgEnabled:
261261+if (kidlispBgEnabled) {
262262+ hud.label(`notepat ${kidlispBackground}`);
263263+}
264264+```
265265+266266+This would show `notepat $roz` in the HUD corner.
267267+268268+#### Step 7 — Toggle KidLisp Background On/Off
269269+270270+Optionally allow toggling the KidLisp background with a keyboard shortcut (e.g., pressing `V` for "visual") or a tap zone, so the user can switch between the KidLisp visual and the normal blue/reactive background during performance.
271271+272272+### Phase 2: Remote Visualizer (~50 lines across 3 files)
273273+274274+#### Step 8 — Fix Session Server Audio Relay
275275+276276+**File:** `session-server/session.mjs` — near line ~1935 (alongside `code` and `slide` handlers)
277277+278278+Add the missing `audio` message handler:
279279+280280+```js
281281+} else if (msg.type === "audio" && msg.content?.codeChannel) {
282282+ const targetChannel = msg.content.codeChannel;
283283+ if (codeChannels[targetChannel]) {
284284+ const packed = pack("audio", msg.content, id);
285285+ for (const subId of codeChannels[targetChannel]) {
286286+ if (subId !== id) connections[subId]?.send(packed);
287287+ }
288288+ }
289289+}
290290+```
291291+292292+This is transient (no state storage, like `slide`) — audio is high-frequency and late joiners just start receiving from the current moment.
293293+294294+#### Step 9 — Notepat Connects as Amplitude Broadcaster
295295+296296+**File:** `system/public/aesthetic.computer/pieces/notepat.mjs` — boot()
297297+298298+When `$code` is active, notepat opens a session-server WebSocket and creates a named channel:
299299+300300+```js
301301+// In boot(), after kidlispBgEnabled is set:
302302+let ampChannel = null;
303303+let ampWs = null;
304304+305305+if (kidlispBgEnabled) {
306306+ // Channel ID: notepat instance identifier
307307+ // Could be random, or derived from a user-chosen name via colon param
308308+ // e.g. notepat $roz:myshow → channel "myshow"
309309+ const colonChannel = colon?.find(c => !wavetypes.includes(c) && !/^\d+$/.test(c));
310310+ ampChannel = colonChannel || `np-${Math.random().toString(36).slice(2, 8)}`;
311311+312312+ const wsUrl = net.sessionServerUrl || "wss://session-server.aesthetic.computer";
313313+ ampWs = new WebSocket(wsUrl);
314314+ ampWs.onopen = () => {
315315+ ampWs.send(JSON.stringify({ type: "code-channel:sub", content: ampChannel }));
316316+ // Send the $code so remote viewers get the KidLisp piece
317317+ ampWs.send(JSON.stringify({
318318+ type: "code",
319319+ content: { codeChannel: ampChannel, piece: kidlispBackground }
320320+ }));
321321+ };
322322+}
323323+```
324324+325325+Display the channel ID in the HUD so the user can share it:
326326+```js
327327+hud.label(`notepat ${kidlispBackground} → ${ampChannel}`);
328328+```
329329+330330+#### Step 10 — Broadcast Amplitude in paint()
331331+332332+**File:** `system/public/aesthetic.computer/pieces/notepat.mjs` — paint()
333333+334334+Throttle to ~30Hz (every other frame) to keep bandwidth light:
335335+336336+```js
337337+let ampBroadcastTick = 0;
338338+339339+// In paint(), after amplitude extraction:
340340+if (ampWs?.readyState === WebSocket.OPEN && ++ampBroadcastTick % 2 === 0) {
341341+ ampWs.send(JSON.stringify({
342342+ type: "audio",
343343+ content: {
344344+ codeChannel: ampChannel,
345345+ amp: amplitude * 10,
346346+ timestamp: Date.now()
347347+ }
348348+ }));
349349+}
350350+```
351351+352352+#### Step 11 — Remote Viewer Connects
353353+354354+A remote machine opens `pj.kidlisp.com/{ampChannel}` — **no changes to pj.html needed**.
355355+356356+pj.html already:
357357+1. Parses channel ID from URL path
358358+2. Connects to session server and subscribes to the channel
359359+3. Receives `code` message → loads KidLisp piece in iframe
360360+4. Receives `audio` message → calls `sendAudioToIframe()` which posts `kidlisp-audio` to the iframe
361361+362362+The only thing that was broken was the session server not relaying audio (Step 8 fixes that).
363363+364364+---
365365+366366+## Amplitude Data Pipelines
367367+368368+### Local (Phase 1)
369369+```
370370+notepat AudioWorklet
371371+ → sound.speaker.poll() (in sim)
372372+ → sound.speaker.amplitudes.left (in paint) [0..~1 range]
373373+ → updateKidLispAudio({ amp: amplitude * 10 })
374374+ → globalKidLispInstance.globalDef.amp [0..10 range]
375375+ → KidLisp piece reads `amp` variable during execution
376376+```
377377+378378+### Remote (Phase 2)
379379+```
380380+notepat AudioWorklet
381381+ → sound.speaker.amplitudes.left (in paint)
382382+ → ampWs.send({ type: "audio", content: { amp: amplitude * 10 } })
383383+ → session-server relays to channel subscribers
384384+ → pj.html receives { type: "audio", content: { amp } }
385385+ → pj.html posts { type: "kidlisp-audio", data: { amp } } to iframe
386386+ → iframe's boot.mjs receives postMessage
387387+ → disk.mjs updateKidLispAudio({ amp })
388388+ → globalKidLispInstance.globalDef.amp
389389+ → KidLisp piece reads `amp`
390390+```
391391+392392+Both paths end at the same `globalDef.amp` — the KidLisp piece code is identical whether it runs locally or remotely.
393393+394394+---
395395+396396+## Visual Compositing Order (in paint)
397397+398398+```
399399+1. KidLisp background renders to full screen (replaces wipe)
400400+2. Waveform bars overlay (if visualizerFullscreen/recitalMode)
401401+3. Top bar piano illustration
402402+4. Note names / chord display
403403+5. Toggle buttons
404404+6. Active key highlights
405405+7. Touch interaction zones
406406+8. .com superscript (if notepat.com)
407407+```
408408+409409+The KidLisp painting writes to the pixel buffer first. All subsequent notepat `ink()`/`line()`/`box()`/`write()` calls paint **on top**, producing the layered effect naturally — no transparency/z-index DOM tricks needed.
410410+411411+---
412412+413413+## Files Changed
414414+415415+| File | Phase | Change |
416416+|------|-------|--------|
417417+| `notepat.mjs` | 1 | `kidlispBackground`/`kidlispBgEnabled` state, `$` param parsing, `api.kidlisp()` in `paint()`, amplitude forwarding |
418418+| `disk.mjs` | 1 | Possibly expose `updateKidLispAudio` on paint API if not already there |
419419+| `session-server/session.mjs` | 2 | Add `audio` message relay handler (~10 lines) |
420420+| `notepat.mjs` | 2 | WebSocket connection, channel creation, amplitude broadcast in `paint()` |
421421+422422+**Estimated scope:** Phase 1 ~30 lines, Phase 2 ~50 lines across 2 files.
423423+424424+---
425425+426426+## Edge Cases & Considerations
427427+428428+1. **Performance:** Heavy KidLisp programs (many shapes, recursion) running at notepat's framerate could cause jank. Mitigation: the `kidlisp()` helper already has persistent painting caching, so static parts don't re-render. Timing expressions (`0.15s`) self-throttle.
429429+430430+2. **Visual modes:** When `paintPictureOverlay` (turtle drawing mode) or `recitalMode` is active, the KidLisp background should probably be disabled/hidden since those modes have their own full-screen visuals. The cascade handles this naturally if we gate the KidLisp rendering with `!paintPictureOverlay && !recitalMode && !visualizerFullscreen`.
431431+432432+3. **Projector mode:** In projector mode, notepat already wipes black and draws minimal UI. KidLisp background could optionally show in projector mode too (for live performance aesthetics). This is a design choice.
433433+434434+4. **Multiple `$` codes:** Could support `notepat $roz $wave` for layered KidLisp backgrounds. The `kidlisp()` helper already supports multiple regions — just call it twice with different source codes and positions.
435435+436436+5. **KidLisp code that uses `wipe`:** If the `$code` itself contains `wipe` commands, the kidlisp() helper handles this via its `shouldReset` logic. This is fine — the KidLisp piece controls its own background clearing within its painting region.
437437+438438+6. **Cleanup:** When notepat piece changes, the global KidLisp instance's `persistentPaintings` are managed by disk.mjs lifecycle. No special cleanup needed in notepat.
439439+440440+---
441441+442442+## Future Extensions
443443+444444+- **Bottom bar on notepat.com:** "Available as Ableton Extension" marquee (already planned separately).
445445+- **KidLisp code editor inside notepat:** Long-press the `$roz` label to open an inline editor for the background code.
446446+- **Beat-reactive globals:** Forward `beat`, `kick`, `bass`, `mid`, `treble` from notepat's audio analysis to KidLisp — same `updateKidLispAudio` call, just add more fields. (Note: notepat currently runs in `performanceMode: "disabled"` which skips heavy analysis in the worklet. Enabling frequency band analysis would require changing this to `"enabled"` or `"bands-only"`.)
447447+- **Shared tempo:** Forward notepat's BPM to KidLisp's `bpm` global for beat-synced visuals.
448448+- **Named channels:** `notepat $roz:liveset` creates channel `liveset` instead of random — shareable, memorable for performances.
449449+- **QR code display:** Show a QR code on notepat's screen (tap a button) linking to `pj.kidlisp.com/{channel}` so audience members can see the visuals on their phones.
450450+- **FF1 cast integration:** `ac-ff1 cast pj.kidlisp.com/{channel}` sends the remote visualizer directly to the FF1 art computer.
451451+- **Multi-notepat jam:** Multiple notepat instances on different devices broadcast to the same channel — the remote visualizer merges their amplitude signals (max, average, or per-player).
452452+- **Audience participation:** Remote viewers on `pj.kidlisp.com/{channel}` could send interaction events back (taps → visual effects), making the visualizer bidirectional.
+438
reports/llm-high-metaphors.md
···11+# Beyond "Documentation": Metaphors for LLM-Driven Development in Aesthetic Computer
22+33+**Date:** 2026-02-08
44+**Source:** [federicopereiro.com/llm-high](https://federicopereiro.com/llm-high/) by Federico Pereiro
55+66+---
77+88+## The Original Framework (Pereiro's "cell")
99+1010+Pereiro proposes that LLM agents are the new high-level language — what JS/Python did to Java, agents now do to all languages. His framework has **two stocks** and **two flows**:
1111+1212+| Role | Name | Nature |
1313+|------|------|--------|
1414+| Stock | **Documentation** | Markdown spec pages — purpose, entities, endpoints, constraints, coding standards |
1515+| Stock | **Implementation** | Codebase + data. Reconstructable from documentation. |
1616+| Flow | **Dialogs** | Agent conversation streams. Inspectable, joinable by humans. |
1717+| Flow | **Tasks** | Discrete work items, nestable, with status tracking. |
1818+1919+He calls the stocks the "tions" (documenta-**tion**, implementa-**tion**) and notes the frontend of the system is now the documentation + agents; the backend is the substrate.
2020+2121+---
2222+2323+## Why "Documentation" Doesn't Fit AC
2424+2525+"Documentation" smells like enterprise software — API references, Confluence pages, Swagger specs. It's passive, bureaucratic, retrospective. AC's culture is none of those things. AC is:
2626+2727+- **A creative coding platform** rooted in Processing's sketchbook model
2828+- **Piece-based** — small interactive programs, not microservices
2929+- **Playful** — commands like `wipe("purple")`, handles like `@jeff`, KidLisp
3030+- **Art-adjacent** — descended from No Paint, gallery shows, whistlegraph
3131+- **Live** — drag-and-drop hot reload, multiplayer channels, performance
3232+3333+The "stock of truth that agents reconstruct from" needs a word that carries creative intent, not compliance.
3434+3535+---
3636+3737+## Alternative Metaphors
3838+3939+### 1. **Score** ★★★★★
4040+4141+A musical score is a set of instructions for performance — not the performance itself. Strudel is already in AC's orbit. A score:
4242+- Prescribes behavior without being the behavior
4343+- Is interpreted by performers (agents) with room for expression
4444+- Can be read, annotated, conducted
4545+- Has a long history in experimental/conceptual art (Fluxus event scores, Sol LeWitt wall drawings, Yoko Ono's *Grapefruit*)
4646+4747+**Vocabulary:** "The score describes the system. Agents perform it. The implementation is what's playing."
4848+4949+### 2. **Sketch / Sketchbook** ★★★★
5050+5151+AC already lives in Processing's sketchbook lineage. A sketch is:
5252+- Intentional but loose — it captures *what you mean* without specifying every pixel
5353+- Iterative — you sketch, erase, redraw
5454+- The natural first artifact of any creative process
5555+- Already in AC's DNA (pieces *are* sketches)
5656+5757+**Risk:** Might feel too informal for the "source of truth" role. Sketches are disposable; the stock shouldn't be.
5858+5959+### 3. **Blueprint** ★★★★
6060+6161+Blueprints are generative specifications — they describe something that will be built by someone else (the agents). Unlike documentation:
6262+- They're forward-looking, not retrospective
6363+- They assume a builder will interpret them
6464+- They carry authority ("build it like this")
6565+- They're visual/spatial, not just textual
6666+6767+**Risk:** Slightly too architectural/industrial for AC's art-kid energy.
6868+6969+### 4. **Script** (theatrical) ★★★★
7070+7171+A script is dialogue + stage directions. Agents are the cast. The human is the director.
7272+- Scripts expect interpretation and staging
7373+- Scripts evolve through rehearsal (agent iteration)
7474+- "Rewrite the script" is a natural phrase for changing system behavior
7575+- There's already precedent: AC pieces are basically scripts for the runtime
7676+7777+**Risk:** Collision with "script" meaning shell/JS script. Could confuse.
7878+7979+### 5. **Spell / Grimoire** ★★★½
8080+8181+Spells are incantations that produce effects. A grimoire is a collected book of spells.
8282+- AC already has a magical/whimsical register (KidLisp, `wand`, `witch`)
8383+- "Cast a spell" = "run a piece"
8484+- The grimoire is the collected knowledge that agents draw from
8585+- Fits the idea that you write words and things *happen*
8686+8787+**Risk:** Too cute? Could alienate people who want to take the system seriously.
8888+8989+### 6. **Map** ★★★½
9090+9191+Maps describe territory without being the territory. Agents navigate by the map.
9292+- "The map is not the territory" — the stock is not the implementation
9393+- Maps can be zoomed, annotated, updated
9494+- You explore a map; you don't just read it
9595+9696+**Risk:** Lacks creative/generative energy. Maps describe what *is*, not what *should be*.
9797+9898+### 7. **Prompt** ★★★
9999+100100+AC already has a prompt (the command line). The entire stock could be seen as "the prompt" — the human's intent expressed in natural language that agents execute against.
101101+- Extremely literal for the LLM era
102102+- Already in AC's vocabulary
103103+- "The prompt" as the system's source of truth has a nice recursion to it
104104+105105+**Risk:** Overloaded term. Everyone uses "prompt" for everything now.
106106+107107+### 8. **Manifesto** ★★★
108108+109109+A manifesto declares intent and principles. Agents carry them out.
110110+- Art-world precedent (Futurism, Fluxus, Dogme 95)
111111+- A manifesto is opinionated and alive — not neutral reference material
112112+- "The manifesto says X, so the system does X"
113113+114114+**Risk:** Manifestos are usually one-off declarations, not living documents.
115115+116116+### 9. **Recipe** ★★★
117117+118118+Recipes are procedural but expressive. They assume a cook (agent) who interprets.
119119+- "A pinch of salt" = room for agent judgment
120120+- Natural for describing flows and processes
121121+- Cookbooks are a nice organizational metaphor
122122+123123+**Risk:** Doesn't scale to system-level architecture. Good for pieces, not for the whole.
124124+125125+### 10. **Seed** ★★½
126126+127127+Seeds grow into implementations. You plant the seed, the agent grows it.
128128+- Organic, generative
129129+- Fits AC's creative ethos
130130+131131+**Risk:** Too vague. What does "editing a seed" mean? Seeds are black boxes.
132132+133133+---
134134+135135+## Recommendation for AC
136136+137137+**Score** is the strongest metaphor. It fits AC's creative identity, has deep roots in experimental art practice, naturally accommodates the relationship between specification and execution, and works at every scale — a single piece has a score, and the whole system has a score.
138138+139139+The four-part framework adapted for AC:
140140+141141+| Pereiro's Term | AC Term | What It Is |
142142+|----------------|---------|------------|
143143+| Documentation | **Score** | Markdown pages describing intent, entities, constraints, style |
144144+| Implementation | **Performance** (or just "the system") | Running code + data — the score being played |
145145+| Dialogs | **Sessions** | Agent conversation streams (already an AC concept) |
146146+| Tasks | **Tasks** (or **Cues**) | Discrete work items — "cues" if you want to stay in the theater/music metaphor |
147147+148148+**"The score describes it. The agents perform it. The system is live."**
149149+150150+---
151151+152152+## Mixed Metaphor Option
153153+154154+You don't have to pick one. AC could use different words at different scales:
155155+156156+- **Score** for the system-level source of truth
157157+- **Sketch** for individual piece-level descriptions
158158+- **Prompt** for ephemeral one-off agent instructions
159159+160160+This mirrors how AC already has layers: the platform, pieces, and the command prompt.
161161+162162+---
163163+164164+## Open Questions from Pereiro (through AC's lens)
165165+166166+### 1. How do we store the score alongside the implementation?
167167+168168+AC already has a precedent: `AGENTS.md`, `llm.md`, `STORY.md`, and `WRITE-A-PIECE.md` live at the repo root — authoritative markdown that agents and humans both read. The `plans/` directory (40+ files) and `docs/` (7 files) hold deeper specs and investigations.
169169+170170+**The problem:** These are scattered and unranked. There's no distinction between "this is the score — canonical, authoritative, what agents should reconstruct the system from" and "this is a plan, a sketch, a conversation artifact." Everything lives in flat directories with no hierarchy.
171171+172172+**Proposal:** A `score/` directory at the repo root, clearly separated:
173173+174174+```
175175+score/
176176+ README.md ← "Score for AA" (the double-A score) — the overture
177177+ pieces.md ← What pieces are, how the lifecycle works (boot/paint/sim/act/beat)
178178+ kidlisp.md ← The language spec
179179+ infrastructure.md ← Session server, Redis, multiplayer, auth, deploy
180180+ social.md ← Handles, chat, moods, channels, sharing
181181+ creative-philosophy.md ← The instrument metaphor, sketchbook model, whistlegraph lineage
182182+ style.md ← Code conventions, naming, file structure
183183+ electron.md ← Desktop app architecture
184184+```
185185+186186+The score is **not** the `plans/` directory. Plans are drafts, investigations, explorations. The score is what survived — the distilled intent. An agent should be able to read `score/` and reconstruct the system's architecture from scratch. `plans/` is the archive of past rehearsals. `reports/` is analysis. The score is the source of truth.
187187+188188+The existing `AGENTS.md` and `llm.md` would migrate into `score/` or become views derived from it.
189189+190190+### 2. How do we use version control?
191191+192192+This is the hardest question. Pereiro identifies four artifacts: **Score** (documentation), **Performance** (implementation), **Sessions** (dialogs), and **Tasks**. How does git handle each?
193193+194194+**What AC does now:**
195195+- **Score & Performance** share one repo, one branch (`main`), no CI/CD gating. Manual deploys. Works because there's one primary author (you).
196196+- **Sessions** (agent dialogs) — partially enter git via `copilot/*` branches and agent-generated files in `plans/`. But most dialog is ephemeral (VS Code chat, Copilot sessions) and never persisted.
197197+- **Tasks** — `TODO.txt` at root, plus ad-hoc tracking in agent conversations. No formal task system in git.
198198+- **Secrets** — sidecar `aesthetic-computer-vault/` repo, gitignored from main, with its own version history. Good pattern.
199199+- **Assets** — CDN-synced, not in git, no manifest. State lives outside version control.
200200+201201+**Tensions exposed by the score metaphor:**
202202+203203+**a) The score should change slower than the performance.**
204204+A musical score gets revised between performances, not during one. In git terms: score changes should be deliberate, reviewed, infrequent commits. Implementation changes can be rapid and agent-driven. But right now they're in the same commit stream with no distinction. You can't `git log score/` and see only the moments the intent changed.
205205+206206+**Possible approach:** Score changes get their own commits with a `score:` prefix. Or score files live in a separate branch/worktree that gets merged into `main` intentionally. Or — simpler — just the discipline of committing score changes separately with clear messages.
207207+208208+**b) Sessions (dialogs) are valuable but enormous.**
209209+Agent conversations contain reasoning, dead ends, discoveries. They're useful for understanding *why* the system is the way it is. But they're too large for git (a single Copilot session can be thousands of lines). They also contain sensitive context.
210210+211211+**Possible approach:** Don't put sessions in git. Archive them as timestamped markdown in a `sessions/` directory that's gitignored locally but backed up elsewhere (like the vault pattern). Or keep only the *summary* of each session — a one-paragraph "what happened and what changed" that becomes a git-tracked changelog entry.
212212+213213+**c) Agent branches need guardrails.**
214214+The `copilot/*` branches create PRs against `main` with no automated tests. As agent volume increases, this becomes a quality risk. The score metaphor helps here: agents should be performing *from* the score, and their output should be validated against it.
215215+216216+**Possible approach:** A lightweight CI step that runs `npm test` and the KidLisp spec suite before any merge to `main`. Not a full pipeline — just the smoke test. The score could also contain explicit acceptance criteria that agents check themselves against.
217217+218218+**d) The monorepo is the orchestra pit.**
219219+80+ top-level directories, 317-line `.gitignore`, node_modules in 15 places, Rust targets, Playdate builds, N64 ROMs. This is the accumulation of every instrument that's ever been tried. Some are active, many are dormant.
220220+221221+**Possible approach:** The score explicitly names what's active and what's archival. A `score/instruments.md` that maps each top-level directory to its status: `system/` = 1st violin (primary), `kidlisp/` = piano (core), `ac-electron/` = cello (active), `kidlisp-n64/` = theremin (experimental, dormant). Agents can then know what to touch and what to leave alone.
222222+223223+**e) Redis state and CDN assets are outside git.**
224224+The performance includes state that git doesn't track: Redis data, CDN-hosted assets, deployed Netlify functions. There's no manifest describing what's deployed.
225225+226226+**Possible approach:** A `score/state.md` that describes what state exists where (Redis schemas, CDN asset structure, environment variables) so that the system is reconstructable even from the git history alone. Not the secrets themselves — just the shape of them.
227227+228228+### 3. MCP as the orchestra's connection to the audience
229229+230230+Pereiro's insight: MCP is like XMLHTTPRequest — it lets agents reach into any external service and pull data/functionality into your canvas. This breaks application silos.
231231+232232+AC already has MCP (`artery/emacs-mcp.mjs`, `.vscode/mcp.json`). The current use is internal — controlling Emacs, managing the dev environment. But the score metaphor extends this further:
233233+234234+- **MCP servers as guest musicians.** External services (Stripe, Auth0, Tezos, Firebase) are guest performers who play from their own parts but are conducted by AC's score. The score describes what AC expects from each service; MCP is the protocol by which agents bring them into the performance.
235235+- **AC as MCP server for others.** AC's pieces could be exposed as MCP tools — letting *other people's* agents play AC's instruments. `notepat` as an MCP tool. `wipe("purple")` callable from any agent. This is the "Scores for Social Software" idea made literal: your score becomes playable by anyone's conductor.
236236+- **The audience participates.** URL-addressable pieces already mean anyone with a browser is in the audience. MCP could mean any agent with the right connection is in the orchestra. The score is the shared page they all read from.
237237+238238+---
239239+240240+## Why "Score" Was Already in AC's Blood
241241+242242+After deeper repo research, it's clear AC has been a score system from the start — maybe before you had the word for it.
243243+244244+### Evidence
245245+246246+1. **The README manifesto:** *"AC's client interface is designed to function like a musical instrument, on which users discover their own memorizable paths in the network of commands and published pieces. As users grow their literacy through play and exploration, they are able to improvise, recombine, and expand their performable repertoire."* — That's describing a score culture, not a documentation culture.
247247+248248+2. **Whistlegraph literally is a score.** A whistlegraph is a drawing made while sound is recorded. It's a multimedia graphic notation — playable, performable, archivable. The GitHub org is `whistlegraph`. The whole project has been organized around this idea since before it had a programming platform attached.
249249+250250+3. **Pieces are scores.** Each `.mjs` file describes a performance that the runtime executes. `boot → paint → sim → act → beat` — this is a temporal structure. `beat` is literally a BPM hook. The lifecycle *is* musical.
251251+252252+4. **`merry` is a setlist.** The `merry` / `merryo` commands chain pieces in timed sequences. That's a concert program. A score for an evening.
253253+254254+5. **The prompt is a conductor's podium.** You stand at it and call cues. `notepat`. `tape notepat`. `share notepat`. Prefix commands are like orchestration marks on a score — they modify how a piece is performed.
255255+256256+6. **KidLisp pieces use `$` embedding** — one piece calling another as a layer. That's instrumentation. Voices in a score.
257257+258258+7. **The mudras.** The `writing/` directory has Indian classical hand gesture notation (`mudras-notation.jpg`, `asamyuta-hasta`). Graphic notation for the body.
259259+260260+8. **The `stage` piece** is a 3D colored-block melody system (A-G). A literal spatial score.
261261+262262+9. **`notepat`** has melody notation and a typing mode. It's a score *writer*.
263263+264264+---
265265+266266+## Scores for Social Software — The Card Deck Submission
267267+268268+Lauren Lee McCarthy (p5.js) and Casey Reas (Processing) invited you to contribute a score to their "Scores for Social Software" card deck. They explicitly reference **Fluxus scores**, **Notations** (Cage/Knowles), **do it** (Obrist), and **Oblique Strategies** (Eno/Schmidt).
269269+270270+This is the exact convergence. Their definition:
271271+> *"We think of social software as any program, system, or tool that structures, mediates, or intervenes in the world around us. We think of a score as a way of choreographing events in time through diagrams and/or text."*
272272+273273+AC *is* social software. And now you're also naming its internal knowledge system a "score." The card, the repo concept, and the platform philosophy are the same thing seen from three distances.
274274+275275+### Title Variations for the SCORE.md / General Concept
276276+277277+**The winner: "Score for AA" (the double-A score)**
278278+279279+AA = Aesthetic Ants. Also:
280280+- **Aesthetic Ants** — the primary expansion. The score is *for* the ants. They follow it.
281281+- **Agentic Ant** — what each one does (it has agency, barely)
282282+- **Aesthetic Agents** — the wider reading: human agents (artists, users) and software agents (ants, LLMs, bots) all follow the same score
283283+- **AA batteries** — small, portable, powers everything
284284+- **AA meetings** — a program you follow one day at a time, one small step
285285+- **aa lava** — Hawaiian: rough, slow-moving, builds landscape gradually (like ants)
286286+- **AA paper size** — a sheet you write scores on
287287+288288+The commit prefix is `ant:` — or could be `aa:` to lean into it.
289289+"Check the double-A" / "follow the double-A" / "the ants are running on double-A."
290290+291291+**Other variations considered:**
292292+293293+2. **Score for a Computer & Its Friends** — gentler, more universal. "Its Friends" = pieces, users, agents, other software.
294294+3. **Score for Computers & People** — austere, Fluxus-register. Echoes "Composition 1960 No. 7" (La Monte Young).
295295+4. **Score for Anyone with a Browser** — populist, true, funny.
296296+5. **Score for a Prompt and All That Follows** — poetic, captures the cascade (type a word → piece loads → people play → things get shared → code gets minted).
297297+6. **Score for Software that Wants to be Touched** — about mobile-first, about intimacy with computation.
298298+7. **Score for Pals** — stripped down. The pals are left undefined. Could be people, pieces, bots, agents.
299299+8. **Piece for Pals** — uses AC's own word. A score is a piece. A piece is a score.
300300+301301+**Dropping "Score for" (shorter, stranger):**
302302+303303+9. **Aesthetic.Computer & Pals** — just the title, no framing word. It *is* a score by context.
304304+10. **& Pals** — the minimal version. On a card, with nothing else, it's a statement: the software is the ampersand — a connector between unnamed friends.
305305+306306+**Completely different register:**
307307+308308+11. **A Score for the Prompt** — focuses on the command-line-as-instrument idea. The prompt is the silence before the first note.
309309+12. **Overture for Social Software** — overture = the opening piece that previews all themes. AC's `prompt` *is* an overture.
310310+13. **Études for the Networked Hand** — étude = study piece for developing technique. AC has actual keyboard études in `writing/`. "Networked Hand" = touchscreen + multiplayer.
311311+14. **Notation for Sharing a Screen** — the screen is the shared page of the score. URL-addressable = everyone reads the same page.
312312+15. **Instructions for Pals** — Fluxus-direct. "Pals" does the warmth work.
313313+314314+### Possible Card Scores (for the Lauren/Casey submission)
315315+316316+These are draft scores in the Fluxus/Oblique Strategies tradition, sized for a 2.75" × 4.75" card. Each one encodes something true about AC's philosophy:
317317+318318+---
319319+320320+**Score #1: "prompt"**
321321+322322+```
323323+Open a blank page.
324324+Type one word.
325325+Wait for whatever happens.
326326+327327+If nothing happens, type another word.
328328+If something happens, invite someone to watch.
329329+If they want to try, hand them the keyboard.
330330+331331+Repeat until the blank page is no longer blank,
332332+or until everyone has gone home.
333333+```
334334+335335+*(This is literally how AC works. The prompt is the score.)*
336336+337337+---
338338+339339+**Score #2: "channel"**
340340+341341+```
342342+Find two screens.
343343+Put the same address in both.
344344+Change something on one.
345345+Watch the other.
346346+347347+Now find three screens.
348348+Now find a classroom.
349349+Now find a stranger.
350350+351351+The channel is open
352352+until you close this window.
353353+```
354354+355355+*(Based on the `channel` feature — live code broadcasting.)*
356356+357357+---
358358+359359+**Score #3: "&"**
360360+361361+```
362362+Write a program.
363363+Give it a name.
364364+Put it at a URL.
365365+366366+Now it is social software.
367367+368368+Anyone who visits the URL
369369+is performing the program with you,
370370+whether you know them or not.
371371+```
372372+373373+*(The radicalism of URL-addressable creative software.)*
374374+375375+---
376376+377377+**Score #4: "wipe"**
378378+379379+```
380380+wipe("purple")
381381+382382+Every program begins
383383+by deciding the color
384384+of nothing.
385385+```
386386+387387+*(AC's `wipe` command — clearing the canvas — is the first line of every piece.)*
388388+389389+---
390390+391391+**Score #5: "pals"**
392392+393393+```
394394+Make software for one person.
395395+That person will show someone.
396396+That someone will want to change something.
397397+Let them.
398398+399399+The software now has two authors.
400400+Neither of them wrote most of it.
401401+All of them are pals.
402402+```
403403+404404+*(The No Paint → AC pipeline: users contributing stamps, becoming co-authors.)*
405405+406406+---
407407+408408+**Score #6: "beat"**
409409+410410+```
411411+boot once.
412412+paint every frame.
413413+act on every touch.
414414+beat at 120 bpm.
415415+416416+( Software has a pulse.
417417+ It was always alive.
418418+ You just weren't counting. )
419419+```
420420+421421+*(AC's piece lifecycle hooks — boot, paint, sim, act, beat — as revelation.)*
422422+423423+---
424424+425425+### For the SCORE.md in the Repo
426426+427427+I'd suggest the repo file should be titled:
428428+429429+# Score for AA
430430+431431+("The double-A score." AA = Aesthetic Ants. Also: Agentic Ant, Aesthetic Agents.)
432432+433433+And the card for Lauren and Casey could be any of the above — but **Score #1 ("prompt")** or **Score #4 ("wipe")** feel strongest. #1 because it *is* exactly how AC works and it's universally legible. #4 because it's the shortest, strangest, and most poetic — and `wipe("purple")` is genuinely beautiful code.
434434+435435+The card title on the back would read:
436436+> **Jeffrey Alan Scudder**
437437+> *prompt* (or *wipe*)
438438+> 2026