feat(interactive): add interactive Claude REPL provider with origin-based routing - #156
feat(interactive): add interactive Claude REPL provider with origin-based routing#156ryuhaneul wants to merge 2 commits into
Conversation
5cd132a to
0161037
Compare
|
Ha, love this — you're ahead of all of us here. Quick heads up: Anthropic paused the But I'm keeping it open. Let's keep it in sync with upstream so we can flip it on when it's needed. I'll do a review pass on the routing + tmux pool. Really nice work 🙏 |
|
@ryuhaneul Claude answered it 😂 I've removed the notice for now; Antrophic sent out emails saying not to do that for the time being! Yay. https://x.com/i/status/2066608004464861546 If anything changes, I'll get back to you on your PR! Thanks for your work! |
|
Thanks — really glad it resonated! And good to hear Anthropic paused it; no rush totally makes sense. I'll keep the branch current if anything shifts on their end. Honestly, I've tried a lot of the similar agent frameworks out there, and I haven't found anything as pleasant to actually live in day-to-day as Ductor. Thank you for building it — it's genuinely become part of my daily workflow. Happy to help however I can. 🙏 |
0161037 to
fd4cae8
Compare
|
Status update after a full review pass (as part of a repo-wide PR triage): keeping this open as agreed — the billing change is paused, so no merge for now. The review confirmed the architecture is solid (nonce-based stop-hook/transcript extraction instead of TUI scraping, fail-closed origin routing verified across all 11 AgentRequest construction sites, clean env allowlisting). Two blockers to address before we flip it on, whenever that day comes:
Also worth noting for the rebase whenever it happens: main has since merged per-session reasoning effort (#164), append_system_prompt_files (#157), codex stdin prompts (#137) and topic-scoped kills (#166) — Thanks again for keeping this in sync 🙏 |
73de643 to
4cebbe8
Compare
…ased routing As of June 15, 2026, `claude -p` / Agent SDK calls draw a separate monthly credit rather than the Claude plan's interactive limits (see PleasePrompto#154). Human chat in Ductor is inherently interactive, so this routes only that path through a persistent interactive `claude` REPL instead of headless `-p`. Unattended automation (cron, heartbeat, webhooks, background tasks, memory flush, inter-agent injection) deliberately stays headless -- an explicit boundary, not a limit workaround. Scope (origin-based routing): a turn uses the interactive REPL only when origin is a human chat message, provider is `claude`, and `claude_interactive` is enabled. Everything else stays on `-p`. Opt-in, default off, fail-closed. What it adds: - Persistent tmux-backed `claude` REPL pool (spawn/send/kill/idle-evict), keyed per (transport, chat, topic). - Turn completion via a Stop hook + transcript nonce extraction. - `/interactive on|off` command (hot-reload toggle). - A dedicated `claude:i` session bucket, isolated from the `-p` `claude` bucket. - The REPL uses the same Claude config dir and the same DUCTOR_* environment as the `-p` path, so authentication and the agent tooling / inter-agent helpers behave identically inside an interactive turn. - Fail-fast on fatal REPL screens (rejected/inaccessible model, invalid key, not-logged-in / 401) instead of blocking until timeout. - `env -i` allowlist for the REPL subprocess (no host environment leak). Not a complete implementation; the `/interactive` toggle starts a separate conversation thread (interactive and `-p` sessions are intentionally isolated), and automation paths are intentionally excluded.
4cebbe8 to
2b46163
Compare
Motivation
As of June 15, 2026,
claude -p/ Agent SDK calls draw a separate monthlycredit rather than the Claude plan's interactive limits (see #154). Human chat
in Ductor is inherently interactive — a person sends each message on demand —
so this routes only that path through a persistent interactive
claudeREPLinstead of headless
-p. Unattended automation (cron, heartbeat, webhooks,background tasks, memory flush, inter-agent injection) deliberately stays
headless. This is an explicit boundary, not a limit workaround.
Background. I've been running a version of this on my own Ductor instance
since Anthropic's May 2026 announcement about Agent SDK /
claude -pbilling.As upstream is moving in the same direction, I've refactored it to match
upstream conventions and am submitting it here for consideration.
Note (June 16, 2026). Anthropic has since indicated the SDK-credit
transition will not be enforced immediately — headless
-pstill draws thenormal subscription limits for now. So there's no urgency to adopt interactive
mode today; I'm opening this for direction/discussion rather than as a
time-sensitive change.
Scope (origin-based routing)
A turn uses the interactive REPL only when all hold:
claude,claude_interactiveis enabled in config.Everything else stays on
-p. Opt-in, default off, fail-closed.What it adds
claudeREPL pool (spawn / send / kill / idle-evict),keyed per
(transport, chat, topic)./interactive on|offcommand (hot-reload toggle).claude:isession bucket, isolated from the-pclaudebucket.DUCTOR_*environment as the
-ppath, so authentication and the agent tooling /inter-agent helpers behave identically inside an interactive turn.
not-logged-in / 401) instead of blocking until timeout.
env -iallowlist for the REPL subprocess (no host environment leak).Validation
Not fully validated, but verified working in the following:
Automated (test suite): added targeted tests for origin-based routing /
human-only gating,
claude:ibucket isolation, Stop-hook + transcript nonceextraction (including the tool-result boundary), fatal-screen fail-fast
detection, Claude config-dir parity with
-p,DUCTOR_*env injection(host + container) with the
env -ino-leak guarantee, per-(transport, chat, topic)session identity, and transport-scoped abort. Full suite green.Manual end-to-end (a live single-agent instance):
-p(no separate login);/interactive on|offtoggles REPL ↔-p;DUCTOR_HOMEresolution);DUCTOR_AGENT_NAME/ interagent port; async results return to theoriginating chat/topic);
auth-failed / rejected-model screen.
Not yet exercised: long-running soak, multi-transport concurrency, and the
toggle-unification path below.
Status / limitations
This is not a complete/exhaustive implementation — it covers the
human-chat path and is offered for discussion:
/interactivestarts a separate conversation thread: theinteractive (REPL) and headless (
-p) sessions are intentionally isolated,so context does not carry across the toggle. Unifying them is future work.