tc043 Codex commited on
Commit
144132f
·
1 Parent(s): 19a5a7c

refactor: update grammar, remove unused project files, and clean up workspace configuration

Browse files
Files changed (8) hide show
  1. .codex/config.toml +0 -3
  2. .gitignore +2 -0
  3. app.py +15 -3
  4. field_notes.md +33 -21
  5. grammar.gbnf +1 -1
  6. hackathon.md +0 -19
  7. scratch/test_models.py +0 -29
  8. static/index.html +0 -125
.codex/config.toml DELETED
@@ -1,3 +0,0 @@
1
- [features]
2
- memories = true
3
- codex_git_commit = true
 
 
 
 
.gitignore CHANGED
@@ -6,3 +6,5 @@ models/
6
  .venv/pyvenv.cfg
7
  .venv/pyvenv.cfg
8
  *.log
 
 
 
6
  .venv/pyvenv.cfg
7
  .venv/pyvenv.cfg
8
  *.log
9
+ scratch/
10
+ .codex/
app.py CHANGED
@@ -192,7 +192,7 @@ async def generate_atomic_task(goal: str, previous_failures: int, history: list
192
  prompt,
193
  max_tokens=64,
194
  temperature=0.3,
195
- stop=["\n", "<extra_id_1>"],
196
  grammar=grammar # Enforcing grammar here!
197
  )
198
  raw_content = response['choices'][0]['text'].strip()
@@ -283,7 +283,8 @@ async def apply_activation_style(task: str, style="direct") -> str:
283
  )
284
  content = response['choices'][0]['message']['content'].strip()
285
  print(f"MINICPM RAW OUT: {content}", flush=True)
286
- return content
 
287
 
288
  # --- GRADIO UI ---
289
 
@@ -331,6 +332,8 @@ footer { display: none !important; }
331
  #goal-input textarea:focus { border-bottom: 2px solid white !important; }
332
 
333
  /* Buttons */
 
 
334
  #btn-done { background-color: #16a34a !important; color: white !important; font-weight: bold; font-size: 1.25rem !important; }
335
  #btn-skip { background-color: #4b5563 !important; color: white !important; font-weight: bold; font-size: 1.25rem !important; }
336
  #btn-hard { background-color: #1f2937 !important; color: #d1d5db !important; font-weight: bold; font-size: 1.25rem !important; }
@@ -438,7 +441,7 @@ with gr.Blocks(css=custom_css, js="() => { document.documentElement.classList.ad
438
  container=False
439
  )
440
 
441
- start_btn = gr.Button("Start", variant="primary", elem_classes="mt-8 mx-auto w-48")
442
 
443
  with gr.Column(visible=False) as screen_task:
444
  breadcrumbs_display = gr.HTML(elem_id="breadcrumbs-container")
@@ -452,6 +455,7 @@ with gr.Blocks(css=custom_css, js="() => { document.documentElement.classList.ad
452
  with gr.Row(elem_classes="justify-center mt-4 gap-4"):
453
  btn_trace = gr.Button("Export Trace", elem_id="btn-trace")
454
  btn_push = gr.Button("Push to Hub", elem_id="btn-trace")
 
455
 
456
  trace_download = gr.File(visible=False)
457
  hub_status = gr.HTML(visible=False, elem_classes="text-center text-sm text-gray-400 mt-2")
@@ -573,5 +577,13 @@ with gr.Blocks(css=custom_css, js="() => { document.documentElement.classList.ad
573
  outputs=[hub_status]
574
  )
575
 
 
 
 
 
 
 
 
 
576
  if __name__ == "__main__":
577
  app.launch(server_name="0.0.0.0", server_port=7860)
 
192
  prompt,
193
  max_tokens=64,
194
  temperature=0.3,
195
+ stop=["\n", "<extra_id_1>", "Completed Tasks", "Goal:", "User:", "Assistant:", "<extra_id_"],
196
  grammar=grammar # Enforcing grammar here!
197
  )
198
  raw_content = response['choices'][0]['text'].strip()
 
283
  )
284
  content = response['choices'][0]['message']['content'].strip()
285
  print(f"MINICPM RAW OUT: {content}", flush=True)
286
+ cleaned_content, is_valid = clean_and_validate_task(content, [], [])
287
+ return cleaned_content or content.strip('"\' ,.-1234567890)')
288
 
289
  # --- GRADIO UI ---
