Skip to content

Configuration

Configure startup, global and conversation settings, platform credentials, sandbox limits, and environment variables.

mikan requires a global settings file before normal startup. Create it once, review it, then start mikan with a workspace:

Terminal window
mikan onboard
mikan --sandbox=host /path/to/workspace

mikan creates a missing state directory with mode 0700. An existing directory must be owned by the current user and must not be world-writable. For sandboxed modes, keep it outside the workspace so tools cannot reach credentials or administrator settings.

ScopePathPurpose
Global~/.mikan/settings.jsonRequired defaults for every conversation
Conversation~/.mikan/conversations/<officeKey>/settings.jsonPartial overrides for one conversation

Conversation settings are host-authoritative. Older <workspace>/<officeKey>/settings.json files are migrated on first access and are not read again from the sandbox-visible workspace.

Every conversation is an office, identified by its platform plus the platform’s raw conversation id. Storage paths use the office key derived from both — v1-<platform>-<readable-id>-<hash>, for example v1-slack-c0aaaaaa1-1f4b9c0d2e3a5b7c — so two platforms that happen to share a raw conversation id can never address each other’s files, settings, or credentials. The same key names the office directory in the workspace, its state directory, and its vault.

Office keys are not reversible to a raw platform id, so the host keeps a registry at ~/.mikan/office-registry.json recording each office’s platform and conversation id. Use mikan office list to read it.

Upgrading from a release that stored conversations under raw platform ids migrates those directories, vaults, and state trees to the office-key layout on the next start; see Deployment.

mikan onboard creates:

{
"llm": {
"provider": "anthropic",
"model": "claude-sonnet-4-6",
"thinkingLevel": "off"
},
"slack": {
"replyMode": "top-level"
},
"sandbox": {
"cpus": "0.5",
"memory": "1g",
"boost": {
"cpus": "2",
"memory": "4g"
},
"defaultSharedVault": ""
}
}

The values below are generated by onboarding. llm.provider, llm.model, and llm.thinkingLevel are required in the resolved global configuration; the other fields may be omitted.

FieldOnboarding valueDescription
llm.provideranthropicMain AI provider
llm.modelclaude-sonnet-4-6Main model name
llm.thinkingLeveloffoff, minimal, low, medium, high, xhigh, or max
sandbox.cpus0.5CPU limit for mikan-managed image runtimes
sandbox.memory1gMemory limit for mikan-managed image runtimes
sandbox.boost.cpus2Temporary CPU limit applied by /pi-sandbox boost
sandbox.boost.memory4gTemporary memory limit applied by /pi-sandbox boost
office.visibilityunsetConversation-only override: private narrows a Slack public channel to a private office; nothing can widen
sandbox.defaultSharedVaultemptyShared vault copied into eligible membership-trust image conversations
github.*repos when GitHub is chosenWhere the GitHub adapter answers and who may trigger it; see GitHub
slack.replyModetop-levelSlack response mode: top-level or thread

/pi-model writes a partial conversation override, and /pi-sandbox visibility <private|default> writes the conversation’s office.visibility override; the admin portal exposes the same switch per office.

Slack auto-reply is changed with /pi-auto-reply on|off|jev and stored in the conversation office as an auto-reply (on) or auto-reply.jev (jev) marker file; off removes both. Marker contents are ignored: on answers every unaddressed human message in the channel, and jev lets Jev decide per message whether it addresses mikan. The retired autoReply and llm.autoReply JSON settings are ignored, and the daemon lists them at startup so they can be removed.

Office visibility follows the Slack conversation type (ADR 0008); conversations on Telegram, Discord, and GitHub are always private. Public channels are public offices: every other office can read them under /workspace/public/<office key> (read-only), and they may write the workspace-global MEMORY.md and skills/. Private channels, DMs, group DMs, externally shared channels, and conversations whose kind has not been observed are private offices: visible only to themselves, reading shared knowledge and public offices without writing back. Every office receives the same mount shape; nothing mounts the workspace root.

Only oci:* enforces visibility. host and container:* run every office in one filesystem and are trusted deployments; a private office there is served with a one-time logged warning.

