0042 — Come home, the rest: the model, other agents, a Slack bot with one key
Status: accepted · 2026-10-02
- Status: accepted
- Date: 2026-10-02
- Builds on: ADR 0035 (Come home), ADR 0010 and ADR 0012 (providers, every one at once), ADR 0013 (skills), ADR 0018 (channels)
Context
ADR 0035 left three things behind, each a sentence in its known limits:
- Hermes's model choice.
~/.hermes/config.yamlsays which model it answers with (model.default) and through what (model.provider:openrouter,anthropic,openai-codex,nous,custom…). OpenClaw says it too, inopenclaw.json(agents.defaults.model,provider/modelor{ primary }). Someone who chose Sonnet there gets whatever Conch's default is here. - OpenClaw's agents beyond
main. OpenClaw can run several agents (agents.list), each with its own workspace (workspace-<id>by default): its ownSOUL.md,IDENTITY.md,USER.md, memories and skills, and cron jobs that run as it (agentId). A "work" agent with its own voice and its own notes stayed behind entirely. - A Slack bot with one of its two keys. Conch talks to Slack over Socket
Mode, which needs a bot token (
xoxb-) and an app-level token (xapp-). OpenClaw's Slack in HTTP mode keeps a bot token and a signing secret and no app token; a Hermes.envcan have either alone. Such a bot wasn't offered.
Decision
1. The model: matched by what it is, said in words, undone by the ledger
Conch has no provider called "openrouter" or "nous" in the sense Hermes
does, and the same model has different ids in different places
(anthropic/claude-sonnet-4.5 on OpenRouter, claude-sonnet-4-5-20250929 on
the Anthropic API, sonnet in Claude Code). So import/model.ts matches by
what the model is against what each connected provider offers now
(ProviderService.models(), the model picker's own list):
- Providers are tried in order: the one the app named (
anthropic→ Anthropic API, then Claude Code;openrouter→ OpenRouter;openai-codex→ Codex; a server on localhost → the model on this computer), then where its maker's models run (Anthropic → Claude Code, Anthropic API, OpenRouter), then anything else connected. - In each, the very model (same id once dates, dots and makers are set aside), else one of its family (Sonnet, Opus, Haiku, GPT-5). A provider's own "Default" is never taken for a model. A model on this computer only maps to one on this computer, and the other way round.
- The plan says it in a person's words: "Use Claude Sonnet, as in Hermes", with "New chats start with Sonnet 4.5 on Claude Code, the nearest here to Claude Sonnet 4.5. Now it's Claude Code's own choice." It's ticked only if you haven't chosen a default model yourself (like the name, ADR 0035), and marked as already here when it's the same.
- When it can't be placed, nothing changes and a sentence in "What stays behind" says why: the provider isn't connected ("Connect it in Providers, then pick the model in any chat"), Conch can't connect to it at all ("Kimi"), or nothing connected offers it.
- When the app's own key would connect its provider (an OpenRouter key in Hermes's
.env), the item is offered unticked, saying it needs that key ticked too. Keys still never come over unless ticked. The model is brought last, after keys, and matched again against a fresh list, so a key that just came over can bring its provider; if it didn't, the outcome says so. - It's applied as the model picker's "make default" would be: the default provider and its model (
providers.use,preferences.model). Chats you already have keep theirs. The ledger keeps what was there before (before.preferences,model: nullfor the provider's own choice) and Undo puts exactly that back (UpdateSettingsBody.modelnow takesnull).
config.yaml is read by a small YAML reader in read.ts, like the JSON5
one: nested maps, lists of words, quotes, comments and block text, every
value a string, no anchors, tags or types. A file that isn't YAML (tabs, a
quote or bracket left open, a line that's no setting) throws, and the plan
says "Its config.yaml couldn't be read, so its model choice stays behind."
A model: it can't make sense of says so too. Only the model is read: a
custom_providers entry's api_key stays where it is, and tests check the
plan and the ledger for it. No new dependency: a full YAML parser (yaml)
would read more than Conch needs and parse more than it should trust.
The terminal (pnpm conch import) has no provider list, so there the model
is a sentence: it comes over in Conch itself.
2. Other agents: each persona as a skill, everything else as the main one's
Conch has one assistant, with one name and one set of instructions
(Persona), and everything you set up belongs to Conch and reaches every
provider (working agreement 9). There's no persona or profile switcher, and
adding one for imports alone would give people two ways to do the same
thing. What Conch does have for "behave this way in this chat" is a
skill: a SKILL.md you pick in ⌘K or the composer, read first,
held to what it says it needs, and working with every provider. So:
| In the other agent's workspace | Comes over as |
|---|---|
SOUL.md (+ IDENTITY.md) | A skill of Conch's own, "Talk as Atlas" (atlas), off: "For this chat, answer as Atlas, the "work" agent you had in OpenClaw", then its words |
USER.md | Added to About you, unless it's the main agent's |
MEMORY.md, daily notes | Memories, as they are (daily notes unticked). One the main agent has too comes over once, from there |
skills/ | Skills, off, like the main agent's; a name already taken is the main agent's |
Cron jobs with its agentId | Draft routines, "as Atlas" |
Memories aren't tagged with the agent. Conch's memory is about the person, one place for every chat (ADR 0032), and recall by meaning finds a work fact in a work chat by itself; a tag nothing reads would only be noise, and changing the words would change what was remembered. Where each came from is in the plan ("From Atlas's MEMORY.md").
Agents are found in agents.list (a valid id only, so ../escape goes
nowhere) and, for a damaged or forgetful config, by their workspace-<id>
folders. Links aren't followed, as everywhere in Come home. An agent's
auth-profiles.json only fills a key the main agent didn't have.
The plan shows each agent together, under its name, after everything
else, with one tick for all of it (ImportItem.agent, Nacre
ImportPreview's agent sections). Its persona goes through scanText
first: one that reads like orders starts unticked. Everything it adds is in
the ledger like the rest, so Undo takes it back.
3. A Slack bot with one key: offered, then finished on the Slack setup
The bot is offered like any other (never ticked): "Its bot token, from
Hermes's .env. Slack needs one more key: Conch shows you where to get it,
then waits for your hello." Bringing it connects nothing; the outcome says
which key is missing (finish: 'slack-key') and the summary links to
Finish connecting Slack (/channels/new/slack?from=hermes).
The Slack setup picks up from there:
GET /api/import/slack?source=…says which key Conch has (has), whose bot it is (checked with Slack), and the app's id (A0…, from inside anxapp-token, or frombots.infofor a bot token). Never the key. A key Slack no longer accepts is a sentence, and the setup starts fresh.- The app counts as made ("Made, in Hermes") and the key it has as done.
The missing one is the current step, with a button straight to that
app's own page:
- no app-level token: Socket Mode (
api.slack.com/apps/<id>/socket-mode): turn on Enable Socket Mode, keep theconnections:writescope it suggests, Generate, copy; - no bot token: Install App (
…/install-on-team): Install (or Reinstall) to Workspace, Allow, copy the Bot User OAuth Token.
- no app-level token: Socket Mode (
- The pasted key is checked as it lands, then
POST /api/import/:source/slack(sudo mode, like any import) reads the other key from the app's folder again, connects throughChannelService.create(keys from two apps are caught there), and adds the channel to the ledger: Undo takes it back. - Opening Connect Slack yourself shows the same offer with Use it; it's only used if you press it.
A Slack check now also returns the app id, so the ordinary setup links to that app's Basic Information page once a key gives it away.
Consequences
- Moving over keeps the model you were used to, or says plainly why not, and takes nothing else with it. Undo is exact.
- A second OpenClaw agent arrives as a skill you choose per chat, with its notes and jobs. Conch stays one assistant.
- A Slack bot that answered over HTTP becomes a Socket Mode bot with one extra key and one button to its page; no key is ever shown to the page.
- Security: no new place keys live; the plan, the ledger, logs and the page
never see a key (tests check each, including a key in
config.yaml). Words from another agent are read first, skills from it come off, and finishing Slack needs a recent sign-in like the rest of Come home. - Known limits:
- A family match is a choice Conch made for you: it says "the nearest here" and starts unticked if you'd chosen a model yourself.
- Another agent's own model (
agents.list[].model) isn't brought: a skill can't choose a model. Its routines run with your default. - A routine that ran as another agent comes over in its words but not its voice: the persona skill starts off, so a routine can't use it until you turn it on.
- OpenClaw's
bindings(which agent answers which chat app) have no counterpart: a bot answers as your assistant, and you can pick the skill. - Slack's page addresses are Slack's to change; a link that moves still lands in your apps, one click from the right page.