Core interface reference
The supported boundaries between mikan's platform, runtime, harness, session, execution, vault, and host layers.
This page documents mikan’s architectural interfaces: the contracts that cross a subsystem, process, persistence, or package boundary. It is not an inventory of every exported helper. A helper used only inside one subsystem is an implementation detail even when TypeScript currently marks it export.
Stability levels
Section titled “Stability levels”| Level | Meaning | Compatibility expectation |
|---|---|---|
| Public | Imported by npm consumers | Preserve or version deliberately |
| Host integration | Used to embed the runtime or add a platform/runtime backend | Preserve while the integration exists |
| Wire / persisted | Stored on disk or sent across a process/network boundary | Migrate explicitly; readers should tolerate supported older forms |
| Internal | Connects modules inside the mikan CLI | May change with coordinated call sites and tests |
src/index.ts is an explicit export list rather than a wildcard re-export, and src/test/public-api.test.ts snapshots it so an accidental addition fails CI. It exports only what an embedder needs; harness, session, and sandbox internals are not part of the package interface.
Boundary map
Section titled “Boundary map”flowchart TD P["Platform adapter"] --> I["Normalized intake"] I --> R["Conversation runtime"] R --> H["Agent harness"] H --> T["Agent tools"] T --> E["ExecutionEnv"] R --> S["Session and chat stores"] R --> V["Vault and execution resolver"] O["Office identity and layout"] --> R O --> S O --> V W["Web and events"] --> RThe runtime is the coordinator. Platforms must not know harness persistence, the harness must not know platform SDK objects, and tools must not know whether execution is local, containerized, or remote. The office module supplies the identity and paths that the runtime, the stores, and the vault all address a conversation by.
Maintainer ownership and seam register
Section titled “Maintainer ownership and seam register”The following eight owner scopes are the long-lived division of responsibility. An owner may change its implementation freely behind its ports, but changing a port requires updating both sides and the contract tests named below. These scopes describe code ownership, not process or privilege isolation.
| Owner scope | Primary modules | Owns | Hands off |
|---|---|---|---|
| Platform entry | src/adapters/, src/types.ts | SDK normalization, intake ordering, platform response transport, and MessagingInfo including trustModel | Platform-neutral ConversationEvent, ConversationContext, and capability ports |
| Office identity | src/office/ | OfficeAddress/OfficeKey derivation, the Workspace/Office layout values, office materialization and workspace projection, and the registry journal | Frozen Office values with precomputed paths, plus projection resolution from current policy; the durable raw-id ↔ office mapping |
| Runtime/session | src/runtime/, src/sessions/ | Session-key grammar, per-session serialization, chat/session synchronization, runner lifecycle, stop/reset, and shutdown | Validated events to the runner; office and actor inputs to execution resolution |
| Harness/tools | src/harness/, src/harness/tools/ | Model turns, execution resolution, budgets, tool schemas, skills, subagents, and host capability calls | ConversationResponder output and ExecutionEnv operations; it does not receive platform SDK objects |
| Scheduled events | src/events/ | Event wire protocol, parser/builder, the office-confined host store, filename validation, and scheduler lifecycle | EventStore to harness/Web consumers; normalized events to platform messaging |
| Sandbox/workspace | src/sandbox/ | Execution environments, mount topology, runtime/host path context, managed container lifecycle, and file transport | A Pi ExecutionEnv with declared path and credential capabilities |
| Vault/web/security | src/vault/, src/adapters/web/, src/sandbox/identity.ts | Credential identity and injection policy, host-only portal/token surfaces, mount collision checks, and security-sensitive authorization decisions | Actor/trust inputs to vault policy; credentials only to the selected environment |
| Config/CLI | src/settings/, src/cli/, src/migrations/, src/adapters/commands/ | Boot/flag grammar, settings persistence, state migrations, and the command manifest | Effective settings and deterministic command inventory |
A conversation harness instance is context-isolated from other instances, but that is not a filesystem or process sandbox. A sandbox’s execution environment changes where tools run and what mounts they receive; host mode and the unmanaged container:* mode do not enforce office visibility. Keep these concerns separate when reviewing a seam.
Cross-owner seams
Section titled “Cross-owner seams”The producer/consumer pairs below are the supported bridges. The named producer owns the representation and ordering; the consumer must use the port or canonical parser rather than reconstructing it.
| Seam | Producer → consumer | Invariants, ordering, and failure behavior |
|---|---|---|
| Platform intake | Platform entry → Runtime/session | Intake order is magic word → trigger policy → attachments → log → busy policy → queue → dispatch. stop bypasses trigger policy, attachment work, and queueing. Invalid conversation or foreign session identity fails before queueing; attachment failures propagate. |
| Office identity | Platform entry → Office identity → Runtime/session, stores, vault, and settings | Adapters produce an OfficeAddress at intake. Consumers take Office/Workspace values, never a directory basename. Office.ensure() records the office in the registry before creating the directory, so no office directory exists that the registry cannot enumerate. |
| Session identity | Platform entry / Runtime/session → Runtime/session, harness, and stores | src/sessions/session-key.ts owns conversationId[:suffix], a raw platform value. A platform override is accepted only when it belongs to the conversation; otherwise derivation throws. Runtime state is keyed by office plus session key, so a session key can never select another office’s state. No consumer splits session keys directly. |
| Command inventory | Config/CLI → Platform entry, Runtime/session, and web/session view | COMMAND_MANIFEST is the single inventory. Built-ins consume recognized command forms; unmatched slash text remains an agent prompt; stop is a magic word, not a handler. |
| Runtime to harness | Runtime/session → Harness/tools | handleEvent derives the key and serializes one key while allowing other keys to run concurrently. Thread bootstrap waits for a running parent session to seal. A platform tool pack is created per runner because bindRun is mutable. |
| Run output | Harness/tools → Platform entry | The harness emits response source: platform-neutral Markdown/GFM, with user mentions written as <@userName>. Converting that into native presentation is wholly the adapter’s job — Slack resolves <@userName> to <@U…> on every outgoing path, and no adapter may steer the model toward platform-native markup. Optional responder capabilities are feature-detected. For one run, response operations retain call order; adapters may buffer or stream, but must not expose harness persistence or SDK objects to the harness. |
| Runtime ↔ sandbox path | Runtime/session → Sandbox/workspace → Harness/tools | Runtime supplies the host workspace and a runtime cwd of <runtime workspace root>/<officeKey>; the office key names the same segment on both sides, so the path does not change meaning at the boundary. The sandbox registry supplies the path context (getSandboxPathContext). Prompts and tool output use runtime paths, while host-side attachment translation uses the declared context. Paths outside the mapped root are not translated. This is a path contract, not a claim of filesystem isolation. |
| Sandbox file transport | Sandbox/workspace → Harness/tools | Tools use the ExecutionEnv file operations, never shell quoting protocols. The container environment sends them to Pi’s pi-env daemon in the container as framed bytes, and failures return Pi’s FileError codes. |
| Trust and vault | Platform entry → Runtime/session → Vault/web/security → Sandbox/workspace | MessagingInfo.trustModel is passed into actor resolution; vault policy is keyed to that value, not platform-name strings. Omitted trust defaults to membership; open-trigger never receives an ambient shared-vault copy, and only oci use that ambient path. Explicit vault provisioning remains a separate administrative decision. |
| Settings and live runners | Config/CLI (commands and admin are adapters) → config disk and Runtime/session | applyConversationSettings clears the cached runner before writing an LLM change and refuses both actions while busy. applyOfficeVisibility behaves the same way for a visibility change, since the mount set is baked into the runner’s environment. Global LLM settings write first, then clear idle runners, clear busy ones once their run settles, and report those as stale. Sandbox resource limits and Slack reply settings are re-read at use time and do not clear runners. |
| Web capability bridge | Vault/web/security → Runtime/session and commands | Portal stores are optional runtime services. A portal URL without its backing store fails when that portal is used; absent stores otherwise produce disabled/not-configured behavior. Route payloads are bundled-UI contracts, not a general REST API. |
| Scheduled events | Harness/tools and Admin → Runtime/session through OfficeEventStore | Records live in the office’s host-only State dir; no sandbox mounts them. The event tool and Admin reach an office’s events only through a store bound to that office, and a payload must address the store’s own office. Writers use buildEventPayload; readers use parseEventPayload. Secrets do not belong in event text. |
| History persistence | Platform entry → log.jsonl; Runtime/session and Harness/tools → the office’s sessions.db | Platform truth and agent truth remain separate. Chat sync copies platform messages into a session as attributed user entries, deduplicated by message ID, and never writes an assistant message. |
These bridges deliberately distinguish context isolation from sandboxing. A new owner must not infer credential authorization from a path or infer platform trust from a platform name.
1. Package interface
Section titled “1. Package interface”The npm entry point is @geminixiang/mikan, implemented by src/index.ts.
Supported entry-point groups
Section titled “Supported entry-point groups”| Group | Main symbols | Consumer |
|---|---|---|
| Runtime embedding | createConversationRuntime, ConversationRuntime, ConversationRuntimeOptions | A host that supplies platform and execution dependencies |
| Office and workspace | createWorkspace, createOfficeAddress, officeKey, Workspace, Office, OfficeAddress, OfficeKey | Any embedder: createConversationRuntime requires a Workspace value |
| Platform contract | MessagingBot, ConversationEvent, ConversationContext, ConversationResponder, MessagingInfo | Platform adapters and embedders |
| Commands | CommandHandler, CommandContext, CommandServices, defaultCommandHandlers | Hosts adding deterministic commands to the defaults |
| Execution | SandboxConfig | Hosts choosing the execution backend |
| Models | MikanModels, CreateMikanModelsOptions | Hosts supplying their own model list |
Only paths declared by the package exports map are public. Importing any other generated dist/ path or a source path under src/ is unsupported.
2. Platform intake and response interface
Section titled “2. Platform intake and response interface”Source: src/types.ts (re-exported by src/index.ts).
ConversationEvent
Section titled “ConversationEvent”The platform-neutral trigger delivered to MessagingEventHandler.
| Field | Contract |
|---|---|
address | Canonical OfficeAddress (platform + raw conversationId), created by the adapter at intake |
conversationKind | direct or shared; affects session and credential policy |
ts | Triggering platform message identifier |
thread_ts | Optional parent/root platform message identifier |
user | Platform user identifier |
text | Text after platform mention removal |
attachments | Files already downloaded to host paths |
sessionKey | Optional platform-selected override; otherwise derived by session policy |
vaultConversationId | Optional alternate identity used only for credential routing |
ConversationContext
Section titled “ConversationContext”Carries the office identity, the richer normalized message, the response port, and platform metadata for the same run:
interface ConversationContext { address: OfficeAddress; message: ConversationMessage; responder: ConversationResponder; platform: MessagingInfo;}ConversationMessage and ConversationEvent currently duplicate message id, session, conversation kind, user, text, attachments, and thread identity — and both now carry address. Adapters must keep both representations consistent. This is an internal compatibility seam, not a desirable model for new integrations.
ConversationResponder
Section titled “ConversationResponder”The runtime/harness output port. Required operations cover final text, replacement, diagnostics, tool status, typing/working state, file upload, and response deletion. replaceResponse sets the whole main message; { final: true } marks the final write. Showing the answer while it is written (showsPartialAnswer) and reaction (react) are optional capabilities.
Capability rule: callers must feature-detect optional methods. An adapter may buffer output or implement streaming natively, but it must preserve call order for a single run.
MessagingBot
Section titled “MessagingBot”The host-facing platform port. It combines:
- lifecycle:
start()andstop(); - outbound messages:
postMessage,updateMessage, and optional thread replies, private replies, and bot-response logging; - intake scheduling:
enqueueEvent; - discovery/policy:
getMessagingInfo().
MessagingInfo.trustModel is security-sensitive. membership permits the ambient shared vault when the sandbox type is oci. A surface where anyone can trigger the agent must return open-trigger.
MessagingBot is the single host-facing adapter interface; lifecycle and messaging capabilities are not split across a second shallow interface.
3. Runtime orchestration interface
Section titled “3. Runtime orchestration interface”Source: src/runtime/types.ts and src/runtime/conversation-runtime.ts.
createConversationRuntime(options) returns the public ConversationRuntime orchestrator. Inside src/runtime/, SessionLifecycle is the runner resource authority: it owns per-session serialization, materialization, leases, settlement, invalidation, disposal, idle eviction, and shutdown. The orchestrator owns command, stop/reset, and platform-facing run workflow.
Input methods
Section titled “Input methods”| Method | Semantics |
|---|---|
handleEvent(event, bot, context) | Serialize by office plus derived session key, then dispatch a command or agent run |
runSession(options) | Execute one already-serialized unit; intended for controlled host use |
handleStop(address, sessionKey, bot) / forceStop(address, sessionKey) | Cooperative/user-visible stop versus immediate internal stop |
handleNewCommand(...) | Abort, reset persisted session state, and discard the cached runner |
Control and inspection
Section titled “Control and inspection”| Method | Semantics |
|---|---|
isRunning(address, sessionKey) | Current in-process run state |
getRunningSessions() | Snapshot for admin/observability surfaces; each entry carries its address |
refreshConversation(address) | Clear a conversation’s cached runners so the next run rebuilds from settings; returns false while one is busy |
refreshAllConversations() | Clear every idle conversation’s runners, clear busy ones once their run settles, and return the busy addresses |
shutdown(timeoutMs?) | Reject new work, stop runners, and wait for in-flight work up to the deadline |
Every session-scoped entry point takes an OfficeAddress. There is no raw-conversation-id bridge; platform and Admin surfaces carry the platform-scoped address explicitly.
ConversationRuntimeOptions supplies a workspace (the Workspace value from createWorkspace), sandbox/resource services, optional portal token stores, optional commands, model registry, proactive platform operations, and platform tool-pack factories. Missing vault and portal stores degrade to disabled implementations; setting a portal URL without its store is an error when used.
Concurrency invariant: calls for one office and session key are serial; different session keys may run concurrently. Every runner use holds an internal lifecycle lease, so settings invalidation, eviction, and shutdown cannot dispose it mid-operation. A platform tool pack is created per runner, never shared globally, because bindRun mutates its run binding.
4. Command interface
Section titled “4. Command interface”Sources: src/adapters/commands/manifest.ts, src/adapters/commands/types.ts, and src/adapters/commands/registry.ts.
COMMAND_MANIFEST is the platform-facing inventory used to derive slash forms and native registration. CommandHandler.tryHandle(context) is the execution contract. Handlers run in order and the first true result consumes the message.
Recognized built-in command forms are consumed before runner creation. An unmatched slash-prefixed message is still a normal agent prompt. stop is a platform intake magic word, not a CommandHandler; session is the only accepted bare command.
CommandContext contains normalized actor/conversation identity, response and bot ports, command text, privacy state, and CommandServices. Command handlers must not reach into a platform SDK event.
5. Harness interface
Section titled “5. Harness interface”Sources: src/harness/runner.ts, src/sessions/session-store.ts, src/harness/models.ts, and src/harness/types.ts.
MikanAgentSession
Section titled “MikanAgentSession”Owns the model turn loop, message persistence, retries, compaction, budget circuit breakers, skills, bounded subagents, and abort behavior. Its externally meaningful operations are prompt/run, subscribe, abort, model/thinking selection, and disposal.
SessionStore
Section titled “SessionStore”Owns an office’s sessions: one pi-durable SQLite storage, sessions.db in the office’s State dir (ADR 0018). The top-level session is the storage’s root conversation, every other session is a conversation found by its session key, and a session’s name and last run record live in a durable document. SessionHeader (id, createdAt, parentSessionId) identifies a session and its fork parent. One process owns a live storage. mikan migrate imports 0.5.3 session files. Entries form one linear transcript of message, custom, and compaction entries; read the model’s view with buildSessionContext() and the entries still in context with getContextEntries() instead of reading the storage.
MikanModels
Section titled “MikanModels”MikanModels resolves built-in pi-ai models and the custom providers in ~/.mikan/models.json; provider credentials come from that file or from environment variables. Vault credentials are a different boundary: they are injected into tool execution, not used to authenticate the host-side model client.
Subagent profiles
Section titled “Subagent profiles”A subagent is always launched with a named profile — free-form per-run configuration is not a supported surface. loadSubagentProfiles resolves them: the built-in profiles in src/harness/subagent-profiles.ts are the definition of record (worker, software-engineer, devops-engineer, data-scientist, account-manager, business-development, creative-producer, ad-operations-specialist, summarizer), and a <workspace>/agents/<name>.md file patches the built-in of the same name rather than replacing it, so an operator can override model: without restating tools and prompt. A file whose name matches no built-in defines a new profile and must supply its own tools. A malformed file becomes a diagnostic and is skipped, never thrown.
Harness events and budgets
Section titled “Harness events and budgets”Harness listeners observe model/tool lifecycle events. Budget settings cap tokens, cost, duration, and LLM calls. A budget trip aborts the run and emits budget_exceeded; it is a terminal outcome, not a retry signal.
6. Tool and platform capability interface
Section titled “6. Tool and platform capability interface”Source: src/harness/tools/types.ts.
Core tools use the execution environment and host services supplied by the runner. EventStore from src/events/index.ts is the canonical CRUD port for one office’s scheduled events. Invalid event JSON remains listable with payload: null so an admin can diagnose or delete it.
PlatformToolPackFactory creates a private PlatformToolPack per runner. bindRun selects whether its tools apply to the current platform/conversation. The factory boundary keeps GitHub-specific tools out of the core tool list while preventing cross-conversation mutable binding.
7. Sandbox and execution environment interface
Section titled “7. Sandbox and execution environment interface”Sources: src/sandbox/types.ts and src/sandbox/registry.ts.
SandboxConfig
Section titled “SandboxConfig”The discriminated union currently supports host, container, and oci. Parsing syntax and maturity are documented under Sandbox.
ExecutionEnv
Section titled “ExecutionEnv”The execution boundary is Pi’s ExecutionEnv from @earendil-works/pi-durable/env, the interface Pi’s own read, write, edit, and bash tools are written against. mikan adds no interface of its own:
| Sandbox | Environment |
|---|---|
host | Pi’s NodeExecutionEnv |
container | pi-env’s RemoteExecutionEnv, talking to Pi’s pi-env daemon in the container over one exec -i connection |
oci | None directly: actor/vault resolution provisions a container, then uses its container env |
Every environment must pass Pi’s registerEnvConformance suite; src/test/container-execution-env.test.ts runs it. Tool implementations call the environment’s file operations instead of building cat, printf, or quoting protocols.
SandboxAdapter
Section titled “SandboxAdapter”An adapter recognizes one CLI value, optionally validates it, and optionally creates an environment. oci deliberately has no direct environment: actor/vault resolution provisions a concrete container first.
Adapter registration is a closed list inside src/sandbox/registry.ts, and the package exports neither SandboxAdapter nor the registry.
8. Vault and execution-resolution interface
Section titled “8. Vault and execution-resolution interface”Source: src/vault/types.ts; the injection and ambient-copy policy live in src/vault/index.ts.
VaultManager resolves credential env and mount files by canonical actor key, lists and mutates private/shared vaults, and reports whether storage is enabled. Secrets live under the host-only state directory.
Credential flow is:
- Runtime supplies platform trust, conversation, user, and sandbox topology.
- The execution resolver resolves the Workspace projection, then selects the actor/vault identity.
VaultManagerresolves env and mounts; collision and backend-capability checks validate the complete grant.- One execution decision returns the projection, runtime path context, and execution environment configured with only the credentials intended for that run.
- Prompt memory/skills and filesystem execution consume that same decision rather than recomputing authorization independently.
Host sandbox execution never receives vault injection. Open-trigger platforms never receive an ambient shared vault. These are security invariants, not convenience defaults.
9. Office, session, chat, and identity interfaces
Section titled “9. Office, session, chat, and identity interfaces”Sources: src/office/*, src/sessions/*, src/adapters/shared.ts, and src/sandbox/identity.ts.
Workspace and Office
Section titled “Workspace and Office”A conversation is an office: one persistent working area and data boundary. Two values express it, and both are frozen.
const workspace = createWorkspace({ root, stateDir });const office = workspace.office(createOfficeAddress("slack", "C0AAAAAA1"));Workspace is constructed once per process and owns the deployment-global surfaces (root, stateDir, memoryPath, skillsDir, agentsDir, reservedNames) plus the office factory. Office precomputes one conversation’s paths — key, dir, memoryPath, skillsDir, attachmentsDir, logPath, and the host-only stateDir and sessionsPath — and exposes ensure().
Rules that cross this boundary:
- Values, not strings. Consumers accept an
Office; they do not join paths from a root and an id, and they never infer identity from a directory basename. ensure()is the only materialization seam. It records the office in the registry, then creates the directory. That order means a crash can leave a record without a directory (harmless — recreated on the next message) but never an anonymous directory the registry cannot enumerate. It is idempotent and cheap enough for per-message paths.- Office values are memoized per address, so repeated
workspace.office(address)calls on hot paths cost a map lookup rather than a SHA-256.
OfficeAddress and OfficeKey
Section titled “OfficeAddress and OfficeKey”OfficeAddress is { platform, conversationId } — the platform plus its raw conversation id. officeKey(address) derives the storage identity v1-<platform>-<readable-id>-<16 hex digits>, where the readable middle is diagnostic and the SHA-256 prefix is the identity component. The same key names the office directory on the host, inside sandbox runtimes, and in the vault, so two platforms sharing a raw conversation id can never reach each other’s files or credentials.
Office keys are not reversible. OfficeRegistry (office-registry.json, host-only) is the durable raw-id ↔ office mapping. Admin enumeration and mikan office list read it through listRegisteredOffices.
The two records
Section titled “The two records”| Record | Purpose |
|---|---|
<office>/log.jsonl | Platform truth: user/bot messages and attachments |
~/.mikan/conversations/<office>/sessions.db | Agent truth: prompts, model/tool messages, forks, and compaction |
Do not merge them. Chat history can rebuild a missing top-level agent session, while agent sessions contain data that never appeared on the platform.
Session keys
Section titled “Session keys”Session keys use conversationId[:suffix] and stay raw platform values — the grammar never sees an office key. Only src/sessions/session-key.ts owns it; callers must use deriveSessionKey, conversationIdOf, and threadSuffixOf, never split strings directly. Runtime state is addressed by office plus session key, which is what makes a raw, non-globally-unique key safe.
ChatHistorySync resolves top-level/thread scope, bootstraps new sessions, synchronizes new platform log entries, resets sessions, and coordinates a thread waiting for an active parent run to seal.
Attachments
Section titled “Attachments”saveIncomingAttachments(office, items) in src/adapters/shared.ts is the one owner of the attachment convention: sanitized <timestamp>_<name> filenames under office.attachmentsDir, and the workspace-relative <officeKey>/attachments/<file> path the agent reads. It materializes the office before its first write and returns failures per item rather than throwing, so each adapter keeps its own failure policy.
10. Event-file interface
Section titled “10. Event-file interface”Source: src/events/index.ts.
Each office’s scheduled events are JSON files in its host-only State dir, ~/.mikan/conversations/<officeKey>/events/. The canonical union is:
type EventFilePayload = | { type: "immediate"; conversationId: string; text: string /* common optional fields */ } | { type: "one-shot"; conversationId: string; text: string; at: string } | { type: "periodic"; conversationId: string; text: string; schedule: string; timezone: string };Common optional fields are platform, conversationKind, and userId. channelId is accepted only as a legacy read alias. All writers use buildEventPayload; all readers use parseEventPayload.
OfficeEventStore is bound to one office: another office’s filename is not found, create never overwrites, and a payload must address the store’s own office. The agent reaches its events only through the event tool; no sandbox mounts the directory.
11. Persisted filesystem interface
Section titled “11. Persisted filesystem interface”| Path | Owner | Visibility / compatibility |
|---|---|---|
~/.mikan/settings.json | config | Host-only global settings |
~/.mikan/office-registry.json | office registry | Host-only office inventory |
~/.mikan/conversations/<officeKey>/settings.json | config/admin | Host-only conversation overrides |
~/.mikan/models.json | harness model catalog | Host-only custom providers/models |
~/.mikan/vaults/** | vault/login | Host-only secrets and mount files |
<workspace>/MEMORY.md | agent/user | Sandbox-visible durable memory |
<workspace>/skills/**/SKILL.md | skills | Sandbox-visible prompt instructions |
<workspace>/agents/*.md | subagent profiles | Per-install model patches over built-ins |
<workspace>/<officeKey>/log.jsonl | platform adapters | Append-only platform history |
~/.mikan/conversations/<officeKey>/sessions.db | session store | Host-only agent history (pi-durable SQLite) |
~/.mikan/conversations/<officeKey>/events/*.json | event store | Host-only scheduled events |
MEMORY.md, skills, events, and agents are the reserved workspace-root names (RESERVED_WORKSPACE_NAMES); every other workspace-root entry is an office directory. Conversation-scoped vault directories under ~/.mikan/vaults/ are named by office key for conversation-scoped sandboxes; host mode keys by user and container mode by container name.
The state directory must not be inside the sandbox-visible workspace. Important state files use atomic private writes; append-only logs use append semantics.
12. HTTP interface
Section titled “12. HTTP interface”Source: src/adapters/web/server.ts and portal modules. These routes support mikan’s bundled UI; they are not a general public REST API.
| Surface | Routes | Authentication model |
|---|---|---|
| Health | GET /health | None; returns { "ok": true } |
| GitHub webhook | POST /github/webhook | Webhook signature (GITHUB_WEBHOOK_SECRET) |
| Login | GET /link, POST /api/link/complete, POST /api/oauth/start, GET /oauth/callback | Short-lived link token plus OAuth state |
| Session viewer | GET /session, GET /session/stream, optional POST /session/message | Session-view token |
| Admin | GET /admin, /admin/api/* | Admin token |
Admin endpoints cover conversation inventory/state/usage, global and conversation settings, models, workspace files, skills, events, platform configuration, and login/session links. Route payloads are internal UI contracts and may evolve together with the bundled frontend.
13. Host/runtime safety boundaries
Section titled “13. Host/runtime safety boundaries”Path and cache safety
Section titled “Path and cache safety”attachis fail-closed: paths must stay inside the runtime workspace and are read through the run’sExecutionEnv, then staged in a host-private file before the platform uploader receives them. Host paths are never passed through from model input, and remote sandboxes use the same environment instead of a host-path fallback.- Conversation-scoped cached-runner changes clear idle runners before writing and refuse busy writes. Global LLM changes persist first, mark busy conversations for invalidation, and discard their runners only after active run settlements finish.
- Every vault key is one safe path segment. Reads fail closed, and writes or shared-vault copies reject invalid keys before filesystem access.
Contract-test matrix
Section titled “Contract-test matrix”These suites protect the cross-owner contracts rather than individual helpers. When a seam changes, run every suite in its row together; if a change crosses rows, run the union. A public, persisted, or wire change also requires the full npm test run.
| Contract row | Tests to run together | What the row protects |
|---|---|---|
| Platform intake, response, and command inventory | src/test/adapters-intake.test.ts, src/test/slack-context.test.ts, src/test/discord-context.test.ts, src/test/telegram-context.test.ts, src/test/github-context.test.ts, src/test/command-manifest.test.ts | Normalized event/context identity, magic-word and busy ordering, attachment handoff, responder capabilities, and one command inventory across platforms |
| Office identity and layout | src/test/office-address.test.ts, src/test/office-layout.test.ts, src/test/office-registry.test.ts, src/test/workspace-projection.test.ts | Key derivation and validation, frozen layout values, record-before-mkdir ordering, and projection mount composition |
| State migrations | src/test/migrations.test.ts, src/test/migrate-sessions-sqlite.test.ts, src/test/cli-commands.test.ts | 0.5.3 upgrade end to end, idempotence, dry run, ownership, and the applied-migration record |
| Session identity and runtime ordering | src/test/session-key.test.ts, src/test/session-lifecycle.test.ts, src/test/session-runtime.test.ts, src/test/chat-history-sync.test.ts, src/test/session-store.test.ts | Session ownership/path validation, runner leases and settlement, per-key serialization, parent-thread bootstrap, chat/session separation, and session storage |
| Harness, tools, skills, and subagents | src/test/harness-runner.test.ts, src/test/harness-skills.test.ts, src/test/subagent-native.test.ts, src/test/subagent-tool.test.ts, src/test/agent-runner.test.ts | Run lifecycle, persistence, skill discovery, bounded delegated runs, and the runner-to-tool seam |
| Runtime, workspace projection, and sandbox transport | src/test/execution-resolver.test.ts, src/test/sandbox-path-context.test.ts, src/test/container-execution-env.test.ts, src/test/agent-prompt.test.ts, src/test/agent-attachments.test.ts | Host/runtime path mapping, mount composition and collision failures, credential-capability errors, and shell-hostile file round trips |
| Trust, vault, and portal security | src/test/vault-policy.test.ts, src/test/vault.test.ts, src/test/login.test.ts, src/test/oauth-link-server.test.ts, src/test/token-store.test.ts | trustModel policy, identity-key lookup, explicit credential provisioning, login/OAuth token boundaries, and disabled portal behavior |
| Settings and live-runner cache | src/test/settings-apply.test.ts, src/test/settings.test.ts, src/test/model-command.test.ts, src/test/sandbox-command.test.ts, src/test/admin-portal.test.ts | Writer-seam ordering, leaf-level settings merge, busy/stale outcomes, and command/admin agreement with disk and cached runners |
| Event-file scheduling wire format | src/test/event-format.test.ts, src/test/event-tool.test.ts, src/test/events.test.ts | Builder/parser compatibility, legacy reads, queue-full behavior, routing, and deletion/retention ordering |
| Public package boundary | src/test/public-api.test.ts, src/test/embedder-example.test.ts, src/test/boot.test.ts | Export-map consumers, embedding construction, and CLI boot/flag grammar |
For a focused run, pass the exact files to Vitest, for example:
npm test -- src/test/session-key.test.ts src/test/session-runtime.test.ts src/test/chat-history-sync.test.ts src/test/session-store.test.tsChange protocol
Section titled “Change protocol”Before changing a seam, classify it as Public, Host integration, Wire / persisted, or Internal. Then verify:
- The producer and consumer still use the same canonical type, parser, builder, or port.
- Identity, trust, path, concurrency, lifecycle, and ordering semantics are stated in this page and covered by the corresponding contract row.
- Older supported persisted data still loads, or the change includes an explicit migration and reader compatibility.
- Optional capabilities remain feature-detected; an absent optional method is not treated as a failed required operation.
- Errors fail at the owning boundary: invalid identity before queueing, malformed state without silent replacement, busy settings without a disk/cache split, and unsafe credential or mount resolution without a fallback escalation.
- The implementation can remain behind an existing port instead of expanding a cross-owner contract.
Do not use context isolation as a synonym for sandboxing. Update this page when a supported producer, consumer, invariant, or contract-test row changes.
