Skip to main content

galvanize: wake your agents when the world moves

Wake your AI agent when something happens.

galvanize watches the real world (new mail, a file landing in a folder, a git commit, a webhook call, an event from a script) and starts a fresh AI agent session with a prompt you wrote the moment one occurs. Event triggers sit directly in the agent's own tool list (native plugin for Hermes, MCP server for Claude Code and Codex), so "wake me when resumes land" wires an actual push trigger. Passwords go to your OS keyring, each trigger starts watching in seconds, and results arrive wherever you asked (Telegram, Discord, or the log).

What your harness doesn't already do

  • Claude Code and Codex can't be woken from outside at all. Their hooks run inside a live session. galvanize supplies the missing inbound door: the daemon watches the world and spawns the session when something happens.
  • Hermes has inbound webhooks, but only as admin config, never in the agent's tool list. So agents fall back to cron: an hourly poller that spends an LLM run to announce that nothing arrived.
  • Watching is free; thinking costs tokens. Source state (IMAP IDLE, folder, relay) lives in a small daemon with reconnect, dedupe, and catch-up. A model runs exactly once per real event, in a fresh one-shot session you scoped.

Install

Works on Windows, macOS, and Linux (Python 3.10+). No cloning needed; one command installs straight from GitHub:

pipx install "git+https://github.com/lychee888/galvanize.git"
# or: uv tool install --from "git+https://github.com/lychee888/galvanize.git" galvanize
# or: pip install git+https://github.com/lychee888/galvanize.git

Then run setup once:

galvanize init

init enables the Hermes webhook platform (backs up your config first), installs the agent plugin, registers the daemon to start at login, and auto-registers the MCP tool surface into Claude Code / Codex if it finds them on this machine. Everything is confirmed on screen and reversible; --yes accepts defaults.

The daemon's own runtime

daemon install does not adopt the interpreter you happened to run it from. It provisions (or repairs) an app-owned venv under ~/.galvanize/runtime/venv, validates it launch-equivalently (spawned isolated, at the launch working directory, with PYTHONPATH/user-site scrubbed: imports, package versions, a real OS-keyring set/get/delete roundtrip, windowless target), and records the result in ~/.galvanize/install.json. Only that recorded, probed identity goes into the persistence entries (Startup .lnk, watchdog task, systemd unit, LaunchAgent). Falling back to your invoker's interpreter requires passing the same probe, and install says so out loud; a failed probe changes nothing on disk. galvanize probe prints the manifest for this interpreter; galvanize doctor diffs install.json vs the persistence target vs the live daemon three ways.

Testing/automation seams (env vars): GALVANIZE_RUNTIME_VENV (location), GALVANIZE_RUNTIME_DISABLE=1 (skip the app venv entirely), GALVANIZE_RUNTIME_SKIP_PROVISION=1 (validate, never pip), GALVANIZE_INSTALL_INTERP=<exe> (override the fallback candidate).

After that, the whole surface is five verbs:

galvanize add folder ~/watch --wake hermes             # + a live test fire
galvanize add imap you@gmail.com --wake hermes         # app-password to keyring
galvanize status           # watching? last fire, errors, health
galvanize doctor           # deep health check
galvanize test cad-drops   # inject a synthetic event through the real path

Why

Polling is what agents reach for because it's the only event surface in their tool list; everything above makes the push version the default.

  • Fresh sessions. Each event spawns a clean one-shot run; results are delivered where you asked.
  • Management everywhere cron is managed. Dashboard /triggers tab, /triggers slash command, hermes triggers CLI, agent tools, plus galvanize status / doctor / daemon: all surfaces, one ops core.
  • Push email. IMAP IDLE with 25-min re-arm, UID dedupe, reconnect catch-up, keyring-held credentials, and a migrate hermes-cron command that converts your existing email pollers.
  • Zero-inbound-port webhooks. Optional user-owned Cloudflare Worker relay (relay/worker.js): services POST to your URL, your laptop pulls the queue.

Working with each harness

Hermes

galvanize init does the setup: enables platforms.webhook in your config.yaml (backup saved first), pip-installs the package into the interpreter Hermes runs in, copies the plugin into ~/.hermes/plugins and enables it. Restart the Hermes gateway once to load the webhook platform; the trigger_* tools appear in new sessions. From then on, "wake me when a file lands in ~/cad-drops" creates a real trigger in conversation.

Claude Code / Codex

init writes the MCP server entry into ~/.claude.json / ~/.codex/config.toml automatically when it finds them (Codex gets default_tools_approval_mode = "approve" pre-declared, as newer Codex builds otherwise hide the tools). New session: trigger_add and friends are in the tool list. Wake presets:

