Disciplines · Audits

Hermes Parity — Build Checklist

ToolRegistry, AgentRun, iris MemoryManager + persistence stores, Telegram channel

5sections6 minread

On this page

Goal: reach feature parity with Nous Research's Hermes agent (multi-channel autonomous AI assistant) by wiring Oshun's existing-but-isolated agent infrastructure into one running assistant, then closing the genuinely-absent gaps.

Spine = iris agentic core (libs/iris/agents/core/src/agentic/* — real LLM provider adapters + LLM↔tool loop). nous = sovereign/local inference + RAG. psyche = computer-use + strongest memory subsystem. maat = orchestration.

Phase 0 — Wire the spine (one agent runs end-to-end)#

  • 0.1 Read + map real interfaces: AgenticLoop, provider adapters, ToolRegistry, AgentRun, iris MemoryManager + persistence stores, Telegram channel
  • 0.2 Create assembly library that composes loop + provider + tools + memory into a runnable agent → libs/oshun/assistant (@oshun/assistant): provider factory (fail-loud), Assistant over governed AgentRunManager, ConversationMemory port + in-memory impl, 13 tests green
  • 0.3 Wire memory to a real Postgres store → PostgresConversationMemory (durable impl of the same ConversationMemory port; parameterized SQL, lazy schema, bigint handling; avoids the broken in-memory MemoryManager + its 0.5 placeholder entirely). Semantic/vector recall (Qdrant) is a separate later enhancement, not in scope here. 33 tests green
  • 0.4 Register the real built-in tools on the assembled agent — web_fetch + current_time + web_search (default on); shell, python (sandboxed, opt-in), fs_read/fs_list/fs_search/fs_write (root-scoped, opt-in). 28 tests green
  • 0.5 Bridge the assembled agent to Telegram (first live channel) → src/channels/: TelegramAssistantBridge (pure update→Assistant.ask→reply handler, crisis-safe, chunked), TelegramClient (real Bot API getUpdates/sendMessage over injectable fetch), runTelegramAssistant long-poll loop. DESIGN: a SEPARATE honest agentic handler — the existing Sophia bot's renderer throws on uncited answers (grounded-or-abstain), so routing agentic replies through it would abstain or fabricate citations; the Sophia bot is left untouched. 49 tests green
  • 0.6 Typecheck + unit tests for the assembly (tsc lib+spec exit 0; 13/13 vitest); commit + push

Phase 1 — Real I/O surfaces#

  • 1.1 Replace Playwright-stub browser automation with real Playwright → src/tools/browser.tool.ts: 5 real tools (browser_navigate/click/type/ extract_text/screenshot) over an injectable PageController; createPlaywrightPageController lazily imports playwright, single headless page, fail-loud if absent. Hermetic unit tests use a fake controller (NO Chromium). Real-Chromium smoke (scripts/browser-smoke.mts, env-gated, outside vitest) VERIFIED PASS: example.com HTTP 200, "Example Domain" extracted, 16KB PNG saved, no orphan. 86 tests green
  • 1.1b Removed the dead simulated @iris/agents/computer-use/browser package (browser-automation.ts browserInstance:null, findSimulatedElement, simulateNavigation) + its two tsconfig.base.json path mappings. Re-verified ZERO importers across the repo; superseded by the real browser tools (1.1). @oshun/assistant still typechecks clean.
  • 1.2 Wire nous/isis media as agent tools — image_generate (createIsisImageGenerator adapts @isis/ai-providers Flux/SD3.5, structural so no heavy import) + text_to_speech (createOpenAITextToSpeech self-contained key-gated client) + real FilesystemMediaSink + vision (createSharedVisionDescriber sends a real base64 image block to an @oshun/ai vision provider; magic-byte MIME sniffing never trusts a caller/data-URL claim). All opt-in. 76 tests green
  • 1.3 De-stub iris automation-executor — all 6 fabricating executors made real: api_call (real injectable fetch, real status/headers/body, guarded AbortController, failOnHttpError→retry), data_transform (real expr-eval + dotted lookupNested mapping; errors surfaced), click/input/navigation (delegate to an injected PageController, FAIL LOUD when absent), custom (fail loud w/o a registered handler). 127 tests (+21); tsc lib+spec clean; mutation-tested load-bearing. delay/conditional left (already real).

Phase 2 — Autonomous scheduling#

Delivered as libs/oshun/assistant/src/scheduling/ (cohesive subsystem; 64 tests).

  • 2.1 Schedule store + recurring scheduler — ScheduleStore port + InMemoryScheduleStore (atomic single-flight claimDue) + durable PostgresScheduleStore (SqlQueryable, mirrors PostgresConversationMemory); pure runSchedulerTick + startScheduler over INJECTABLE timers/clock (no real timers in tests); coalesce drains post-sleep misses to one fire. Opt-in multi-replica RedisLeaseGuard (SET NX PX, injectable connector, lazy ioredis, fail-loud) cedes the tick when another replica is leader — cross-process single-flight.
  • 2.2 NL→schedule parser — parseNaturalSchedule: real bounded grammar → 5-field cron via installed cron-parser@4.9.0 (injected module, v4+v5 fail-loud); tz-correct one-shots via Intl offset (NOT chrono, which ignores IANA tz); min-interval guard; exact-epoch tests for every branch.
  • 2.3 Unattended agent-task runner — AgentTaskRunner runs Assistant.ask and delivers result over ScheduleDelivery (Telegram-chunked / webhook / channel-router), all fail-loud when unconfigured; reschedules next fire.

Phase 3 — Auto-skill library#

Delivered as libs/oshun/assistant/src/skills/ (66 tests; full lib now 216).

  • 3.1 Voyager-style skill registry — Skill recipe model (name/whenToUse/ recipe/recommendedTools/embedding/useCount); SkillStore port + InMemorySkillStore + durable PostgresSkillStore (mirrors the SqlQueryable durable-store triad); skill_save/skill_search/skill_run AgenticTools, opt-in via builtinTools. skill_run re-feeds the recipe to Assistant.ask (lazy resolver, fail-loud at call) with a privilege clamp (clampAllowedTools intersects with the channel allow-list, never widens).
  • 3.2 Seed from iris template-library + workflow-recorder + usage-patterns — seedSkills converts their REAL shapes structurally (no heavy import), idempotent dedupe by (name, source).
  • 3.3 Retrieval + reuse — pure ranker: real cosine over an injected embedder (exclude dim-mismatch) + IDF-weighted lexical fallback (no fabricated scores); both drop ≤0; exact-epoch... exact-score hermetic tests with a fake embedder. SkillRetriever + relevantSkillsContext.

Phase 4 — Breadth#

Channels (4.1–4.3) delivered in libs/oshun/assistant/src/channels/ reusing the Telegram triad (pure bridge → thin client over injectable transport → runner); shared crisis/statusFallback/chunk extracted to safety.ts. Full lib now 313 tests.

  • 4.1 Signal channel — honest infra-blocked seam: REAL SignalJsonRpcClient (JSON-RPC 2.0 over injectable newline transport) + pure SignalAssistantBridge; default stdio transport spawns signal-cli … jsonRpc and FAILS LOUD on ENOENT / daemon-exit / nonzero (kills child on abort). signal-cli is absent here + no registrable number, so the LIVE channel is infra-blocked (documented).

  • 4.2 Inbound email (IMAP) — pure EmailAssistantBridge (strips quoted reply-chain before crisis scan; honors Auto-Submitted/Precedence:bulk) + REAL RFC3501 IMAP client over injectable node:tls Duplex (with {N} literal framing) + outbound reply via the existing real sendEmailViaSmtp (threading headers). Fail-loud without host/creds.

  • 4.3 Discord + Slack inbound bots — Slack real-now (Events API HMAC verify + ack-before-post + event_id dedupe; reuses sendSlackViaWebApi); Discord Gateway over the global WebSocket (zero new deps) with op11-ACK-miss reconnect + REST 429 retry (reuses sendDiscordViaBot) + bonus ed25519 Interactions webhook path. Per-space tenant scoping (slack:<team>/discord:<guild>). Exec backends (4.4–4.6) delivered in libs/iris/agents/tools/code/src/exec-backends/ behind an ExecBackend port (mapping to ExecutionResult + the turbo-mode TerminalExecutionResult shape); 63 hermetic tests (fake ssh2/spawn/fetch — no real host/container/network).

  • 4.4 SSH exec backend — REAL SshExecBackend over ssh2 (declared in package.json + catalog): host-key fail-CLOSED by default (sha256 pin via hostVerifier), POSIX single-quote escaping of every env/cwd value, deterministic timeout/abort teardown (TERM→grace→close→force-resolve+end), reuses createOutputCapture. isAvailable() = config-completeness only.

  • 4.5 Modal exec backend — ModalHttpExecBackend + ModalCliExecBackend; FAIL LOUD on any contract violation (non-2xx / missing-or-non-number exitCode / missing stdout-stderr) — never assumes exit 0. sandbox_runner Python is reference-only (SDK pinned in a comment).

  • 4.6 Singularity/Apptainer exec backend — full real argv-builder (--containall/--no-home/--writable-tmpfs/--net none/--pwd/--bind/--env) + spawn + capture + timeout(SIGKILL) + byte-cap, injected-spawn tested; the live Linux run is the only infra-blocked piece (fail-loud when absent).

  • 4.7 Integrated isolated-subagent + terminal + Python-RPC pipeline → libs/oshun/assistant/src/subagents/: SubagentRunner spawns a governed CHILD run via AgentRunManager (own transcript, allowed-tools SUBSET, budget reservation) bound to a scrubbed-env terminal session + a REAL persistent Python-RPC bridge (spawn('python3',['-u','-c',…]), JSON-RPC over stdio, id-correlated, fail-loud). Fixes: deterministic teardown by returned-envelope runId (+ refund reservation on throw), no recursion deadlock (children lack delegate; over-cap rejects), env never inherits process.env, parent+child resource disposal, kill-cascade identity inheritance. Opt-in via builtinTools. 39 tests (full lib 352).