Hold a natural spoken conversation with a coding agent — Claude Code or OpenAI's Codex — while it does real agentic work. Your Mac runs everything — the audio pipeline, the sessions, the state.
FAQ
remote-workstreams is a Claude Code plugin with 5 hand-picked skills for development work, indexed on Flowy. Install it with the command on its page. It includes deploy-rw, deploy-rw, role-convo. Its skills do not fire on their own yet. Request auto-invocation to have Flowy route them as you prompt. Free and open source.
> /plugin marketplace add ryan-scheinberg/remote-workstreams> /plugin install remote-workstreams@remote-workstreams
Hold a natural spoken conversation with a coding agent — Claude Code or OpenAI's Codex — while it does real agentic work. Your Mac runs everything — the audio pipeline, the sessions, the state. Your iPhone is a thin browser client reached over your own tailnet. No cloud infrastructure beyond the STT and TTS APIs; nothing between your phone and your Mac but Tailscale.
The core design: every model interaction is a real, interactive agent session,
living as a window in one tmux session on your Mac. Claude Code and Codex CLI are
both first-class engines — either alone is a complete install, and with both you
pick per conversation and per workstream. The phone and the laptop drive the same
sessions — walk over, tmux attach, keep typing. Every session inherits your full CLI setup (skills, hooks, instructions,
permission rules) natively, and no model API key exists anywhere — all model use
rides the CLIs' own auth. A persistent conversation session talks with you; planner
and injector sessions turn that conversation into workstreams — execution
sessions you watch as live cards on the phone. Claude sessions launch with
--remote-control, so each one also shows up in the Claude iOS app — you get a
native ping when a session goes live, and a second window into any workstream. If
the phone drops — call, dead spot, Safari suspending — the sessions live on in tmux;
reconnect and resume mid-conversation.
+ Workstream marks the conversation since the last launch, has a planner
session distill it into a stint plan, and launches an execution session on it —
one button, no review step.Send latest
routes the newest conversation delta through an injector session into that
workstream; Check in has the conversation read the workstream's transcript and
answer out loud.plans row picks who runs the planner and injector — opus
thinking at high or luna at xhigh. Every row offers only the engines wired on
the Mac — a Codex-only box shows the Codex tiers alone. Every button on the phone
arms-then-confirms — the armed label states the consequence (blue swap? is
safe, red clear? wipes the conversation).The model name carries the engine: pick a Claude model and the session is Claude
Code; pick a Codex model and it's Codex CLI (model lists live in
remote_workstreams/engines.py). A Claude-to-Claude conversation pick switches the
live session in place; switching engines (or between Codex models) starts a fresh
conversation, announced before you confirm. Running workstreams always keep the
engine and model they launched with. Codex workstreams are renamed to their stint
title in ChatGPT as they launch and archived when you tap End. The planner and injector sessions behind
+ Workstream and Send latest run on whichever engine installed the service
(store-configurable), so a single-engine box is fully functional either way.
Engine differences that show: Codex has no known-in-advance session id, so the service discovers each session's rollout file on disk; Codex workstreams run with full filesystem access and approval prompts disabled because the phone-approval relay is a Claude Code hook; and Codex sessions don't resume across service restarts — a dead Codex conversation window starts fresh. Sol and Terra use Codex's V2 multi-agent runtime.
The launchd service runs codex from its Homebrew-aware PATH. Codex conversations
also appear in the ChatGPT app's history, so a live workstream can be opened there
without separate pairing.
iPhone (Safari PWA) ──WebSocket/HTTPS over Tailscale──> Mac
├─ FastAPI service (launchd, persistent)
│ ├─ Audio pipeline: Deepgram STT ⇄ VAD ⇄ Cartesia TTS
│ ├─ tmux session "voice": convo + workstream
│ │ sessions — Claude Code or Codex CLI
│ │ (attach from any terminal)
│ ├─ Transcript tailing: the session's JSONL is the chat
│ ├─ Store: SQLite (credentials, session ids, markers)
│ └─ Static PWA
└─ tailscale serve (TLS on the MagicDNS name)
role-root skill on your global skill path launches every
workstream into a role instead of a bare session — deploy-rw detects it and
falls back cleanly if it's missing.
ryan-scheinberg/harness is the
reference implementationThe install is one guided skill — run it from whichever CLI you prefer. It wires every engine you have (asking first), so installing through one still makes the other pickable from the phone.
From Claude Code:
/plugin marketplace add ryan-scheinberg/remote-workstreams
/plugin install remote-workstreams@remote-workstreams
/remote-workstreams:deploy-rw
From Codex:
codex plugin marketplace add ryan-scheinberg/remote-workstreams
codex plugin add remote-workstreams@remote-workstreams
then start codex and ask for $deploy-rw.
The deploy is run by the agent on your Mac. It confirms every system-touching action with you before running it, and it is safe to re-run — it doubles as repair. What it does:
~/remote-workstreams)/healthztailscale servetailscale serve
exposes it, and only devices on your tailnet can reach it at all.remote-workstreams) — provider keys, and
only a hash (scrypt) of the PIN. Nothing secret in config files.Measured, not promised: every turn logs endpoint → transcript → first-sentence → first-audio timestamps. Replies come from a real interactive session, so expect the pace of a thoughtful colleague, not a kiosk — sentence-chunked streaming TTS starts speaking as soon as a reply lands, and barge-in (speak over the assistant and it stops) keeps you in control.
uv sync # install (Python 3.12/3.13)
uv run pytest # test suite (no live API calls; SDK boundaries are mocked)
uvx ruff check . # lint
| Path | What it is |
|---|---|
remote_workstreams/substrate.py | tmux substrate — spawn/inject/kill Claude Code and Codex sessions as windows |
remote_workstreams/transcript.py | Claude Code transcript JSONL parsing (the only CC-format-aware module) |
remote_workstreams/rollout.py | Codex rollout JSONL parsing (the only Codex-format-aware module) |
remote_workstreams/engines.py | model ↔ engine registry — which models run on which CLI |
remote_workstreams/convo.py | ConvoBridge — the voice/UI face of the persistent conversation session |
remote_workstreams/protocol.py | WebSocket messages client ⇄ server, audio formats |
remote_workstreams/config.py | Runtime config; REMOTE_WORKSTREAMS_* env overrides |
remote_workstreams/keychain.py | Secrets via the macOS Keychain; env vars win in dev/tests |
remote_workstreams/adapters/ | STTAdapter, TTSAdapter + Deepgram, Cartesia implementations |
remote_workstreams/audio/ | Pipeline state machine (listening/thinking/speaking/interrupted), round-trip test |
remote_workstreams/server/ | FastAPI service, WebSocket, workstreams, approvals, SQLite store, auth |
remote_workstreams/web/ | The static PWA |
hooks/ | ask_phone.py — the phone-approval relay hook client |
skills/ | role-convo, role-stint-plan, role-inject, and the deploy-rw skill |
plugins/claude-code/ | Claude Code plugin wrapper (/remote-workstreams:deploy-rw + skills) |
plugins/codex/ | Codex plugin wrapper ($deploy-rw; carries a copy of the deploy skill — pinned by a test) |
tests/ | pytest, mirroring module names |
tailscale funnel can expose the service to the public internet. This is documented as
a pointer only and is unsupported: remote-workstreams's auth assumes the tailnet perimeter,
and v1 has no public-internet hardening (rate limiting, lockout). Don't do it unless you
understand exactly what you're removing.
.agents/
plugins/
marketplace.json
.claude-plugin/
marketplace.json
.gitignore
.python-version
AGENTS.md
deploy/
com.remote-workstreams.server.plist.template
docs/
phone-detail.png
phone-main.png
phone-menu.png
hooks/
ask_phone.py
LICENSE
plugins/
claude-code/
.claude-plugin/
plugin.json
commands/
deploy-rw.md
skills/
deploy-rw
role-convo
role-inject
role-stint-plan
codex/
.codex-plugin/
plugin.json
skills/
deploy-rw/
scripts/
check.sh
install_service.sh
store_secret.sh
SKILL.md
pyproject.toml
README.md
remote_workstreams/
__init__.py
adapters/
__init__.py
cartesia_tts.py
deepgram_stt.py
stt.py
tts.py
ambient.py
audio/
__init__.py
chunker.py
echo.py
pipeline.py
roundtrip.py
state.py
bootstrap.py
config.py
convo.py
engines.py
keychain.py
protocol.py
rollout.py
server/
__init__.py
__main__.py
api.py
app.py
approvals.py
auth.py
logs.py
runtime.py
store.py
workstreams.py
ws.py
substrate.py
transcript.py
web/
app.js
audio-worklet.js
audio.js
icon.svg
index.html
manifest.webmanifest
pairing.js
styles.css
ui.js
skills/
deploy-rw/
scripts/
check.sh
install_service.sh
store_secret.sh
SKILL.md
role-convo/
SKILL.md
role-inject/
SKILL.md
role-stint-plan/
SKILL.md
tests/
conftest.py
server_fakes.py
test_api.py
test_approvals.py
test_ask_phone.py
test_audio_chunker.py
test_auth.py
test_bootstrap.py
test_cartesia_tts.py
test_config.py
test_convo.py
test_deepgram_stt.py
test_echo.py
test_integration_assembly.py
test_live_convo.py
test_pipeline.py
test_plugins.py
test_protocol.py
test_rollout.py
test_roundtrip.py
test_state.py
test_store.py
test_substrate.py
test_transcript.py
test_web_assets.py
test_workstreams.py
test_ws.py
uv.lock© 2026 Flowy · Free and open source
Built for Claude Code · Not affiliated with Anthropic