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),Assistantover governedAgentRunManager,ConversationMemoryport + in-memory impl, 13 tests green - 0.3 Wire memory to a real Postgres store →
PostgresConversationMemory(durable impl of the sameConversationMemoryport; 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),runTelegramAssistantlong-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 injectablePageController;createPlaywrightPageControllerlazily 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/browserpackage (browser-automation.tsbrowserInstance:null, findSimulatedElement, simulateNavigation) + its twotsconfig.base.jsonpath mappings. Re-verified ZERO importers across the repo; superseded by the real browser tools (1.1).@oshun/assistantstill typechecks clean. - 1.2 Wire nous/isis media as agent tools —
image_generate(createIsisImageGeneratoradapts@isis/ai-providersFlux/SD3.5, structural so no heavy import) +text_to_speech(createOpenAITextToSpeechself-contained key-gated client) + realFilesystemMediaSink+vision(createSharedVisionDescribersends 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 —
ScheduleStoreport +InMemoryScheduleStore(atomic single-flight claimDue) + durablePostgresScheduleStore(SqlQueryable, mirrors PostgresConversationMemory); purerunSchedulerTick+startSchedulerover INJECTABLE timers/clock (no real timers in tests); coalesce drains post-sleep misses to one fire. Opt-in multi-replicaRedisLeaseGuard(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 installedcron-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 —
AgentTaskRunnerrunsAssistant.askand delivers result overScheduleDelivery(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 —
Skillrecipe model (name/whenToUse/ recipe/recommendedTools/embedding/useCount);SkillStoreport +InMemorySkillStore+ durablePostgresSkillStore(mirrors the SqlQueryable durable-store triad);skill_save/skill_search/skill_runAgenticTools, opt-in via builtinTools. skill_run re-feeds the recipe toAssistant.ask(lazy resolver, fail-loud at call) with a privilege clamp (clampAllowedToolsintersects with the channel allow-list, never widens). - 3.2 Seed from iris template-library + workflow-recorder + usage-patterns —
seedSkillsconverts 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) + pureSignalAssistantBridge; default stdio transport spawnssignal-cli … jsonRpcand 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 injectablenode:tlsDuplex (with{N}literal framing) + outbound reply via the existing realsendEmailViaSmtp(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 (reusessendDiscordViaBot) + bonus ed25519 Interactions webhook path. Per-space tenant scoping (slack:<team>/discord:<guild>). Exec backends (4.4–4.6) delivered inlibs/iris/agents/tools/code/src/exec-backends/behind anExecBackendport (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
SshExecBackendoverssh2(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), reusescreateOutputCapture.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_runnerPython 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/:SubagentRunnerspawns a governed CHILD run viaAgentRunManager(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 inheritsprocess.env, parent+child resource disposal, kill-cascade identity inheritance. Opt-in via builtinTools. 39 tests (full lib 352).