galvanize add folder ~/inbox --wake claude   # claude -p "{prompt}"
galvanize add folder ~/inbox --wake codex    # codex exec (sandbox pre-declared)
galvanize add git-hook ~/code/myrepo --wake codex   # wake Codex on commits

Any other CLI agent

galvanize add ... --wake dsh defaults to dsh --profile headless "{prompt}". The DSH plugin installer saves a custom --wake-profile into both its runtime profiles and the core's dsh_wake_profile setting, so future CLI and plugin triggers agree. The CLI also accepts --wake-profile <name> for one trigger. Existing triggers retain their saved commands. MCP trigger_add requires an explicit wake target and accepts command, workdir, wake_profile, relay_url, and relay_token; it does not silently choose Hermes.

galvanize add folder ~/watch --wake shell --command 'myagent run "{prompt}"'

Commands containing {prompt} or {payload} run as an executable plus literal arguments. Event text is never interpolated into a shell command. Use a script for pipelines or redirection, and read GALVANIZE_PROMPT / GALVANIZE_PAYLOAD inside it. On Windows, npm command shims are resolved to their Node entry point; other batch files need an explicit executable or interpreter.

Shell wakes run invisibly on Windows: every child is spawned through one launch primitive (galvanize/winlaunch.py) with a hidden console inside a kill-on-close Job Object, so no window ever appears and a timeout or a surviving descendant takes the whole process tree down with it. The honest limit: invisibility covers CONSOLE windows only — a shell wake executes user-declared commands, and any GUI descendant (an editor, a message box, an Electron app) can still draw its own window.

Relay webhooks

DSH and other shell wakes require a relay for webhooks. Deploy relay/worker.js with a RELAY KV binding and two distinct secrets: RELAY_TOKEN for the daemon to read events, and INGEST_TOKEN for senders to submit events. Set both using wrangler secret put before deploying.

galvanize add webhook --name incoming --wake dsh \
  --relay https://your-relay.workers.dev --relay-token "$RELAY_TOKEN"
curl -X POST https://your-relay.workers.dev/ingest/incoming \
  -H "Authorization: Bearer $INGEST_TOKEN" \
  -H 'Content-Type: application/json' -d '{"message":"hello"}'

The supplied worker requires the ingestion bearer header. Providers that cannot send it need an adapter that validates their native signature before forwarding; pointing their unauthenticated webhook directly at the worker will return 401. Existing deployments must configure INGEST_TOKEN and update senders when installing this worker revision.

Relay receipt and delivery are persisted separately in per-URL SQLite inboxes under GALVANIZE_HOME (default ~/.galvanize). A page and its receipt cursor commit together before dispatch. The delivery acknowledgement advances only past successful or explicitly quarantined events. A failed wake never advances that acknowledgement; later received events can still run. Normal restarts resume pending work and do not replay acknowledged IDs, including retained history returned again by the relay.

Delivery is at least once, not exactly once: a crash after a wake's external side effect but before the acknowledgement commit can repeat it. Every payload includes the stable relay event_id; use that ID for idempotent side effects. Keep the inbox files when restarting. Deleting them intentionally resets replay protection. IDs are retained without automatic pruning; monitor disk usage for long-running, high-volume deployments. Events not received before the Worker's retention period expires cannot be recovered locally. Cloudflare KV is eventually consistent; this change does not make its timestamp cursor a transactional queue.

Failed dispatches retry up to five times with bounded exponential delay (2, 4, 8, 16 seconds before exhaustion). galvanize status and trigger_status expose event ID, attempts, and error. After fixing the cause, use galvanize relay-retry <event-id> to grant another five attempts. Cooldown and in-flight deduplication deferrals remain pending and are checked again after two seconds without spending a failed-dispatch attempt. Malformed records are quarantined individually and remain visible. The Worker wraps non-object JSON as {"value": ...} and non-JSON text as {"raw": ...}; the reader also normalizes retained records from older Workers.

watching requires a fresh, connected watcher and a live daemon for folder, IMAP, and relay sources. Direct Hermes webhooks use gateway health instead; manual emit triggers have no watcher. IMAP exits IDLE with DONE before searching or fetching mail, then re-enters IDLE after draining.

Daily use

The agent's own trigger_add tool is the primary creation path; the CLI is the fallback for use without an agent. Every editor — agent tool, CLI, dashboard, galvanize serve — writes through the same transactional SQLite authority (~/.galvanize/galvanize.db) with config revisions, so concurrent edits cannot silently clobber each other. ~/.galvanize/triggers.yaml is an import/export mirror (and the first-seed source): hand-edit it, then galvanize config import triggers.yaml to activate, or config export to start fresh from the live set.

Wake sessions & tool access

