# Hermes Parity — Build Checklist

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)

- [x] 0.1 Read + map real interfaces: AgenticLoop, provider adapters,
      ToolRegistry, AgentRun, iris MemoryManager + persistence stores, Telegram
      channel
- [x] 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
- [x] 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
- [x] 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
- [x] 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
- [x] 0.6 Typecheck + unit tests for the assembly (tsc lib+spec exit 0; 13/13
      vitest); commit + push

## Phase 1 — Real I/O surfaces

- [x] 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
- [x] 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.
- [x] 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
- [x] 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).

- [x] 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.
- [x] 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.
- [x] 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).

- [x] 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).
- [x] 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).
- [x] 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.

- [x] 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).
- [x] 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.
- [x] 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).

- [x] 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.
- [x] 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).
- [x] 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).
- [x] 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).
