Skip to the page
Conch
DocsGitHub

Decisions

0042 — Come home, the rest: the model, other agents, a Slack bot with one key

Status: accepted · 2026-10-02

Context

ADR 0035 left three things behind, each a sentence in its known limits:

  • Hermes's model choice. ~/.hermes/config.yaml says which model it answers with (model.default) and through what (model.provider: openrouter, anthropic, openai-codex, nous, custom…). OpenClaw says it too, in openclaw.json (agents.defaults.model, provider/model or { 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 own SOUL.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 .env can 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):

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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: null for the provider's own choice) and Undo puts exactly that back (UpdateSettingsBody.model now takes null).

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 workspaceComes 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.mdAdded to About you, unless it's the main agent's
MEMORY.md, daily notesMemories, 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 agentIdDraft 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 an xapp- token, or from bots.info for 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 the connections:write scope it suggests, Generate, copy;
    • no bot token: Install App (…/install-on-team): Install (or Reinstall) to Workspace, Allow, copy the Bot User OAuth Token.
  • 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 through ChannelService.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.