tandemir
v0.0.58
Published
Companion daemon for builders working with AI sidekicks. Tandemir watches files in a project, runs lightweight agents in sandboxes on each save, and surfaces each agent's output — plus a chat with an orchestrator — in a built-in browser UI.
Readme
tandemir
Companion daemon for builders working with AI sidekicks. Tandemir watches files in a project, runs lightweight agents in sandboxes on each save, and surfaces each agent's output — plus a chat with an orchestrator — in a built-in browser UI.
Status: Slice 3. Web UI, WebSocket protocol, orchestrator chat, a model-picker dropdown, an agent settings panel, @-tuning, and specialist Apply/Reject proposals are in. The heavy (opencode) runtime lands in a later slice.
What it does
- You point tandemir at a project directory containing
.tandemir/agents/<name>.jsonconfigurations. - Each agent declares the files it watches, the folder it owns, and the artifact path it produces.
- When watched files change, the agent runs in a sandboxed copy of the project, calls an LLM with file tools (
write_file,read_file,list_files), and writes its output. - Files written under the agent's
ownedFolderare auto-synced back to the workspace. - The daemon serves a SolidJS UI:
- Chat — talk to the orchestrator; it can spawn agents and propose file writes you Apply or Reject. Tune any agent inline with
@AgentName <note>. - One tab per agent — live artifact preview, recent run logs, and a ⚙ settings panel (model dropdown, prompt, watches, owned folder, debounce, tunings).
- + — configure and start a new agent.
- Chat — talk to the orchestrator; it can spawn agents and propose file writes you Apply or Reject. Tune any agent inline with
- A per-agent static HTTP route under a random prefix serves the artifact iframe; the UI listens for
artifact_changedevents on the WebSocket to refresh.
Prerequisites
- Bun 1.3+
- An OpenRouter API key
Quick start
bun install
(cd web && bun install)
bun run build
export OPENROUTER_API_KEY=sk-or-...
bun run start examplebun run start boots the daemon, serves the built UI, and auto-opens your browser. Use --no-open to skip the browser, --port N to bind a different port (default 7777).
Edit example/spec.md, save, and watch the agent's artifact tab refresh.
Editing agents (Slice 3)
- Click the ⚙ on an agent tab to open its settings — change model (dropdown), prompt, watches, owned folder, or debounce, then Save.
- Tune an agent inline from chat:
@AgentName <note>adds a sticky tuning and reruns the agent. Remove tunings from the settings tab. - When an agent writes files outside its owned folder, those writes appear in chat as Apply/Reject proposals (capped at 5 per run).
Split-host layout
The UI supports a multi-pane layout with up to 4 hosts. Each host has its own tab strip and renders its active tab independently.
- Layout dropdown (top toolbar) — choose between: Single, 2 Columns, 2 Rows, 1+2 Columns, 1+2 Rows, or Grid (2×2).
- Drag and drop — reorder tabs within a host or drag them between hosts. An insert indicator shows the drop position.
- Focus — click a host pane to focus it. New tabs open in the focused host. The focused host shows an accent-colored top border.
- Persistence — layout and tab assignments are saved to localStorage and restored on page reload.
Development
Two-terminal workflow with the Vite dev server in front of the daemon:
# Terminal A — daemon (no browser auto-open; UI comes from Vite below)
OPENROUTER_API_KEY=sk-or-... bun run src/cli.ts start example --no-open
# Terminal B — Vite dev server with HMR
cd web && bun run devThen open http://127.0.0.1:5173/. Vite proxies /ws (the WebSocket) and the per-agent static routes (/<12-hex>/...) to the daemon on :7777, so HMR works for the UI while the backend stays live.
bun test # run the suite
bun run typecheck # tsc --noEmitProject layout
src/
cli.ts CLI entry; wires bus, manager, orchestrator, server, ws
config.ts Loads .tandemir/config.json + agents/*.json; enforces invariants
paths.ts safeJoin and path helpers
manager.ts Watcher → debounce → run lifecycle per agent
runner.ts One agent run: sandbox → LLM tool loop → diff → sync
watcher.ts Debounced chokidar wrapper
sandbox.ts Per-run temp dirs under .tandemir/sandboxes/
tools.ts Sandbox-bounded file tools (write/read/list)
llm.ts OpenRouter SDK + specialist tool loop driver
diff.ts Classifies sandbox-vs-workspace changes
sync.ts Applies owned-folder changes back to the workspace
server.ts Hono server: WS upgrade, static SPA, per-agent prefixes
protocol.ts Wire types shared with the web UI (events + commands)
bus.ts Typed publish/subscribe event bus
chatlog.ts Persistent chat history under .tandemir/chat.log
proposals.ts Persistent proposal store (used in later slices)
artifact-watcher.ts Watches each agent's artifactPath; emits artifact_changed
orchestrator.ts Orchestrator agent (chat + spawn/close tools)
orchestrator-client.ts OpenRouter client tuned for the orchestrator
ws.ts WebSocket handler: routes commands, mirrors bus events
types.ts Shared types
web/
src/
main.tsx SolidJS entry; layout toolbar + host grid
store.ts App store (agents, chat, configure tabs, toasts)
layout-store.ts Multi-host layout state, tab assignment, persistence
ws.ts Client WebSocket; mirrors server protocol
protocol.ts Mirror of src/protocol.ts for the browser
components/
LayoutToolbar.tsx Layout preset dropdown + connection status
HostGrid.tsx CSS grid container rendering N host panes
HostPane.tsx One host: tab strip + routed content area
HostTabStrip.tsx Per-host tab strip with drag-and-drop
ChatTab.tsx, AgentTab.tsx, ConfigureTab.tsx, SettingsTab.tsx, etc.
tests/
layout-store.test.ts Unit tests for layout state logic
vite.config.ts Dev server + /ws and /<prefix>/ proxies to :7777
tests/ Unit tests + e2e
docs/superpowers/
specs/ 2026-05-19-tandemir-design.md
plans/ 2026-05-19-tandemir-slice-1-artifact-pipeline.md
2026-05-19-tandemir-slice-2-ui-orchestrator.md
example/ Minimal demo projectConfiguration
Environment variables
| Variable | Required | Default | Description |
|---|---|---|---|
| OPENROUTER_API_KEY | Yes | — | Your OpenRouter API key. |
| OPENROUTER_BASE_URL | No | https://openrouter.ai/api/v1 | Base URL for the OpenRouter-compatible API. Override to use a proxy, self-hosted endpoint, or alternative provider. |
| TANDEMIR_PORT | No | 7777 | Default port when --port is not specified on the CLI. |
| TANDEMIR_ORCHESTRATOR_MODEL | No | anthropic/claude-haiku-4-5 | Default orchestrator model when not set in .tandemir/config.json. Per-project config takes precedence. |
| OPENCODE_BIN | No | ~/.opencode/bin/opencode | Path to the opencode binary for heavy agents. Falls back to $PATH lookup if not set and the default path doesn't exist. |
Project config
.tandemir/config.json (optional — defaults shown):
{
"defaultDebounceMs": 1000,
"defaultModel": "anthropic/claude-sonnet-4-6",
"orchestrator": {
"enabled": true,
"model": "anthropic/claude-haiku-4-5",
"prompt": "You are the orchestrator. Help the user manage their agents."
}
}orchestrator is optional. If omitted or enabled: false, the Chat tab still loads but no orchestrator responds.
The orchestrator.model field takes precedence over TANDEMIR_ORCHESTRATOR_MODEL.
.tandemir/agents/<name>.json:
{
"name": "Mike",
"description": "Builds a small hello-world HTML based on spec.md.",
"prompt": "You write a single-page HTML file that reflects the user's spec.",
"runtime": "light",
"model": "anthropic/claude-haiku-4-5",
"watches": ["spec.md"],
"ownedFolder": "prototype",
"artifactPath": "prototype/index.html",
"debounceMs": 1000,
"inflightStrategy": "queue",
"tunings": []
}Invariants enforced at load:
- No two agents share an
ownedFolder(single writer per folder). - No two agents share a
name. artifactPathmust be insideownedFolder.- No
watchesglob may match theownedFolder. runtimemust be"light"for now ("opencode"lands in Slice 4).inflightStrategymust be"queue"for now ("cancel-restart"lands in Slice 4).
Design and roadmap
The full design lives in docs/superpowers/specs/2026-05-19-tandemir-design.md.
Slice plans:
- Slice 1:
docs/superpowers/plans/2026-05-19-tandemir-slice-1-artifact-pipeline.md - Slice 2:
docs/superpowers/plans/2026-05-19-tandemir-slice-2-ui-orchestrator.md
Upcoming slices:
- Slice 4 —
opencodesubprocess integration (heavy agents), CLI lifecycle (tandemir stop), polish.
CLI
usage: tandemir start <project-dir> [--port N] [--no-open]
tandemir stop <project-dir>
tandemir --help
Flags:
--port N Port to bind (default: TANDEMIR_PORT or 7777).
--no-open Do not auto-open the browser.See Environment variables above for all supported env vars.