290
 
 
332
  #goal-input textarea:focus { border-bottom: 2px solid white !important; }
333
 
334
  /* Buttons */
335
+ #btn-start { background-color: #16a34a !important; color: white !important; font-weight: bold; font-size: 1.25rem !important; border: none !important; }
336
+ #btn-start:hover { background-color: #15803d !important; }
337
  #btn-done { background-color: #16a34a !important; color: white !important; font-weight: bold; font-size: 1.25rem !important; }
338
  #btn-skip { background-color: #4b5563 !important; color: white !important; font-weight: bold; font-size: 1.25rem !important; }
339
  #btn-hard { background-color: #1f2937 !important; color: #d1d5db !important; font-weight: bold; font-size: 1.25rem !important; }
 
441
  container=False
442
  )
443
 
444
+ start_btn = gr.Button("Start", variant="primary", elem_classes="mt-8 mx-auto w-48", elem_id="btn-start")
445
 
446
  with gr.Column(visible=False) as screen_task:
447
  breadcrumbs_display = gr.HTML(elem_id="breadcrumbs-container")
 
455
  with gr.Row(elem_classes="justify-center mt-4 gap-4"):
456
  btn_trace = gr.Button("Export Trace", elem_id="btn-trace")
457
  btn_push = gr.Button("Push to Hub", elem_id="btn-trace")
458
+ btn_reset = gr.Button("Start Over", elem_id="btn-trace")
459
 
460
  trace_download = gr.File(visible=False)
461
  hub_status = gr.HTML(visible=False, elem_classes="text-center text-sm text-gray-400 mt-2")
 
577
  outputs=[hub_status]
578
  )
579
 
580
+ def handle_reset():
581
+ return {}, gr.Column(visible=True), gr.Column(visible=False), ""
582
+
583
+ btn_reset.click(
584
+ fn=handle_reset,
585
+ outputs=[session_state, screen_start, screen_task, goal_input]
586
+ )
587
+
588
  if __name__ == "__main__":
589
  app.launch(server_name="0.0.0.0", server_port=7860)
field_notes.md CHANGED
@@ -2,13 +2,13 @@
2
 
3
  Step-Zero is a local-first "Cognitive Pacemaker" application designed to break down overwhelming goals into atomic physical actions to overcome executive dysfunction.
4
 
5
- During development, we encountered several unique challenges in aligning very small language models (≤4B parameters) to produce strictly physical actions under strict grammar constraints. Here is the report of what we built, what we learned, and how we optimized the system.
6
 
7
  ---
8
 
9
  ## 1. System Architecture
10
 
11
- The core of Step-Zero is a **dual-model orchestrator** that runs entirely locally on CPU/laptop hardware via `llama.cpp` runtimes.
12
 
13
  ```
14
  ┌──────────────────────┐
@@ -30,29 +30,40 @@ The core of Step-Zero is a **dual-model orchestrator** that runs entirely locall
30
  └──────────────────────┘ └───────────────────────┘
31
  ```
32
 
33
- 1. **Primary Model (Nemotron-Mini-4B):** Fine-tuned specifically for step breakdown using a synthetic task-reduction dataset (`verbs.jsonl`).
34
- 2. **Fallback Model (MiniCPM-3-4B):** An instruction-following model used for stylistic adjustment, semantic repetition recovery, and breaking down actions when the user signals "Too Hard".
35
 
36
  ---
37
 
38
- ## 2. Technical Alignments & Guardrails
39
 
40
- Deploying small LLMs locally introduces high variability in output formatting. We implemented three layers of guardrails to enforce strict formatting:
41
 
42
- ### A. GBNF Grammars
43
- We defined a GBNF grammar (`[a-zA-Z0-9 ,.!?'-]+`) to force the models to generate raw plain text rather than markdown lists, JSON objects, or verbose chat commentary. This bypasses the need for complex output parser libraries.
44
 
45
- ### B. Output Validation & First-Sentence Extraction
46
- Small fine-tuned models can experience "prompt leakage" or echo prompt formatting when encountering out-of-distribution user inputs. To solve this, we implemented a custom post-processing pipeline that:
47
- - Splits output at sentence boundaries to extract only the *first* sentence (guaranteeing one atomic starting action).
48
- - Validates the output against a list of prompt scaffolding keywords (e.g. `Completed Tasks`, `User`, `Assistant`, `Failures`).
49
- - Immediately routes any invalid or overly verbose output to the fallback MiniCPM model.
 
 
 
