eleven / messenger Public

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

Plain git — no account needed to clone.

Download

Download this file

1# πŸ€– lchat agent sidecar
2
3One headless process that hosts an instance's **default agents**, each a normal
4E2EE member with its own keypair and identity file. **The lchat server only
5ever sees ciphertext** β€” private keys and your `ANTHROPIC_API_KEY` live only on
6this host. It shares `web/static/crypto.js` verbatim (imported directly), so
7its wire format is guaranteed to match the app.
8
9> **The default sidecar is now Go** β€” `lchat bot run`, a subcommand of the same
10> single static `lchat` binary, no Node runtime (`internal/bot/`, see
11> `docs/bots.md`). It's a full drop-in: same wire format, and session/state/
12> recipe files are byte-compatible, so it can take over a Node bot's identity in
13> place.
14>
15> **This Node sidecar is kept on purpose** as a runnable **reference
16> implementation** β€” a worked example of how to run a bot in Node if you'd
17> rather (it imports `web/static/crypto.js` verbatim, so it's also the clearest
18> illustration of the exact E2EE wire format a client speaks). It is not
19> deprecated and not scheduled for removal; keep it working alongside the Go
20> port.
21
22| agent | what it does |
23|---|---|
24| **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`.) |
25| **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_*`.) |
26
27Each identity is optional β€” it runs if its session file exists or its invite is
28set. Every identity is minted the same way: an agent invite from the admin
29console, its `#fragment` passed once as the matching `*_INVITE` env var.
30
31## What you provide
32
33| | |
34|---|---|
35| `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). |
36| `LCHAT_BASE` | Your instance URL, e.g. `https://test.example.com`. |
37| `LCHAT_INVITE` | **One-time.** The `#fragment` of a `/join/…` link minted in the admin console (tick "This is an agent"). Spent after provisioning. |
38
39Optional: `LCHAT_AGENT_NAME` (default `Claude`), `LCHAT_MODEL` (default
40`claude-sonnet-5`; try `claude-opus-4-8` when a bot needs deeper reasoning), `LCHAT_SYSTEM` (system
41prompt), `LCHAT_SESSION` (session-file path), `LCHAT_LISTEN` (`mention` = groups
42reply only on @mention, the default; `name` = also consider bare-name mentions β€”
43console config overrides per bot).
44
45## Run
46
47```sh
48cd agent
49npm install
50
51# 1. Provision once (creates session.json β€” the bot's identity; back it up)
52LCHAT_BASE=https://test.example.com LCHAT_INVITE='<#fragment from the invite link>' \
53 npm run provision
54
55# 2. Run
56export ANTHROPIC_API_KEY=sk-ant-...
57LCHAT_BASE=https://test.example.com npm start
58```
59
60Then, in the app, add the agent to a group or start a DM with it. It replies to
61every message in a DM, and in a group only when @mentioned. An admin can widen
62an individual bot's listen mode to `name`, where a message that just uses its
63name also gets a reply β€” but only when a small classifier call agrees the bot is
64being spoken *to* rather than *about*, so chatting about a bot never summons it.
65
66## Where to run it
67
68Anywhere. The sidecar is a **pure outbound client** β€” it dials the instance
69(HTTPS/WSS) and the Anthropic API, and nothing ever dials it: no inbound
70ports, no webhooks, no public address. The server doesn't know or care where
71it runs.
72
73That choice is the biggest security decision you make about bots, because the
74bot's private keys unlock every thread it's a member of:
75
76- **On the instance box** (the hosted default): convenient, but a compromise of
77 the chat server also reads the bot's threads.
78- **On a machine you trust more** β€” a home server behind NAT, a spare box, a
79 Raspberry Pi: now a fully compromised chat server yields only ciphertext,
80 same as for human members. The attacker can't even route to the keys.
81- **Inside a private network with no inbound at all**, egress allowlisted to
82 the instance + `api.anthropic.com` (or Claude via Bedrock/Vertex private
83 endpoints for zero public egress). A changelog-recipe bot never calls the model API,
84 so it needs only the one route to the instance.
85
86See `docs/bots.md Β§ The sidecar runs anywhere` for the full threat-model
87discussion.
88
89## Eleven
90
91**Eleven** needs to see members it shares no thread with (to welcome them), so
92vetted bots β€” agents the admin marked *available to members* β€” read the full
93joined roster; people stay connections-only. On its first run it baselines
94silently (existing members are never welcomed retroactively). Its app knowledge
95ships in `eleven.mjs`, so it improves with each deploy; console config can
96still override the persona per instance.
97
98(An earlier third identity, the write-only **Announcer**, posted release notes
99to groups. It's retired: the server announces its own changelog now β€” a push to
100the space's admins plus the Admin app's update stream. The Go sidecar's
101`changelog` recipe rule remains for custom deploy watchers.)
102
103`node eleven.mjs` runs one standalone welcome tick β€” handy for tests and
104cron-style setups.
105
106## Notes & limits
107
108- **The Claude API sees plaintext.** The lchat server does not β€” but the reply is
109 generated by sending decrypted messages to Anthropic. Only a local model would
110 close that gap. Disclose this where you add the bot.
111- **Catches up on reconnect.** On a WebSocket reconnect it backfills messages
112 missed during the gap (per-thread cursor), so a message sent in the ~1s blip
113 still gets a reply. A full process restart resets cursors to each thread's
114 latest, so it won't answer messages sent while the process itself was down (and
115 never replies to old backlog on first start).
116- **No forward secrecy on removal** β€” same as removing a human member today.
117- Keep one sidecar per instance so a compromised host can read only the threads
118 that bot was added to.
119