> ## Documentation Index
> Fetch the complete documentation index at: https://docs.openhome.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Vibe Coding Tips

> Prompts, patterns, and guardrails for LLM-assisted OpenHome development — building Abilities with Claude and dashboards with the Replit Agent.

You can ship an OpenHome Ability (and a companion dashboard) in an afternoon if you brief the LLM well. This page collects the prompts, patterns, and guardrails that make the difference between a plausible demo and something you actually leave running.

Two surfaces covered:

* [**Vibe coding Abilities with Claude**](#vibe-coding-abilities-with-claude) — terminal-based, CLI-driven, Python on the OpenHome sandbox
* [**Vibe coding dashboards with the Replit Agent**](#vibe-coding-dashboards-with-the-replit-agent) — browser-based, Flask + single-file HTML

Both share the same [Live Editor patterns](#openhome-live-editor-patterns) and [guardrails](#shared-patterns-and-guardrails).

***

## The rule of two tabs

When building, keep two browser tabs open side by side. **You need to see both sides of the pipe at once.**

* **Tab 1:** OpenHome Live Editor → Ability logs
* **Tab 2:** Your terminal (Claude + CLI) or Replit (server logs)

If a POST fails, you see it in both within a second. Fastest feedback loop voice AI has ever had.

***

## Vibe coding Abilities with Claude

The [OpenHome CLI](/guides/getting-started/cli) was designed so an AI coding agent — Claude, Cursor, anything that supports tool use — can drive the build loop end-to-end.

### Setup

<Steps>
  <Step title="Install the CLI">
    ```bash theme={"system"}
    npx openhome-cli
    ```

    Paste your [OpenHome API key](https://app.openhome.com/dashboard/settings) when prompted.
  </Step>

  <Step title="Point Claude at the project">
    In your terminal, launch Claude Code (or your preferred agent) in the directory where your Ability lives. Claude has full tool-use access to the CLI.
  </Step>

  <Step title="Give Claude context">
    Share the [SDK Reference](/api-sdk/sdk-reference) and [Simple Abilities Cookbook](/building-abilities/cookbook) up front. These two documents are the entire operating context for Ability development.
  </Step>
</Steps>

### The context you must give Claude

Before asking Claude to write any Ability code, paste or reference these four things:

1. **[SDK Reference](/api-sdk/sdk-reference)** — every method, every sandbox rule, every prompt pattern
2. **[Simple Abilities Cookbook](/building-abilities/cookbook)** — the 100+ examples so Claude understands the Ability shape
3. **[Voice-First Best Practices](/guides/best-practices/voice-first)** — the UX rules that distinguish a demo from a product
4. **What you want to build** — one paragraph, plain English, with concrete triggers and behaviors

Without all four, Claude will generate plausible-looking but wrong code — the worst outcome.

### Scaffolding prompt for a new Ability

Copy and adapt:

```
I want to build an OpenHome Ability.

CONTEXT (read first):
- SDK Reference: https://docs.openhome.com/api-sdk/sdk-reference
- Cookbook: https://docs.openhome.com/building-abilities/cookbook
- Voice-First rules: https://docs.openhome.com/guides/best-practices/voice-first

THE ABILITY:
Name: Meeting Notes
Category: skill (hotword-triggered)
Trigger words: "take notes", "start meeting notes", "note this meeting"
Behavior:
  - On trigger, start recording with self.capability_worker.start_audio_recording()
  - Loop listening for "meeting finished" via user_response()
  - When finished, stop recording, get audio bytes
  - POST to Deepgram with diarize=true, utterances=true, smart_format=true
  - LLM-summarize the diarized transcript into action items + speaker summary
  - Speak a 2-sentence recap, write the full notes to a persistent file
  - resume_normal_flow() on every exit path

CONSTRAINTS:
- Follow every sandbox rule in the SDK Reference
- Use session_tasks.create(), not asyncio.create_task
- No print(), use editor_logging_handler
- Keep speak() calls to 1–2 sentences

Architect the solution first (files, loops, patterns). Then write the code.
```

### Two-shot development

The worst failure of AI-assisted coding is generating a plausible implementation of the **wrong design**. Two-shot it:

<Steps>
  <Step title="Shot 1 — architecture in prose">
    Before any code: *"Walk me through the architecture. What files, what loops, what prompts, what storage patterns? Flag any tradeoffs."*
  </Step>

  <Step title="Shot 2 — full code">
    Only after you've agreed on the shape: *"Now write the full implementation. Follow every sandbox rule."*
  </Step>
</Steps>

**Talking through the shape before typing is the single highest-leverage habit in Ability development.**

### Sandbox rules to pre-brief

Claude doesn't know OpenHome's sandbox until you tell it. The ones it gets wrong most often:

* **No `asyncio.create_task`** — use `self.worker.session_tasks.create()`
* **No `asyncio.sleep`** — use `self.worker.session_tasks.sleep()`
* **No `print()`** — use `self.worker.editor_logging_handler.info() / .error()`
* **No raw `open()`** — use the file storage API (`write_file`, `read_file`, `check_if_file_exists`)
* **No top-level `import os`, `import signal`, `import json`** outside the register block
* **`#{{register capability}}`** is a literal comment tag, not a function call
* **Every `main.py` exit path must call `resume_normal_flow()`** — even exception handlers and timeouts

Pre-brief Claude on these once, and it'll stop making the same mistakes.

### Log-driven iteration

When something breaks, **paste the live Live Editor logs directly into Claude** and ask it to diagnose.

* Don't summarize
* Don't filter
* Don't try to identify the problem yourself first

The logs are the ground truth. Let the model see them raw.

### Deploy and test in a loop

The CLI's superpower is that Claude can build → deploy → test without you clicking anything:

```
1. Claude writes main.py
2. Claude: `openhome deploy ability .`  (CLI packages and uploads)
3. Claude: `openhome chat --agent <your agent>`  (chat with the Ability)
4. Claude reads the response, adjusts main.py, loops
```

This is the productivity unlock. See [OpenHome CLI](/guides/getting-started/cli) for the full command surface.

***

## Vibe coding dashboards with the Replit Agent

Replit's Agent is good, but only as good as the brief you give it. Be explicit about three things: **what is POSTing in**, **what the frontend will poll**, and **that state lives in memory**.

### Master prompt for the backend

Copy this, adapt the `<ability>` placeholder, paste it into the Replit Agent:

```
Build a Flask server that receives POSTs from an external Python client
(an OpenHome ambient voice ability) and serves a polling-based dashboard.

ENDPOINTS TO ACCEPT (all JSON POST):
- POST /api/<ability>/session_start — fires once when ability starts
- POST /api/<ability>/update — fast cycle updates every 10s
  Contains: tone, scene, speaker_state, running_log, cycle_id, elapsed
- POST /api/<ability>/deep — slow cycle updates every 60s
  Contains: deep_insight, emotional_arc, notable_moments
- POST /api/<ability>/heartbeat — lightweight keepalive every 10s when idle
- POST /api/<ability>/session_end — fires once when ability stops

READ ENDPOINTS (GET, returns JSON):
- GET /api/state — returns the current consolidated state object
- GET /api/history — returns the last N cycle snapshots

STORAGE:
- In-memory Python dict. No database. Last-write-wins per session_id.
- Keep the last 100 cycle snapshots in a collections.deque.
- On cold start, state is empty; the ability will re-flood within one cycle.

FRONTEND:
- Single index.html, vanilla JS, polls /api/state every 2 seconds.
- Tabs: LIVE, TIMELINE, SPEAKERS, INSIGHTS, RAW DATA.
- Use Tailwind via CDN. Dark theme. No frameworks.
- Each tab reads from the same STATE object; no extra API calls per tab.

CORS:
- Allow all origins. The POSTs come from a sandboxed environment
  with no fixed IP address.

ROBUSTNESS:
- Every endpoint must accept unexpected fields without erroring.
- Log the full payload to console on receipt for debugging.
- Return {"ok": true} on success. Never 500 on a missing field.
- Nested dicts should merge, not overwrite; lists should replace.

Bind to host="0.0.0.0", port=8080.
```

### Frontend-specific prompt

Run this as a **second prompt** after the backend is working. Mixing backend and frontend in one prompt produces muddled output.

```
Build a single index.html dashboard that polls GET /api/state every 2s
and renders the current state of an ambient voice ability.

LAYOUT:
- Fixed top header: session ID, elapsed time, connection status dot
  (green = updated in last 15s, amber = 15–60s stale, red = >60s)
- Five tabs below: LIVE, TIMELINE, SPEAKERS, INSIGHTS, RAW DATA
- Tab content fills the rest of the viewport

LIVE TAB:
- Large card showing current room energy / dominant tone
- Grid of speaker cards (one per speaker in speaker_registry)
- Each card: name/id, gender+age, current tone, energy bar, vocal quality

TIMELINE TAB:
- Vertical list of running_log entries, newest at top
- Each entry: timestamp badge, observation text
- Subtle color coding by entry type (arrival, topic shift, tone shift)

SPEAKERS TAB:
- Expanded speaker profiles with voice_signature, emotional_arc, undercurrent
- "Present" / "Left the room" badges

INSIGHTS TAB:
- The latest deep_insight payload rendered as a narrative card

RAW DATA TAB:
- Pretty-printed JSON of the full STATE object, in a scrollable pre block

STYLE:
- Tailwind via CDN. Dark theme (slate-900 background, slate-100 text).
- Accent: indigo-400. Use rounded-2xl cards with subtle ring-1 ring-slate-700.
- No frameworks. No build step. One file.
```

See [Build a Companion Dashboard](/guides/best-practices/companion-dashboard) for the full architecture this dashboard is serving.

***

## OpenHome Live Editor patterns

The Live Editor is where most of your Ability development happens. A few patterns will save you hours.

### Prompts at the top of the file

Every configurable LLM prompt, every URL, every constant belongs at the top of `main.py` as a named constant. When you want to fork an Ability for a new persona, you change the constants — **not the logic**. This rule is absolute.

```python theme={"system"}
# =============================================================================
# PROMPTS
# =============================================================================

SYSTEM_PROMPT = """..."""
INTENT_PROMPT = """..."""
FAREWELL_PROMPT = """..."""

# =============================================================================
# CONSTANTS
# =============================================================================

DASHBOARD_URL = "https://..."
HEARTBEAT_INTERVAL = 10
MAX_SPEAKERS = 4
```

### Use the register-capability tag, not a function call

Inside your `MatchingCapability` class, use the literal comment tag `#{{register capability}}` — not `register_capability()`. The platform rewrites the tag at load time. Writing the function call manually throws sandbox errors.

```python theme={"system"}
class YourCapability(MatchingCapability):
    worker: AgentWorker = None
    capability_worker: CapabilityWorker = None

    #{{register capability}}
```

### Log generously in dev, sparely in production

During development, log every cycle, every POST, every API response. `editor_logging_handler` is your only window into the Ability's behavior.

**Before shipping to the Marketplace**, trim log lines to the bare minimum — one line per cycle, plus errors. A silent Ability in production is a good Ability.

***

## Shared patterns and guardrails

These apply whether you're vibe coding the Ability or the dashboard.

### Forbidden imports and patterns

| Don't                                                | Use instead                                                   |
| ---------------------------------------------------- | ------------------------------------------------------------- |
| `import os`, `import signal`, raw `open()`           | Platform helpers — `play_from_audio_file()`, file storage API |
| `import redis`, `RedisHandler`, `connection_manager` | *(direct infra access is blocked)*                            |
| `print()`                                            | `self.worker.editor_logging_handler.info()` / `.error()`      |
| `asyncio.sleep`, `asyncio.create_task`               | `session_tasks.sleep()`, `session_tasks.create()`             |

Full list: [SDK Reference → Sandbox rules](/api-sdk/sdk-reference#sandbox-rules).

### The `session_tasks` pattern

All background work — heartbeats, parallel LLM calls, dashboard POSTs — goes through `self.worker.session_tasks.create(coro)`. This guarantees clean shutdown when the session ends.

```python theme={"system"}
# Good
self.worker.session_tasks.create(self.heartbeat_loop())

# Bad — blocked by sandbox
asyncio.create_task(self.heartbeat_loop())
```

### The two feedback loops

| Layer                           | Feedback mechanism                                                      |
| ------------------------------- | ----------------------------------------------------------------------- |
| Ability code in the Live Editor | `editor_logging_handler.info()` — visible live in the Editor's log pane |
| Dashboard POSTs                 | Replit's server logs — every request dumped to console                  |
| OpenHome CLI                    | Local terminal output + `openhome-cli` log lines                        |

Use whichever surface is live for the layer you're debugging. Don't try to guess.

### Never send raw audio to a dashboard

Keep audio local to the Ability. Send transcripts, summaries, tone descriptors, metadata — never PCM or WAV bytes.

* 1 minute of 16kHz 16-bit audio = **2 megabytes**
* 1 minute of transcript = **2 kilobytes**

## See also

* [OpenHome CLI](/guides/getting-started/cli) — terminal-based Ability development
* [SDK Reference](/api-sdk/sdk-reference) — methods, prompts, patterns, sandbox rules
* [Simple Abilities Cookbook](/building-abilities/cookbook) — 100+ copy-remix-ship ideas
* [Build a Companion Dashboard](/guides/best-practices/companion-dashboard) — architecture + snippets for the dashboard side
* [Voice-First Best Practices](/guides/best-practices/voice-first) — the UX rules LLMs forget