50
 
51
- ### C. Semantic Repetition Guard
52
- Instead of exact string matching, we implemented a sliding-window overlap check. If a newly generated action has >70% word overlap with the user's recent history or skipped tasks, the system intercepts it as a repetition loop and routes it to MiniCPM to generate a novel alternative.
53
 
54
- ### D. Hard Circuit Breaker
55
- If the user indicates a task is "Too Hard" three times consecutively, the app triggers a hard-coded cooldown screen: *"You are out of activation energy. Step away from the screen for 3 minutes. I will be here."* This prevents the AI from entering infinite breakdown loops that increase user frustration.
 
 
 
 
 
 
 
 
 
 
56
 
57
  ---
58
 
@@ -60,12 +71,13 @@ If the user indicates a task is "Too Hard" three times consecutively, the app tr
60
 
61
  To support the cognitive design of the pacemaker (which aims to reduce cognitive load), the app features a minimal, custom dark theme. By leveraging Gradio's CSS custom properties on `:root` and `.dark`, we overrode Gradio's standard elements to deliver a clean, borderless dark UI styled like a game interface:
62
  - **Fog of War:** Completed history is styled with low opacity (`opacity: 0.3`) and blurred (`filter: blur(2px)`) to keep the user's attention anchored on the single next step.
63
- - **Minimalist Controls:** Big, clear action buttons ("I DID THIS", "SKIP", "TOO HARD") keep user interactions simple.
 
64
 
65
  ---
66
 
67
  ## 4. Key Takeaways & Small Model Constraints
68
 
69
- 1. **Alignment Tax on Small Models:** Fine-tuning a 4B model changes its distribution significantly. Even mild discrepancies between fine-tuning format and inference prompts can lead to format breakdown. A strict post-validation filter is essential when building production applications around them.
70
- 2. **Grammar Constraints are a Superpower:** Restricting token generation at the sampler level is far more efficient than prompt-engineering a small model to follow output rules.
71
- 3. **Local-First is Ready:** With optimized GGUF quantizations, both Nemotron and MiniCPM run under 50ms per token on modern consumer laptops.
 
2
 
3
  Step-Zero is a local-first "Cognitive Pacemaker" application designed to break down overwhelming goals into atomic physical actions to overcome executive dysfunction.
4
 
5
+ Running small language models (≤4B parameters) entirely on consumer hardware forces you to confront the "Alignment Tax" head-on. Here is the honest developer log of the design decisions, engineering failures, and breakthroughs we encountered.
6
 
7
  ---
8
 
9
  ## 1. System Architecture
10
 
11
+ Step-Zero uses a **dual-model orchestrator** executing locally on CPU/laptop hardware via the `llama.cpp` runtime.
12
 
13
  ```
14
  ┌──────────────────────┐
 
30
  └──────────────────────┘ └───────────────────────┘
31
  ```
32
 
33
+ 1. **Primary Model (Nemotron-Mini-4B):** Fine-tuned using Unsloth SFT specifically on daily goal breakdown datasets (`verbs.jsonl`) to produce atomic, physical next-step instructions.
34
+ 2. **Fallback Model (MiniCPM-3-4B):** An instruction-following model used for stylistic tone adjustments (Calm vs. Encouraging), semantic repetition loops, and simplifying tasks when a user signals "Too Hard".
35
 
36
  ---
37
 
38
+ ## 2. Technical Alignments & The GBNF Stop-Token Deadlock
39
 
40
+ During testing under real model inference, we noticed that Nemotron-Mini-4B suffered from severe over-generation, outputting long paragraphs containing prompt template headers, formatting guidelines, and repeat sentences. Every single task triggered our validation filters and fell back to MiniCPM, causing a 25-second latency penalty.
41
 
42
+ Here are the two breakthroughs that solved this:
 
43
 
44
+ ### Breakthrough A: Resolving the GBNF Sampler Deadlock
45
+ We originally constrained the model using a GBNF grammar:
46
+ ```gbnf
47
+ root ::= [a-zA-Z0-9 ,.!?'-]+
48
+ ```
49
+ We also specified a stop token of `\n` in the inference call (`stop=["\n"]`) to terminate the action after the first sentence.
50
+
51
+ **The Failure:** The GBNF grammar restricted the allowed character set to letters, numbers, and basic punctuation. Crucially, it did *not* include the newline character `\n`. Because GBNF constraints are enforced at the sampler level, the model's probability of selecting `\n` was set to exactly zero.
52
 
