Generated from docs/softmap.toml by docs/tools/softmap.py, which refuses to build if the data breaks the layering rules or disagrees with the repository. Edit the data in the same change that builds or alters a component.
2
Map
Click a component to draw its relationships. Each separate piece of software is its own framed stack, with its layers top to bottom. The border shows status; a red dot marks components that handle untrusted input.
calls (selected component calls this)called bynotifies (event or callback, upward)notified by
Select a component on the map.
3
Conventions
Calls go down. A component may call its own layer or a lower one, never a higher one, and the call graph has no cycles.
Events go up. Lower layers reach higher ones only through events or a handler the higher layer registered.
Plugins meet core at a contract. Core never imports provider code, and a plugin never touches the databases.
Outside text is data. Anything that handles text from outside is marked untrusted, and its text reaches a model only as UntrustedText.
Mutations are gated in code. The operation registry's confirm gate, not a prompt, decides whether anything changes: a change is a proposal until a person confirms it.
The oaken command. Built from the operation registry at plugin load, so a plugin's commands appear when it loads. Talks to the running core, or runs in process for the commands that need no core.
HTTP and server-sent events on 127.0.0.1 with token auth. Routes are generated from the operation registry; a guard checks Host, Origin, content type, token, rate and size before any route runs. Also serves the MCP doors.
The local web console: settings and flow, never content. It draws every plugin's row, settings modal and New instance dialog from the plugin's declared UI, and calls the same operations the CLI does.
Every action core offers is one operation: typed input and output, a permission class, an MCP mode and an optional preview. The CLI, API, MCP and console are generated from it. A change is a proposal until a person confirms it.
Registers the format, triage, store and embed job kinds and runs them.
IntakeItem
dataclass
Subject, source, body, labels, key, the parts a formatter made, and what earlier triagers said.
Verdict
dataclass
accept, reject or unsure, with a reason and labels.
Inherits
Class
Bases
What it is
IntakePayload
BaseModel
StorePayload
IntakePayload
External none
Notes
Triage rule: any reject ends it; otherwise every triager answers and the last to answer decides. Each answering triager's labels are written under its own name.
The built-in triagers: dedupe (a repeat from the same source), rules (the owner's ordered rules) and llm (a model asks about your criteria). Others, such as the signal triagers, ship in oaken-plugins.
Picks a model backend and embedder for each call, caches answers by content, keeps a ledger of spend, and refuses a remote backend for a confidential item.
Discovers plugins (entry points, extension directories, oaken-plugins), checks each against its kind's contract, starts them store-first, makes named instances of templates, and says what each is: template, instance or plain.
BasePlugin and the base for each kind, plus the mixins: BaseTemplatePlugin (an instance is how it runs) and BaseMultitenant (several workers may run it). Each plugin writes its api_version and requires_core.
contextual settings, the template pill and the template part of the UI spec.
BaseMultitenant
mixin
Marks a plugin several workers may run at once.
BaseProvider
class
Brings items in with submit().
BaseTriager
class
Rates an item before it is stored; any reject ends it.
Inherits
Class
Bases
What it is
BaseAction
BaseAnalyzer
BaseAnalyzer
BasePlugin
What triagers, enrichers and actions share. Deterministic by default; a plugin that asks a
BaseCache
BasePlugin
The router's answer cache, swapped for another store. Override all five members. `key` may return
BaseEmbedder
BasePlugin
Turns text into vectors. Analyzers reach it only through the router (`embed`).
BaseEnricher
BaseAnalyzer
BaseFormatter
BasePlugin
Splits a raw item into named parts (`oaken_core.parts.Part) before triage. formats` lists
BaseFriendlyNames
BasePlugin
Add to a plugin's bases to give the console and the API (the MCP endpoint) friendly host names, such as `oaken.localhost`.
BaseIngestor
BasePlugin
Stores an item intake accepted and returns its real `SubjectId`. The product's own: core
BaseLLMAnalyzer
BaseAnalyzer
Add to a kind's base for an analyzer that asks a model through the router:
BaseLLMBackend
BasePlugin
Runs a chat model. The router calls `generate; where the model runs (locality`) is the
BaseProvider
BasePlugin
A source: it gets items from outside (a file, a socket, a service) and puts each into intake with
BaseStore
BasePlugin
A store hands core a `Database`. Core and plugins write portable SQL against it.
BaseTemplatePlugin
BasePlugin
Add to a plugin's bases when it only runs as a named instance that is given what makes it what it is: an agent's
BaseTriager
BaseAnalyzer
Rates an intake item before it is stored. Lowest `rank first. Any reject ends it; otherwise every triager answers and the last to answer decides. item.verdicts` holds what the ones before said.
BaseVecAnalyzer
BaseAnalyzer
Add to a kind's base for an analyzer that uses the embedder and vector store, only through
BaseVectorStore
BasePlugin
Stores vectors under a collection and a key, and finds the nearest. A collection never mixes
External none
Notes
Imports the router for the LLM request types; see the router.
The shapes a plugin and core exchange: context, health, operations, job handlers, scheduled tasks, and the UI spec (settings sections and field overrides, buttons, pages, the row and the template part).
What could be added: the oaken-plugins package and the extension directories, listed without importing anything. Reads each plugin's summary, api_version and requires_core from its source.
Sources that bring items into intake. Core ships socket, the machine's door: a template, one instance per Unix path or loopback port, each with its own source and format.
The built-in ingestor: stores each accepted item (body and parts) as a row. It steps aside when a product installs its own, and oaken items list filters it by label, part and date.
The durable job store and the runner. Jobs have a kind, a queue, a priority, retries and leases; a crash loses nothing. Handlers are registered by core and by plugins.
Recurring jobs by interval or cron (UTC). A due schedule enqueues its job and moves its next run in one transaction, so a crash neither repeats nor loses a run. Plugins seed their own tasks once.
External workers with their own tokens that lease jobs over the API or MCP. Off unless enabled; each agent is bounded by the kinds, queues and quotas of its token.
The portable-SQL handle every store provides: execute, fetch, transactions and one migration runner across core and plugin tables. SQLite ships as the default store plugin.
Every class that takes part in an in-core inheritance, read from the live package by docs/tools/classmap.py into docs/softmap.classes.toml. Subclasses nest under their base; the arrow names a base from outside the package.
_Body← BaseModel(Local API)
CallRequest(Local API)
ApiClient← ABC(CLI)
HttpApiClient(CLI)
InProcessApiClient(CLI)
CliError← Exception(CLI)
CoreUnavailable(CLI)
_Base(CLI)
AgentClient(CLI)
HttpApiClient(CLI)
OakenError← Exception(no component)
AIBackendError(no component)
ConfigError(no component)
Conflict(no component)
InvalidInput(no component)
InvalidResult(no component)
NotFound(no component)
PermissionDenied(no component)
ConfidentialRefused(no component)
ConfirmationInvalid(no component)
NotExposed(no component)
SendDisabled(no component)
PluginError(no component)
ProviderError(no component)
AuthError(no component)
QuotaExceeded(no component)
StorageError(no component)
IndexMismatch(no component)
Unavailable(no component)
Deferred(no component)
PermanentJobError(Jobs)
LabelError(Labels)
EventLog(no component)
Tracer(no component)
Event(Events and log)
JobStateChanged(Events and log)
LogEvent(Events and log)
PluginEvent(Events and log)
PluginHealthChanged(Events and log)
QueueStateChanged(Events and log)
TraceEvent(Events and log)
IntakePayload← BaseModel(Intake)
StorePayload(Intake)
BaseLabel(Labels)
ReservedLabel(Labels)
_Quiet← Handler(no component)
_FileHandler← RotatingFileHandler(no component)
_StreamHandler← StreamHandler(no component)
AgentRef← BaseModel(Operation registry)
AgentGrantIn(Operation registry)
JobRef← BaseModel(Operation registry)
CompleteIn(Operation registry)
FailIn(Operation registry)
ScheduleRef← BaseModel(Operation registry)
ScheduleEditIn(Operation registry)
SecretRef← BaseModel(Operation registry)
SecretSetIn(Operation registry)
BaseMultitenant(Plugin base classes)
LlmTriager(Triagers)
BasePlugin(Plugin base classes)
BaseAnalyzer(Plugin base classes)
BaseAction(Plugin base classes)
BaseEnricher(Plugin base classes)
BaseLLMAnalyzer(Plugin base classes)
LlmTriager(Triagers)
BaseTriager(Plugin base classes)
NearJunk(Model router)
DedupeTriager(Triagers)
LlmTriager(Triagers)
RulesTriager(Triagers)
BaseVecAnalyzer(Plugin base classes)
NearJunk(Model router)
BaseCache(Plugin base classes)
BaseEmbedder(Plugin base classes)
BaseFormatter(Plugin base classes)
JsonFormatter(Formatters)
BaseFriendlyNames(Plugin base classes)
BaseIngestor(Plugin base classes)
ItemsIngestor(Items ingestor)
BaseLLMBackend(Plugin base classes)
BaseProvider(Plugin base classes)
SocketProvider(Providers)
BaseStore(Plugin base classes)
SqliteStore(no component)
BaseTemplatePlugin(Plugin base classes)
SocketProvider(Providers)
LlmTriager(Triagers)
BaseVectorStore(Plugin base classes)
SqliteVecStore(no component)
MemoryVectors(Model router)
StubBackend(Model router)
StubEmbedder(Model router)
6
Call flows
Each step is one call from one component into another, checked against the declared relationships at build time.