Hermes plugin
A Hermes plugin (plugin.yaml + register(ctx), Python) whose dashboard tab is the same Agent Apps manager the OpenClaw and dsh plugins ship, plus the build + operate tools, the six framework skills, and the /agent-app command.
Before you install
What the plugin does on your machine beyond the tools it registers:
- Runs the framework CLIs. Every tool, the tab and
hermes agent-apprunagent-appora2app(theagent-app-frameworknpm package, which you install yourself) as subprocesses, never through a shell. They get only the environment the framework needs (system and user basics, locale, proxies, git and SSH, toolchain homes,A2APP_*andAGENT_APP_*, Node's settings,HERMES_HOME), never your provider keys or other secrets, and neither do the apps they install and serve. Installed from the catalog, the plugin has no copy of the skills and loads the ones that CLI ships. - Asks before risky calls. Through Hermes's own approval prompt, which the model cannot answer: installing or inspecting anything other than an app from the default marketplace, re-running an operation the app holds for approval, and writing or running an app's code outside
$A2APP_HOME/appsand the session's working directory (export'sout, install'sinto, a new app's folder, the app a serve or validate runs). Where nobody can answer (cron, the tab's sessions, bridge runs), these calls follow the same settings as Hermes's own dangerous commands (approvals.cron_mode,approvals.single_query_mode), which refuse them by default. The tab's Import panel, where you click Install yourself, is unaffected. - Starts Hermes turns. A build from the tab, or a message in an app's session panel, runs
hermes --cli chat -Qas a child process with your own model configuration. A command Hermes flags as dangerous followsapprovals.single_query_mode(defaultdeny), and the child never inherits the host's/yolo, session or gateway state. - Leaves apps running. An app you build, install or launch keeps serving on a loopback port, as a background process, until you pause or delete it.
- Writes outside its own data. Apps go in
$A2APP_HOME/apps(default~/.a2app/apps). On load the plugin adds ahermesentry to$A2APP_HOME/harnesses.json(only when there is none), so that a bridge you start yourself (agent-app <dir> bridge start) can run Hermes turns when an app queues work. Turn logs go to$HERMES_HOME/plugin-data/agent-app/logs. - Reads outside its own data. The tab reads its app sessions' transcripts from Hermes's session database, and the app registry in
$A2APP_HOME. - Network. The Marketplace reads its catalogue from GitHub (
CraftOS-dev/agent-app-collection); Import downloads the zip, git or GitHub URL you give it; Connect calls the app address you enter. An app's own build and dependency install reach the package registries it uses. Neither the plugin nor the CLI sends telemetry. - Stores a credential. Connect signs Hermes in to a remote app as you; the CLI keeps that credential in
$A2APP_HOME/credentials.json(owner-only), and Disconnect revokes it. - In hermes-webui only, the tab's backend listens on a loopback port behind a token generated at each start, which it writes to
runtime.jsonbeside the web UI extension.
Surfaces
- Agent Apps dashboard tab (dashboard/): a browser-style tab strip with one tab per Agent App (status dot; the name greys out when the app is offline) and a New + tab. An app tab embeds the running app; an offline app shows Launch; the active tab's ⋯ menu offers Pause / Launch, Open in browser, Show session, and Delete (two-step confirm;
agent-app remove --yesbacks data up before deleting). The New + tab has a build form (name, requirement, stack, optional port) that starts the build directly, and the Marketplace and Import panels below. - Connect panel: an Agent App that runs on another machine, by its address (
https://crm.example.com, or just the domain). Connect opens the app at once in a tab of its own in the tab strip, on its approval page: the person signs in to the app there as themselves and approves, the page hands the confirmation code back to this one (postMessage), and the plugin finishesa2app <url> loginwith it. Nothing opens a browser tab. Hermes's agent then holds a credential that acts for that person, so it can use the app too, and the same tab becomes the app (marked ↗, with Open in browser and Disconnect, which revokes the credential), still signed in. An app is embedded where the host's frame policy allows it: the dashboard frames any app, and hermes-webui frames only origins listed inHERMES_WEBUI_CSP_FRAME_EXTRA. Where it cannot be, the tab says exactly what to add and links to the approval page in the browser instead. - Marketplace and Import panels: the same as OpenClaw's. The Marketplace searches the catalogue and filters by tag, a card per app (preview, name, version, tags, description, whether it runs here), with a small form for an app's customizable fields. Import takes a zip (uploaded to a private temporary file, then
--from <path>), a folder path, or a git or GitHub URL, inspects it first and shows the report (kind, installable or why not, what it will run, what install removes, warnings), then installs. An install is a job the page polls every second, one row per step ofinstall --events, and the app opens in its tab when it is up; an error shows the CLI's message and a retry, and the page never gives up on a running install. A CraftBot Living UI app or foreign code is explained and handed, with a name, to the agent and the importer skill in a new app's session (a turn like a build's). - Per-app session panel: each app has its own Hermes session (id
agent-app-<dir name>-<hash>). The right panel renders its transcript and a composer. Build progress shows up there as it happens, and evolve or operate requests are messages into that session. A failed turn shows why (for example, no model configured). - Tools: the twenty-two
agent_app_*tools of the shared engine, in the toolsetagent_app, each shelling a realagent-appora2appverb: build, operate, and the package toolsagent_app_catalogue,agent_app_inspect,agent_app_install(the CLI's result, withtrustandnext),agent_app_exportandagent_app_list_apps. A relativedirresolves against the calling session's working directory. Apre_tool_callhook sends the risky calls to Hermes's approval prompt (see Before you install). - Skills: the six framework skills, registered as plugin skills (
agent-app:creator,agent-app:modify, ...) and loaded withskill_view. Hermes keeps plugin skills out of the<available_skills>index (they do get/agent-app:<skill>commands in the CLI, TUI and Desktop, but not on messaging platforms), so the plugin also registers a system-prompt section that routes any app request to the right one. /agent-app <request>: hands the request to the agent. In the classic CLI the command injects it as the next turn. On messaging platforms apre_gateway_dispatchhook rewrites/agent-app <request>(or/agent_app, for Telegram) into the request before dispatch. The TUI and the dashboard chat give plugin commands no way to start a turn, so there the command says to send the request as a normal message, which the routing section still covers.hermes agent-app <args...>: a passthrough to the CLIs, routed by verb (build/evolve verbs toagent-app, the rest toa2app), output streamed and exit code propagated.- The same tab in hermes-webui (webui/): an "Agent Apps" rail button in hermes-webui, as a web UI extension. See hermes-webui.
- Bridge route: on load the plugin registers Hermes with
agent-app <dir> bridge, the service that starts a harness when an app queues work, as ahermesprofile in$A2APP_HOME/harnesses.json:hermes --cli chat -Q --query-file -with the prompt on stdin, one turn in the app's directory. The framework has no built-in Hermes profile, so without this the bridge cannot see Hermes. Written once; an existinghermesentry (your own) is kept.
How it works
register(ctx) (init.py) runs in every Hermes process and registers the tools, skills, routing section, command, gateway hook, passthrough, and bridge route. The tab is a separate half: hermes dashboard imports dashboard/plugin_api.py at startup and mounts its router at /api/plugins/agent-app/, where the dashboard's own session-token auth covers it. The page (dashboard/manager.js) is a classic script on the dashboard plugin SDK and calls that API with the SDK's fetchJSON.
A session turn is one subprocess, hermes --cli chat -Q --resume <session> --source tool --in <app dir> --query-file -, with the message on stdin. This is how Hermes runs its own background workers (the kanban dispatcher), so a turn uses your configured model, toolsets, and approval policy, and nothing depends on agent internals. Turns on one session run one at a time, in order, and a turn never inherits the host process's own session or approval state (a session-less /yolo there does not reach it). The tool source keeps these sessions out of the CLI, TUI and Desktop session pickers and the dashboard's Chats view; they live in the tab (the dashboard's Sessions page still lists them under Automation and All). Deleting an app stops its session's running turn before the app is removed. Each turn's output is appended to $HERMES_HOME/plugin-data/agent-app/logs/<session>.log, and the transcript is read back from the session database. New apps are created under $A2APP_HOME/apps/<slug> (default ~/.a2app/apps), as in the other managers. App status comes from agent-app list --json, polled by the page.
The tab's package routes (GET /catalogue, POST /inspect, POST /install and GET /install?job=, POST /upload, POST /import/agent) run the same CLI as the tools. An upload is the zip itself, streamed to a private file of at most 512 MiB (the most install reads); it is removed once installed, and swept after a day otherwise. The connect routes (POST /connect and GET /connect?job=, POST /connect/confirm, POST /app/disconnect) run a2app <url> login and logout; GET /apps lists connected apps after this machine's, each with remote, its status (online, offline, unreachable, expired), who Hermes acts as, and whether this page may embed it.
engine.py is a Python port of the shared TypeScript engine (../integration-starter/): same tool names, schemas, and argv, same kickoff prompts (build, and the importer's install, import and adopt), same verb tables and install steps, same harness-file rules, and the same install jobs read from install --events. No shell is used. On Windows an npm .cmd shim is read for the script it runs and Node is started on that script directly, so a field value holding & | ^ < > % or quotes reaches the CLI intact.
Embedding: an app listens on the Hermes machine's loopback interface, so the tab embeds it when the dashboard is open over http on that machine (http://127.0.0.1:9119 by default). Opened from another host or over HTTPS, the tab shows the app's URL in place of a frame that could not load.
Hermes Desktop does not load dashboard plugins (it has its own plugin SDK), so the tab appears in hermes dashboard and, as an extension, in hermes-webui. The tools, skills, command, and passthrough work on every Hermes surface.
hermes-webui
hermes-webui runs the agent in-process, so the plugin's tools, skills, and routing section load in its chats once the plugin is enabled. It cannot start a turn from a plugin command, so there /agent-app says to send the request as a normal message. It hosts a dashboard plugin's tab only as a sandboxed iframe and never runs plugin_api.py, and an app framed inside that sandbox has no origin of its own (no cookies or storage, its requests to itself refused). So the tab comes as an extension instead:
- webui/agent-apps.js runs in the web UI's main page. It adds an Agent Apps rail button and fills the web UI's own plugin page view with the manager (dashboard/manager.js, mounted through
window.__AGENT_APP_MANAGER__.mount). Settings → Plugins → Open for agent-app lands in the same place. - The tab's API is served by the plugin itself: when it loads in the hermes-webui server process (and only there), webui_backend.py starts the same routes on a loopback port, on its own thread (the web UI loads plugins midway through its own imports, and
register()must not wait on them). It publishes its address and a token inruntime.jsonbeside the extension's files, which the web UI serves like them, so the backend is exactly as protected as the web UI. Each request needs that token, the web UI's own origin (or none, for a local tool), and a loopbackHost.
Install both halves, allow framing local apps, and restart the web UI:
python harness-plugins/hermes/scripts/build.py --install --webui
hermes plugins enable agent-app --no-allow-tool-override
echo 'HERMES_WEBUI_CSP_FRAME_EXTRA="http://127.0.0.1:* http://localhost:*"' >> ~/.hermes/.env
./ctl.sh restart # in the hermes-webui checkout
--webui copies webui/ and the manager into the web UI's managed extension directory ($HERMES_WEBUI_STATE_DIR/extensions/agent-app, default ~/.hermes/webui) and records it as the web UI's gallery installer does, so Settings → Extensions lists it and can disable or uninstall it. Without the frame setting the tab still works, and links to running apps instead of embedding them.
A web UI started by a supervisor may not have your shell's PATH, so give the plugin absolute paths in ~/.hermes/.env (Hermes loads it in every process): AGENT_APP_CLI, A2APP_CLI, and HERMES_BIN (the hermes session turns run). Turns use Hermes's own model configuration (hermes model), not a model picked in the web UI's composer.
Build and install
Requires the framework CLIs on PATH, release 0.2.0, the one this plugin was tested with (npm i -g agent-app-framework@0.2.0; from a cloned repo, pnpm -r build then npm link in framework/cli). The skills and the tools' arguments come from that CLI, so with any other release the first tool call in a session says which one is installed, and the plugin logs it. The Hermes home is $HERMES_HOME, default ~/.hermes (%LOCALAPPDATA%\hermes on native Windows).
From the Hermes plugin catalog:
hermes plugins install agent-app --enable
From a cloned repo, scripts/build.py stages a self-contained dist/ (the plugin plus the six skills from the repo-root ../../skills) and --install copies it to $HERMES_HOME/plugins/agent-app:
python harness-plugins/hermes/scripts/build.py --install
hermes plugins enable agent-app --no-allow-tool-override
hermes dashboard
With Hermes's own installer, from GitHub:
hermes plugins install CraftOS-dev/Agent-App/harness-plugins/hermes --enable
That clones this directory, which holds no copy of the skills, so the plugin uses the ones the installed CLI ships (agent-app skills --path).
Hermes plugins are opt-in: nothing loads until agent-app is in plugins.enabled, which hermes plugins enable writes, and it takes effect in the next session. Restart a running hermes dashboard after enabling, because the dashboard mounts plugin APIs at startup.
Configuration
AGENT_APP_CLI(build/evolve, defaultagent-app) andA2APP_CLI(operate, defaulta2app): the binaries to shell. A value ending in.js/.mjs/.cjsis run with Node directly.A2APP_HOME: the framework home (default~/.a2app), where new apps andharnesses.jsongo.HERMES_BIN: thehermesthe tab runs session turns with. Default: the dashboard's own interpreter (python -m hermes_cli.main, the same install and profile) when it runs in Hermes's venv, else thehermeslauncher.- hermes-webui:
AGENT_APP_WEBUI_BACKEND=0keeps the tab backend from starting;AGENT_APP_WEBUI_BACKEND_PORTfixes its port (default: a free one);AGENT_APP_WEBUI_ORIGINSadds origins allowed to call it, for a web UI reached at other thanhttp://127.0.0.1:<HERMES_WEBUI_PORT>orlocalhost. - Approvals: session turns and bridge runs are single-query runs, so a command Hermes flags as dangerous, and a tool call the plugin sends to the approval prompt, follow
approvals.single_query_mode(defaultdeny: the call is blocked and the agent is told). The plugin grants nothing on your behalf.
Tests
python harness-plugins/hermes/test/test_plugin.py
Standard library plus Node (the fake CLIs are .js entries, answering in the packaging contract's shapes); the dashboard API tests also need FastAPI and are skipped without it (the hermes-webui backend's do not). The suite loads the plugin the way Hermes's loader does and the backend the way the dashboard does, with fakes for the plugin context and the hermes binary, and checks argv and kickoff prompts against the compiled TypeScript engine when @a2app/integration-starter is built.
README from the reviewed commit fecfa42; it updates when the author re-pins.

CraftOS