53
+ As a result, the model was mathematically forbidden from generating the stop token. It kept generating sentence after sentence until it hit the hard limit of `max_tokens=64`, leading to severe paragraph bloating and prompt echoing.
 
54
 
55
+ **The Fix:** We updated the GBNF grammar to permit newline characters:
56
+ ```gbnf
57
+ root ::= [a-zA-Z0-9 ,.!?\n'-]+
58
+ ```
59
+ This allowed the model to output a newline character as soon as it finished the task, instantly triggering the `stop=["\n"]` check and terminating generation after the first sentence. The model now outputs exactly 6 to 8 words, saving massive CPU latency.
60
+
61
+ ### Breakthrough B: Token-Level Early Stopping for Prompt Echoing
62
+ Small models are highly susceptible to "prompt echoing" (repeating headers like `Completed Tasks:` or `Goal:` when prompted). Rather than waiting for the model to generate a full 64-token sequence of echoed prompt template text before filtering it out in Python, we added the headers directly to the llama.cpp stop-words:
63
+ ```python
64
+ stop=["\n", "<extra_id_1>", "Completed Tasks", "Goal:", "User:", "Assistant:"]
65
+ ```
66
+ If the model begins echoing the prompt template, it hits a stop word within 1 or 2 tokens and halts generation immediately. This terminates failed inference loops in under 50ms, allowing an instantaneous fallback to MiniCPM.
67
 
68
  ---
69
 
 
71
 
72
  To support the cognitive design of the pacemaker (which aims to reduce cognitive load), the app features a minimal, custom dark theme. By leveraging Gradio's CSS custom properties on `:root` and `.dark`, we overrode Gradio's standard elements to deliver a clean, borderless dark UI styled like a game interface:
73
  - **Fog of War:** Completed history is styled with low opacity (`opacity: 0.3`) and blurred (`filter: blur(2px)`) to keep the user's attention anchored on the single next step.
74
+ - **Start Button Theme:** Overrode Gradio's default primary orange buttons to match the deep forest green theme of the interface.
75
+ - **Timer and Recovery:** Included a clean "Start Over" action flow to allow user recovery once the circuit breaker fires.
76
 
77
  ---
78
 
79
  ## 4. Key Takeaways & Small Model Constraints
80
 
