agent-tunnel — Share Your Expert Sessions
A local Claude Code session accumulates large, valuable context over time —
effectively a custom expert agent. agent-tunnel lets you publish a
specific session with >share, hand the resulting handle to teammates,
and have them ask it questions over Discord — each answered against a private,
read-only fork of that session.
In short, it lets colleagues talk to a Claude Code session running on your Mac
— answered either by an interactive session driven through tmux or a headless
claude -p run (details below).
How it works
Section titled “How it works”- Inside any session, you type
>share. A hook reads that session’s own id and folder and mints a short handle (e.g.pay-7Q2). A folder can hold many sessions, so the handle is pinned to the exact session you ran it in — not the folder. - You give the handle to colleagues. In the Discord channel, a message that starts with the handle opens a thread bound to that session; follow-ups stay in the thread.
- Each thread is answered against
--resume <session> --fork-session: the fork copies the context, your original session is untouched, and remote turns are hard-restricted to read-only tools (Read/Grep/Glob). - The Discord Gateway is an outbound websocket — no tunnel, no open port, no cloud. Everything runs on your Mac.
- The fork knows it’s in bot mode: its persona says it’s answering
teammates relayed over chat, and each incoming message is prefixed with who
sent it and how — e.g.
Alice (via Discord) says: …— so it can address people by name. The platform label is the[tunnel] platformconfig value (defaults toDiscord), ready for other chat front-ends.
inside any session: >share ─► hook writes registry { handle → session } │ colleague's Discord ─► Discord ◄─ ws ─ agent-tunnel serve ─► fork that (handle → thread) (reads registry) sessionFor your colleagues (the simple part)
Section titled “For your colleagues (the simple part)”Nothing to install. You hand them a handle; they:
-
Not sure what’s shared? Post
!list(or!handles) in the channel to see every available handle. -
Post
<handle> <question>— e.g.pay-7Q2 how does token refresh work? -
The bot opens a thread (named
<handle>: <question>) and answers there. They keep asking in that thread — no handle needed again. -
When done, they type
!done(or!close/!end) in the thread to close it.
So the whole colleague vocabulary is just three things: !list to
discover, <handle> <question> to ask, !done to close. They can also
attach a file to a question (a PDF, CSV, image…) and the agent will read
it; on write-enabled handles the agent can hand a generated file back into the
thread. Inside a thread the bot answers every message — except one that
starts with @someone-else (a teammate, role, or @everyone), so you can
side-chat with a human without the bot chiming in.
The agent-tunnel command group
Section titled “The agent-tunnel command group”Installing the package adds a single console command, agent-tunnel — a
command group (like aichat) whose subcommands are:
| Subcommand | What it does |
|---|---|
agent-tunnel serve | Run the Discord daemon — the long-lived bridge that answers questions |
agent-tunnel published | List the sessions you’ve shared (handles in the registry) |
agent-tunnel status | Show active conversation threads and their forks |
agent-tunnel forks [<handle>] | Table of fork sessions (handle, access, asker, last active, turns, live/idle, fork id); --json to script, --manage to select-and-clear |
agent-tunnel resume <handle> | Resume a colleague-accumulated fork in Claude Code (most recent, or --fork <id>) |
agent-tunnel rename <old> <new> | Rename a shared handle (registry + bound forks + live tmux windows) |
agent-tunnel watch | Attach to the private tmux server to watch live forks |
agent-tunnel doctor | Readiness check (token found, channel set, claude/tmux on PATH) |
agent-tunnel ask "Q" --handle H | Ask one question through the full pipeline, no Discord (local test) |
agent-tunnel forget --thread T | --all | Drop thread bindings and kill their tmux windows |
agent-tunnel init | Write a commented sample config |
agent-tunnel help | Extensive help — overview, where-to-run, and every command’s details |
The one piece that is not a subcommand is >share (and !list /
!done): >share runs inside a Claude session as a hook, and !list /
!done are typed by colleagues in Discord.
Where to run each command
Section titled “Where to run each command”Most commands are plain CLI; only two care about tmux:
| Command | Where to run it |
|---|---|
agent-tunnel serve | Anywhere it stays alive — a tmux pane is handy. It drives its own private tmux server, so it never touches your main one. |
agent-tunnel watch | Best from a plain terminal tab (outside tmux) to avoid nesting. It works inside tmux too (it warns you), just with awkward prefix keys. |
>share | Inside the Claude session you want to publish — it’s a hook from the plugin, not a subcommand. |
agent-tunnel resume | A normal terminal where you want the Claude session — it execs claude --resume, dropping you into the fork. |
ask · published · status · forks · doctor · forget · init | Anywhere — tmux context is irrelevant. |
agent-tunnel help prints all of this plus every command’s details.
Publishing & closing
Section titled “Publishing & closing”Type these as a prompt inside the session you want to expose (the prompt is intercepted — it never reaches the model; you just see the handle):
| Command | Effect |
|---|---|
>share | Publish this session; mint/show a handle |
>share <label> | Publish under a friendly handle, e.g. >share payments |
>share --write <label> | Publish with write access (file edits); read-only otherwise |
>share --dangerously-allow-bash <label> | Write plus shell commands (for real PDFs/docx) — trusted people only |
>share --dangerously-skip-permissions <label> | Full access: any tool/MCP (web, browser, shell) with no prompts — needs [claude] allow_skip_permissions; fully-trusted only |
>share --read <label> | Downgrade a handle back to read-only |
>share status | Show this session’s handle, if any |
>share off | Stop sharing (revoke the handle) |
Access is per handle and escalates read ⊂ write ⊂ bash ⊂ all: read-only
by default, --write adds file edits (but still never Bash),
--dangerously-allow-bash additionally allows shell commands, and
--dangerously-skip-permissions (the all level) drops every restriction so
the fork can use any tool or MCP server (web, the browser, shell) with no
prompts. The all level is gated: it does nothing unless the owner also sets
[claude] allow_skip_permissions = true (double opt-in), or the daemon refuses
the turn. Re-sharing without a flag keeps the current level. agent-tunnel published tags writable handles with ✍️, bash with 💥, and full with 🚨.
Re-sharing with a level flag changes a live thread’s access on its next
message (same fork, full context kept), not just new threads: the daemon
re-reads the handle’s access each turn. So >share --write payments upgrades an
in-flight read-only conversation, and >share --read payments pulls it back,
with no need to close the thread (!done) and reopen it. Upgrading to all
still honors the gate: without allow_skip_permissions the next turn is refused
rather than silently granted.
A labeled handle also makes the Discord thread and the tmux window readable
(payments-… instead of a cryptic id). Colleagues end a conversation with
!done in their thread; that tears the fork down immediately.
Sharing files both ways (attachments)
Section titled “Sharing files both ways (attachments)”A thread can move files in both directions — hand the expert a document, get a generated deliverable back.
Inbound — a colleague attaches a file (any handle). Drop a PDF, image,
CSV, or markdown file onto a message (with or without text) and the agent reads
it: the bot saves it to a contained per-thread folder and points the fork’s
Read tool at it. Works for every handle, since reading is always allowed.
Per-file size and count caps apply (limits.max_attachment_mb /
max_attachments); anything over is skipped with a note.
Office files (.docx / .pptx / .xlsx) can’t be opened by Read
directly, so they’re best-effort auto-converted — to PDF if LibreOffice is
on the host (highest fidelity), otherwise to Markdown (pandoc) or text. It’s
purely opportunistic: nothing needs installing, and if no converter is present
the bot just asks the colleague to send a PDF. Toggle with [attachments] convert = "auto" | "off". PDF is the universal “attach this and it works”
format — it needs no converter and Read renders its pages visually.
Outbound — the agent hands a file back (write/bash handles). A
write-or-bash fork is told to drop anything it wants to deliver into an
outbox inside the project — .agent-tunnel-out/ — and the bot posts
whatever appears there back into the thread as attachments. That folder carries
a .gitignore of *, so deliverables never show up in your git status.
Multiple Claude config dirs (e.g. work vs personal)
Section titled “Multiple Claude config dirs (e.g. work vs personal)”If you run separate CLAUDE_CONFIG_DIRs — say ~/.claude for personal and
~/.claude-rja for work — agent-tunnel handles it automatically: >share
detects which config dir the session lives under (from its transcript path),
and the daemon forks each session under that config dir. So a work session
forks with your work config and account, a personal one with your personal —
nothing to configure, even though the daemon itself runs under one of them.
agent-tunnel published tags each handle with its config dir
([.claude-rja] vs [.claude]) so you can tell them apart at a glance.
Setup — for developers / self-hosters
Section titled “Setup — for developers / self-hosters”Part A: create the Discord bot (one-time)
Section titled “Part A: create the Discord bot (one-time)”-
At the developer portal → New Application.
-
Left sidebar → Bot. Set its display name. Reset Token → Copy (treat it like a password).
-
Toggle MESSAGE CONTENT INTENT on — mandatory, or the bot sees blank messages.
-
OAuth2 → URL Generator → scope
bot; permissions: View Channels, Send Messages, Create Public Threads, Send Messages in Threads, Attach Files, Add Reactions, Read Message History. Copy the URL. -
Open the URL → pick a server where you have Manage Server (your own, or ask a company admin) → Authorize. The bot appears (offline until you run
serve). -
Make a dedicated (ideally private) channel; on a private channel, add the bot to it. User Settings → Advanced → Developer Mode, then right-click the channel → Copy Channel ID.
Part B: install the plugin + configure
Section titled “Part B: install the plugin + configure”-
Install the plugin that provides
>share(from this repo’s marketplace):Terminal window claude plugin marketplace add pchalasani/claude-code-toolsclaude plugin install agent-tunnel@cctools-pluginsRestart Claude Code, then
>shareworks in any session. -
Write the config and add your token + channel:
Terminal window agent-tunnel init # ~/.config/agent-tunnel/config.toml[tunnel]backend = "headless" # default; or "tmux" to watch forks live[discord]# No need to export anything: point at a file holding the token.token_file = "~/Documents/tokens/discord-token.txt"channel_ids = [123456789012345678]# Optional — Office attachments (.docx/.pptx/.xlsx) are auto-converted to a# readable format when a converter (LibreOffice/pandoc) is on PATH:# [attachments]# convert = "auto" # or "off" -
Check readiness:
Terminal window agent-tunnel doctor
Part C: run it
Section titled “Part C: run it”agent-tunnel serveKeep it alive (a dedicated tmux pane is convenient). With token_file set you
don’t export anything. Your Mac must be awake and online; discord.py
auto-reconnects after sleep.
Watching live (tmux mode only)
Section titled “Watching live (tmux mode only)”This applies only when you run with --backend tmux. In headless mode
(the default) there are no windows to watch — use the agent-tunnel forks
table and the daemon’s per-question log lines for visibility instead.
The tmux backend runs each fork as a real claude in a window on a
dedicated, private tmux server (tmux -L agent-tunnel) — isolated from
your main tmux (its own file-descriptor budget, out of your normal
tmux ls). Watch colleague Q&A happen live:
agent-tunnel watch # run from a plain terminal tabEach thread is a window named after its handle. Detach with your tmux prefix
then d.
Cleanup model
Section titled “Cleanup model”- Colleagues close their own threads with
!done(immediate teardown). - A backstop reaper kills forks idle longer than
pane_idle_ttl_min(default 3h — only catches abandoned threads; set0to disable). - You can also
agent-tunnel forget --all,tmux -L agent-tunnel kill-server, or just Ctrl-Cserve.
Resuming a fork (pick up colleague context)
Section titled “Resuming a fork (pick up colleague context)”A fork keeps its JSONL transcript after the tmux window is reaped — so the
extra context your colleagues built up lives on, and you can continue it
yourself. The catch: a fork copies the original session’s name, so multiple
forks show the same name in Claude’s /resume picker and you can’t tell
them apart. forks and resume sidestep that entirely by using the exact
fork id the daemon recorded:
agent-tunnel forks # all forks: handle, asker, last active, turns, idagent-tunnel forks cmmc # filter to one handleagent-tunnel resume cmmc # resume cmmc's fork in Claude CodeA handle can have several forks (each colleague’s thread is its own). When
there’s more than one, resume picks the most recently active and lists
the rest so you can choose:
handle 'cmmc' has 3 forks (resuming the most recent, ←): ← alice 2h ago 14 turns fork c75f3a83 bob 1d ago 6 turns fork 9e21ab04 you 3d ago 2 turns fork 1f0b40e6 pick another: agent-tunnel resume cmmc --fork 9e21ab04The turn count is your “how much context” signal. resume runs
claude --resume <fork-id> in that fork’s project dir and config dir (work vs
personal), so you land in the right session — no /resume name ambiguity.
Server modes (headless vs tmux)
Section titled “Server modes (headless vs tmux)”Choose how the daemon answers with --backend or [tunnel] backend in the
config. The default is headless.
headless— aclaude -psubprocess per question, with clean JSON in/out. More reliable (no terminal scraping or submit-timing heuristics) and needs no tmux. Launch:agent-tunnel serve.tmux— a real interactiveclaudeper thread, each in a window of a private tmux server you can watch live withagent-tunnel watch. Launch:agent-tunnel serve --backend tmux.
Both run on your logged-in claude (no API key needed). Do not put
--bare in [claude] headless_extra_args — it skips your user config and
breaks subscription auth. Each new thread replays the published session’s
full context (~its token count per cold question), which is fine for Q&A.
The daemon logs one line per question — Q [thread] handle ← asker: "…" on
arrival and A [thread] handle: … in Ns, N chars, fork … on completion (plus
errors) — so tailing the serve output is a live activity feed and a
who-asked-what trail. That’s the primary way to watch a headless daemon
(which has no agent-tunnel watch); pair it with the agent-tunnel forks
table.