The retired door-policy settings (sandbox.workspace.doorPolicy, layout, visibility, and legacy sandbox.image.workspaceMount) are ignored if present and never change the projection. No setting lets an office reach another private office.

Jev is the decision model behind auto-reply jev mode, Memory capture, DM task intent, and the jev and jev_browser tools. mikan uses the first of these pi-ai built-in Jev models whose provider has a key: openrouter/~typesafe/jev-latest (OPENROUTER_API_KEY), typesafe/jev-latest (TYPESAFE_API_KEY), vercel-ai-gateway/typesafe-ai/jev, cloudflare-workers-ai/typesafe/jev, and opencode/jev-1.13. With none, those features fall back to their behavior without Jev.

To send Jev through a proxy that speaks the System One protocol, give the built-in provider only a baseUrl and an apiKey in models.json, as Pi does for proxies. Its built-in models stay, and mikan sends them to the proxy with that key:

{
"providers": {
"typesafe": { "baseUrl": "https://llm-gateway.example.com/v1", "apiKey": "<gateway key>" }
}
}

Every skill under the workspace skills/ directory and the conversation’s own skills/ is listed in the system prompt by default. skills in the global or conversation settings turns skills off and on with Pi’s resource rules, by the skill’s directory relative to its skills/ root: !pattern excludes matching skills by glob, +path adds one back, and -path excludes one even when added back. A conversation entry for a workspace skill decides it for that conversation; without one, the global entries apply.

{ "skills": ["!acme-skills/vendors/**", "+acme-skills/vendors/google-genai"] }

The admin portal’s Skills pages toggle the same entries: the workspace page writes +/- to the global settings, and a conversation page cycles a skill between inherited, +, and -. An excluded skill is only left out of the prompt; its files stay readable in the sandbox.

mcpServers connects Model Context Protocol servers and exposes their tools to the agent as mcp__<server>__<tool>. Servers run on the host: credentials placed in env (stdio) or headers (HTTP) stay in the server process and are never visible to the model or the sandbox.

{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_..." }
},
"internal-docs": {
"url": "https://mcp.example.com/mcp",
"headers": { "Authorization": "Bearer ..." }
}
}
}

Each entry uses exactly one transport: command (+ optional args, env) spawns a stdio server; url (+ optional headers) connects over streamable HTTP. disabled: true turns an entry off without deleting it.

MCP tools are not declared to the model. The system prompt lists each server once, with the first line of its description or of the server’s own instructions, and the model reaches the tools from codemode scripts, which receive each complete result and return only what the answer needs. "exposure": "deferred" instead lets the model load a server’s tools with tool_search and call them directly.

Global and per-conversation mcpServers merge per server name: a conversation entry overrides (or disables) the same-name global entry and other global entries stay available. The admin portal has panels for both scopes; changes take effect on the next response. An unreachable server logs a warning and the rest still load.

The Admin MCP panels also include a small repository-owned Marketplace. Installing a reviewed preset shows its exact host command or remote endpoint, credential requirements, source, target scope, and security warning before writing an ordinary mcpServers entry. Local package versions are pinned. Marketplace installation does not create a separate installed database, run an automatic updater, or treat catalog inclusion as a security certification. Local stdio presets execute on the mikan host; remote presets receive the tool calls and data sent to them.

OpenConnector is an ordinary MCP server with a deployment default. OPENCONNECTOR_ENDPOINT names the default open-connector server and OPENCONNECTOR_ADMIN_TOKEN is the host-only credential that mints per-conversation runtime tokens. When a Slack or GitHub conversation has not declared open-connector in global or conversation settings, mikan creates a token named mikan:slack:<workspace-id>:<channel-id> or mikan:github:<conversation-id> (one per issue or pull request) (copying the current OpenConnector deployment action/proxy policy), and saves it as a normal mcpServers entry in that conversation’s host-only settings. From then on the entry appears in the Admin MCP panel and can be tested, disabled, removed, or replaced with a self-hosted OpenConnector like any other server; removing it re-provisions the default on the next response, disabling it turns the integration off. The admin token is only ever sent to the default endpoint’s origin and never enters settings or sandboxes.