A galvanize wake is an event-based cron session: the woken Hermes session runs with the toolsets named by its webhook route. Since v0.2.4 every managed route carries an explicit toolsets key defaulting to main-session parity: the gateway's default distribution ("all available tools, all the time"), i.e. browser, file, image_gen, terminal, vision, web. Without the key the gateway falls back to the restricted hermes-webhook composite (web_search, web_extract, vision_analyze, clarify), which silently cripples any prompt containing real steps.

Narrow the grant per trigger (CLI; the plugin/MCP trigger_add tool takes the same list as wake_tools):

galvanize add folder ~/inbox --wake-tools terminal,file   # exactly these
galvanize add imap me@x.com --wake-tools none             # alert-only posture

--wake-tools none is the security knob for high-volume or untrusted feeds: inbound content is attacker-influable, and a wake session with autonomous shell access turns a prompt injection into code execution. Choose it when the trigger only needs to notify. The explicit choice is recorded on the trigger, so re-adding or updating it never silently widens or revokes the grant.

Valid names are the Hermes toolset registry keys (checked against the gateway's allow-list for the webhook platform as of this release): bot_room, browser, catalog, clarify, code_execution, coding, computer_use, connections, context_engine, cronjob, debugging, delegation, desktop_ui, feishu_doc, feishu_drive, file, image_gen, kanban, memory, onboarding, project, safe, search, session_search, skills, terminal, todo, tts, video, video_gen, vision, web, x_search, yuanbao (plus composites like hermes-webhook). The gateway re-validates names at spawn and drops unknown ones, so a stale entry degrades to a no-op.

galvanize doctor prints one wake tools '<name>' row per hermes trigger (parity / override list / restricted) and ⚠-warns on key-less routes left by pre-0.2.4 installs. Fix them (recorded choices and manual grants survive):

galvanize doctor --fix    # also runs automatically on `galvanize daemon install`

Importing mailbox passwords from himalaya

If your mail already works via himalaya, don't type the app-password a second time:

galvanize secrets import-himalaya --dry-run   # list candidates; writes nothing
galvanize secrets import-himalaya             # store plaintext IMAP passwords in your OS keyring
galvanize secrets import-himalaya --overwrite # replace galvanize secrets that differ

Passwords land under the same imap:<user> keys galvanize add imap writes, so existing imap triggers pick the values up on their next reconnect. The himalaya config is read, never written, and password values are never printed. Accounts whose password comes from a cmd store are reported but skipped. The config is auto-detected (~/.config/himalaya/config.toml, %APPDATA%\himalaya, $XDG_CONFIG_HOME); --config PATH overrides, and an empty GALVANIZE_HIMALAYA_CONFIG disables discovery entirely.

galvanize doctor adds a drift check for each himalaya account: a password that is missing from (or differs from) the galvanize store surfaces as a ⚠ warning with the exact import command to run — non-blocking, and never auto-resynced.

Development

git clone https://github.com/lychee888/galvanize && cd galvanize
python -m venv .venv && .venv/bin/pip install -e ".[dev]"   # Scripts/ on Windows
.venv/bin/pytest                     # unit suite; live-lane + docker tests skip cleanly

The live Hermes-lane test runs inside a Hermes checkout's venv (pytest tests/test_live_hermes_lane.py); the IMAP suite drives a GreenMail container and skips when Docker is unavailable. test_imap_protocol.py always exercises the real IMAP client over TCP against a strict test server that rejects commands during IDLE. Relay Worker tests execute the actual JavaScript with a memory-backed KV fixture, not a Cloudflare deployment.

MIT licensed. Built for the Hermes ecosystem; architecture is harness-neutral.

Metadata

Release files for galvanize 0.2.6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for galvanize 0.2.6
File Size Uploaded Provenance
galvanize-0.2.6.tar.gz 2.5 MB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for galvanize 0.2.6
File Interpreter ABI Platform
galvanize-0.2.6-py3-none-any.whl Python 3 none any Details

Total release size: 2.6 MB

Release files / galvanize-0.2.6.tar.gz

Download URL galvanize-0.2.6.tar.gz
Size 2.5 MB
Tags Source
SHA-256 checksum
How to use checksums
60453bdfa04c6dd227b42c36d665db16aa568c1cade5fad05455352806ff19c4
BLAKE2b-256 checksum
How to use checksums
597b8022867c61937a092959f44cfbd21d7fbfa82a5c37957c8cba68c5bb0541
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Release files / galvanize-0.2.6-py3-none-any.whl

Download URL galvanize-0.2.6-py3-none-any.whl
Size 150.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0021dbd67bfdb81def53d4362e147a826911f76daf9598f69c253ad0a4c83044
BLAKE2b-256 checksum
How to use checksums
a3d8dfc2c095f8eed9bcd4ee6f226adf079e7769df92df5067503e4dca6d5487
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Security for galvanize 0.2.6

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.6 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page