with one click
hermes-cron-as-dataspace
// Replace Hermes' cron scheduler (jobs.py + scheduler.py) with scheduled facts in a Syndicate dataspace. Each scheduled job is an assertion `(scheduled ?id
// Replace Hermes' cron scheduler (jobs.py + scheduler.py) with scheduled facts in a Syndicate dataspace. Each scheduled job is an assertion `(scheduled ?id
Maps the 18 OCapN/CapTP/E-rights patterns reinvented piecemeal across codex-rs to their canonical Spritely/Goblins equivalents and the hermes-* bridge skills that formalize each correspondence. Use when auditing capability architecture in codex-rs, planning a Hoot/Goblins port, or reasoning about which E-rights primitive a codex-rs module implicitly implements.
Compositional passive inference vs emergent active inference (Hedges Feb 2024) — chain rule, continuations, Siegel-stack cortex mapping, GF(3) triad across monad-bayes / nashator / zig-syrup.
Bridge Hermes' ACP (Agent Client Protocol) transport onto OCapN/CapTP for RPC and Syndicate for the registry/presence layer. The dual (R+D) row of the rubric — invocation/response naturally fits CapTP, while session/agent discovery fits Syndicate dataspace facts. Removes ACP's bespoke wire while keeping its ergonomics.
Replace Hermes' regex-based dangerous-command detector + per-session approval state with a Goblins revocable forwarder — every authority grant has explicit lifecycle (count-limited, time-limited, user-revocable). Approval becomes a cap operation, not a string-pattern guess.
Replace Hermes' multi-credential pool (raw API keys in process memory + file store) with persistent OCapN SturdyRefs wrapped in revocable forwarders. Each provider key becomes an unguessable, revocable cap reference; rotation = swap forwarder; the LLM never sees the bearer string.
Re-ground Hermes' ContextEngine plugin lifecycle (on_session_start / update_from_response / should_compress / compress / on_session_end) on Syndicate dataspace observers. Token-state becomes published facts; compression is a subscriber that reacts to threshold-crossings; multiple engines coexist by observing the same dataspace.
| name | hermes-cron-as-dataspace |
| description | Replace Hermes' cron scheduler (jobs.py + scheduler.py) with scheduled facts in a Syndicate dataspace. Each scheduled job is an assertion `(scheduled ?id |
| type | bridge |
| parent | hermes-goblins-bridge |
| row | 12 |
| proto | D |
| polarity | 0 |
| status | stub |
Phase 2. Closes the witness-row triplet (mem + session + ctx + cron) all share vat-snapshot + dataspace.
/Users/bob/i/hermes-agent/cron/
scheduler.py — APScheduler-style loop, due-time wakejobs.py — job definitions (cronjob_tools surface for the LLM to schedule new jobs)__init__.py — registryAuthority pattern: scheduler thread holds direct callable references (or shell command strings via cronjob_tools). Schedule state lives in a JSON file (or in-memory dict) read by the scheduler thread. LLM-scheduled jobs go through tools/cronjob_tools.py → validate_within_dir (path security) before write.
A ^cron-dataspace actor publishes scheduled facts; an in-vat fiber observes (scheduled ?id #:at ?t #:cap ?c) facts and a clock fact (now ?t). When now ≥ at, the fiber fires the cap.
(define (^cron-dataspace bcom)
(define ds (spawn ^dataspace))
(methods
((schedule cap #:at when #:repeat (interval #f))
(define id (mint-id))
(assert! ds `(scheduled ,id #:at ,when #:cap ,cap
#:repeat ,interval))
id)
((unschedule id) (retract! ds `(scheduled ,id . _)))
((list-due before)
(filter (lambda (f) (<= (fact-at f) before))
(query ds '(scheduled ?id . _)))))
;; In-vat fiber — Mandy syscaller-free for clock I/O
(syscaller-free-fiber
(lambda ()
(let loop ()
(define due (list-due (current-time)))
(for-each fire! due)
(sleep 1) (loop)))))
(define (fire! fact)
(<- (fact-cap fact))
(if (fact-repeat fact)
(assert! ds `(scheduled ,(fact-id fact)
#:at ,(+ (current-time) (fact-repeat fact))
#:cap ,(fact-cap fact)
#:repeat ,(fact-repeat fact)))
(retract! ds `(scheduled ,(fact-id fact) . _))))
Scheduled job = a cap + a time. LLM schedules by passing a cap it already holds (e.g. a ^revocable wrapping a tool); cron has no special authority of its own, only the caps it was given.
| Hermes call | Goblins message | Notes |
|---|---|---|
cron.add_job(fn, trigger, args) | (<- cron 'schedule (spawn ^job-cap fn args) #:at t) | job is a cap, not a callable string |
cron.remove_job(id) | (<- cron 'unschedule id) | retract fact |
| recurring job | #:repeat seconds | re-asserts after fire |
cronjob_tools (LLM-facing) | LLM schedules via own caps | cron has no FS / shell authority |
| due-time check | observer in vat | not a thread |
| persistence | vat-snapshot of dataspace | survives crash |
| audit | dataspace query | full schedule + history if logged |
syscaller-free-fiber (Mandy A) for clock; vat queue still drains.^revocable max-uses=1, it fires once even if scheduled to repeat.<-), don't wait; observer pattern handles completion notification.cron = cron_dataspace()
# Schedule:
job_cap = make_cap(lambda: log.append('fired'))
jid = cron.schedule(job_cap, at=now() + 1, repeat=5)
sleep(2); assert log == ['fired']
sleep(5); assert log == ['fired', 'fired']
# Cap with max_uses limits repetition naturally:
once = revocable(job_cap, max_uses=1)
cron.schedule(once, at=now(), repeat=1)
sleep(3); assert log[-3:] == ['fired', 'fired', 'fired'] # only 1 new fire post-revoke
# Unschedule:
cron.unschedule(jid)
sleep(10); assert no_new_fires_in(log)
# Survive restart:
snap = vat_snapshot(cron)
del cron; cron2 = vat_restore(snap)
# Pending jobs continue from current wall-clock, not snapshot-time
| Property | Hermes (status quo) | Goblins (this bridge) |
|---|---|---|
| Job representation | callable / shell-string | cap |
| Cron's own authority | full (whatever it imports) | only caps it holds |
| Persistence | JSON / in-memory | vat-snapshot |
| Concurrency | scheduler thread | in-vat fiber + observers |
| Schedule audit | grep file | dataspace query, observable live |
| Retract atomicity | rm-from-list race | dataspace retraction |
| Failure mode | "fired with stale args" | cap revocation atomic |
~/i/goblins-adapter/tests/cron-dataspace-bisim.scm (todo). Probe: schedule + sleep + unschedule + snapshot + restore + verify post-restore behavior matches non-restart timeline (modulo wall-clock translation).
Phase 2 priority. Closes the four-row D cluster (rows 9, 10, 11, 12). Once shipped, Hermes' time-shifted, persisted, observable surfaces all use one primitive: dataspace + vat-snapshot.