- X-Oikos-Session-Id header → context fallback for _session_id - sessionIDFromArgsOrContext() reads from args, then header - AssentWindowActive falls back to session-ID-only LIKE lookup - windowActiveLike() for LIKE-pattern autonomy_settings queries - mcpBearerToken: package-level resolved token replaces os.Getenv() in decide_approval - Bump 0.36.0 → 0.37.0
22 KiB
dsh-as-agent: replace nomos with DeepSeek Harness
Date: 2026-08-16
Status: Active
Scope: cmd/nomos/ → dsh sidecar; oikos stays as Go backend behind MCP
1. Summary
Replace the nomos agent (cmd/nomos/, ~5,500 LOC) with DeepSeek Harness (dsh), a TypeScript/Cordis agent harness where everything is a plugin — model adapters, tool registry, agent loop, session log, Web UI. dsh runs as a Node.js sidecar alongside the oikos API, connecting via the existing MCP interface (67+ tools). Custom dsh plugins bridge oikos's Postgres-backed session model, policy engine, and approval gating.
oikos-web (Svelte 5 SPA) is replaced by dsh's built-in Web UI. The Go backend remains untouched — dsh is purely an agent/UI replacement.
2. Session persistence: Postgres vs dsh SQLite
dsh ships with @deepseek-ai/dsh-session backed by JSONL or SQLite (event-sourced log). oikos uses flat Postgres tables (agent_sessions, agent_messages, agent_activity, session_plan_steps, etc.) with ~2,200 lines of domain logic in internal/nomos/session/store.go.
| dsh SQLite (default) | oikos Postgres | |
|---|---|---|
| Model | Event-sourced: every event appended, deriveMessages() projects model history |
Flat: typed tables, aggregate columns (message_count, tool_call_count), precomputed views |
| Tightness with oikos data | dsh owns session data in isolation; oikos backend cannot JOIN across sessions→entities | Session data lives in oikos Postgres alongside entities, executions, knowledge, events — one FK graph |
| Cross-cutting queries | dsh would need its own API for "all sessions touching host X" | SELECT ... FROM agent_messages JOIN entities ... works directly — no bridge |
| Auto-upsert knowledge | dsh would replicate nomos's autoUpsertKnowledge logic |
Direct INSERT INTO knowledge_entities in the same DB — atomic, no network hop |
| Agent activity/audit | dsh would replicate agent_activity table writes |
Already exists: agent_activity with entity FK, tool_name, success, duration, tokens |
| Plan + execution linking | dsh would replicate nomos_plan_executions join table |
Already exists: executions ↔ session_plan_steps ↔ agent_sessions |
| dsh ecosystem compatibility | Full — dsh's event-sourced model, built-in compaction, fork, replay, persistence seams all work out of the box | Partial — must write a custom persistence plugin implementing dsh's session-persistence seam against Postgres |
| Session events (chunks, boundaries) | dsh stores raw assistant/chunk and turn/start/turn/end events for faithful replay |
oikos stores only the rolled-up assistant/tool messages — loses per-chunk granularity |
| Migration cost | None — dsh owns its storage | Medium — must write the Postgres persistence plugin (~1 week) |
| DB schema churn | None | Adds migration files for dsh's event-sourced log format alongside existing flat tables |
Verdict: Postgres is the right choice. The tight coupling with entities, knowledge, executions, and the event bus (observability.Event via PG NOTIFY) is too valuable to sever — it's what makes oikos oikos rather than a generic agent host. The cost is writing a custom dsh-session-persistence-postgres plugin that maps dsh's event-sourced SessionEvent log onto Postgres rows, while preserving enough granularity for dsh's deriveMessages() to reconstruct model history faithfully. The oikos flat tables (agent_sessions, agent_messages) become projections/views over the event log, maintained for backward compat with the REST API.
3. Architecture
┌─────────────────────────────────────────┐ MCP (JSON-RPC over HTTP)
│ dsh (Node.js) │ ◄────────────────────────────┐
│ │ │
│ ┌───────────────────────────────────┐ │ │
│ │ dsh-base bundle │ │ │
│ │ - dsh-agent-loop (turn/step) │ │ │
│ │ - dsh-llm-deepseek (model) │ │ │
│ │ - dsh-tools (tool pipeline) │ │ │
│ │ - dsh-session (event log) │ │ │
│ │ - dsh-interaction (approvals) │ │ │
│ │ - dsh-web-app (built-in UI) │ │ │
│ └───────────────────────────────────┘ │ │
│ │ │
│ ┌───────────────────────────────────┐ │ │
│ │ oikos dsh plugins │ │ │
│ │ │ │ │
│ │ @oikos/dsh-mcp-tools │──┤ tools/list + tools/call │
│ │ → discovers 67+ tools via MCP │ │ to oikos MCP server │
│ │ → ctx.tools.register() each │ │ │
│ │ │ │ │
│ │ @oikos/dsh-policy │──┤ classify_command / │
│ │ → tools/pre-execute listener │ │ preflight MCP tools │
│ │ → calls oikos classification │ │ │
│ │ → returns allow/deny/ask │ │ │
│ │ │ │ │
│ │ @oikos/dsh-session-pg │──┤ INSERT/UPDATE/SELECT │
│ │ → implements session- │ │ on oikos Postgres │
│ │ persistence seam vs Postgres │ │ │
│ │ │ │ │
│ │ @oikos/dsh-task-tools │ │ (same Postgres pool) │
│ │ → set_goal, propose_plan, etc. │──┤ │
│ │ → writes oikos tables directly │ │ │
│ │ → emits oikos observability │ │ │
│ │ Events for SSE fan-out │ │ │
│ │ │ │ │
│ │ @oikos/dsh-experiences │ │ │
│ │ → homelab-specific UI nodes │ │ │
│ │ and workflows │ │ │
│ └───────────────────────────────────┘ │ │
│ │ │
│ dsh Web UI (replaces oikos-web) │ │
│ → Serves at :3080 (dsh default) │ │
│ → oikos pages migrated as dsh │ │
│ ConversationNodes + custom views │ │
└─────────────────────────────────────────┘ │
│
┌───────────────────────────────────────────────────────────────────────┤
│ oikos (Go service — unchanged) │
│ │
│ ┌──────────────────────┐ ┌──────────────────────────┐ │
│ │ MCP server: 67+ tools │ │ REST API (chi) │ │
│ │ - Entity, Ops, │ │ - /api/v1/entities │ │
│ │ Knowledge, Analysis │ │ - /api/v1/executions │ │
│ └──────────────────────┘ │ - /api/v1/knowledge │ │
│ │ - health, metrics, etc. │ │
│ ┌──────────────────────┐ └──────────────────────────┘ │
│ │ Policy engine │ │
│ │ (internal/policy) │ ┌──────────────────────────┐ │
│ └──────────────────────┘ │ Postgres │ │
│ │ - entities, relationships │ │
│ ┌──────────────────────┐ │ - executions, signals │ │
│ │ Scheduler + probes │ │ - agent_sessions, messages│ │
│ └──────────────────────┘ │ - knowledge_entities │ │
│ │ - events (SSE NOTIFY) │ │
│ ┌──────────────────────┐ │ - agent_activity │ │
│ │ Secrets (Infisical) │ └──────────────────────────┘ │
│ └──────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
4. Phases
Phase 1: Scaffold + MCP tool bridge (2 weeks)
Goal: dsh boots, connects to oikos MCP, agents run real tasks through oikos tools.
-
Create
oikos-dsh/monorepo alongsideoikos-web/- Workspace root with pnpm,
tsconfig,vitest oikos-dsh/bundles/oikos-profile/— profile YAML composingdsh-base+ oikos pluginsoikos-dsh/plugins/— custom plugins directory
- Workspace root with pnpm,
-
@oikos/dsh-mcp-toolsplugin- On
apply(ctx): connect to oikos MCP (configured URL + token) - Call
tools/list, for each tool:ctx.tools.register(mcpToolDef) - On execute: forward to oikos
tools/call, stream result back - Handle
_session_idscoping for concurrent task isolation - Cache tool list, invalidate on reconnect
- On
-
Boot dsh with the profile
dsh --profile oikos- Validate connectivity at startup
- Run
get_entity,list_entitiesthrough dsh → confirm roundtrip
-
Port the golden evals
evals/golden.yaml→ dsh vitest test suite- Assert tool calls, completion, plan structure same as nomos
- Gate: 4/4 golden evals pass (trivial_readonly, plan_advances_on_proceed, ui_complaint_no_rerun, knowledge_preferred_over_rerun)
Check: dsh agent answers "What is the state of lxc:dns?" through oikos MCP, returns the same answer nomos would.
Phase 2: Postgres session persistence (1.5 weeks)
Goal: dsh sessions write to oikos Postgres, not dsh SQLite.
-
Design the bridging schema
- Add
session_event_logtable:(session_id UUID, seq INT, event_type TEXT, payload JSONB, created_at TIMESTAMPTZ) - This is the event-sourced log dsh needs for
deriveMessages() agent_sessions,agent_messagesbecome materialized projections — updated via triggers or application-level write-throughagent_activitystays as-is for the audit/operations views
- Add
-
@oikos/dsh-session-pgplugin — implements dsh's session-persistence seamsubscribe("session/event")→ append row tosession_event_logsubscribe("session/flush")→ commit/notify- On load:
SELECT * FROM session_event_log WHERE session_id = $1 ORDER BY seq→ rebuildSession - Session lifecycle:
session/created→ ensure row inagent_sessions;session/disposed→ finalize outcome/summary - Use oikos
pgxpool(via Node.jspgmodule)
-
@oikos/dsh-task-toolsplugin — replaces nomos's task tools- Register
set_goal,propose_plan,update_plan_step,complete_task,ask_operator - Each writes directly to oikos Postgres tables (
agent_sessions,session_plan_steps,session_questions) - Emit oikos
observability.Eventvia PGNOTIFYfor SSE live-updates - Mirror nomos's business logic: auto-append writeback step, refuse complete without writeback, plan generation tracking, completion ordering, auto-complete-if-plan-done safety net
- Register
-
Verify against evals — all golden evals pass through dsh with Postgres persistence
Check: Create a session in dsh, verify agent_sessions and agent_messages rows appear in oikos Postgres. Read them back from oikos REST API.
Phase 3: Policy bridge (1 week)
Goal: dsh respects oikos risk classification and approval gating.
-
@oikos/dsh-policyplugin- Listen on
tools/pre-executewaterfall - For each tool call: call oikos
preflightorclassify_commandMCP tool - Map risk class to dsh decision:
readonly→allow(no gate)reversible_low→allow(auto-execute, same as nomos)config_mutation→ check oikos assent window; if active →allow, else →askdestructive→ check oikos destructive window; if active →allow, else →askwith typed-confirmation requirement
askreturns a dshInteraction— the UI shows an approval dialog; operator decides → tool continues or is denied
- Listen on
-
Backend: no changes needed — oikos MCP
classify_commandandpreflightalready exist
Check: A run call with config_mutation risk triggers an approval dialog in dsh UI. "Go ahead" in chat grants it.
Phase 4: UI migration (3-4 weeks)
Goal: dsh Web UI replaces oikos-web.
-
dsh Web UI basics
- dsh ships its own Web UI: session list, chat window with tool cards, assistant chunks, turn/step boundaries
- No changes needed for basic agent chat — it works out of the box
-
Custom ConversationNodes for oikos pages
- Entity Graph page — reimplement sigma.js graph as a dsh Web Client plugin
- ConversationNode listens for tool/call events, renders entity graph
- Health/Type color mode toggle, filter presets (All, Problems, Infra)
- Port
EntityGraph.svelte's logic to a dsh conversation node
- Operations page — execution list, approval management
- Use dsh's existing
interactionUI for approvals - Custom node for execution history + systemctl status
- Use dsh's existing
- Knowledge page — wiki browser, search, quick-open
- dsh already has
search_knowledgetool; add a Knowledge conversation node - Port
WikiTree,WikiReader,WikiOverviewfrom oikos-web
- dsh already has
- Signals page — signal list, ack/mute/resolve
- Custom node reading from oikos REST API (via dsh
agent.injector API call)
- Custom node reading from oikos REST API (via dsh
- Overview/Dashboard — fleet summary, health counts
- dsh
get_health_summaryalready exists; render as dashboard cards
- dsh
- Config page — API token, server URL, theme settings
- dsh has
settingsandcredentialsseams; hook into them
- dsh has
- Desktop shell / mascot — app launcher, dock, taskbar, Cluck mascot
- dsh has no desktop paradigm — either skip the shell or implement as a ConversationNode
- Mascot can be ported as a persistent UI element
- Entity Graph page — reimplement sigma.js graph as a dsh Web Client plugin
-
Route mapping
oikos-web page dsh equivalent Overview.svelte Custom dashboard ConversationNode EntityGraph.svelte Custom entity-graph ConversationNode Ops.svelte Custom operations ConversationNode Signals.svelte Custom signals ConversationNode Knowledge.svelte / KnowledgeBase.svelte Custom knowledge ConversationNode Config.svelte dsh settings/credentials Chat session Built-in dsh chat window Learning.svelte Custom learning ConversationNode AppStore.svelte Custom app-store ConversationNode -
CSS theme migration
- oikos uses dark terminal aesthetic (cyberspace theme, amber/green, dithered images)
- dsh has its own light/dark theme — customize via CSS overrides in the profile
- Port the GlyphIndicator, MascotLayer, and other visual signatures
Check: All major oikos-web pages have a functional equivalent in dsh UI. Entity graph renders with force layout and health coloring.
Phase 5: Experiences as plugins (ongoing)
With the bridge complete, "experiences" are standard dsh plugins registered in the profile:
| Plugin | What it does |
|---|---|
@oikos/dsh-incident-response |
Guided workflow: detect signal → classify → run remediation → verify → document with upsert_knowledge. Uses dsh plan-mode for structured steps. |
@oikos/dsh-infra-deploy |
Provision LXCs with blast radius visualization. Pre-flight check via get_blast_radius, then step-by-step run with approval gates. |
@oikos/dsh-knowledge-autosync |
Background ctx.jobs that periodically audits knowledge gaps (orphan docs, stale entities) and suggests upserts. |
@oikos/dsh-session-review |
Port the session-review skill from .agents/skills/ to a dsh tool: given a session ID, analyze transcripts, compare objective to outcome, propose fixes. |
@oikos/dsh-fleet-dashboard |
Real-time fleet health with drill-down. Uses oikos SSE event stream + dsh Web Client custom rendering. |
Each plugin:
- Registers tools on
ctx.tools(model-visible capabilities) - Registers ConversationNodes on the Web Client (UI components)
- Listens on
agent/*orsession/eventfor reactive behavior - Is independently versioned and hot-loadable via Cordis
5. Deleted code
On completion of Phases 1-3, the following oikos code is decommissioned:
cmd/nomos/— entire directory (~5,500 LOC): agent.go, server.go, mcp.go, store.go (the old flat store), assent.go, continue.go, tasks.go, turngate.go, messagequeue.go, retrycap.go, plus testsnomos/— SOUL.md, config.yaml, skills/internal/nomos/session/— moved to dsh plugin, but the domain types and some logic may be extracted into a sharedoikos-dshnpm packageinternal/httpapi/chat-related endpoints — replaced by dsh's own agent session endpointscompose/web/— web service in docker-compose (served oikos-web SPA)desktop/— Wails desktop wrapper (dsh Web UI is a PWA, no native wrapper needed)
The following oikos code stays:
internal/httpapi/— REST API for entities, executions, knowledge, signals, healthinternal/mcp/— the 67+ MCP tools (now serving dsh instead of nomos)internal/policy/— risk classification engineinternal/scheduler/— health checks, metrics, probesinternal/secrets/— Infisical/SOPS integrationinternal/nomos/assent/,internal/nomos/session/domain types (may be extracted to shared package)
6. Migration path
The cutover is a rolling deployment:
- Deploy dsh alongside nomos — both agent runtimes run in parallel during development.
compose/dsh/joins the docker-compose stack. - Port the UI incrementally — dsh UI and oikos-web coexist on different ports: dsh on
:3080, oikos-web on:3000. The Caddy reverse proxy routes/chat/*and/to dsh during testing. - Switch the default route — once dsh passes all golden evals and the UI covers the main pages, Caddy routes all traffic to dsh. oikos-web becomes available at
/legacyduring the transition. - Cleanup — remove
cmd/nomos/,compose/web/,oikos-webrepo (or archive).
7. Risks
| Risk | Mitigation |
|---|---|
| dsh breaking changes — dev preview, no semver | Pin a specific git commit + pnpm-lock.yaml. Pin in VERSION file. Stretch: fork the core packages we depend on. |
| Session persistence bridge lag — dsh expects event-sourced model, oikos has flat tables | Accept dual-write during migration. The session_event_log table feeds dsh's deriveMessages(); legacy agent_messages stays for REST API backward compat until all consumers migrate. |
| UI migration scope — entity graph, desktop shell, mascot are non-trivial ports | Start with chat + operations (90% of daily use). Entity graph and mascot come after. The old oikos-web stays readable during transition. |
| Golden eval regressions — subtle behavioral differences between nomos and dsh agent loops | Run evals in CI on every dsh change. Nomos stays deployed until evals pass at parity. |
| Team TS inexperience — you said TS is OK, but ramp-up for Go developers | Start with small plugins (MCP bridge is ~200 LOC). The dsh extension cookbook is well-documented. |
| Performance — every tool call crosses TS → HTTP → Go | Same architecture as nomos (which also crossed HTTP). Latency is the same. The MCP server is fast (no serialization overhead beyond JSON). |
| oikos-web features not supported by dsh UI — desktop shell, window management, mascot | Assess during Phase 4. If the desktop paradigm is essential, implement it as a dsh conversation node (which can render any HTML/CSS) rather than maintaining two UIs. |