At least one complete platform credential set is required for normal bot mode:

PlatformRequired environment variablesOptional variables
SlackSLACK_APP_TOKEN, SLACK_BOT_TOKEN—
TelegramTELEGRAM_BOT_TOKEN—
DiscordDISCORD_BOT_TOKEN—
GitHubGITHUB_AGENT_TOKEN, GITHUB_WEBHOOK_SECRET, and LINK_PORT, plus github.repos in settings.jsonThe other github.* settings

See Platform adapters for platform-specific setup and permissions.

Command or optionPurpose
mikan onboardCreate the required global settings file
mikan [--sandbox=<mode>] [working-directory]Start configured platform bots; the working directory defaults to ~/.mikan/workspace
--sandbox=host | container:<name> | oci:<image>Select tool execution mode; default is host
mikan envShow the full environment-variable inventory and what is currently set
mikan --versionPrint the installed version
mikan --helpShow CLI usage and the platform-token summary
mikan office listList each office key with its platform and conversation id
mikan migrate --sandbox=<mode> [--dry-run] [--owner <id>=<platform>]Apply pending State migrations with the daemon stopped

mikan migrate accepts --workspace <dir>, which defaults to ~/.mikan/workspace.

mikan owns one OpenTelemetry trace and metric pipeline and exports it with standard OTLP over HTTP/protobuf. The pipeline is enabled only when an explicit OTLP endpoint is configured and OTEL_SDK_DISABLED is not true; zero configuration remains a no-op. Use OTEL_EXPORTER_OTLP_ENDPOINT for a Collector, or the per-signal OTEL_EXPORTER_OTLP_TRACES_ENDPOINT and OTEL_EXPORTER_OTLP_METRICS_ENDPOINT. A base endpoint receives /v1/traces and /v1/metrics; per-signal endpoints must include their complete path. Authentication belongs in the corresponding *_HEADERS variable, never in an endpoint URL.

For local Arize Phoenix, set OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:6006/v1/traces. Phoenix ingests traces, not the OTLP metrics service; send metrics to a Collector or another metrics backend. mikan emits content-free standard GenAI attributes plus a minimal OpenInference projection on the same spans, so Phoenix can show provider, model, token counts, durations, status, session attribution, and tool names without duplicate spans.

SENTRY_DSN enables Sentry issue reporting and links errors to the active OpenTelemetry trace. Sentry does not create a second application trace or metric pipeline. To send the same OTLP traces to Phoenix and Sentry, configure an OpenTelemetry Collector to fan out one stream; Sentry’s direct OTLP ingestion currently supports traces/logs, not OTLP metrics.

Only http/protobuf is supported. OTEL_TRACES_EXPORTER=none and OTEL_METRICS_EXPORTER=none disable individual signals. mikan never emits prompts, completions, message text, tool arguments/results, file contents, credentials, tokens, or absolute paths. It does emit content-free operational metadata such as model IDs, token/cost totals, timings, payload sizes, tool categories, and retry/compaction/budget counts. Platform conversation, session, message, thread, and user identifiers are exported as raw operational IDs so traces can be mapped back to their source; human-readable usernames, channel names, and workspace names remain excluded. Resource attributes are allowlisted; do not place secrets or paths in OTEL_RESOURCE_ATTRIBUTES. Shutdown first drains conversation work, then flushes/shuts down OTLP, then closes Sentry.

Environment variables read through mikan’s configuration helper also accept a MIKAN_ prefix. For example, MIKAN_SLACK_APP_TOKEN and MIKAN_LINK_URL are fallbacks for SLACK_APP_TOKEN and LINK_URL; the unprefixed value takes precedence.

The daemon’s full environment interface is declared as a manifest in the source tree; mikan env prints the annotated inventory grouped by platform and feature, with the current status of each variable, so you can audit a deployment without reading code.

mikan writes logs to stdout/stderr. Use PM2, systemd, Docker, or your hosting platform to route and retain them.