eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

πŸ€– lchat agent sidecar

One headless process that hosts an instance's default agents, each a normal E2EE member with its own keypair and identity file. The lchat server only ever sees ciphertext β€” private keys and your ANTHROPIC_API_KEY live only on this host. It shares web/static/crypto.js verbatim (imported directly), so its wire format is guaranteed to match the app.

> The default sidecar is now Go β€” lchat bot run, a subcommand of the same > single static lchat binary, no Node runtime (internal/bot/, see > docs/bots.md). It's a full drop-in: same wire format, and session/state/ > recipe files are byte-compatible, so it can take over a Node bot's identity in > place. > > This Node sidecar is kept on purpose as a runnable reference > implementation β€” a worked example of how to run a bot in Node if you'd > rather (it imports web/static/crypto.js verbatim, so it's also the clearest > illustration of the exact E2EE wire format a client speaks). It is not > deprecated and not scheduled for removal; keep it working alongside the Go > port.

| agent | what it does | |---|---| | Claude | The plain assistant: replies to every DM; in groups only when @mentioned (the default listen mode β€” the console can widen a bot to also answer its bare name, gated by a cheap "am I being addressed?" classifier). (runtime.mjs, identity via LCHAT_SESSION/LCHAT_INVITE.) | | Eleven | The app guide: same chat loop with a shipped-in persona + app reference (updated with every deploy), and it DMs each new member a welcome when they join. (eleven.mjs, LCHAT_ELEVEN_*.) |

Each identity is optional β€” it runs if its session file exists or its invite is set. Every identity is minted the same way: an agent invite from the admin console, its #fragment passed once as the matching *_INVITE env var.

What you provide

| | | |---|---| | ANTHROPIC_API_KEY | The only recurring secret + cost. Every DM and every group @mention is one Claude API call billed to your account (bots in the "name" listen mode also spend a fraction-of-a-cent Haiku call per bare-name mention to decide whether to answer β€” LCHAT_CLASSIFIER_MODEL overrides). | | LCHAT_BASE | Your instance URL, e.g. https://test.example.com. | | LCHAT_INVITE | One-time. The #fragment of a /join/… link minted in the admin console (tick "This is an agent"). Spent after provisioning. |

Optional: LCHAT_AGENT_NAME (default Claude), LCHAT_MODEL (default claude-sonnet-5; try claude-opus-4-8 when a bot needs deeper reasoning), LCHAT_SYSTEM (system prompt), LCHAT_SESSION (session-file path), LCHAT_LISTEN (mention = groups reply only on @mention, the default; name = also consider bare-name mentions β€” console config overrides per bot).

Run

cd agent
npm install

# 1. Provision once (creates session.json β€” the bot's identity; back it up)
LCHAT_BASE=https://test.example.com LCHAT_INVITE='<#fragment from the invite link>' \
  npm run provision

# 2. Run
export ANTHROPIC_API_KEY=sk-ant-...
LCHAT_BASE=https://test.example.com npm start

Then, in the app, add the agent to a group or start a DM with it. It replies to every message in a DM, and in a group only when @mentioned. An admin can widen an individual bot's listen mode to name, where a message that just uses its name also gets a reply β€” but only when a small classifier call agrees the bot is being spoken to rather than about, so chatting about a bot never summons it.

Where to run it

Anywhere. The sidecar is a pure outbound client β€” it dials the instance (HTTPS/WSS) and the Anthropic API, and nothing ever dials it: no inbound ports, no webhooks, no public address. The server doesn't know or care where it runs.

That choice is the biggest security decision you make about bots, because the bot's private keys unlock every thread it's a member of:

  • On the instance box (the hosted default): convenient, but a compromise of the chat server also reads the bot's threads.
  • On a machine you trust more β€” a home server behind NAT, a spare box, a Raspberry Pi: now a fully compromised chat server yields only ciphertext, same as for human members. The attacker can't even route to the keys.
  • Inside a private network with no inbound at all, egress allowlisted to the instance + api.anthropic.com (or Claude via Bedrock/Vertex private endpoints for zero public egress). A changelog-recipe bot never calls the model API, so it needs only the one route to the instance.

See docs/bots.md Β§ The sidecar runs anywhere for the full threat-model discussion.

Eleven

Eleven needs to see members it shares no thread with (to welcome them), so vetted bots β€” agents the admin marked available to members β€” read the full joined roster; people stay connections-only. On its first run it baselines silently (existing members are never welcomed retroactively). Its app knowledge ships in eleven.mjs, so it improves with each deploy; console config can still override the persona per instance.

(An earlier third identity, the write-only Announcer, posted release notes to groups. It's retired: the server announces its own changelog now β€” a push to the space's admins plus the Admin app's update stream. The Go sidecar's changelog recipe rule remains for custom deploy watchers.)

node eleven.mjs runs one standalone welcome tick β€” handy for tests and cron-style setups.

Notes & limits

  • The Claude API sees plaintext. The lchat server does not β€” but the reply is generated by sending decrypted messages to Anthropic. Only a local model would close that gap. Disclose this where you add the bot.
  • Catches up on reconnect. On a WebSocket reconnect it backfills messages missed during the gap (per-thread cursor), so a message sent in the ~1s blip still gets a reply. A full process restart resets cursors to each thread's latest, so it won't answer messages sent while the process itself was down (and never replies to old backlog on first start).
  • No forward secrecy on removal β€” same as removing a human member today.
  • Keep one sidecar per instance so a compromised host can read only the threads that bot was added to.