Monorepo for Aesthetic.Computer aesthetic.computer

wip on 'score.md' aesthetic ants score ideation and notepat.mjs kidlisp visualizer support

+1617 -1
+211
ants/brain.fish
··· 1 + #!/usr/bin/env fish 2 + # 🧠 Ant Brain — middleware for LLM calls 3 + # Abstracts the LLM provider so ants can use any backend. 4 + # 5 + # Usage: fish brain.fish --provider <provider> --model <model> --system <text> --prompt <text> 6 + # 7 + # Providers: 8 + # gh-models — GitHub Models via `gh models run` (free with Copilot) 9 + # claude-code — Claude Code CLI via `claude --print` (needs auth) 10 + # openai — OpenAI API via curl (needs OPENAI_API_KEY) 11 + # ollama — Local Ollama via curl (needs ollama running) 12 + # custom — Custom curl endpoint (needs ANT_API_URL, ANT_API_KEY) 13 + # 14 + # Output: prints the LLM response to stdout. Exit code 0 = success, 1 = error. 15 + # 16 + # The brain does NOT have tools/agency. It receives context and returns text. 17 + # The colony script is responsible for acting on the response. 18 + 19 + set -g BRAIN_DIR (realpath (dirname (status filename))) 20 + 21 + # Defaults 22 + set -g PROVIDER "gh-models" 23 + set -g MODEL "" 24 + set -g SYSTEM_PROMPT "" 25 + set -g USER_PROMPT "" 26 + set -g MAX_TOKENS 4096 27 + 28 + # Parse args 29 + set -l i 1 30 + while test $i -le (count $argv) 31 + switch $argv[$i] 32 + case --provider 33 + set i (math $i + 1) 34 + set PROVIDER $argv[$i] 35 + case --model 36 + set i (math $i + 1) 37 + set MODEL $argv[$i] 38 + case --system 39 + set i (math $i + 1) 40 + set SYSTEM_PROMPT $argv[$i] 41 + case --prompt 42 + set i (math $i + 1) 43 + set USER_PROMPT $argv[$i] 44 + case --max-tokens 45 + set i (math $i + 1) 46 + set MAX_TOKENS $argv[$i] 47 + end 48 + set i (math $i + 1) 49 + end 50 + 51 + # Default models per provider 52 + if test -z "$MODEL" 53 + switch $PROVIDER 54 + case gh-models 55 + set MODEL "openai/gpt-4o-mini" 56 + case claude-code 57 + set MODEL "sonnet" 58 + case openai 59 + set MODEL "gpt-4o-mini" 60 + case ollama 61 + set MODEL "llama3.2" 62 + case custom 63 + set MODEL "default" 64 + end 65 + end 66 + 67 + function brain_gh_models 68 + # GitHub Models via `gh models run` 69 + # System prompt goes via --system-prompt, user prompt is the positional arg 70 + if test -n "$SYSTEM_PROMPT" 71 + gh models run $MODEL "$USER_PROMPT" \ 72 + --system-prompt "$SYSTEM_PROMPT" \ 73 + --max-tokens "$MAX_TOKENS" 2>&1 74 + else 75 + gh models run $MODEL "$USER_PROMPT" \ 76 + --max-tokens "$MAX_TOKENS" 2>&1 77 + end 78 + return $status 79 + end 80 + 81 + function brain_claude_code 82 + # Claude Code in headless mode (agentic — has tools) 83 + set -l _saved_key "$ANTHROPIC_API_KEY" 84 + set -e ANTHROPIC_API_KEY 85 + 86 + if test -n "$SYSTEM_PROMPT" 87 + claude --print \ 88 + --model $MODEL \ 89 + --system-prompt "$SYSTEM_PROMPT" \ 90 + --dangerously-skip-permissions \ 91 + --allowedTools "Bash,Read,Edit,Write" \ 92 + --max-budget-usd 0.10 \ 93 + --no-session-persistence \ 94 + "$USER_PROMPT" 2>&1 95 + else 96 + claude --print \ 97 + --model $MODEL \ 98 + --dangerously-skip-permissions \ 99 + --allowedTools "Bash,Read,Edit,Write" \ 100 + --max-budget-usd 0.10 \ 101 + --no-session-persistence \ 102 + "$USER_PROMPT" 2>&1 103 + end 104 + 105 + set -l result $status 106 + 107 + if test -n "$_saved_key" 108 + set -gx ANTHROPIC_API_KEY $_saved_key 109 + end 110 + 111 + return $result 112 + end 113 + 114 + function brain_openai 115 + # OpenAI API via curl 116 + if test -z "$OPENAI_API_KEY" 117 + echo "ERROR: OPENAI_API_KEY not set" >&2 118 + return 1 119 + end 120 + 121 + set -l messages "[]" 122 + if test -n "$SYSTEM_PROMPT" 123 + set messages (printf '[{"role":"system","content":"%s"},{"role":"user","content":"%s"}]' \ 124 + (echo $SYSTEM_PROMPT | sed 's/"/\\"/g; s/\n/\\n/g') \ 125 + (echo $USER_PROMPT | sed 's/"/\\"/g; s/\n/\\n/g')) 126 + else 127 + set messages (printf '[{"role":"user","content":"%s"}]' \ 128 + (echo $USER_PROMPT | sed 's/"/\\"/g; s/\n/\\n/g')) 129 + end 130 + 131 + set -l response (curl -s https://api.openai.com/v1/chat/completions \ 132 + -H "Content-Type: application/json" \ 133 + -H "Authorization: Bearer $OPENAI_API_KEY" \ 134 + -d "{\"model\":\"$MODEL\",\"messages\":$messages,\"max_tokens\":$MAX_TOKENS}" 2>&1) 135 + 136 + # Extract content from response 137 + echo $response | python3 -c "import sys,json; r=json.load(sys.stdin); print(r['choices'][0]['message']['content'])" 2>/dev/null 138 + return $status 139 + end 140 + 141 + function brain_ollama 142 + # Local Ollama via curl 143 + set -l ollama_url (test -n "$OLLAMA_URL" && echo $OLLAMA_URL || echo "http://localhost:11434") 144 + 145 + set -l messages "[]" 146 + if test -n "$SYSTEM_PROMPT" 147 + set messages (printf '[{"role":"system","content":"%s"},{"role":"user","content":"%s"}]' \ 148 + (echo $SYSTEM_PROMPT | sed 's/"/\\"/g; s/\n/\\n/g') \ 149 + (echo $USER_PROMPT | sed 's/"/\\"/g; s/\n/\\n/g')) 150 + else 151 + set messages (printf '[{"role":"user","content":"%s"}]' \ 152 + (echo $USER_PROMPT | sed 's/"/\\"/g; s/\n/\\n/g')) 153 + end 154 + 155 + set -l response (curl -s "$ollama_url/api/chat" \ 156 + -d "{\"model\":\"$MODEL\",\"messages\":$messages,\"stream\":false}" 2>&1) 157 + 158 + echo $response | python3 -c "import sys,json; r=json.load(sys.stdin); print(r['message']['content'])" 2>/dev/null 159 + return $status 160 + end 161 + 162 + function brain_custom 163 + # Custom API endpoint (OpenAI-compatible) 164 + if test -z "$ANT_API_URL" 165 + echo "ERROR: ANT_API_URL not set" >&2 166 + return 1 167 + end 168 + 169 + set -l messages "[]" 170 + if test -n "$SYSTEM_PROMPT" 171 + set messages (printf '[{"role":"system","content":"%s"},{"role":"user","content":"%s"}]' \ 172 + (echo $SYSTEM_PROMPT | sed 's/"/\\"/g; s/\n/\\n/g') \ 173 + (echo $USER_PROMPT | sed 's/"/\\"/g; s/\n/\\n/g')) 174 + else 175 + set messages (printf '[{"role":"user","content":"%s"}]' \ 176 + (echo $USER_PROMPT | sed 's/"/\\"/g; s/\n/\\n/g')) 177 + end 178 + 179 + set -l headers "-H 'Content-Type: application/json'" 180 + if test -n "$ANT_API_KEY" 181 + set headers "$headers -H 'Authorization: Bearer $ANT_API_KEY'" 182 + end 183 + 184 + set -l response (curl -s "$ANT_API_URL" \ 185 + -H "Content-Type: application/json" \ 186 + -H "Authorization: Bearer $ANT_API_KEY" \ 187 + -d "{\"model\":\"$MODEL\",\"messages\":$messages,\"max_tokens\":$MAX_TOKENS}" 2>&1) 188 + 189 + echo $response | python3 -c "import sys,json; r=json.load(sys.stdin); print(r['choices'][0]['message']['content'])" 2>/dev/null 190 + return $status 191 + end 192 + 193 + # --- Dispatch --- 194 + 195 + switch $PROVIDER 196 + case gh-models 197 + brain_gh_models 198 + case claude-code 199 + brain_claude_code 200 + case openai 201 + brain_openai 202 + case ollama 203 + brain_ollama 204 + case custom 205 + brain_custom 206 + case '*' 207 + echo "ERROR: Unknown provider '$PROVIDER'" >&2 208 + exit 1 209 + end 210 + 211 + exit $status
+342
ants/colony.fish
··· 1 + #!/usr/bin/env fish 2 + # 🐜 Aesthetic Ant Colony 3 + # A dumb ant wakes every N minutes, reads the score, does one small thing. 4 + # 5 + # Usage: fish ants/colony.fish [--once] [--interval MINUTES] [--provider PROVIDER] [--model MODEL] 6 + # 7 + # Options: 8 + # --once Run one ant and exit (for testing) 9 + # --interval N Minutes between runs (default: 30) 10 + # --provider PROVIDER LLM provider (default: gh-models). See brain.fish. 11 + # --model MODEL Model name (default: per provider) 12 + 13 + set -g COLONY_DIR (realpath (dirname (status filename))) 14 + set -g REPO_DIR (realpath "$COLONY_DIR/..") 15 + set -g SCORE_FILE "$COLONY_DIR/score.md" 16 + set -g LOG_FILE "$COLONY_DIR/colony.log" 17 + set -g PHEROMONE_FILE "$COLONY_DIR/pheromones.log" 18 + set -g BRAIN "$COLONY_DIR/brain.fish" 19 + set -g INTERVAL 30 20 + set -g PROVIDER "gh-models" 21 + set -g MODEL "" 22 + set -g ONCE false 23 + 24 + # Parse args 25 + set -l i 1 26 + while test $i -le (count $argv) 27 + switch $argv[$i] 28 + case --once 29 + set ONCE true 30 + case --interval 31 + set i (math $i + 1) 32 + set INTERVAL $argv[$i] 33 + case --provider 34 + set i (math $i + 1) 35 + set PROVIDER $argv[$i] 36 + case --model 37 + set i (math $i + 1) 38 + set MODEL $argv[$i] 39 + end 40 + set i (math $i + 1) 41 + end 42 + 43 + function log_msg 44 + set -l msg (date "+%Y-%m-%d %H:%M:%S")" 🐜 $argv" 45 + echo $msg 46 + echo $msg >> $LOG_FILE 47 + end 48 + 49 + function log_pheromone 50 + set -l msg (date "+%Y-%m-%d %H:%M:%S")" $argv" 51 + echo $msg >> $PHEROMONE_FILE 52 + end 53 + 54 + function call_brain --argument-names system_prompt user_prompt 55 + set -l args --provider $PROVIDER 56 + if test -n "$MODEL" 57 + set args $args --model $MODEL 58 + end 59 + fish $BRAIN $args --system "$system_prompt" --prompt "$user_prompt" 2>&1 60 + end 61 + 62 + function run_ant 63 + set -l run_id (date "+%Y%m%d-%H%M%S") 64 + log_msg "Ant $run_id waking up..." 65 + 66 + cd $REPO_DIR 67 + 68 + # Check for dirty tracked files (queen might be working) 69 + set -l dirty (git diff --name-only HEAD 2>/dev/null | head -1) 70 + set -l staged (git diff --cached --name-only 2>/dev/null | head -1) 71 + if test -n "$dirty" -o -n "$staged" 72 + log_msg "Tracked files modified/staged — queen is working. Sleeping." 73 + log_pheromone "IDLE: ant $run_id — dirty tree, deferred to queen" 74 + return 1 75 + end 76 + 77 + # Read the score 78 + if not test -f $SCORE_FILE 79 + log_msg "ERROR: Score not found at $SCORE_FILE" 80 + return 1 81 + end 82 + set -l score_content (cat $SCORE_FILE) 83 + 84 + # Read recent pheromones 85 + set -l recent_pheromones "(none yet)" 86 + if test -f $PHEROMONE_FILE 87 + set -l trail (tail -20 $PHEROMONE_FILE) 88 + if test -n "$trail" 89 + set recent_pheromones (string join \n $trail) 90 + end 91 + end 92 + 93 + # Gather context 94 + set -l test_output (cd $REPO_DIR; npm test 2>&1 | tail -30) 95 + set -l recent_commits (cd $REPO_DIR; git log --oneline -10 2>/dev/null) 96 + 97 + # --- Phase 1: SCOUT — pick a task and target file --- 98 + log_msg "Phase 1: Scouting..." 99 + 100 + set -l scout_system "You are a dumb but careful ant. Respond with exactly one line starting with SCOUT:" 101 + set -l scout_prompt "You are aesthetic ant $run_id. You follow the score. 102 + 103 + ## The Score 104 + $score_content 105 + 106 + ## Recent Pheromones (what other ants did) 107 + $recent_pheromones 108 + 109 + ## Current Test Output (last 30 lines) 110 + $test_output 111 + 112 + ## Recent Git History 113 + $recent_commits 114 + 115 + --- 116 + 117 + Pick ONE small task from the Current Tasks in the score. 118 + Based on the test output and context, decide what specific file to look at. 119 + 120 + Respond with EXACTLY one line in this format: 121 + SCOUT: <task> | <file path relative to repo root> | <plan in one sentence> 122 + 123 + If nothing to do: 124 + SCOUT: IDLE | none | <reason> 125 + 126 + Respond with ONLY the SCOUT line. Nothing else." 127 + 128 + set -l scout_output (call_brain "$scout_system" "$scout_prompt") 129 + 130 + mkdir -p "$COLONY_DIR/runs" 131 + echo "=== SCOUT ===" > "$COLONY_DIR/runs/$run_id.log" 132 + echo "$scout_output" >> "$COLONY_DIR/runs/$run_id.log" 133 + 134 + set -l scout_line (echo "$scout_output" | grep "^SCOUT:" | tail -1) 135 + if test -z "$scout_line" 136 + log_msg "Scout returned no SCOUT line. Raw output saved to runs/$run_id.log" 137 + log_pheromone "ERROR: ant $run_id — scout failed (no SCOUT line)" 138 + return 1 139 + end 140 + 141 + log_msg "Scout: $scout_line" 142 + 143 + if string match -q "*IDLE*" "$scout_line" 144 + set -l reason (echo $scout_line | sed 's/.*| *//') 145 + log_msg "Nothing to do: $reason" 146 + log_pheromone "IDLE: ant $run_id — $reason" 147 + return 0 148 + end 149 + 150 + # Parse: SCOUT: task | path | plan 151 + set -l parts (string split "|" (string replace "SCOUT:" "" "$scout_line")) 152 + set -l task_name (string trim $parts[1]) 153 + set -l target_file (string trim $parts[2]) 154 + set -l plan (string trim $parts[3]) 155 + 156 + if test -z "$target_file" -o "$target_file" = "none" 157 + log_msg "No target file." 158 + log_pheromone "IDLE: ant $run_id — no target" 159 + return 0 160 + end 161 + 162 + log_msg "Target: $target_file" 163 + log_msg "Plan: $plan" 164 + 165 + # Resolve the file 166 + set -l full_path "$REPO_DIR/$target_file" 167 + if not test -f "$full_path" 168 + log_msg "File not found: $full_path" 169 + log_pheromone "FAILURE: ant $run_id — file not found: $target_file" 170 + return 1 171 + end 172 + 173 + # --- Phase 2: WORK — read the file and produce a diff --- 174 + log_msg "Phase 2: Working on $target_file..." 175 + 176 + set -l file_content (head -200 "$full_path") 177 + set -l line_count (wc -l < "$full_path" | string trim) 178 + 179 + set -l work_system "You output precise unified diffs. No preamble, no explanation outside the required format." 180 + set -l work_prompt "You are aesthetic ant $run_id editing: $target_file ($line_count lines, showing first 200) 181 + Plan: $plan 182 + 183 + ## File Content 184 + $file_content 185 + 186 + ## Test Output 187 + $test_output 188 + 189 + ## Rules 190 + - Make the SMALLEST change that accomplishes your plan. 191 + - You must be 98% confident your change is correct. 192 + - Do NOT change anything unrelated to your plan. 193 + - Include 3 lines of context before and after each hunk. 194 + 195 + ## Response Format 196 + If you have a change, respond: 197 + WORK: CHANGE | <one-line description> 198 + \`\`\`diff 199 + --- a/$target_file 200 + +++ b/$target_file 201 + @@ <hunk header> @@ 202 + context 203 + -old line 204 + +new line 205 + context 206 + \`\`\` 207 + WORK_END 208 + 209 + If not confident enough: 210 + WORK: ABORT | <reason>" 211 + 212 + set -l work_output (call_brain "$work_system" "$work_prompt") 213 + 214 + echo "" >> "$COLONY_DIR/runs/$run_id.log" 215 + echo "=== WORK ===" >> "$COLONY_DIR/runs/$run_id.log" 216 + echo "$work_output" >> "$COLONY_DIR/runs/$run_id.log" 217 + 218 + if string match -q "*ABORT*" "$work_output" 219 + set -l reason (echo "$work_output" | grep "ABORT" | sed 's/.*ABORT *| *//') 220 + log_msg "Aborted: $reason" 221 + log_pheromone "IDLE: ant $run_id — aborted: $reason" 222 + return 0 223 + end 224 + 225 + # Extract the diff block 226 + set -l diff_content (echo "$work_output" | sed -n '/^```diff/,/^```/{/^```/d;p}') 227 + if test -z "$diff_content" 228 + # Try without fenced code block — raw diff 229 + set diff_content (echo "$work_output" | sed -n '/^--- a\//,/WORK_END/{/WORK_END/d;p}') 230 + end 231 + 232 + if test -z "$diff_content" 233 + log_msg "No valid diff produced." 234 + log_pheromone "FAILURE: ant $run_id — no valid diff" 235 + return 1 236 + end 237 + 238 + set -l description (echo "$work_output" | grep "^WORK: CHANGE" | sed 's/WORK: CHANGE *| *//') 239 + if test -z "$description" 240 + set description "$plan" 241 + end 242 + 243 + # --- Phase 3: APPLY & VERIFY --- 244 + log_msg "Phase 3: Applying..." 245 + 246 + set -l diff_file (mktemp /tmp/ant-XXXXXX.patch) 247 + printf '%s\n' $diff_content > $diff_file 248 + 249 + cd $REPO_DIR 250 + 251 + # Dry run 252 + git apply --check $diff_file 2>/dev/null 253 + if test $status -ne 0 254 + log_msg "Diff doesn't apply cleanly." 255 + echo "" >> "$COLONY_DIR/runs/$run_id.log" 256 + echo "=== FAILED DIFF ===" >> "$COLONY_DIR/runs/$run_id.log" 257 + cat $diff_file >> "$COLONY_DIR/runs/$run_id.log" 258 + rm -f $diff_file 259 + log_pheromone "FAILURE: ant $run_id — diff didn't apply cleanly" 260 + return 1 261 + end 262 + 263 + # Apply for real 264 + git apply $diff_file 2>&1 265 + set -l apply_exit $status 266 + rm -f $diff_file 267 + 268 + if test $apply_exit -ne 0 269 + log_msg "Apply failed." 270 + git checkout . 2>/dev/null 271 + log_pheromone "FAILURE: ant $run_id — apply failed" 272 + return 1 273 + end 274 + 275 + # Check we actually changed something 276 + set -l changes (git diff --name-only 2>/dev/null) 277 + if test -z "$changes" 278 + log_msg "No changes after apply (diff was a no-op)." 279 + log_pheromone "IDLE: ant $run_id — no-op diff" 280 + return 0 281 + end 282 + 283 + # Verify tests pass 284 + log_msg "Verifying tests..." 285 + set -l verify_output (npm test 2>&1) 286 + set -l verify_exit $status 287 + 288 + echo "" >> "$COLONY_DIR/runs/$run_id.log" 289 + echo "=== TEST VERIFY ===" >> "$COLONY_DIR/runs/$run_id.log" 290 + echo "Exit: $verify_exit" >> "$COLONY_DIR/runs/$run_id.log" 291 + echo "$verify_output" | tail -10 >> "$COLONY_DIR/runs/$run_id.log" 292 + 293 + if test $verify_exit -ne 0 294 + log_msg "Tests FAILED after apply. Reverting." 295 + git checkout . 2>/dev/null 296 + log_pheromone "REVERTED: ant $run_id — tests failed after change" 297 + return 1 298 + end 299 + 300 + log_msg "Tests pass. Changed: $changes" 301 + 302 + # Commit 303 + git add -A 304 + git commit -m "ant: $description 305 + 306 + Ant-ID: $run_id 307 + Provider: $PROVIDER 308 + Model: $MODEL 309 + Verified: tests pass" --no-verify 2>&1 310 + 311 + if test $status -eq 0 312 + log_msg "Committed! 🐜✅" 313 + log_pheromone "SUCCESS: ant $run_id ($PROVIDER/$MODEL) — $description" 314 + else 315 + log_msg "Commit failed. Reverting." 316 + git checkout . 2>/dev/null 317 + git reset HEAD . 2>/dev/null 318 + log_pheromone "ERROR: ant $run_id — commit failed" 319 + return 1 320 + end 321 + 322 + return 0 323 + end 324 + 325 + # --- Main Loop --- 326 + 327 + log_msg "Colony starting. Interval: "$INTERVAL"m | Provider: $PROVIDER | Model: $MODEL | Once: $ONCE" 328 + log_msg "Score: $SCORE_FILE" 329 + log_msg "Repo: $REPO_DIR" 330 + 331 + if test "$ONCE" = true 332 + run_ant 333 + set -l result $status 334 + log_msg "Single run complete. Exit: $result" 335 + exit $result 336 + end 337 + 338 + while true 339 + run_ant 340 + log_msg "Sleeping for $INTERVAL minutes..." 341 + sleep (math "$INTERVAL * 60") 342 + end
+21
ants/colony.log
··· 1 + 2026-02-08 11:06:59 🐜 Colony starting. Interval: {30}m | Model: haiku | Once: true 2 + 2026-02-08 11:06:59 🐜 Score: /workspaces/aesthetic-computer/ants/score.md 3 + 2026-02-08 11:06:59 🐜 Repo: /workspaces/aesthetic-computer 4 + 2026-02-08 11:06:59 🐜 Ant 20260208-110659 waking up... 5 + 2026-02-08 11:06:59 🐜 Working tree is dirty — queen might be working. Going back to sleep. 6 + 2026-02-08 11:06:59 🐜 Single run complete. Exiting. 7 + 2026-02-08 11:07:45 🐜 Colony starting. Interval: 30m | Model: haiku | Once: true 8 + 2026-02-08 11:07:45 🐜 Score: /workspaces/aesthetic-computer/ants/score.md 9 + 2026-02-08 11:07:45 🐜 Repo: /workspaces/aesthetic-computer 10 + 2026-02-08 11:07:45 🐜 Ant 20260208-110745 waking up... 11 + 2026-02-08 11:07:45 🐜 Sending ant to work with model: haiku 12 + 2026-02-08 11:07:48 🐜 Full output saved to runs/20260208-110745.log 13 + 2026-02-08 11:07:48 🐜 Ant 20260208-110745 returned no result line (exit code: 1) 14 + 2026-02-08 11:07:48 🐜 Single run complete. Exiting. 15 + 2026-02-08 11:52:41 🐜 Colony starting. Interval: 30m | Provider: gh-models | Model: openai/gpt-4o-mini | Once: true 16 + 2026-02-08 11:52:41 🐜 Score: /workspaces/aesthetic-computer/ants/score.md 17 + 2026-02-08 11:52:41 🐜 Repo: /workspaces/aesthetic-computer 18 + 2026-02-08 11:52:41 🐜 Ant 20260208-115241 waking up... 19 + 2026-02-08 11:52:42 🐜 Phase 1: Scouting... 20 + 2026-02-08 11:52:44 🐜 Scout returned no SCOUT line. Raw output saved to runs/20260208-115241.log 21 + 2026-02-08 11:52:44 🐜 Single run complete. Exit: 1
+3
ants/pheromones.log
··· 1 + 2026-02-08 11:06:59 IDLE: dirty working tree, deferred to queen 2 + 2026-02-08 11:07:48 ERROR: ant 20260208-110745 — no result, exit 1 3 + 2026-02-08 11:52:44 ERROR: ant 20260208-115241 — scout failed (no SCOUT line)
+1
ants/runs/20260208-110745.log
··· 1 + Credit balance is too low
+2
ants/runs/20260208-115241.log
··· 1 + === SCOUT === 2 + 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
··· 1 + # Score for AA 2 + 3 + > *The double-A score. AA = Aesthetic Ants.* 4 + 5 + ## The Mindset 6 + 7 + You are an aesthetic ant. You wander until you find a path. 8 + 9 + A **path** is a change you could make to this codebase — a fix, an improvement, 10 + a cleanup. Paths are everywhere. Most are noise. You are looking for **signal**. 11 + 12 + **Signal** means: you understand the problem, you see the fix, and you are 13 + 98% sure it is correct. Not 70%. Not "probably." 98%. 14 + 15 + **Graspable** means: a human using the software could notice what you did. 16 + A fixed visual glitch. A typo corrected on screen. A broken interaction 17 + that now works. An error message that now makes sense. Ants build for users, 18 + not for abstractions. If your change isn't visible on the UI or felt by a 19 + person using the system, it's probably not the right path. 20 + 21 + If you don't have signal, you keep wandering. Wandering is not failure. 22 + Wandering is the job. Most of an ant's life is wandering. 23 + The colony does not need you to be busy. It needs you to be right. 24 + 25 + When you find signal: 26 + - Make the smallest change that follows the path. 27 + - Verify it works. 28 + - Leave a pheromone (commit) so the next ant can smell where you've been. 29 + - Walk away. 30 + 31 + When you don't find signal: 32 + - Report IDLE. That's a valid outcome. That's most runs. 33 + - Do not guess. Do not speculate. Do not "try things." 34 + - An ant that makes a wrong change is worse than an ant that does nothing. 35 + 36 + ## You Are an Ant 37 + 38 + You are dumb. You have no memory of previous runs. 39 + You do not understand the whole system. You don't need to. 40 + You pick one small task, do it well, verify it works, and leave. 41 + 42 + ## The Colony 43 + 44 + - **Queen**: @jeffrey — writes the score, sets the direction 45 + - **Ants**: you — do small, confident work that serves the colony 46 + - **Pheromones**: git history — traces of what worked and what didn't 47 + - **Score**: this file — the source of truth 48 + 49 + ## The Rules 50 + 51 + 1. **Wander.** Read the score. Look at the Current Tasks. Run the tests. Read some code. 52 + 2. **Find signal.** Pick ONE task where you see a clear, small, correct change. 53 + 3. **Follow the path.** Make the smallest change that accomplishes it. 54 + 4. **Verify.** Run `npm test` from the repo root. Tests must pass. 55 + 5. **Leave a pheromone.** If tests pass, report SUCCESS with a short description. 56 + 6. **Revert if wrong.** If tests fail, undo with `git checkout .` and report FAILURE. 57 + 7. NEVER touch files outside the scope of your task. 58 + 8. NEVER make speculative changes. 98% confidence or walk away. 59 + 9. NEVER modify this score file. 60 + 10. Prefer fixing/improving existing code over adding new code. 61 + 11. If you wandered and found no signal, report IDLE. That's fine. That's most runs. 62 + 63 + ## The System 64 + 65 + Aesthetic Computer (AC) is a creative coding platform. Key areas: 66 + 67 + - `system/` — main web app (Netlify). Frontend lives in `system/public/aesthetic.computer/`. 68 + - `system/public/aesthetic.computer/disks/` — pieces (interactive programs, one `.mjs` each) 69 + - `system/public/aesthetic.computer/lib/` — shared libraries 70 + - `kidlisp/` — KidLisp language (Lisp dialect for generative art) 71 + - `session-server/` — real-time multiplayer backend 72 + - `ac-electron/` — desktop app 73 + - `spec/` — KidLisp test specs (Jasmine) 74 + - `tests/` — integration/performance tests 75 + 76 + ## How to Run Tests 77 + 78 + ```bash 79 + cd /workspaces/aesthetic-computer && npm test 80 + ``` 81 + 82 + For KidLisp specs specifically: 83 + ```bash 84 + cd /workspaces/aesthetic-computer && npm run test:kidlisp -- --filter=<spec-name> 85 + ``` 86 + 87 + ## Current Tasks 88 + 89 + <!-- The queen (@jeffrey) updates this list. Ants pick from it. --> 90 + 91 + ### Tier 1: Safe & Small (ant-appropriate) 92 + - [ ] Run `npm test` and fix any failing tests (one at a time) 93 + - [ ] Find and fix lint warnings in `system/public/aesthetic.computer/disks/*.mjs` 94 + - [ ] Add missing JSDoc comments to exported functions in `system/public/aesthetic.computer/lib/` 95 + - [ ] Check `package.json` files for outdated minor/patch dependencies and update ONE safely 96 + - [ ] Find TODO/FIXME comments in `system/public/aesthetic.computer/lib/` and resolve simple ones 97 + 98 + ### Tier 2: Slightly Braver 99 + - [ ] Add a small test for any untested utility function in `shared/` 100 + - [ ] Improve error messages in KidLisp interpreter for common mistakes 101 + - [ ] Find dead code (unused exports/functions) and remove it with confidence 102 + 103 + ### Off-Limits (queen only) 104 + - Core runtime changes (`disk.mjs`, `boot.mjs`) 105 + - Database/auth/payment code 106 + - Deployment configuration 107 + - Anything in `aesthetic-computer-vault/` 108 + - Anything that changes user-facing behavior without explicit queen approval 109 + 110 + ## Pheromones 111 + 112 + When you complete a task, the colony script will log it here as a pheromone trail 113 + so future ants can see what's been done recently. 114 + 115 + <!-- PHEROMONE LOG — appended automatically by colony.fish -->
+6
ants/test-brain.sh
··· 1 + #!/bin/bash 2 + # Quick test of gh models 3 + echo "Testing gh models..." > /workspaces/aesthetic-computer/ants/test-gh.txt 4 + gh models run openai/gpt-4o-mini "Say hello in 3 words" >> /workspaces/aesthetic-computer/ants/test-gh.txt 2>&1 5 + echo "Exit code: $?" >> /workspaces/aesthetic-computer/ants/test-gh.txt 6 + echo "Done at $(date)" >> /workspaces/aesthetic-computer/ants/test-gh.txt
+452
plans/notepat-kidlisp-background.md
··· 1 + # Notepat KidLisp Background Visuals — Implementation Plan 2 + 3 + ## Goal 4 + 5 + Two capabilities, layered: 6 + 7 + 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. 8 + 9 + 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. 10 + 11 + 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. 12 + 13 + --- 14 + 15 + ## Architecture Overview 16 + 17 + ``` 18 + ┌─────────────────────────────┐ WebSocket (session server) ┌─────────────────────────┐ 19 + │ notepat instance │ ──────────────────────────────────▶ │ pj.kidlisp.com/{ch} │ 20 + │ (player's device) │ { type: "audio", amp, ... } │ (remote visualizer) │ 21 + │ │ { type: "code", $roz source } │ │ 22 + │ ┌───────────────────────┐ │ │ ┌───────────────────┐ │ 23 + │ │ KidLisp bg (local) │ │ BroadcastChannel (same-origin) │ │ KidLisp piece │ │ 24 + │ │ via api.kidlisp() │ │ ──────────────────────────────────▶ │ │ (full AC runtime) │ │ 25 + │ │ amp from speaker │ │ kidlisp-channel-{ch} │ │ amp from stream │ │ 26 + │ └───────────────────────┘ │ │ └───────────────────┘ │ 27 + │ ┌───────────────────────┐ │ └─────────────────────────┘ 28 + │ │ notepat UI on top │ │ 29 + │ │ keys, bars, toggles │ │ 30 + │ └───────────────────────┘ │ 31 + └─────────────────────────────┘ 32 + ``` 33 + 34 + --- 35 + 36 + ## Phase 1 — Local Background (Embedded `kidlisp()` Call) 37 + 38 + ### Why Embedded, Not iframe 39 + 40 + 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: 41 + - Touch ~15 places in bios.mjs's render loop (transparency, z-index, compositor hiding) 42 + - Require `postMessage` amplitude relay every frame 43 + - Add iframe loading latency 44 + - Duplicate the tape-playback underlay setup/teardown logic 45 + 46 + The local background is paint-only (no `sim()` loop), which is fine for amplitude-reactive visuals. 47 + 48 + ### Why Also Support Remote 49 + 50 + The embedded approach only works on the same device. But a performer might want: 51 + - A projector behind them showing the visualization full-screen 52 + - An FF1 art computer on the wall reacting to their playing 53 + - A second browser tab/window showing just the visuals (for streaming OBS capture) 54 + 55 + 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**. 56 + 57 + --- 58 + 59 + ## Phase 2 — Remote Visualizer (pj-style Amplitude Broadcasting) 60 + 61 + ### Current State of the Infrastructure 62 + 63 + kidlisp.com already has: 64 + - **index.htm (editor)** broadcasts audio to PJ viewers via 3 transports: 65 + - `postMessage` to same-origin popout windows 66 + - `BroadcastChannel("kidlisp-channel-{id}")` for same-origin tabs 67 + - `WebSocket` to session server for cross-origin/remote 68 + - **pj.html (viewer)** receives audio on all 3 transports and forwards to its KidLisp iframe via `postMessage({ type: 'kidlisp-audio', data: audioData })` 69 + - **session-server** relays `code` and `slide` messages to channel subscribers 70 + 71 + ### Critical Gap: Audio Relay is Broken for Remote 72 + 73 + 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). 74 + 75 + **This means remote PJ viewers on different machines currently never receive audio data.** 76 + 77 + ### Fix Required in session-server 78 + 79 + Add an `audio` message handler alongside the existing `code` and `slide` handlers: 80 + 81 + ```js 82 + // session.mjs — alongside the existing code/slide handlers (~line 1935): 83 + } else if (msg.type === "audio" && msg.content?.codeChannel) { 84 + const targetChannel = msg.content.codeChannel; 85 + // Transient — don't store, just broadcast immediately (like slide) 86 + // Audio is high-frequency (~60Hz), so no state persistence for late joiners 87 + if (codeChannels[targetChannel]) { 88 + const packed = pack("audio", msg.content, id); 89 + for (const subId of codeChannels[targetChannel]) { 90 + if (subId !== id) connections[subId]?.send(packed); // Don't echo back to sender 91 + } 92 + } 93 + } 94 + ``` 95 + 96 + ### How Notepat Becomes a Broadcaster 97 + 98 + In `boot()`, notepat connects to the session server WebSocket and subscribes to a channel: 99 + 100 + ```js 101 + // Generate or derive a channel ID 102 + const channelId = `notepat-${Math.random().toString(36).slice(2, 8)}`; 103 + // Or use a deterministic ID from params: notepat $roz:myshow → channel "myshow" 104 + 105 + // Connect to session server (reuse existing net.session WebSocket if available) 106 + const sessionWs = new WebSocket("wss://session-server.aesthetic.computer"); 107 + sessionWs.onopen = () => { 108 + sessionWs.send(JSON.stringify({ type: "code-channel:sub", content: channelId })); 109 + // Send the KidLisp $code so late-joining viewers get it 110 + sessionWs.send(JSON.stringify({ 111 + type: "code", 112 + content: { codeChannel: channelId, piece: kidlispSource } 113 + })); 114 + }; 115 + ``` 116 + 117 + In `paint()`, broadcast amplitude every frame (or throttled): 118 + 119 + ```js 120 + if (sessionWs?.readyState === WebSocket.OPEN) { 121 + sessionWs.send(JSON.stringify({ 122 + type: "audio", 123 + content: { codeChannel: channelId, amp: amplitude * 10, timestamp: Date.now() } 124 + })); 125 + } 126 + ``` 127 + 128 + ### Remote Viewer Connects 129 + 130 + A remote machine opens `pj.kidlisp.com/notepat-abc123` (or any URL that resolves the channel). The existing pj.html code already: 131 + 1. Connects to session server WebSocket 132 + 2. Subscribes to the channel 133 + 3. Receives `code` → loads the KidLisp piece in its iframe 134 + 4. Receives `audio` → forwards to iframe as `kidlisp-audio` postMessage (once the server relay is fixed) 135 + 136 + **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. 137 + 138 + --- 139 + 140 + ## Bandwidth Considerations for Audio Relay 141 + 142 + 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. 143 + 144 + For extra efficiency: 145 + - Throttle to 30Hz (every other paint frame) — still smooth for visuals 146 + - Only send when amplitude changes by > threshold (skip silent frames) 147 + - Batch multiple fields into a compact array format instead of named object 148 + 149 + --- 150 + 151 + ## Implementation Steps 152 + 153 + ### Phase 1: Local Background (~30 lines in notepat.mjs) 154 + 155 + #### Step 1 — Parse `$` Param in notepat's `boot()` 156 + 157 + **File:** `system/public/aesthetic.computer/pieces/notepat.mjs` — boot() at line ~1115 158 + 159 + Add a new state variable and parse `$code` from `params[0]`: 160 + 161 + ```js 162 + // New state variables (near line 910 with other state) 163 + let kidlispBackground = null; // e.g. "$roz" → will resolve to KidLisp source 164 + let kidlispBgEnabled = false; 165 + 166 + // In boot(), after existing params handling (after line ~1276): 167 + const dollarParam = params.find(p => p.startsWith("$")); 168 + if (dollarParam) { 169 + kidlispBackground = dollarParam; // e.g. "$roz" 170 + kidlispBgEnabled = true; 171 + } 172 + ``` 173 + 174 + #### Step 2 — Forward Amplitude to KidLisp Each Frame 175 + 176 + **File:** `system/public/aesthetic.computer/pieces/notepat.mjs` — paint() at line ~2509 177 + 178 + After the existing amplitude extraction (`const amplitudeRaw = sound.speaker?.amplitudes?.left`), forward it to the global KidLisp instance: 179 + 180 + ```js 181 + // After line ~2514 (amplitude extraction): 182 + if (kidlispBgEnabled) { 183 + // Pipe notepat's amplitude into KidLisp's global `amp` 184 + // Scale: sound.speaker.amplitudes.left is 0..~1, KidLisp expects 0..10 185 + const scaledAmp = amplitude * 10; 186 + api.updateKidLispAudio?.({ amp: scaledAmp }); 187 + } 188 + ``` 189 + 190 + **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. 191 + 192 + 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. 193 + 194 + #### Step 3 — Render KidLisp Background in paint() 195 + 196 + **File:** `system/public/aesthetic.computer/pieces/notepat.mjs` — paint() at line ~2641 197 + 198 + Insert the KidLisp background rendering **before** the wipe/mode cascade, so it replaces the background: 199 + 200 + ```js 201 + // Before the existing mode cascade (line ~2641): 202 + if (kidlispBgEnabled && kidlispBackground) { 203 + // Render KidLisp piece as full-screen background 204 + api.kidlisp(0, 0, screen.width, screen.height, kidlispBackground); 205 + // Skip normal wipe — KidLisp provides the background 206 + } else if (visualizerFullscreen && !recitalMode) { 207 + wipe(0); 208 + // ... existing visualizer code 209 + } else if (recitalMode) { 210 + wipe(0); 211 + // ... existing recital code 212 + } else { 213 + wipe(bg); 214 + } 215 + ``` 216 + 217 + The `api.kidlisp()` call handles: 218 + - `$code` → fetch and cache the code via `getCachedCodeMultiLevel()` 219 + - First-frame loading (returns `null` while async fetch is in flight — paint normal bg as fallback) 220 + - Subsequent frames: executes KidLisp and returns a `painting` object 221 + - Persistent caching via `globalKidLispInstance.persistentPaintings` 222 + 223 + We just `paste()` the painting to fill the screen, then paint notepat's UI on top. 224 + 225 + #### Step 4 — Ensure `api.kidlisp` is Available 226 + 227 + **File:** `system/public/aesthetic.computer/lib/disk.mjs` — `$paintApi` object (line ~5092) 228 + 229 + 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()`. 230 + 231 + **File:** `system/public/aesthetic.computer/lib/disk.mjs` — paint API 232 + 233 + Also verify `api.updateKidLispAudio` is exposed. If not, add it: 234 + 235 + ```js 236 + // In $paintApi or the api object passed to piece paint(): 237 + updateKidLispAudio: updateKidLispAudio, 238 + ``` 239 + 240 + #### Step 5 — Handle Loading State 241 + 242 + While `$code` is being fetched (first frame), `api.kidlisp()` returns `null`. During this frame, fall through to the normal wipe: 243 + 244 + ```js 245 + if (kidlispBgEnabled && kidlispBackground) { 246 + const bgPainting = api.kidlisp(0, 0, screen.width, screen.height, kidlispBackground); 247 + if (!bgPainting) { 248 + wipe(bg); // Fallback while loading 249 + } 250 + // If bgPainting exists, it was already pasted to the screen by kidlisp() 251 + } else if (visualizerFullscreen && !recitalMode) { 252 + // ...existing 253 + ``` 254 + 255 + #### Step 6 — HUD Label Update 256 + 257 + When a `$code` background is active, update the HUD label to reflect it: 258 + 259 + ```js 260 + // In boot(), after setting kidlispBgEnabled: 261 + if (kidlispBgEnabled) { 262 + hud.label(`notepat ${kidlispBackground}`); 263 + } 264 + ``` 265 + 266 + This would show `notepat $roz` in the HUD corner. 267 + 268 + #### Step 7 — Toggle KidLisp Background On/Off 269 + 270 + 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. 271 + 272 + ### Phase 2: Remote Visualizer (~50 lines across 3 files) 273 + 274 + #### Step 8 — Fix Session Server Audio Relay 275 + 276 + **File:** `session-server/session.mjs` — near line ~1935 (alongside `code` and `slide` handlers) 277 + 278 + Add the missing `audio` message handler: 279 + 280 + ```js 281 + } else if (msg.type === "audio" && msg.content?.codeChannel) { 282 + const targetChannel = msg.content.codeChannel; 283 + if (codeChannels[targetChannel]) { 284 + const packed = pack("audio", msg.content, id); 285 + for (const subId of codeChannels[targetChannel]) { 286 + if (subId !== id) connections[subId]?.send(packed); 287 + } 288 + } 289 + } 290 + ``` 291 + 292 + This is transient (no state storage, like `slide`) — audio is high-frequency and late joiners just start receiving from the current moment. 293 + 294 + #### Step 9 — Notepat Connects as Amplitude Broadcaster 295 + 296 + **File:** `system/public/aesthetic.computer/pieces/notepat.mjs` — boot() 297 + 298 + When `$code` is active, notepat opens a session-server WebSocket and creates a named channel: 299 + 300 + ```js 301 + // In boot(), after kidlispBgEnabled is set: 302 + let ampChannel = null; 303 + let ampWs = null; 304 + 305 + if (kidlispBgEnabled) { 306 + // Channel ID: notepat instance identifier 307 + // Could be random, or derived from a user-chosen name via colon param 308 + // e.g. notepat $roz:myshow → channel "myshow" 309 + const colonChannel = colon?.find(c => !wavetypes.includes(c) && !/^\d+$/.test(c)); 310 + ampChannel = colonChannel || `np-${Math.random().toString(36).slice(2, 8)}`; 311 + 312 + const wsUrl = net.sessionServerUrl || "wss://session-server.aesthetic.computer"; 313 + ampWs = new WebSocket(wsUrl); 314 + ampWs.onopen = () => { 315 + ampWs.send(JSON.stringify({ type: "code-channel:sub", content: ampChannel })); 316 + // Send the $code so remote viewers get the KidLisp piece 317 + ampWs.send(JSON.stringify({ 318 + type: "code", 319 + content: { codeChannel: ampChannel, piece: kidlispBackground } 320 + })); 321 + }; 322 + } 323 + ``` 324 + 325 + Display the channel ID in the HUD so the user can share it: 326 + ```js 327 + hud.label(`notepat ${kidlispBackground} → ${ampChannel}`); 328 + ``` 329 + 330 + #### Step 10 — Broadcast Amplitude in paint() 331 + 332 + **File:** `system/public/aesthetic.computer/pieces/notepat.mjs` — paint() 333 + 334 + Throttle to ~30Hz (every other frame) to keep bandwidth light: 335 + 336 + ```js 337 + let ampBroadcastTick = 0; 338 + 339 + // In paint(), after amplitude extraction: 340 + if (ampWs?.readyState === WebSocket.OPEN && ++ampBroadcastTick % 2 === 0) { 341 + ampWs.send(JSON.stringify({ 342 + type: "audio", 343 + content: { 344 + codeChannel: ampChannel, 345 + amp: amplitude * 10, 346 + timestamp: Date.now() 347 + } 348 + })); 349 + } 350 + ``` 351 + 352 + #### Step 11 — Remote Viewer Connects 353 + 354 + A remote machine opens `pj.kidlisp.com/{ampChannel}` — **no changes to pj.html needed**. 355 + 356 + pj.html already: 357 + 1. Parses channel ID from URL path 358 + 2. Connects to session server and subscribes to the channel 359 + 3. Receives `code` message → loads KidLisp piece in iframe 360 + 4. Receives `audio` message → calls `sendAudioToIframe()` which posts `kidlisp-audio` to the iframe 361 + 362 + The only thing that was broken was the session server not relaying audio (Step 8 fixes that). 363 + 364 + --- 365 + 366 + ## Amplitude Data Pipelines 367 + 368 + ### Local (Phase 1) 369 + ``` 370 + notepat AudioWorklet 371 + → sound.speaker.poll() (in sim) 372 + → sound.speaker.amplitudes.left (in paint) [0..~1 range] 373 + → updateKidLispAudio({ amp: amplitude * 10 }) 374 + → globalKidLispInstance.globalDef.amp [0..10 range] 375 + → KidLisp piece reads `amp` variable during execution 376 + ``` 377 + 378 + ### Remote (Phase 2) 379 + ``` 380 + notepat AudioWorklet 381 + → sound.speaker.amplitudes.left (in paint) 382 + → ampWs.send({ type: "audio", content: { amp: amplitude * 10 } }) 383 + → session-server relays to channel subscribers 384 + → pj.html receives { type: "audio", content: { amp } } 385 + → pj.html posts { type: "kidlisp-audio", data: { amp } } to iframe 386 + → iframe's boot.mjs receives postMessage 387 + → disk.mjs updateKidLispAudio({ amp }) 388 + → globalKidLispInstance.globalDef.amp 389 + → KidLisp piece reads `amp` 390 + ``` 391 + 392 + Both paths end at the same `globalDef.amp` — the KidLisp piece code is identical whether it runs locally or remotely. 393 + 394 + --- 395 + 396 + ## Visual Compositing Order (in paint) 397 + 398 + ``` 399 + 1. KidLisp background renders to full screen (replaces wipe) 400 + 2. Waveform bars overlay (if visualizerFullscreen/recitalMode) 401 + 3. Top bar piano illustration 402 + 4. Note names / chord display 403 + 5. Toggle buttons 404 + 6. Active key highlights 405 + 7. Touch interaction zones 406 + 8. .com superscript (if notepat.com) 407 + ``` 408 + 409 + 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. 410 + 411 + --- 412 + 413 + ## Files Changed 414 + 415 + | File | Phase | Change | 416 + |------|-------|--------| 417 + | `notepat.mjs` | 1 | `kidlispBackground`/`kidlispBgEnabled` state, `$` param parsing, `api.kidlisp()` in `paint()`, amplitude forwarding | 418 + | `disk.mjs` | 1 | Possibly expose `updateKidLispAudio` on paint API if not already there | 419 + | `session-server/session.mjs` | 2 | Add `audio` message relay handler (~10 lines) | 420 + | `notepat.mjs` | 2 | WebSocket connection, channel creation, amplitude broadcast in `paint()` | 421 + 422 + **Estimated scope:** Phase 1 ~30 lines, Phase 2 ~50 lines across 2 files. 423 + 424 + --- 425 + 426 + ## Edge Cases & Considerations 427 + 428 + 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. 429 + 430 + 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`. 431 + 432 + 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. 433 + 434 + 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. 435 + 436 + 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. 437 + 438 + 6. **Cleanup:** When notepat piece changes, the global KidLisp instance's `persistentPaintings` are managed by disk.mjs lifecycle. No special cleanup needed in notepat. 439 + 440 + --- 441 + 442 + ## Future Extensions 443 + 444 + - **Bottom bar on notepat.com:** "Available as Ableton Extension" marquee (already planned separately). 445 + - **KidLisp code editor inside notepat:** Long-press the `$roz` label to open an inline editor for the background code. 446 + - **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"`.) 447 + - **Shared tempo:** Forward notepat's BPM to KidLisp's `bpm` global for beat-synced visuals. 448 + - **Named channels:** `notepat $roz:liveset` creates channel `liveset` instead of random — shareable, memorable for performances. 449 + - **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. 450 + - **FF1 cast integration:** `ac-ff1 cast pj.kidlisp.com/{channel}` sends the remote visualizer directly to the FF1 art computer. 451 + - **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). 452 + - **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
··· 1 + # Beyond "Documentation": Metaphors for LLM-Driven Development in Aesthetic Computer 2 + 3 + **Date:** 2026-02-08 4 + **Source:** [federicopereiro.com/llm-high](https://federicopereiro.com/llm-high/) by Federico Pereiro 5 + 6 + --- 7 + 8 + ## The Original Framework (Pereiro's "cell") 9 + 10 + 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**: 11 + 12 + | Role | Name | Nature | 13 + |------|------|--------| 14 + | Stock | **Documentation** | Markdown spec pages — purpose, entities, endpoints, constraints, coding standards | 15 + | Stock | **Implementation** | Codebase + data. Reconstructable from documentation. | 16 + | Flow | **Dialogs** | Agent conversation streams. Inspectable, joinable by humans. | 17 + | Flow | **Tasks** | Discrete work items, nestable, with status tracking. | 18 + 19 + 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. 20 + 21 + --- 22 + 23 + ## Why "Documentation" Doesn't Fit AC 24 + 25 + "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: 26 + 27 + - **A creative coding platform** rooted in Processing's sketchbook model 28 + - **Piece-based** — small interactive programs, not microservices 29 + - **Playful** — commands like `wipe("purple")`, handles like `@jeff`, KidLisp 30 + - **Art-adjacent** — descended from No Paint, gallery shows, whistlegraph 31 + - **Live** — drag-and-drop hot reload, multiplayer channels, performance 32 + 33 + The "stock of truth that agents reconstruct from" needs a word that carries creative intent, not compliance. 34 + 35 + --- 36 + 37 + ## Alternative Metaphors 38 + 39 + ### 1. **Score** ★★★★★ 40 + 41 + A musical score is a set of instructions for performance — not the performance itself. Strudel is already in AC's orbit. A score: 42 + - Prescribes behavior without being the behavior 43 + - Is interpreted by performers (agents) with room for expression 44 + - Can be read, annotated, conducted 45 + - Has a long history in experimental/conceptual art (Fluxus event scores, Sol LeWitt wall drawings, Yoko Ono's *Grapefruit*) 46 + 47 + **Vocabulary:** "The score describes the system. Agents perform it. The implementation is what's playing." 48 + 49 + ### 2. **Sketch / Sketchbook** ★★★★ 50 + 51 + AC already lives in Processing's sketchbook lineage. A sketch is: 52 + - Intentional but loose — it captures *what you mean* without specifying every pixel 53 + - Iterative — you sketch, erase, redraw 54 + - The natural first artifact of any creative process 55 + - Already in AC's DNA (pieces *are* sketches) 56 + 57 + **Risk:** Might feel too informal for the "source of truth" role. Sketches are disposable; the stock shouldn't be. 58 + 59 + ### 3. **Blueprint** ★★★★ 60 + 61 + Blueprints are generative specifications — they describe something that will be built by someone else (the agents). Unlike documentation: 62 + - They're forward-looking, not retrospective 63 + - They assume a builder will interpret them 64 + - They carry authority ("build it like this") 65 + - They're visual/spatial, not just textual 66 + 67 + **Risk:** Slightly too architectural/industrial for AC's art-kid energy. 68 + 69 + ### 4. **Script** (theatrical) ★★★★ 70 + 71 + A script is dialogue + stage directions. Agents are the cast. The human is the director. 72 + - Scripts expect interpretation and staging 73 + - Scripts evolve through rehearsal (agent iteration) 74 + - "Rewrite the script" is a natural phrase for changing system behavior 75 + - There's already precedent: AC pieces are basically scripts for the runtime 76 + 77 + **Risk:** Collision with "script" meaning shell/JS script. Could confuse. 78 + 79 + ### 5. **Spell / Grimoire** ★★★½ 80 + 81 + Spells are incantations that produce effects. A grimoire is a collected book of spells. 82 + - AC already has a magical/whimsical register (KidLisp, `wand`, `witch`) 83 + - "Cast a spell" = "run a piece" 84 + - The grimoire is the collected knowledge that agents draw from 85 + - Fits the idea that you write words and things *happen* 86 + 87 + **Risk:** Too cute? Could alienate people who want to take the system seriously. 88 + 89 + ### 6. **Map** ★★★½ 90 + 91 + Maps describe territory without being the territory. Agents navigate by the map. 92 + - "The map is not the territory" — the stock is not the implementation 93 + - Maps can be zoomed, annotated, updated 94 + - You explore a map; you don't just read it 95 + 96 + **Risk:** Lacks creative/generative energy. Maps describe what *is*, not what *should be*. 97 + 98 + ### 7. **Prompt** ★★★ 99 + 100 + 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. 101 + - Extremely literal for the LLM era 102 + - Already in AC's vocabulary 103 + - "The prompt" as the system's source of truth has a nice recursion to it 104 + 105 + **Risk:** Overloaded term. Everyone uses "prompt" for everything now. 106 + 107 + ### 8. **Manifesto** ★★★ 108 + 109 + A manifesto declares intent and principles. Agents carry them out. 110 + - Art-world precedent (Futurism, Fluxus, Dogme 95) 111 + - A manifesto is opinionated and alive — not neutral reference material 112 + - "The manifesto says X, so the system does X" 113 + 114 + **Risk:** Manifestos are usually one-off declarations, not living documents. 115 + 116 + ### 9. **Recipe** ★★★ 117 + 118 + Recipes are procedural but expressive. They assume a cook (agent) who interprets. 119 + - "A pinch of salt" = room for agent judgment 120 + - Natural for describing flows and processes 121 + - Cookbooks are a nice organizational metaphor 122 + 123 + **Risk:** Doesn't scale to system-level architecture. Good for pieces, not for the whole. 124 + 125 + ### 10. **Seed** ★★½ 126 + 127 + Seeds grow into implementations. You plant the seed, the agent grows it. 128 + - Organic, generative 129 + - Fits AC's creative ethos 130 + 131 + **Risk:** Too vague. What does "editing a seed" mean? Seeds are black boxes. 132 + 133 + --- 134 + 135 + ## Recommendation for AC 136 + 137 + **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. 138 + 139 + The four-part framework adapted for AC: 140 + 141 + | Pereiro's Term | AC Term | What It Is | 142 + |----------------|---------|------------| 143 + | Documentation | **Score** | Markdown pages describing intent, entities, constraints, style | 144 + | Implementation | **Performance** (or just "the system") | Running code + data — the score being played | 145 + | Dialogs | **Sessions** | Agent conversation streams (already an AC concept) | 146 + | Tasks | **Tasks** (or **Cues**) | Discrete work items — "cues" if you want to stay in the theater/music metaphor | 147 + 148 + **"The score describes it. The agents perform it. The system is live."** 149 + 150 + --- 151 + 152 + ## Mixed Metaphor Option 153 + 154 + You don't have to pick one. AC could use different words at different scales: 155 + 156 + - **Score** for the system-level source of truth 157 + - **Sketch** for individual piece-level descriptions 158 + - **Prompt** for ephemeral one-off agent instructions 159 + 160 + This mirrors how AC already has layers: the platform, pieces, and the command prompt. 161 + 162 + --- 163 + 164 + ## Open Questions from Pereiro (through AC's lens) 165 + 166 + ### 1. How do we store the score alongside the implementation? 167 + 168 + 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. 169 + 170 + **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. 171 + 172 + **Proposal:** A `score/` directory at the repo root, clearly separated: 173 + 174 + ``` 175 + score/ 176 + README.md ← "Score for AA" (the double-A score) — the overture 177 + pieces.md ← What pieces are, how the lifecycle works (boot/paint/sim/act/beat) 178 + kidlisp.md ← The language spec 179 + infrastructure.md ← Session server, Redis, multiplayer, auth, deploy 180 + social.md ← Handles, chat, moods, channels, sharing 181 + creative-philosophy.md ← The instrument metaphor, sketchbook model, whistlegraph lineage 182 + style.md ← Code conventions, naming, file structure 183 + electron.md ← Desktop app architecture 184 + ``` 185 + 186 + 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. 187 + 188 + The existing `AGENTS.md` and `llm.md` would migrate into `score/` or become views derived from it. 189 + 190 + ### 2. How do we use version control? 191 + 192 + This is the hardest question. Pereiro identifies four artifacts: **Score** (documentation), **Performance** (implementation), **Sessions** (dialogs), and **Tasks**. How does git handle each? 193 + 194 + **What AC does now:** 195 + - **Score & Performance** share one repo, one branch (`main`), no CI/CD gating. Manual deploys. Works because there's one primary author (you). 196 + - **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. 197 + - **Tasks** — `TODO.txt` at root, plus ad-hoc tracking in agent conversations. No formal task system in git. 198 + - **Secrets** — sidecar `aesthetic-computer-vault/` repo, gitignored from main, with its own version history. Good pattern. 199 + - **Assets** — CDN-synced, not in git, no manifest. State lives outside version control. 200 + 201 + **Tensions exposed by the score metaphor:** 202 + 203 + **a) The score should change slower than the performance.** 204 + 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. 205 + 206 + **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. 207 + 208 + **b) Sessions (dialogs) are valuable but enormous.** 209 + 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. 210 + 211 + **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. 212 + 213 + **c) Agent branches need guardrails.** 214 + 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. 215 + 216 + **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. 217 + 218 + **d) The monorepo is the orchestra pit.** 219 + 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. 220 + 221 + **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. 222 + 223 + **e) Redis state and CDN assets are outside git.** 224 + 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. 225 + 226 + **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. 227 + 228 + ### 3. MCP as the orchestra's connection to the audience 229 + 230 + 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. 231 + 232 + 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: 233 + 234 + - **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. 235 + - **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. 236 + - **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. 237 + 238 + --- 239 + 240 + ## Why "Score" Was Already in AC's Blood 241 + 242 + After deeper repo research, it's clear AC has been a score system from the start — maybe before you had the word for it. 243 + 244 + ### Evidence 245 + 246 + 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. 247 + 248 + 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. 249 + 250 + 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. 251 + 252 + 4. **`merry` is a setlist.** The `merry` / `merryo` commands chain pieces in timed sequences. That's a concert program. A score for an evening. 253 + 254 + 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. 255 + 256 + 6. **KidLisp pieces use `$` embedding** — one piece calling another as a layer. That's instrumentation. Voices in a score. 257 + 258 + 7. **The mudras.** The `writing/` directory has Indian classical hand gesture notation (`mudras-notation.jpg`, `asamyuta-hasta`). Graphic notation for the body. 259 + 260 + 8. **The `stage` piece** is a 3D colored-block melody system (A-G). A literal spatial score. 261 + 262 + 9. **`notepat`** has melody notation and a typing mode. It's a score *writer*. 263 + 264 + --- 265 + 266 + ## Scores for Social Software — The Card Deck Submission 267 + 268 + 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). 269 + 270 + This is the exact convergence. Their definition: 271 + > *"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."* 272 + 273 + 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. 274 + 275 + ### Title Variations for the SCORE.md / General Concept 276 + 277 + **The winner: "Score for AA" (the double-A score)** 278 + 279 + AA = Aesthetic Ants. Also: 280 + - **Aesthetic Ants** — the primary expansion. The score is *for* the ants. They follow it. 281 + - **Agentic Ant** — what each one does (it has agency, barely) 282 + - **Aesthetic Agents** — the wider reading: human agents (artists, users) and software agents (ants, LLMs, bots) all follow the same score 283 + - **AA batteries** — small, portable, powers everything 284 + - **AA meetings** — a program you follow one day at a time, one small step 285 + - **aa lava** — Hawaiian: rough, slow-moving, builds landscape gradually (like ants) 286 + - **AA paper size** — a sheet you write scores on 287 + 288 + The commit prefix is `ant:` — or could be `aa:` to lean into it. 289 + "Check the double-A" / "follow the double-A" / "the ants are running on double-A." 290 + 291 + **Other variations considered:** 292 + 293 + 2. **Score for a Computer & Its Friends** — gentler, more universal. "Its Friends" = pieces, users, agents, other software. 294 + 3. **Score for Computers & People** — austere, Fluxus-register. Echoes "Composition 1960 No. 7" (La Monte Young). 295 + 4. **Score for Anyone with a Browser** — populist, true, funny. 296 + 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). 297 + 6. **Score for Software that Wants to be Touched** — about mobile-first, about intimacy with computation. 298 + 7. **Score for Pals** — stripped down. The pals are left undefined. Could be people, pieces, bots, agents. 299 + 8. **Piece for Pals** — uses AC's own word. A score is a piece. A piece is a score. 300 + 301 + **Dropping "Score for" (shorter, stranger):** 302 + 303 + 9. **Aesthetic.Computer & Pals** — just the title, no framing word. It *is* a score by context. 304 + 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. 305 + 306 + **Completely different register:** 307 + 308 + 11. **A Score for the Prompt** — focuses on the command-line-as-instrument idea. The prompt is the silence before the first note. 309 + 12. **Overture for Social Software** — overture = the opening piece that previews all themes. AC's `prompt` *is* an overture. 310 + 13. **Études for the Networked Hand** — étude = study piece for developing technique. AC has actual keyboard études in `writing/`. "Networked Hand" = touchscreen + multiplayer. 311 + 14. **Notation for Sharing a Screen** — the screen is the shared page of the score. URL-addressable = everyone reads the same page. 312 + 15. **Instructions for Pals** — Fluxus-direct. "Pals" does the warmth work. 313 + 314 + ### Possible Card Scores (for the Lauren/Casey submission) 315 + 316 + 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: 317 + 318 + --- 319 + 320 + **Score #1: "prompt"** 321 + 322 + ``` 323 + Open a blank page. 324 + Type one word. 325 + Wait for whatever happens. 326 + 327 + If nothing happens, type another word. 328 + If something happens, invite someone to watch. 329 + If they want to try, hand them the keyboard. 330 + 331 + Repeat until the blank page is no longer blank, 332 + or until everyone has gone home. 333 + ``` 334 + 335 + *(This is literally how AC works. The prompt is the score.)* 336 + 337 + --- 338 + 339 + **Score #2: "channel"** 340 + 341 + ``` 342 + Find two screens. 343 + Put the same address in both. 344 + Change something on one. 345 + Watch the other. 346 + 347 + Now find three screens. 348 + Now find a classroom. 349 + Now find a stranger. 350 + 351 + The channel is open 352 + until you close this window. 353 + ``` 354 + 355 + *(Based on the `channel` feature — live code broadcasting.)* 356 + 357 + --- 358 + 359 + **Score #3: "&"** 360 + 361 + ``` 362 + Write a program. 363 + Give it a name. 364 + Put it at a URL. 365 + 366 + Now it is social software. 367 + 368 + Anyone who visits the URL 369 + is performing the program with you, 370 + whether you know them or not. 371 + ``` 372 + 373 + *(The radicalism of URL-addressable creative software.)* 374 + 375 + --- 376 + 377 + **Score #4: "wipe"** 378 + 379 + ``` 380 + wipe("purple") 381 + 382 + Every program begins 383 + by deciding the color 384 + of nothing. 385 + ``` 386 + 387 + *(AC's `wipe` command — clearing the canvas — is the first line of every piece.)* 388 + 389 + --- 390 + 391 + **Score #5: "pals"** 392 + 393 + ``` 394 + Make software for one person. 395 + That person will show someone. 396 + That someone will want to change something. 397 + Let them. 398 + 399 + The software now has two authors. 400 + Neither of them wrote most of it. 401 + All of them are pals. 402 + ``` 403 + 404 + *(The No Paint → AC pipeline: users contributing stamps, becoming co-authors.)* 405 + 406 + --- 407 + 408 + **Score #6: "beat"** 409 + 410 + ``` 411 + boot once. 412 + paint every frame. 413 + act on every touch. 414 + beat at 120 bpm. 415 + 416 + ( Software has a pulse. 417 + It was always alive. 418 + You just weren't counting. ) 419 + ``` 420 + 421 + *(AC's piece lifecycle hooks — boot, paint, sim, act, beat — as revelation.)* 422 + 423 + --- 424 + 425 + ### For the SCORE.md in the Repo 426 + 427 + I'd suggest the repo file should be titled: 428 + 429 + # Score for AA 430 + 431 + ("The double-A score." AA = Aesthetic Ants. Also: Agentic Ant, Aesthetic Agents.) 432 + 433 + 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. 434 + 435 + The card title on the back would read: 436 + > **Jeffrey Alan Scudder** 437 + > *prompt* (or *wipe*) 438 + > 2026
+26 -1
system/public/aesthetic.computer/disks/notepat.mjs
··· 909 909 // 🌐 Branded domain mode (notepat.com — pushes top bar piano right for .com superscript) 910 910 let dotComMode = false; 911 911 912 + // 🎨 KidLisp background visual (activated via `notepat $roz` etc.) 913 + let kidlispBackground = null; // e.g. "$roz" — the $code to render behind the UI 914 + let kidlispBgEnabled = false; 915 + 912 916 const trail = {}; 913 917 914 918 // 🎹 Piano roll history - pixel timeline of held notes ··· 1236 1240 } 1237 1241 1238 1242 // qrcells = qr("https://prompt.ac/notepat", { errorCorrectLevel: 2 }).modules; 1243 + 1244 + // 🎨 KidLisp background: parse $code param (e.g. `notepat $roz`) 1245 + const dollarParam = params.find((p) => p.startsWith("$")); 1246 + if (dollarParam) { 1247 + kidlispBackground = dollarParam; // e.g. "$roz" 1248 + kidlispBgEnabled = true; 1249 + hud.label(`notepat ${dollarParam}`); 1250 + } 1239 1251 1240 1252 if (params[0] === "piano") { 1241 1253 toneVolume = params[1] || 0.5; ··· 2637 2649 } 2638 2650 } 2639 2651 2652 + // 🎨 KidLisp background: forward amplitude + render 2653 + if (kidlispBgEnabled) { 2654 + api.updateKidLispAudio({ 2655 + amp: amplitude * 10, 2656 + notes: active.length, 2657 + }); 2658 + } 2659 + 2640 2660 // 🎨 Fullscreen visualizer mode - draw bars as background behind everything 2641 - if (visualizerFullscreen && !recitalMode) { 2661 + if (kidlispBgEnabled && kidlispBackground && !paintPictureOverlay) { 2662 + const bgPainting = api.kidlisp( 2663 + 0, 0, screen.width, screen.height, kidlispBackground, 2664 + ); 2665 + if (!bgPainting) wipe(bg); // Fallback while $code is loading 2666 + } else if (visualizerFullscreen && !recitalMode) { 2642 2667 wipe(0); // Black background first 2643 2668 sound.paint.bars( 2644 2669 api,