Building a Claudix-Style Agent Plugin: The Three-Process Architecture That Makes It a Real IDE Host
This article details the engineering behind a Claudix-style VS Code plugin that transforms a chat sidebar into a production-grade Agent host, covering the critical three-process architecture (Webview, Extension Host, CLI worker), PID recycling, permission handling without blocking the message pump, persistent configuration via symlinks and backups, and concurrency control for cloud IDE environments.
Core Concept: Three-Process Architecture
The author defines a three-process model as the foundation of any Claudix-style Agent plugin:
Webview – the sidebar UI for display and interaction.
Extension Host – the plugin backbone that decides where to write, when to kill processes, and how to prompt for confirmation.
CLI worker – the bundled command-line binary that actually runs the tool loop (Reason → Tool → Observe).
Additional key terms: Channel ID (conversation connection identifier that must be rebuildable),
Message Pump</strong> (host loop that reads messages; must not be blocked by long waits), <code>Execution Authorization(allow read/write/tool calls, usually via modal), Host Authorization (control process count, write paths, lifetime, clean shutdown), Bundled CLI (CLI binary packaged inside the extension, not relying on system PATH), Symlink Persistence (link config directory to a persistent volume to survive container restarts).
Why a Simple Chat Sidebar Is Not Enough
The author uses an orchestra analogy: Webview = stage, Extension Host = conductor, CLI worker = musicians, config/session persistence = sheet music. Four real-world failure modes illustrate the gap:
Scene A : User clicks stop, then sends again → UI spins forever. Root cause: Host closed the old channel but Webview still holds the stale Channel ID; the message pump never receives a response.
Scene B : Overnight restart loses session and MCP config. Root cause: Config written to home directory, which in cloud IDEs often isn't on a persistent volume.
Scene C : User thinks process stopped, but files keep changing. Root cause: SDK spawns CLI worker but doesn't expose its PID; closing the channel abstraction doesn't kill the OS process.
User expectations: read/write workspace, run terminal, dangerous ops ask for confirmation, survive IDE restart, stop truly stops. All four live in the plugin engineering layer, not in model intelligence.
Industry Landscape
A comparison table positions major players:
Cursor – deep integration, product-grade confirmations, local-first.
Continue – extension + configurable backend, open-source skeleton.
Cline – extension launches Agent, human-in-the-loop confirmation is selling point.
Claude Code / CLI-first – terminal-centric, terminal confirmation flow.
Claudix-style custom – Webview + Extension + Bundled CLI , Modal + modes + ACL, volume persistence + collaboration isolation .
Takeaway: Open-source extensions teach UI scaffolding; CLI tools teach Agent loops; cloud IDE plugins teach how the host survives .
Design Conclusions: Architecture Before UI
Because the CLI worker does the real work, the plugin must control its lifecycle. Because permission prompts happen in Webview, the message pump cannot await them. Because cloud IDE home directories are ephemeral, config roots must be actively symlinked to persistent volumes.
The author distinguishes three roles:
Extension – presentation, confirmation, write decisions, process kill.
Agent SDK – creates connections, message streams, permission callback entry points.
CLI Runtime – actually runs the tool loop.
Mnemonic: SDK builds the instrument, CLI worker is the musician, Extension is the conductor.
Core loop: User input (Webview) → Extension Host (launch/permission/channel) → CLI worker (Reason → Tool → Observe…) → Confirmation Modal → Continue → Stop (close channel + kill worker). Two extra hops vs. headless: UI hop and Host hop.
Two layers of authorization: Execution Authorization (allow file read/tool/write) and Host Authorization (process count, write paths, lifetime, clean stop). The latter is often treated as an implementation detail but is elevated to first-class here.
Mandatory Shells (Because X, Therefore Y)
4.1 Bundled CLI, Not PATH
Cloud IDEs and teammate machines have drifting global CLI versions. The SDK must spawn the binary from the extension directory. Cost: larger install, version sync, packaging errors become P0. But it eliminates "works on my machine" debugging.
4.2 Permission Requests Must Not Block the Pump
Permission modals can hang for minutes. The message pump must dispatch and return immediately; other responses and streaming messages must keep flowing.
// TypeScript – dispatch without await in the pump loop
void dispatchPermission(req).catch(logError); // do NOT await here # Python – headless fallback: deny AskUserQuestion, return text options
async def on_pre_tool_use(name: str, payload: dict) -> dict:
if name == "AskUserQuestion":
return {"decision": "deny", "reason": "Please reply with text choices"}
return {"decision": "allow"}Question-type tools in IDE use a dedicated Modal and never enter an auto-allow whitelist . Copying headless auto-allow behavior into a plugin is copying the wrong homework.
4.3 Stop Must Allow Restart
Stop closes the channel. Next send must not cling to the old Channel ID. Solution: idempotent relaunch on send path – reuse active channel or create new; merge in-flight promises; on failure, flip busy back to false.
4.4 Config & Activation Order
Two-tier config: user-level + workspace-level. Shared containers add: write-permission code adjudication (ACL), not prompt persuasion; per-user isolated config subdirectories.
Activation sequence (wrong order breaks everything):
Persistence first (symlink/backup) – before any default config read.
Bootstrap & dependency injection.
Attach Transport, ACL, Webview.
Counter-intuitive production detail: The primary creator of the symlink may be the container entrypoint script – extension logic often just self-heals because headless runtime may write config before the IDE extension loads.
Hard Problems: Battle-Tested Moats
5.1 PID Recycling: SDK Doesn't Give You the PID, You Still Own It
Symptom: Processes linger after stop; CPU climbs after uninstall. Root cause: SDK spawns internally; closing channel ≠ killing OS process. Since no official PID API, hook spawn at module load (e.g., watch spawn, only own CLI), shutdown with SIGTERM → timeout SIGKILL.
// TypeScript – guarantee clean process table after stop/uninstall
const worker = await watchNextCliSpawn();
await runQuery();
await terminate(worker, { escalateMs: 2000 }); # Python – guarantee cancelled subprocess stops writing
proc = await asyncio.create_subprocess_exec(*cmd)
try:
await run_agent(proc)
finally:
proc.terminate()
try:
await asyncio.wait_for(proc.wait(), timeout=2)
except asyncio.TimeoutError:
proc.kill()Without this: workers accumulate, mistaken for undersized machines; worse, user thinks it's stopped while it keeps writing.
5.2 Concurrent Launch: Multi-Session Restore Causes OOM
Symptom: IDE opens, multiple historical sessions restore simultaneously, container killed. Root cause: N launches all see "memory ok" → spawn together → cgroup OOM. Fix: Serial "memory permit" – FIFO, refresh real available memory before granting, hold lock until spawn completes, timeout release, human-readable "out of memory" error.
5.3 Persistence: Symlink ≠ Backup
Symptom: Home dir evaporates; or symlink exists but content empty. Defense in depth:
Symlink config dir to data volume.
Scheduled backups.
Empty directory auto-restores from backup.
Symlink governs where to write ; backup governs rollback . Doing only one leaves the other failure mode open.
5.4 First Screen: Don't Put CLI Cold-Start on Critical Path
User needs "clickable" first. Mark connected immediately, load config in background; don't block first paint on CLI cold-start. Pre-warming is a speed-up, not a prerequisite.
5.5 Shared Disk ACL: Fail-Closed
Prompt persuasion is not a guardrail. Enforce write permissions in code; if ACL not ready, deny write. "Open first" on shared containers invites accidental deletion.
5.6 No God Class
Day one, split by domain: thin Host + thick handlers (session, permission, persistence, worker registry each own a slice). God class becomes untouchable after three months; production incidents love untouchable code.
End-to-End Conversation Flow
Activate – Decision: Is config dir symlinked to volume? No → symlink / restore backup. Before any config read.
Sidebar First Paint – Decision: Wait for config load or enable click first? Click first ; config loads in background.
Send – Acquire launch permit (serial) → spawn bundled CLI → register PID → SDK query. Decision: Reuse existing active channel if present.
Streaming Render – Message chunks to Webview. Sensitive op → Modal ( non-blocking ); shared-disk path → ACL check. Decision: Question tool? → Dedicated Modal, never auto-allow.
Stop – Close channel + SIGTERM/SIGKILL. Decision: Next send? → Idempotent relaunch, on failure reset busy.
Background – Backup keeps running; container rebuild relies on volume + backup, not wishful thinking about home dir.
Skipping the "Webview + HTTP Chat" shell saves dev time but buys on-call pain: each omitted piece (PID, permission pump, ACL, volume, backup) maps to a production incident class.
Host Selection Matrix
Five dimensions across four host types:
Permission UI : Chat shell (none), Claudix (Modal + modes), Headless (hook text fallback), CLI (terminal flow).
Process Reclamation : Chat (no CLI), Claudix (PID + watchdog), Headless (cancellation scope), CLI (user Ctrl+C).
Cloud IDE Persistence : Chat (relies on backend), Claudix (symlink + backup), Headless (task-state/artifact channel), CLI (local home).
First Screen : Chat (HTTP latency), Claudix (connect first, config later), Headless (pre-warm is speed only), CLI (no Webview).
Collaboration ACL : Chat (rare), Claudix (code adjudication), Headless (runtime injection), CLI (rare).
Choose Claudix-style when: Humans must approve in-editor, workspace is source of truth, must survive restarts & mis-writes. Choose Headless when entry is IM/HTTP with no reliable UI. Choose Chat shell only for demos, no disk writes. Choose CLI for local power users; multi-tenant cloud IDE is not its home turf.
Four Common Pitfalls
Pitfall 1: await Blocks Pump on Permission
Trigger: Transport "await on request" is easiest to write.
Wrong fix: Shorten modal timeout – pump still blocked, just fails faster.
Right fix: Dispatch long-hanging requests and return immediately; pump keeps reading; result returns via callback/response message.
Pitfall 2: Stale Channel ID After Stop
Trigger: Stop only closes Host side; Webview state not reset.
Wrong fix: Tell user "reload window" – product failure.
Right fix: Send path idempotent relaunch; failure resets busy; stop synchronously clears Webview channel reference.
Pitfall 3: Concurrent Multi-Session Launch
Trigger: Promise.all on history restore is tempting.
Wrong fix: Increase container memory – treats symptom, peak still kills neighbors.
Right fix: Serial launch permit + refresh real memory + timeout release + human error message.
Pitfall 4: Symlink Without Backup
Trigger: Symlink once "looks persistent".
Wrong fix: Manual copy when empty dir found – not reproducible.
Right fix: Symlink handles write location; backup handles rollback; empty dir auto-restores.
Extra misdiagnosis: Blaming busy / lost session / zombie writes on "model got dumber". Suspect the plumbing first: channel, PID, volume, pump.
Summary
Claudix-style plugin's moat is three-process engineering, not Chat API wrapping. Webview + Extension + CLI – miss one and it's a toy.
SDK builds connections, CLI does work, Extension decides product behavior. Host authorization equals execution authorization in importance.
Permission pump, PID, volume persistence, concurrent launch – these are week-one must-haves, not "later optimizations".
UI uses Modal, headless uses Hook fallback. Align both strategies; don't copy the wrong channel.
Draw the three-process diagram before writing UI; thin Host + thick handlers. Pretty settings pages can't fix broken pipes.
Claudix-style plugin's moat is three-process engineering, not Chat API wrapping – miss one and it's a toy.
Next article will cover Agent Consumption Authorization : User gives a goal, not a blank check – quotas, visibility, stoppability, before the security deep-dive.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
James' Growth Diary
I am James, focusing on AI Agent learning and growth. I continuously update two series: “AI Agent Mastery Path,” which systematically outlines core theories and practices of agents, and “Claude Code Design Philosophy,” which deeply analyzes the design thinking behind top AI tools. Helping you build a solid foundation in the AI era.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
