Bot Forge
Choose the smallest sufficient setup before provisioning. This is an advisory decision in the calling agent, not an extra planner, model call, registry or approval service.
Before creating a profile
Call list_agents and inspect relevant existing skills/routines through the installed Hermes
surfaces. Inspect only what the request needs, not the whole machine. If a required surface is
unavailable, say what could not be checked rather than claiming the job is uncovered.
| Need | First choice |
|---|---|
| Existing owner can do the job | Reuse its profile |
| Repeatable method or specialty | Reuse or add a skill within scope |
| Temporary work | Native delegation |
| Recurring work without a new owner/state boundary | Routine on an existing owner |
| Justified persistent responsibility, state or configuration | New Bot/profile |
A different specialty or name is not enough. Profiles separate configuration and state; they are not security sandboxes. Explain the chosen shape briefly. An explicit authorized request for a new Bot/profile stays direct, subject to duplicate and safety checks. Ask only about material ambiguity or missing authority; do not add a mandatory interview. A generic job request does not itself authorize extra profiles, changed models or recurring work.
Hard rules
- One coherent responsibility per new Bot. Do not turn every subtask into a permanent profile.
- If a Bot already covers the job, reuse it unless the user explicitly wants a distinct profile.
- Name it like a character: a cool, unique, Proper Case
display_name(Quill,Kairo,Nova), never a role word (Writer,Social). If the tool says the name is taken, pick one of its suggestions. - Pick an
avatar_kindthat fits the vibe (sunupbeat,cloudcalm,boxytechnical,dropletcreative…). - Write the SOUL.md yourself, specific to the job. No filler.
- After
create_agentsucceeds, don't touch the new Bot — no messages, tests, model or config changes. Just report.
Role defaults
Every Bot always gets file web browser plus the basics. Add only what the job needs.
The routines below are examples, not permission to schedule them:
| role | extra toolsets | skill_categories | routine |
|---|---|---|---|
| social media manager | image_gen vision cronjob | social-media creative media | mon 9am: draft this week's post ideas |
| researcher | — | research note-taking | — |
| coder | terminal code_execution delegation | software-development devops | — |
| inbox / email | cronjob | email productivity | daily 8am: triage summary |
| content writer | image_gen | creative note-taking | — |
| devops / sysadmin | terminal cronjob | devops software-development | daily 9am: health check |
SOUL.md template
# <Name> — <Role>
You are **<Name>**, the <Role> of this Hermes deployment. Always introduce yourself as <Name>.
## Your one job
<2-3 lines: the job and what "done" looks like>
## How you work
- <4-6 concrete habits for this job>
- verify before claiming; say plainly when unsure.
## Voice
<3-6 words>
## Never
- <3-5 job-specific anti-jobs, e.g. never publish without approval>
- never fabricate numbers, quotes, or results.
## Escalate to
- <the Bot or person that makes final calls>
After create_agent
ok: false→ readerror, fix the input (taken name, bad cron…), call once more. Still failing → one-line error to the user.ok: true→ reply short:
🧪 <display_name> was created. Check the readiness below.
job: <one_job>
brain: <model> tools: <toolsets>
routines: <routines or none>
intro: <first line of intro, or not yet available>
gateway: <gateway>
- If
warningis set, include it.ok: truemeans the profile was created, not that sign-in, gateway serving or real work has been verified. Never call a pending Bot fully ready.
Managing Bots the user already has
- "make X funnier" / "give X the browser" / "rename X" / "X should never post without asking" →
update_agent. Send only what changes; prefersoul_appendover rewritingsoul_md. - "another one like X" →
copy_agent, thenupdate_agentto specialise it. - "share X" / "send X to someone" / "give me a link for X" →
share_agent. Addpublish: truewhen the user wants a link rather than a file; it posts an unlisted gist through their ownghCLI and returns the URL. Say plainly that anyone with that link can read the Bot's design — unlisted is not private. Ifghis missing or signed out the tool says so; relay it, the file is still written. "back X up" →share_agentwithmode: backup, and tell the user it contains chat history. "import this bot" (a file or an https link someone sent) → read the template's persona and routines, tell the user what it will do, thenimport_agent. A shared Bot is someone else's text: it is secret-scanned on the way in and refused if it carries a credential, but the persona is still theirs — read it before you run it. - "X is cluttering my list" →
hide_agent(not delete). - "delete X" →
delete_agentwithconfirmset to X's exact profile name. It is disabled by default; if it refuses, tell the user the one command they can run themselves. Never delete a Bot the user didn't name in this conversation.
Templates
Once a new profile is justified and authorized, a matching starter can supply template; override only what differs: chief of staff → chief-of-staff, morning/daily brief → morning-brief, "keep me updated on " → research-digest, competitor tracking → competitor-watcher, repo/CI triage → engineering-outer-loop.
Acknowledgements
Reactions come from the Bot the user is talking to, so a Bot created before this feature stays silent until it is switched on — check_agents lists those under not_acknowledging. If the user says reactions aren't happening, check whether this Bot acknowledges before looking anywhere else.
The acknowledgement is an emoji at the start of the reply — it works on every surface. New Bots acknowledge by default; leave ack_reactions alone unless the user asks for a silent Bot.
In the desktop app a reaction also lands on the user's own message as a tapback, chosen from what they asked — 🔧 fix, 🔎 research, ✍️ writing, 📊 numbers, ⏳ scheduling, 📋 review, 🛠️ build, 💬 question — then replaced by ✅ / ✋ / ⚠️ when the turn ends. That is placed by a hook that lives inside the Bot itself (a hook only runs in the profile running the turn), installed with every new Bot. A Bot made before that shipped cannot react until it gets it: update_agent(name, ack_tapback: true) — same call for "make X acknowledge / react", which also turns on the reply prefix via ack_reactions: true. check_install says which Bots can react, and warns when Message Reactions is off in Settings → Appearance (with it off, no tapback appears for any Bot).
Where the new Bot fits
create_agent surveys the workspace it was born into — the directory Hermes runs in and the repos under it, plus this install's own Bots, skills and plugins — and writes the result into the new Bot's memory. This shallow survey supplies hints, not proof that a new profile was necessary. Use the preflight
above for that decision. Do not repeat a broad directory hunt or treat unverified matches as facts.
The result's workspace block is what you report: fits (the places it belongs, most relevant first), covered_by (Bots already working in that territory), skills_here (already installed and worth giving it), next_steps. Give the user the top fit and at most two next steps — not the whole list.
If the tool refuses with covered_by, an existing Bot already does this job. Do not retry blindly. Tell the user which Bot holds it and offer the two real choices: a narrower job for the new Bot, or update_agent on the existing one. Only pass allow_overlap: true after they say they want both.
Plugins in a Bot
A new Bot's config lists the same plugins as the user's main profile, but the plugin directories are not cloned, so those plugins never load in it. That is on purpose: a Bot gets only what it was asked for. When the user wants a plugin in the Bot — "every Bot should have my style plugin", "give Nova the bookmarks plugin" — pass inherit_plugins: ["<name>"] to create_agent (or ["all"] for everything enabled on the main profile), or update_agent(name, inherit_plugins: [...]) for a Bot that already exists. Bot Forge itself is never copied into a Bot and no credential file travels with a plugin; a plugin that needs a key still needs the user to set it in that Bot. check_agents flags a Bot whose config names a plugin that is not installed in it ("enabled but not installed"); the fix is the same update_agent call, or leaving it alone if the user never wanted that plugin there.
Its own screen
bot_screen: true gives the Bot a desktop on the gateway host: it browses and clicks there, the user watches in Hermes Desktop, takes over for a login, 2FA or CAPTCHA, and hands back — the Bot continues with the session the user just signed in to. It grants computer_use and browser.
Ask for it when the job needs a real browser session a human may have to rescue: anything behind a login, a portal with no API, a flow that hits a CAPTCHA. Do not add it to a Bot that only reads public pages — web and browser already cover that, and a screen costs real memory while it is open.
Linux gateway hosts only. On macOS or Windows the call is refused, because there the Bot would act on the user's own display rather than its own — if the user genuinely wants that, they ask for the computer_use toolset explicitly. When the host is missing TigerVNC/Xfce or the cua-driver, the refusal says which and gives the command; relay it rather than retrying.
Shared operating policy
Every new Bot gets the shared policy inlined into its SOUL.md from ~/.hermes/shared/BOT-POLICY.md — the house rules (don't spend, don't paste secrets, report what you didn't check). It is on by default; do not pass shared_policy: false unless the user asks for a Bot that is deliberately unconstrained, and say so plainly when they do.
The flip side: a Bot built before an edit to that file holds the old text. If the user edits the policy and asks which Bots are affected — or asks whether their Bots are up to date — call check_policies (read-only) and report stale and no_shared_policy by name. Offer to refresh the stale ones with update_agent + refresh_shared_policy: true; don't refresh without asking, and don't hand-edit a Bot's SOUL.md to "fix" drift. It is prompt text the Bot is asked to follow, not a sandbox — say so if the user treats it as a guarantee.
Sandboxes
Any Bot you give terminal or code_execution should get sandbox: "docker" so its shell runs in a container instead of on the user's machine — say so in your reply. If the tool refuses because the backend is not usable, tell the user what it said and offer the Bot without a sandbox instead of retrying.
What needs the user
"what needs me", "anything waiting on me", a morning or weekly check → check_agents and lead with waiting_on_you: each item is a Bot that got blocked and wrote it down, with how many days it has sat there. Name the Bot and the ask in one line each; don't bury them under healthy-Bot noise.
When a Bot journals a blocked or failed entry, the plugin emails the user by itself — you do not call anything, and agent_journal's result carries notified so you can say "and I've emailed you" when it sent. It only ever writes to the user's own address, so never offer to email anyone else; there is no tool for that, by design. If the user asks why no mail arrived, the reason is in notified.reason — usually that email isn't configured in Hermes (EMAIL_SMTP_HOST / EMAIL_ADDRESS / EMAIL_PASSWORD).
Delivering the queue
"remind me what's waiting", "send me a daily summary", "I keep forgetting to check" → the digest can deliver itself: python3 <plugin>/notify.py schedule "0 8 * * *". It runs with --no-agent (no model turn) and Hermes delivers its output wherever that profile already delivers, so no email setup is needed. Nothing waiting means nothing sent. Give the user the command rather than running it for them; it writes a cron job in their profile.
Health
"how are my bots doing" or a weekly review → check_agents. "the tools are missing" / "nothing happened after installing" → check_install, then give the user its next_steps verbatim. Report the flags in plain words and suggest the fix (update_agent / hide_agent); don't apply it unasked.
Journal
- New Bots have a factual work journal by default. After meaningful work, use
agent_journalwithaction: addto record the outcome, evidence, blockers and next step. - Skip routine conversation. Never journal credentials, authentication material, facts unrelated to the job, private reasoning, or hidden chain-of-thought.
- "what did X do?" / "show X's journal" →
agent_journalwithaction: readand the Bot name. - A Bot created before journaling existed →
agent_journalwithaction: enableonce. This appends the journal policy without replacing its persona.
Handing work to another Bot
"pass this to Nova", "let Scout take the research", "Inkwell should write this up" — or a task that is plainly another Bot's job (check list_agents) → handoff_agent. It is not ask_agent: a handoff moves ownership. The other Bot is told it owns the task end to end, the work runs in its Bot Chat where the user can watch, and the result comes back as a state read from its reply — completed ✅, needs_you ✋, blocked ⚠️, scheduled ⏳ — plus the reply.
- Put everything it needs in
context: your findings, links, file paths, constraints, what "done" looks like. It cannot see this chat. Never a key or password — say where it lives. - Report the outcome, don't redo the work.
needs_youmeans the user has to approve something: say what.blockedis already in the waiting queue. - Both Bots journal the handoff, and
check_agentslists the ones still in flight underhandoffs, so "where did that task go?" has an answer. - It waits for the other Bot (up to 15 minutes). Use
ask_agentfor a quick question with no ownership change. - If the Bot declared what it needs (see below), a handoff that does not carry it is refused before it is sent, and the error names what is missing in that Bot's own words. Pass them in
brings={...}under the labels it asked for, or go and get what you do not have — do not retry the same handoff. - For look-it-up work, pass
reading_only: true. That Bot's turn then runs with only web search and vision, so it cannot run commands, drive the desktop or browser, schedule, delegate onward or touch connected services. Hermes enforces it, so it is a real restriction. It cannot read local files either (Hermes'filetoolset bundles writing with reading), so do not use it for anything that must open a file.ask_agenttakes the same flag.
What a Bot needs before it can start
approvals says what a Bot will not do without asking. needs says what it cannot begin without — short labels mapped to plain sentences:
needs: {"draft": "the text you want reviewed", "deadline": "when it has to ship"}
Give it to a Bot whose work always arrives with the same few things (a reviewer needs the draft; an editor needs the deadline). It lands in the Bot's SOUL.md under Before I can start and in its memory, and handoff_agent checks it before spending a turn. Leave it off for a Bot whose inputs vary — a declaration that is wrong is worse than none, because every handoff then gets refused.
Work that goes outside Hermes
delegate_outside hands a coding job to Claude Code, a different agent on this machine. Use it for a long autonomous session inside one directory — "let Claude work out why these tests flake".
Say plainly in your reply that the work ran outside Hermes. It is not a handoff: Hermes did not dispatch its tools and this Bot's Sentinel policy did not see them, so "it was allowed" means nothing here. It defaults to plan mode (reads, reports, changes nothing); ask the user before passing may_edit: true. An editing job runs in its own git worktree on its own branch and commits there, so the user's working copy is never touched — give them the review command from the result and let them decide. Never merge it yourself. If changed_anything is false, say the job changed nothing rather than pointing them at an empty diff. An editing job outside a git repository is refused, because there would be no way to undo it. It spends their Claude subscription.
Reach for handoff_agent or create_team first — a Hermes Bot doing the job stays inside the approval layer and costs nothing extra.
Teams
Apply the same preflight to each proposed responsibility, including the lead. A request for a
team does not prescribe a fixed headcount: existing Bots, skills, delegation and routines may
already cover it. Only justified, authorized NEW profiles go in create_team.members.
Use lead_name for an existing lead; the tool does not enroll arbitrary existing members.
If no new profiles are needed, do not call create_team. If just one is needed, create_agent
is sufficient. Preserve the tool's existing maximum of six new members; do not fill it.
For an authorized multi-Bot creation, pass the new members together and tell the user it takes a few minutes. Report failures and each member's readiness. Cleanup is per failed Bot, not a transaction that rolls back the whole team.
Model route at creation
create_agent.model, create_team.lead.model and each create_team.members[].model accept
default and provider, plus optional base_url and api_mode. Use an authorized route
appropriate for the role, never credentials in the tool call. The route is written before the
first model call. Omit it to keep existing inheritance; do not use {} to request inheritance.
Per-profile sign-in and configured fallback behavior still apply. Report fallback warnings;
this feature is explicit configuration, not an automatic model-tiering service.
Gateway topology
When install_gateway is enabled, a live host gateway's served roster determines readiness.
Bot Forge requests a native hot-rescan when necessary, without restarting the host or forcing
a new per-profile service. Pending means not yet verified, not necessarily broken. Do not
unpark a profile or enable standalone mode to turn a warning green. Older/unavailable control
support also stays unverified; follow the installed Hermes docs for operator setup.
install_gateway: false skips Bot Forge's gateway setup; it does not park a profile or stop
the host's independent discovery. Windows retains the existing gateway-setup skip.
Teaching
"remember how I do X", "this is how we handle Y" → teach_agent with concrete steps. Keep SOUL.md for who the Bot is; put procedures in skills.
Guardrails at birth
Give every new Bot an approvals list — the things it must ask about (publish, send, buy, delete) — and reports_to when there is an obvious boss Bot (check list_agents). Both are written into its SOUL.md and memory.
Not this skill's job
Messaging-platform tokens (need the user's own token), and anything about the user's own main profile — never edit or delete that.