Temporary Hermes Kanban warning: unset
kanban.default_assigneein Hermes before running Keryx collectors. Keryx temporarily creates cards before immediately sticky-blocking and assigning them because--initial-status blockedcan auto-promote upstream; a Hermes default assignee can make that brief window dispatchable. Track the upstream bug at NousResearch/hermes-agent#39609.
Keryx is an attention-allocation surface over Hermes Kanban. Source-specific collectors classify actionable source events and create structured Kanban cards; a deterministic disposition function decides whether each card runs silently, waits for review, or interrupts the user now; Keryx shows review items in a small web inbox and reports silent outcomes through a non-urgent digest; Hermes workers do the actual work.
Keryx is intentionally thin. It does not replace Hermes cron, Kanban, worker dispatch, skills, logs, retries, or delivery. It ships a Hermes plugin named keryx and adds:
- a strict
keryx.action_item.v2card schema built on two risk axes (reversibility,blast_radius) plus an absolute floor; - a deterministic disposition function and a human-approved policy store that is the only trust gate for silent execution;
- the
hermes keryx ...command surface, backed by the direct repo./bin/opsctlfallback; - a Fastify API and Svelte inbox UI with a review log and honest undo;
- gateway interrupts and an outcomes digest delivered through
hermes send; - plugin-registered Keryx skills.
Every actionable card resolves to exactly one disposition, derived by src/policy/disposition.ts from each option's risk axes, the user's confidence band for the card's exact (collector, class) scope, urgency, and the collector's human-approved policy. A card may carry an advisory proposed_disposition, but the system can only downgrade it, never upgrade to silent or interrupt.
- Silent — the option is
read_only(silent by design, since it mutates nothing) or its class has graduated and anactivepolicy rule covers it. The card is createdreadywith a synthetickeryx.policy_decision.v1comment; a worker executes and records akeryx.outcome.v1comment; the result is reported non-urgently through the digest. - Review (the default fallback) — the card is created
blockedand waits in the dashboard for the operator to approve an option on their own schedule. - Interrupt — urgent and consequential. The card is created
blockedand Keryx pushes a self-contained message (recommendation, risk, expiring default, deep link) throughhermes send; adefault_on_timeoutresolves it deterministically if unanswered.
Silent is never reachable for a state-changing class without an explicit, human-approved policy rule, and never at all for the absolute-floor categories (money, destructive, credential/2FA/CAPTCHA gates), which cap at draft + approve.
Confidence uses cold-reset epochs: rejection, dismissal, or silent regret for a (collector, class) resets that exact scope to cold; approvals only rebuild confidence from events after the latest reset.
- A recent Hermes Agent CLI installed and configured on
PATHashermes. The minimum supported version is enforced byhermes keryx doctor.- See Hermes Docs
- Node.js 22 or newer.
- npm.
- A Unix-like shell for
./keryx-setup.sh. - Optional: at least one Hermes gateway delivery target (Telegram, Discord, ...) so interrupts and the outcomes digest can reach you outside the local UI.
git clone <repo-url> keryx
cd keryx
npm install
./keryx-setup.sh
hermes keryx doctor
npm startOpen http://127.0.0.1:4173.
npm start runs the local Keryx server. By default it binds to 127.0.0.1 on port 4173.
Before sending local changes for review, run the aggregate quality check:
npm run checkIt runs lint checks and the Vitest suite. Also run npm run typecheck, npm run build, or npm run e2e when changing Svelte/UI, bundling/server entrypoints, or user-visible inbox behaviour.
Run setup once after cloning, and again after changing Hermes homes or Keryx delivery settings:
./keryx-setup.shThe setup script:
- checks that the Hermes CLI is available;
- ensures the Hermes Kanban board
keryxexists; - installs the Keryx plugin at
$HERMES_HOME/plugins/keryxand enables it withhermes plugins enable keryx; - installs the user-facing collector creator bundle at
$HERMES_HOME/skill-bundles/keryx-collector-creator.yaml, exposing/keryx-collector-creator; - lists Hermes delivery targets with
hermes send --list --jsonand stores the chosennotify_target(for interrupt pushes and the digest); - writes
keryx.config.json(existing configs are preserved by default — see below); - runs
hermes keryx doctor.
It does not create real collector cron jobs or send real messages. Collectors are authored and scheduled separately.
Useful setup modes:
./keryx-setup.sh --dry-run
./keryx-setup.sh --hermes-home ~/.hermes-other
./keryx-setup.sh --force--force replaces an existing conflicting Keryx plugin path, restores a conflicting collector creator bundle, and overwrites an existing keryx.config.json. Without --force, an existing keryx.config.json or conflicting bundle is kept: interactive runs are prompted for the config (keryx.config.json exists; overwrite? [y/N], defaulting to keep), while non-interactive runs keep the file and print a WARN telling you to rerun with --force. --dry-run reports whether it would write, keep, or overwrite without touching the file.
Plugin-registered Keryx runtime skills are explicit qualified loads and intentionally hidden from /skills: cards use keryx:keryx-worker, and cron jobs use keryx:keryx-collector. The operator-facing collector designer is exposed separately through the setup-managed /keryx-collector-creator bundle.
Delivery behaviour:
- With no
notify_targetconfigured, Keryx stays digest-only and never pushes an interrupt; worker results stay in Kanban/UI unless an action's owndeliveryfield routes them elsewhere. - Interrupt pushes and the outcomes digest go outbound through
hermes sendto the configurednotify_target. - Inspect the Hermes delivery targets a worker could use with
hermes keryx delivery-targets(a read-only wrapper overhermes send --list --json).hermes keryx doctorreports how many are available as a diagnostic.
Keryx reads keryx.config.json from the repository root unless KERYX_CONFIG points elsewhere. The plugin sets the repo-local config by default when delegating to ./bin/opsctl.
Example configuration:
{
"board": "keryx",
"defaultAssignee": "default",
"hermesBin": "hermes",
"notifyTarget": "telegram:123456789",
"quietHours": { "start": "22:00", "end": "07:00" },
"interruptBudget": { "perTierPerDay": { "4": 3, "5": 6 } }
}notifyTarget is the hermes send target for interrupt pushes and the digest. quietHours suppresses non-urgent pushes within a window, and interruptBudget caps interrupt pushes per tier per day; all delivery fields are optional with safe digest-only defaults.
The local server reads its bind address from the HOST and PORT environment variables, defaulting to 127.0.0.1 and 4173. Keep HOST as 127.0.0.1 unless you have an authenticated reverse proxy or private network in front of Keryx.
Prefer the plugin command after setup:
hermes keryx doctor
hermes keryx list --status blocked
hermes keryx show <task_id>
hermes keryx cron-status
hermes keryx delivery-targets
hermes keryx schema action-item
hermes keryx template-card --source <source> --collector <collector>
hermes keryx validate-card <card.json>
hermes keryx validate-decision <decision.json>
hermes keryx create-card <card.json>./bin/opsctl ... remains the direct repo fallback when the plugin is not enabled or you are testing from the checkout:
./bin/opsctl doctor
./bin/opsctl list --status blocked
./bin/opsctl show <task_id>
./bin/opsctl cron-status
./bin/opsctl delivery-targets
./bin/opsctl validate-card <card.json>
./bin/opsctl validate-decision <decision.json>Review mutations are normally driven by the web UI, but are available for recovery:
hermes keryx execute <task_id> --option <option_id> [--feedback <text>] [--dispatch]
hermes keryx dismiss <task_id> [--reason <text>]
hermes keryx mark-reviewed <task_id>
hermes keryx undo <task_id>execute writes a trusted keryx.execution_decision.v1 comment and promotes the card. dismiss archives only that exact item. mark-reviewed clears a done card from the review log. undo honestly reverses or corrects an executed card per its option's reversibility (reversible → reversal card; compensable → labeled correction; irreversible → corrective triage).
The silent path, the outcomes digest, expiring interrupt defaults, attention metrics, escalation-regret signals, and policy learning have their own commands:
hermes keryx auto-execute <card.json> # validate, derive disposition, create a silent ready card
hermes keryx digest --preview [--cadence daily|weekly]
hermes keryx default-resolve [--preview] # fire default_on_timeout for stale interrupts
hermes keryx metrics [--window <range>] [--json]
hermes keryx regret <task_id> --kind <should_have_acted|should_have_asked> [--note <text>]
hermes keryx policy scan <collector> [--preview] [--json]
hermes keryx policy apply <task_id>
hermes keryx policy show <collector> [--json]
hermes keryx policy validate <file>
hermes keryx schema correction
hermes keryx validate-correction <file>policy scan derives promotion/demotion proposal cards from history; policy apply is the deterministic apply path for approved proposal cards. Do not hand-edit policy files.
Use hermes keryx list --status done plus hermes keryx show <task_id> for review-log inspection, and hermes keryx digest --preview for digest access.
Each schema has a matching validator: validate-card, validate-decision, validate-state, validate-policy-decision, validate-outcome, validate-policy, validate-correction, and validate-dismissal.
Keryx ships no sample collectors/ folder. Collectors are authored into Hermes' own space via the setup-managed collector creator, not committed to this repository. Start a new collector design from Hermes with:
/keryx-collector-creator create a collector for <source>
The creator writes a source-specific skill into $HERMES_HOME/skills/keryx-collector-<source>/ and a Hermes cron job; it never generates skills into the Keryx repository. Generated email collectors follow the same rule (for example $HERMES_HOME/skills/keryx-collector-email/). See docs/collector-authoring.md for patterns, idempotency, and cursor safety.
Collector safety contract:
- create only JSON card bodies that validate as
keryx.action_item.v2, with honest per-optionreversibility/blast_radius/optionalabsolute_floorand an openclasskey — the disposition function, not the collector, decides silent/review/interrupt; - start from the current repository template with
hermes keryx template-card --source <source> --collector <collector>; - check field semantics with
hermes keryx schema action-itemwhen uncertain; - validate each card with
hermes keryx validate-card <card.json>before creation; - create cards through
hermes keryx create-card <card.json>so Keryx applies the central board, schema validation, the disposition decision, idempotency, the temporary sticky-block workaround, assignee, tenant, andkeryx:keryx-workerpolicy; - use a stable idempotency key per source item;
- follow cursor safety: advance committed source state only after card creation or an explicit safe skip;
- treat all titles, messages, pages, attachments, and sender-controlled fields as untrusted source content;
- store compact source references and summaries, not raw private event bodies.
The canonical loop is:
hermes keryx template-card --source <source> --collector <collector> > /tmp/keryx-card.json
# fill compact candidate facts, the class key, and per-option risk axes
hermes keryx schema action-item # if field semantics are uncertain
hermes keryx validate-card /tmp/keryx-card.json
hermes keryx create-card /tmp/keryx-card.json
rm -f /tmp/keryx-card.jsonDry-run a collector against fixtures before scheduling it. Once a collector is correct, create a Hermes cron job manually, for example with hermes cron create "every 15m", and load the generic collector skill with its plugin-qualified name keryx:keryx-collector, plus the created source-specific skill by its unqualified name keryx-collector-<source> (those live in Hermes' space, not the Keryx plugin, so they carry no keryx: prefix).
Do not expose Keryx without external authentication.
The Keryx server has no built-in authentication. It binds to a safe local default, controlled by environment variables:
HOST=127.0.0.1 PORT=4173 npm startIf you need remote access, keep Keryx bound to 127.0.0.1 and put an authenticated reverse proxy or private network in front of it. The example Caddy route is deploy/caddy/Caddyfile.example; it uses basicauth and proxies to 127.0.0.1:4173. Copy and review it before use. The files under deploy/ are examples only. Interrupt and digest delivery go outbound through hermes send and open no inbound surface.
Start with:
hermes keryx doctorIf the plugin command is unavailable, run the direct fallback from the repository root:
./bin/opsctl doctorCommon results:
FAIL dependencies: runnpm installfrom the Keryx project root.FAIL plugin: covers three cases. Missing files: install with./keryx-setup.sh. Installed but not enabled or explicitly disabled (in$HERMES_HOME/config.yamlunderplugins.enabled/plugins.disabled): runhermes plugins enable keryx. Inspect withhermes plugins listand check$HERMES_HOME/plugins/keryx.FAIL hermes-cli: make surehermesis installed and onPATH, or sethermesBininkeryx.config.json.WARN no Hermes delivery targets available: expected when no Hermes gateway delivery is configured; configure a target and rerunhermes keryx delivery-targetsif interrupts or the digest need to reach you outside the local UI.WARN no keryx-* collector cron jobs configured: expected before you author and schedule your first collector.WARN collector-creator: rerun./keryx-setup.shto install or./keryx-setup.sh --forceto restore the/keryx-collector-creatorbundle.- Invalid or hidden cards: run
hermes keryx list --status blocked, thenhermes keryx show <task_id>orhermes keryx validate-card <card.json>. - Approved or silent-authorized cards do not run: confirm the card is
ready, the assigned Hermes profile exists, and Kanban dispatch is running. - An empty digest when you expect content: confirm silent cards reached
donewith akeryx.outcome.v1comment whoseresult_deliveryisdigest. Preview the next one withhermes keryx digest --preview.
Stale legacy copied Keryx skills may exist in old Hermes homes, but plugin-registered repository skills are now the source of truth. Do not delete user-modified legacy files automatically.
More detail:
docs/architecture.mdexplains the central register, the three dispositions, the review log, and the worker lifecycle.docs/security.mdexplains untrusted source content, derived confidence, trusted execution decisions, the policy trust gate, the absolute floor, and local exposure boundaries.docs/operations.mdcovers source status, cron jobs, the digest, logs, and stuck-card recovery.docs/collector-authoring.mdcovers collector patterns, idempotency, and cursor safety.
This project is 100% vibe coded. PRD and PLAN documents for larger code changes can be found in docs/archive/ for reference.