# Oikos — Component Views > Companion to [the Model](README.md) and [the Framework](framework.md). > Where README.md's nine Views cut across the whole system by *concern* > (requirements, behavior, risk...), this document cuts across it by > *component* — one View per running part of the System, going one layer > deeper into its own internal structure than the whole-system Views do. > Per [framework.md](framework.md) §4, each section below is still a View > and must still answer Holt's three questions; they're stated once per > section rather than as a separate table, since here the Stakeholder is > almost always the same ("an engineer about to change this component") > and the Notation is the same (prose + Mermaid) throughout. **How to use this alongside the other two documents:** if you're deciding *whether something belongs in the Model*, read [framework.md](framework.md). If you're asking *what does the system do and why*, read [README.md](README.md). If you're about to **change code in a specific package** and want to know its internal shape, its own state, and what's already known to be broken or dormant inside it before you touch it, read the relevant section here. ## Contents | Component | Path | Status | |---|---|---| | [1. oikos api](#1-oikos-api) | `internal/httpapi`, `internal/mcp`, `internal/policy` | ✅ live — the decision/execution gate | | [2. oikos scheduler](#2-oikos-scheduler) | `internal/scheduler`, `internal/checkdefaults` | ✅ live — the observe loop | | [3. oikos notifier](#3-oikos-notifier) | `internal/notifier` | ✅ live — approval delivery | | [4. nomos](#4-nomos-agent-gateway) | `cmd/nomos` | ✅ live — the agent, unauthenticated gateway | | [5. web control room](#5-web-control-room) | `web/src` | ✅ live — standalone SPA | | [6. PostgreSQL/TimescaleDB](#6-postgresqltimescaledb) | `migrations/`, `seeds/` | ✅ live — the System's own source of truth | | [7. Dormant components](#7-dormant-components) | `internal/actuator`, `internal/learning` | 🔴 compiled, never started | | [8. Auxiliary components](#8-auxiliary-components) | `cmd/webhook`, `cmd/desktop` | ✅ live — deploy + packaging, not decision logic | --- ## 1. oikos api **Stakeholders:** engineers extending the MCP tool surface, the `run` gate, or REST endpoints; anyone debugging why a specific command was or wasn't classified the way they expected. **Why this View earns its place:** this is the single component where the highest-consequence findings in [Risk & Safety](README.md#8-risk--safety) live — extending it without knowing its internal shape is how the kill-switch gap and the two-classifiers problem happened in the first place. ### oikos api — Internal structure | File | Lines | Role | |---|---|---| | `internal/httpapi/server.go` | 842 | `NewHandler` (routing entry, L75), `combinedAuth` (L229), OIDC JWKS discovery/fetch/validate (L337-524), `GetActor` (L526), OIDC config/token/callback handlers (L575-712), `ListenAndServe` (L811) | | `internal/httpapi/impl.go` | 1,639 | Entity CRUD, lifecycle transitions + preconditions (per [ADR-0014](../adr/0014-entity-model.md)) | | `internal/httpapi/phase3.go` | 2,627 | Executions, approvals (`DecideApproval`), `sshExec`, `executeApprovedAction`, autonomy-settings read/write endpoints — the **largest single file in the component** | | `internal/httpapi/sse.go` | 366 | `LISTEN/NOTIFY` fan-out, ring-buffer replay | | `internal/httpapi/activity.go` | 215 | `agent_activity` read endpoints | | `internal/httpapi/knowledge.go` | 286 | Knowledge search/content endpoints | | `internal/httpapi/dashboard.go` | 172 | `dashboard/summary` | | `internal/httpapi/learning_view.go` | 129 | `learning/timeline`, `learning/trend` | | `internal/httpapi/problem.go` | 71 | RFC 9457 `problem+json` error envelope | | `internal/httpapi/default_checks.go` | 13 | Thin wrapper calling `internal/checkdefaults` on entity creation | | `internal/mcp/server.go` | 1,691 | All 33 MCP tool registrations (`get_entity` at L76 through `list_my_secrets` at L753), `sshExec` (L1031), `resolveExecTarget` (L1223), **`classifyAndGate`** (L1264-1417) | | `internal/policy/command.go` | 174 | `ClassifyCommand` (L108) — the **live** classifier, `computeCommandRisk` (L127), `allSegmentsReadOnly` (L157), `riskRank` (L26) | | `internal/policy/classify.go` | 157 | `ClassifySignal` (L46) — **dead code, zero callers** (see [Roadmap §9.2](README.md#92-code-real--dead-code--schema-only-matrix)) | ### oikos api — internal call structure: the `run` gate, by file README.md's [§3.3](README.md#3-functional-architecture) shows the *decision logic* of the `run` gate. This shows the *code path* — which file hands off to which — because they're not the same question: the decision flowchart tells you what happens, this tells you where to go fix it. ```mermaid flowchart LR MCP["mcp/server.go\nrun tool handler, L366"] --> GATE["mcp/server.go\nclassifyAndGate, L1264"] GATE --> RESOLVE["mcp/server.go\nresolveExecTarget, L1223"] GATE --> CLASSIFY["policy/command.go\nClassifyCommand, L108"] CLASSIFY --> RISK["policy/command.go\ncomputeCommandRisk, L127\nallSegmentsReadOnly, L157"] GATE -->|read_only or window open| EXEC["mcp/server.go\nsshExec, L1031"] GATE -->|otherwise| APPROVAL["phase3.go\ncreateApproval"] APPROVAL -->|operator decides| DECIDE["phase3.go\nDecideApproval"] DECIDE --> EXEC2["phase3.go\nsshExec\n(separate implementation)"] style CLASSIFY fill:#e8f5e9,stroke:#2e7d32 style EXEC fill:#fff3e0,stroke:#e65100 style EXEC2 fill:#fff3e0,stroke:#e65100 ``` The two orange boxes are the same finding stated visually: `mcp/server.go` and `phase3.go` each have **their own `sshExec`**, independently written, not sharing an implementation. Fix one path's SSH handling and the other is untouched — verified during the Roadmap audit, not assumed. ### oikos api — Interfaces this component owns Full catalogs live in [README.md §5](README.md#5-interfaces-icd) (33 MCP tools, REST groups, SSE event types) — not repeated here. What's specific to *this* component's internal ownership: `internal/mcp/server.go` owns every MCP tool; `internal/httpapi/{impl,phase3,sse,activity,knowledge, dashboard,learning_view}.go` own the REST surface between them, split by resource area rather than by file size; `internal/policy/command.go` is a pure function library with no HTTP surface of its own, called only from `classifyAndGate`. ### oikos api — Status and known issues All of the following are detailed with evidence in [Roadmap & Traceability](README.md#9-roadmap--traceability) and [Risk & Safety](README.md#8-risk--safety) — cross-referenced here so an engineer opening this specific package sees them before making a change, not after: - `policy.ClassifySignal` (in this component) is dead code; the schema it reads (`autonomy_settings.global.auto_act`, `never_auto_act.*`) is therefore not enforced by anything in the live request path — [§8.1](README.md#81-the-kill-switch-gap-verified-most-important-finding-in-this-model). - `notifier.VerifyApprovalToken` (a different component, §3 below) is dead; `phase3.go:DecideApproval` reimplements token verification inline instead of calling it. - `domain.Execution`'s state constants are descriptive only — `phase3.go` writes ad-hoc SQL string statuses that don't map 1:1 onto them. - SSH host key verification is disabled (`InsecureIgnoreHostKey`) on the actuation path — open gap B4. --- ## 2. oikos scheduler **Stakeholders:** engineers adding a new probe kind or debugging why a signal did or didn't fire. **Why this View earns its place:** the scheduler is the only component that runs unattended on a fixed interval with no operator or agent triggering it — its failure modes look different from every request-driven component above. ### oikos scheduler — Internal structure | File | Lines | Role | |---|---|---| | `internal/scheduler/scheduler.go` | 761 | Everything — no sub-packages | | `internal/scheduler/init.go` | 13 | `RunnerForMain()` — the only thing `cmd/oikos`'s `scheduler` role calls | | `internal/checkdefaults/defaults.go` | — | `ForEntityType` (L60-123), `Ensure` (L144-204), `DefaultInterval` (L133-142) — default check provisioning on entity creation | Key functions inside `scheduler.go`: `Run` (L36-64, the tick loop, default 30s), `runCheckPass` (L67-94, loads `check_defs`, dispatches with a 10-worker `errgroup` limit), `runCheck` (L104-186), `executeCheck` (L237-254, the kind dispatcher), `resolveSignal` (L206-225), `evaluateSeverity` (L737-760), `staleSweep` (L286-333, 3× fastest interval / 5 min floor). ### oikos scheduler — behavior specific to this component: the probe dispatch ```mermaid flowchart TB TICK["Run tick, every 30s"] --> LOAD["ListEnabledCheckDefs"] LOAD --> DISPATCH["executeCheck: dispatch by kind"] DISPATCH --> HTTP["checkHTTP, L336"] DISPATCH --> TCP["checkTCP, L393"] DISPATCH --> DISK["checkDisk, L424"] DISPATCH --> CERT["checkCertExpiry, L474"] DISPATCH --> PING["checkPing, L545"] DISPATCH --> SSH["checkSSHScript, L613"] HTTP & TCP & DISK & CERT & PING & SSH --> RESULT["checkResult struct\nhealth, signalKind, evidence, metrics"] RESULT -->|healthy| RESOLVE["resolveSignal\nraw SQL, bypasses Signal.CanTransition"] RESULT -->|unhealthy| UPSERT["UpsertSignal\ndedup by target+kind"] RESULT --> METRICS["INSERT metric_samples"] RESULT --> STATUS["UpsertEntityStatus"] ``` `checkSSHScript` (L613-724) is the odd one out: it shells out to the system `ssh` binary directly (`BatchMode=yes`, `StrictHostKeyChecking=no`) rather than using a Go SSH library, restricted to scripts matching `^[a-z][a-z0-9_-]+\.sh$` at a fixed path `/opt/oikos/checks/