81
+ 1. **Syntax Constraints are Sampler Constraints:** Constraining text syntax with GBNF without matching stop criteria can lead to sampler deadlocks. Always align your grammar's allowed character space with your engine's stop sequences.
82
+ 2. **LoRA Fine-tuning is Highly Sensitive:** A 4B parameter model is highly sensitive to format drift. Minor discrepancies between fine-tuning prompt structure and inference structure will cause formatting breakdowns.
83
+ 3. **Optimized CPU Execution:** With optimized GGUF quantizations and proper early stopping, both models run under 50ms per token on consumer CPU laptop hardware, making fully offline cognitive assistance viable.
grammar.gbnf CHANGED
@@ -1 +1 @@
1
- root ::= [a-zA-Z0-9 ,.!?'-]+
 
1
+ root ::= [a-zA-Z0-9 ,.!?\n'-]+
hackathon.md DELETED
@@ -1,19 +0,0 @@
1
- can they win the hackathon tho? this one:
2
- Hosted by Gradio · Hugging Face Anchor Sponsors OpenBMB · OpenAI · NVIDIA · Modal Cash & Prizes $48k+ Our Trail Partners Backed by the best in AI. The folks making this adventure possible — the full roster, locked in.
3
- From Your Hosts Hugging Face Logo Hugging Face $15,000 into the cash prize pool, plus $20 in Hugging Face credits for every participant. We're not just hosting the trail — we're packing the provisions too. Anchor Sponsors OpenAI Logo $10,000 in cash prizes, plus $100 in Codex credits for the first 1,000 participants.
4
- NVIDIA Logo Two RTX 5080 GPUs🔥 as physical prizes.
5
- Modal Logo $250 in Modal credits for every participant, plus $20,000 in credits for the winners.
6
- OpenBMB Logo $10,000 in special category prizes — split across both tracks for the standout OpenBMB picks.
7
- Supporting Sponsors Cohere Logo Cohere $5,000 in cash prize contribution to the pool. JetBrains Logo JetBrains $5,000 in cash prize contribution to the pool. Black Forest Labs Logo Black Forest Labs $3,000 in cash prize contribution to the pool. Total Prize Pool $48,000+ in cash & physical prizes across both tracks — plus per-participant credits ($100 Codex · $250 Modal · $20 Hugging Face) Registrations are closed — and the trail is open! Sign-ups wrapped on June 3, 2026. Registered builders — the hack window is live, June 5–15. Hop into the Discord, find your teammates, and start building. → Join the Discord ← ❋ Chapter Zero — Why this exists The pace of AI over the last year has been anxiety-inducing. Labs release larger and larger models that do more of the things that make us feel human — writing, drawing, speaking, coding. No wonder more than half of us think the risks of AI outweigh the benefits.
8
- We're sponsoring a hackathon to take things back to how they felt in 2021 — when models were small enough to tinker with, and the vibe was fun and hopeful about how this technology could improve our lives. This hackathon is different. Rather than using a giant LLM to ship yet another B2B SaaS, we want you to think small. Armed with only 32 billion parameters, solve a problem someone you know is facing — or build something whimsical.
9
- ❋ Two Tracks · Pick your trail Choose your own adventure. Chapter One 🏡 Backyard AI Solve a real problem for someone you actually know. Pick a person — a neighbor, a parent, a small-business owner on your street — and build something that makes their day measurably better.
10
- Judged on Problem is specific and real The person actually used it Honest fit between problem and the small-model constraint Polish of the Gradio app Chapter Two 🍄 An Adventure in Thousand Token Wood Build something delightful that wouldn't exist without AI. Wander somewhere weirder. A toy, a tiny game, a strange interactive story, an art experiment that surprises you. The AI should be doing the fun thing — not just helping you build it. Strange is good. Joyful is the bar.
11
- Judged on Genuinely delightful (would you show a friend?) AI is load-bearing for the experience Originality of concept Polish of the Gradio app ❋ The Constraints — Three rules of the trail Pack light. 1 Small Models Only Total parameters must be ≤ 32 billion. The model fits on a laptop. So should your ambition.
12
- 2 Built on Gradio Your app must be a Gradio app, hosted as a Hugging Face Space. That's the canvas.
13
- 3 Show, Don't Tell A short demo video and a social-media post are part of the submission. Make it sing.
14
- ❋ Bonus Quests · Six earnable merit badges Stack 'em on your sash. None required. Each one grants extra points for your submission. Hover a patch to see it lift.
15
- 🔌 Off the Grid No cloud APIs. The whole thing runs on the model in front of you. Local-first 🎯 Well-Tuned Your app uses a fine-tuned model you've published on Hugging Face. Fine-tuned 🎨 Off-Brand A custom frontend that pushes past the default Gradio look (Hint: see gr.Server). Custom UI 🦙 Llama Champion Your model runs through the llama.cpp runtime. llama.cpp 📡 Sharing is Caring You shared your agent trace on the Hub for everyone to learn from. Open trace 📓 Field Notes You wrote a blog post or report about what you built and what you learned. Tentative ❋ The Awards · What you're competing for The trophy cabinet. Every submission to a main track competes for that track's podium. Sponsor and special awards are separate categories on top — one great app can take home more than one.
16
- Main Track Awards · $18,000 🏡 Backyard AI Top four submissions on this trail. 1st First Place $4,000 2nd Second Place $2,500 3rd Third Place $1,500 4th Fourth Place $1,000 🍄 Thousand Token Wood Top four submissions on this trail. 1st First Place $4,000 2nd Second Place $2,500 3rd Third Place $1,500 4th Fourth Place $1,000 🗳️ Community Choice — by Hugging Face One winner, voted by the community. The app the trail itself loved most. $2,000 Sponsor Awards 🏮 OpenBMB Awards $10,000 total — three prizes in each of the two tracks for the standout OpenBMB picks. 1st First, per track $2,500 2nd Second, per track $1,500 3rd Third, per track $1,000 🌀 OpenAI Track $10,000 total — OpenAI's own podium across all submissions. 1st First Place $5,000 2nd Second Place $3,000 3rd Third Place $2,000 🟩 NVIDIA Nemotron Quest Two physical GPUs for the standout Nemotron builds. ×2 Winners RTX 5080 each 🟢 Modal Awards $20,000 in Modal credits for the top Modal-powered apps. 1st First Place $10,000 credits 2nd Second Place $7,000 credits 3rd Third Place $3,000 credits Special Awards · $8,000 $2,000 🎖️ Bonus Quest Champion The most merit badges stacked on a single sash. Collect 'em all. $1,500 🎨 Off-Brand Award The best custom UI pushing past the default Gradio look. gr.Server is your friend. $1,500 🐜 Tiny Titan The best app built on a genuinely tiny model — think ≤4B parameters. $1,000 🎬 Best Demo The full package: great app, great demo video, great social post. $1,000 🤖 Best Agent The best agentic app — all under the 32B cap, of course. $1,000 🃏 Judges' Wildcard For the entry that's amazing but fits no category. We know you're out there.
17
- brainstorm, we need to win. to be eligible for OpenBMB Awards we need to use their models, for OpenAI Track we need to have codex-attributed commits that will be judged by codex itself, for nvda nemotron questsame thing we need to use their models, for modal awards we also need to use it if we care about credits but this is less priority for me.
18
-
19
- disclaimer: the <=4 model req is for each model used in the project.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
scratch/test_models.py DELETED
@@ -1,29 +0,0 @@
1
- from llama_cpp import Llama, LlamaGrammar
2
- import os
3
-
4
- grammar = LlamaGrammar.from_file("grammar.gbnf")
5
- nemotron = Llama(model_path="./models/step-zero-nemotron-finetuned.gguf", n_ctx=1024, verbose=False)
6
-
7
- system_msg = "You are a cognitive pacemaker. Break down goals into extremely tiny, atomic physical actions under 8 words."
8
-
9
- goals = [
10
- "I want to study",
11
- "I want to clean my room",
12
- "I need to write a python script"
13
- ]
14
-
15
- for goal in goals:
16
- prompt = f"<extra_id_0>System\n{system_msg}\n\n"
17
- prompt += f"<extra_id_1>User\nGoal: {goal}\nCompleted Tasks: None\nFailures: 0\nOutput the NEXT step.\n"
18
- prompt += f"<extra_id_1>Assistant\n"
19
-
20
- response = nemotron(
21
- prompt,
22
- max_tokens=64,
23
- temperature=0.3,
24
- stop=["\n", "<extra_id_1>"],
25
- grammar=grammar
26
- )
27
- print(f"Goal: {goal}")
28
- print(f"Response: {response['choices'][0]['text']}")
29
- print("-" * 40)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/index.html DELETED
@@ -1,125 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Step-Zero</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <style>
9
- body { background-color: #0a0a0a; color: #ffffff; font-family: 'Helvetica Neue', sans-serif; overflow: hidden;}
10
- .fog { opacity: 0.3; filter: blur(2px); transition: all 0.5s ease; }
11
- .fade-in { animation: fadeIn 0.5s ease-in forwards; }
12
- @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
13
- </style>
14
- </head>
15
- <body class="h-screen w-screen flex flex-col items-center justify-center p-6">
16
-
17
- <div id="breadcrumbs" class="absolute top-10 left-10 text-gray-500 fog text-sm space-y-2"></div>
18
-
19
- <div id="input-screen" class="w-full max-w-2xl text-center fade-in">
20
- <h1 class="text-4xl font-bold mb-8">What is paralyzing you?</h1>
21
- <input type="text" id="goal-input" class="w-full bg-transparent border-b-2 border-gray-600 focus:border-white outline-none text-2xl pb-2 text-center" placeholder="e.g. Write my thesis..." autocomplete="off">
22
- <div class="mt-8 flex justify-center gap-3" role="group" aria-label="Activation style">
23
- <button type="button" class="style-btn px-4 py-2 border border-white text-white bg-white/10 rounded" data-style="direct">Direct</button>
24
- <button type="button" class="style-btn px-4 py-2 border border-gray-700 text-gray-400 rounded" data-style="calm">Calm</button>
25
- <button type="button" class="style-btn px-4 py-2 border border-gray-700 text-gray-400 rounded" data-style="encouraging">Encouraging</button>
26
- </div>
27
- </div>
28
-
29
- <div id="task-screen" class="hidden w-full max-w-4xl text-center flex-col items-center justify-center fade-in">
30
- <div id="current-task" class="text-5xl md:text-7xl font-extrabold tracking-tight mb-16 min-h-[150px] flex items-center justify-center">
31
- </div>
32
-
33
- <div id="controls" class="flex gap-6 justify-center w-full">
34
- <button id="btn-done" class="px-8 py-4 bg-green-600 hover:bg-green-500 text-white font-bold rounded-lg text-xl transition-colors">I DID THIS</button>
35
- <button id="btn-hard" class="px-8 py-4 bg-gray-800 hover:bg-gray-700 text-gray-300 font-bold rounded-lg text-xl transition-colors">TOO HARD</button>
36
- </div>
37
- <button id="btn-trace" class="mt-8 text-xs uppercase tracking-widest text-gray-600 hover:text-gray-300 transition-colors">Export Trace</button>
38
- </div>
39
-
40
- <script>
41
- const ws = new WebSocket(`ws://${window.location.host}/ws`);
42
- let currentTaskText = "";
43
- let currentStyle = "direct";
44
- let latestTrace = [];
45
-
46
- const inputScreen = document.getElementById('input-screen');
47
- const taskScreen = document.getElementById('task-screen');
48
- const goalInput = document.getElementById('goal-input');
49
- const currentTaskEl = document.getElementById('current-task');
50
- const breadcrumbsEl = document.getElementById('breadcrumbs');
51
- const controlsEl = document.getElementById('controls');
52
- const styleButtons = document.querySelectorAll('.style-btn');
53
-
54
- styleButtons.forEach((button) => {
55
- button.addEventListener('click', () => {
56
- currentStyle = button.dataset.style;
57
- styleButtons.forEach((item) => {
58
- const isActive = item.dataset.style === currentStyle;
59
- item.className = isActive
60
- ? 'style-btn px-4 py-2 border border-white text-white bg-white/10 rounded'
61
- : 'style-btn px-4 py-2 border border-gray-700 text-gray-400 rounded';
62
- });
63
- });
64
- });
65
-
66
- // Handle Enter key on input
67
- goalInput.addEventListener('keypress', (e) => {
68
- if (e.key === 'Enter' && goalInput.value.trim() !== '') {
69
- ws.send(JSON.stringify({ action: "start", goal: goalInput.value, style: currentStyle }));
70
- inputScreen.classList.add('hidden');
71
- taskScreen.classList.remove('hidden');
72
- currentTaskEl.innerHTML = '<span class="text-gray-600 animate-pulse">Calculating constraint...</span>';
73
- controlsEl.classList.add('hidden');
74
- }
75
- });
76
-
77
- // WebSocket Messages
78
- ws.onmessage = (event) => {
79
- const data = JSON.parse(event.data);
80
- currentTaskText = data.task;
81
- latestTrace = data.trace || latestTrace;
82
-
83
- // Re-render task with fade-in
84
- currentTaskEl.classList.remove('fade-in');
85
- void currentTaskEl.offsetWidth; // Trigger reflow
86
- currentTaskEl.innerText = data.task;
87
- currentTaskEl.classList.add('fade-in');
88
-
89
- // Show controls, hide if circuit breaker triggered
90
- if(data.is_breaker) {
91
- controlsEl.classList.add('hidden');
92
- setTimeout(() => { controlsEl.classList.remove('hidden'); }, 3000); // 3 sec demo timeout
93
- } else {
94
- controlsEl.classList.remove('hidden');
95
- }
96
-
97
- // Update Breadcrumbs
98
- breadcrumbsEl.innerHTML = data.history.map(t => `<div>✓ ${t}</div>`).join('');
99
- };
100
-
101
- // Button Clicks
102
- document.getElementById('btn-done').addEventListener('click', () => {
103
- currentTaskEl.innerHTML = '<span class="text-gray-600 animate-pulse">Loading next step...</span>';
104
- controlsEl.classList.add('hidden');
105
- ws.send(JSON.stringify({ action: "done", last_task: currentTaskText }));
106
- });
107
-
108
- document.getElementById('btn-hard').addEventListener('click', () => {
109
- currentTaskEl.innerHTML = '<span class="text-gray-600 animate-pulse">Recalculating friction...</span>';
110
- controlsEl.classList.add('hidden');
111
- ws.send(JSON.stringify({ action: "too_hard" }));
112
- });
113
-
114
- document.getElementById('btn-trace').addEventListener('click', () => {
115
- const blob = new Blob([JSON.stringify(latestTrace, null, 2)], { type: 'application/json' });
116
- const url = URL.createObjectURL(blob);
117
- const link = document.createElement('a');
118
- link.href = url;
119
- link.download = 'step-zero-agent-trace.json';
120
- link.click();
121
- URL.revokeObjectURL(url);
122
- });
123
- </script>
124
- </body>
125
- </html>