Skip to content

Latest commit

 

History

History
98 lines (67 loc) · 4.18 KB

File metadata and controls

98 lines (67 loc) · 4.18 KB

AGENTS.md

Compact guidance for agents working in this repo. Every line here is something you'd likely get wrong without help.

Core constraint

When using duet, produce HTML artifacts — not markdown plans, not text descriptions. The entire point is that plans, dashboards, wireframes, architecture diagrams, and code diffs should be rendered as HTML so humans can visually review and annotate them. Markdown plans are invisible feedback opportunities. HTML artifacts are actionable.

If you are an AI agent integrating with duet: your output for visual review must be an HTML file, opened via duet open. Text-only output bypasses the review layer entirely.

Commands

# Run all tests
pytest

# Run a single test
pytest tests/test_core.py -k test_name

# Run the CLI
python -m duet.cli open path/to/artifact.html
python -m duet.cli export path/to/artifact.html
python -m duet.cli poll path/to/artifact.html

# Get artifact-type guidance (playbooks for landing pages, dashboards, etc.)
python -m duet.cli playbook
python -m duet.cli playbook landing-page

# Get design system guidance + CDN snippets
python -m duet.cli design

# Run the server standalone
python -m duet.server_main --port 4387

# Install agent hooks
duet setup hooks

No pip install -e . needed for dev — just run directly. The duet script entry point requires package install.

Architecture

CLI → detached server

duet/cli.py spawns a detached background server (duet/server_main.py) if one isn't running. The CLI talks to the server via httpx on localhost:4387. State persists to ~/.duet/state.json.

Do not import server modules from CLI code. The boundary is HTTP, not Python imports.

Server routes (duet/server.py)

DuetServer._register_routes() on the FastAPI app. Key endpoints:

  • GET /api/poll?file=... — long-poll, blocks until feedback arrives (used by AI agents)
  • GET /events/{key} — SSE for browser clients (reload, chat sync, agent presence)
  • GET /artifact/{key}/ — serves HTML with annotation SDK injected

State (duet/session_store.py)

JSON-file store with asyncio.Lock. Sessions keyed by sha256(filepath)[:16]. All mutations must be inside async with self._lock. The take_feedback method atomically drains prompts + warnings.

Export (duet/export_bundle.py)

Inlines local CSS/JS/images as data URIs or <style>/<script> blocks. Remote references left as-is. Caps: 10MB per asset, 25MB per bundle. SDK script stripped from exports.

Environment

All config via env vars — no config files:

  • DUET_PORT (default 4387)
  • DUET_HOST (default 127.0.0.1)
  • DUET_STATE_DIR (default ~/.duet)
  • DUET_HTML_APP_API_URL, DUET_HTML_APP_TOKEN (publishing)
  • DUET_IDLE_TIMEOUT_MS, DUET_EXPORT_MAX_ASSET_BYTES, DUET_EXPORT_MAX_BUNDLE_BYTES

Agent integration loop

The intended workflow for an AI agent using duet:

  1. duet open artifact.html — starts session, opens browser
  2. Human annotates, sends feedback
  3. duet poll artifact.html — blocks until feedback arrives (structured: element + prompt + DOM snapshot)
  4. Agent makes changes to the HTML file
  5. File watcher triggers live reload in browser
  6. Repeat from step 2

Gotchas

  • Server must be running before CLI commands that talk to it — CLI auto-starts it but there's a brief startup race
  • ~/.duet/whiteboards/<key>/<index>.json — sidecar files for Excalidraw scenes
  • JS/CSS assets in duet/assets/ are vendored plain files, no build step. SDK assembled at runtime from artifact-sdk.js + mermaid-node.js
  • Playbooks source is duet/assets/playbooks.json (serialized from upstream JS module)
  • Tests are in tests/ — test_core.py, test_e2e.py, test_whiteboard_channel.py, test_e2e_browser.py (requires Kimi WebBridge daemon)
  • duet playbook returns artifact-type guidance (landing pages, dashboards, etc.) — use it to shape what you generate
  • duet design returns design system guidance with DaisyUI CDN snippets — use it for styling

Conventions

  • Python 3.10+ required, compiles to 3.10 syntax
  • httpx for all HTTP, never requests
  • asyncio.Event for long-poll blocking, not sleep loops
  • html.parser.HTMLParser for HTML rewriting in exports (not lxml for that part)