kortix-system

v2026.09.25

Complete Kortix sandbox system reference. Covers: container image, s6 services, filesystem layout, persistence model, environment variables, secrets management (API for setting/getting/deleting env vars), ports, runtimes, init scripts, cloud mode, desktop environment, deployments (deploy apps to live *.style.dev URLs), cron triggers (scheduled agent execution), semantic search (lss), session search & management (API + on-disk queries), skill creation guide, and all installed tooling. Load this skill when you need to: understand the sandbox, debug services, configure the environment, set API keys/secrets, deploy web apps/APIs/static sites, schedule cron jobs, search files semantically, query session data, or create new skills.

GitHub
Install command
npx skhub add kortix-ai/kortix-system
Markdown
SKILL.md

Kortix Sandbox System Architecture

The Kortix sandbox is a Docker container running Alpine Linux with a full XFCE desktop, noVNC remote access, and the OpenCode AI agent platform. This document is the definitive reference for how the system works.

Container Image

  • Base: lscr.io/linuxserver/webtop:latest (Alpine Linux + XFCE + noVNC)
  • Process manager: s6-overlay v3 with s6-rc.d (NOT the older services.d)
  • Entry point: /opt/startup.sh → exec unshare --pid --fork /init (PID namespace so s6 gets PID 1)
  • User: abc (UID 1000, set via PUID=1000). All services run as abc via s6-setuidgid abc.

Key Paths

  • /workspace/ — Docker volume. ONLY thing that persists across restarts. All user files live here.
  • /opt/opencode/ — OpenCode config: agents, tools, skills, plugins, commands, opencode.jsonc.
  • /opt/kortix-master/ — Kortix Master proxy + secret store + deployer server.
  • /app/secrets/ — Docker volume. Encrypted secret storage.

Persistence Model

Critical: Only TWO things persist across container restarts:

PathVolumeWhat
/workspaceworkspaceAll user data, agent memory, sessions, config
/app/secretssecrets_dataEncrypted API keys and environment variables

Everything else is ephemeral. /opt, /usr, /etc, /tmp — all reset on container rebuild. If you install packages via apk add or npm install -g, they will be lost on rebuild. Only /workspace survives.


Projects — How They Work

A project in the Kortix sandbox is a git repository. The project system is entirely automatic — no manual creation required.

Detection

When OpenCode encounters a directory, it runs Project.fromDirectory(directory):

  1. Walk up the directory tree looking for .git
  2. If found, extract the first root commit SHA (git rev-list --max-parents=0 --all)
  3. That SHA becomes the project's permanent, stable ID
  4. Cache the ID in .git/opencode for fast subsequent lookups
  5. If no .git found or no commits exist, fall back to id: "global"

Discovery (Workspace Scanning)

On startup and periodically, OpenCode scans $KORTIX_WORKSPACE (/workspace) for all .git directories. Every git repo found becomes a project automatically:

  • Clone a repo into /workspace/my-app/ → it becomes a project
  • Create a new repo with git init && git commit → it becomes a project
  • Delete the repo → it disappears from the project list

Identity

Project IDs are based on git history, not filesystem paths:

  • Renaming or moving a repo keeps the same project ID
  • Cloning the same repo on another machine produces the same ID
  • IDs are 40-character hex strings (SHA-1 commit hashes)

Project-Session Relationship

Sessions are scoped to projects:

  • Every session has a projectID field linking it to a project
  • Listing sessions only returns those belonging to the current project
  • Creating a session automatically assigns it to the current project

Project API

MethodRouteDescription
GET/projectList all projects (triggers workspace scan)
GET/project/currentGet the current project for this request's directory
PATCH/project/:projectIDUpdate project name, icon, or commands

Services & Ports

All services are managed by s6-rc.d as longruns. Service scripts live at /etc/s6-overlay/s6-rc.d/svc-*/run.

ServiceScriptInternal PortHost PortDescription
Kortix Mastersvc-kortix-master800014000Reverse proxy + secret store + deployer. Entry point for API access.
OpenCode Websvc-opencode-web311114001Web UI (SolidJS app from opencode-ai npm package)
OpenCode Servesvc-opencode-serve4096(proxied via 8000)Backend API server. Not exposed directly — proxied by Kortix Master.
Desktop (noVNC)(base image)6080 / 608114002 / 14003XFCE desktop via VNC. HTTP / HTTPS.
Presentation Viewersvc-presentation-viewer321014004Serves generated slide decks
Agent Browser Stream(agent-browser)922314005Playwright browser automation WebSocket
Agent Browser Viewersvc-agent-browser-viewer922414006Browser session viewer UI (HTML + SSE bridge)
lss-syncsvc-lss-sync——File watcher daemon for semantic search indexing

Kortix Master Details

Kortix Master (/opt/kortix-master/src/index.ts, runs via Bun on port 8000) is the main entry point. It handles:

RouteTargetDescription
/env/*Local (SecretStore)Secret/env var management (GET/POST/PUT/DELETE)
/api/integrations/*Kortix API (proxied)OAuth integration tools (7 routes including internal /token)
/kortix/deploy/*Local (Deployer)Local app deployment (start/stop/status/logs)
/kortix/healthLocalHealth check (includes version, OpenCode readiness)
/kortix/portsLocalContainer→host port mappings
/kortix/updateLocalSelf-update mechanism (POST only)
/lss/searchLocal (lss CLI)Semantic search API
/lss/statusLocal (lss CLI)LSS index health
/proxy/:port/*localhost:{port}Dynamic port proxy (any service inside container)
/* (catch-all)localhost:4096Everything else proxied to OpenCode API

The dynamic port proxy (/proxy/:port/*) injects a Service Worker into HTML responses to rewrite all subsequent requests through the proxy prefix. It also handles WebSocket upgrades for proxied services.

Kortix Master Authentication

The master uses a localhost-bypass auth model:

  • From inside the sandbox (localhost/loopback): No auth required. Curl, tools, scripts running inside the container can call localhost:8000 freely — no tokens, no headers.
  • From outside the sandbox (kortix-api, frontend proxy, host machine): Must provide INTERNAL_SERVICE_KEY as a Bearer token or ?token= query param.

Two tokens exist with opposite directions:

TokenDirectionPurpose
INTERNAL_SERVICE_KEYexternal → sandboxHow kortix-api authenticates TO the sandbox. Required for external requests to port 8000 (mapped to host port 14000).
KORTIX_TOKENsandbox → externalHow the sandbox authenticates TO kortix-api. Used for outbound requests (cron, integrations, LLM proxy, deployments). Also used as the SecretStore encryption key.

Unauthenticated routes (always open, even externally): /kortix/health, /docs, /docs/openapi.json.

External access example (from host machine or another container):

curl http://127.0.0.1:14000/env \
  -H "Authorization: Bearer $INTERNAL_SERVICE_KEY"

Environment Variables

Core Config

VariableValueDescription
OPENCODE_CONFIG_DIR/opt/opencodeWhere agents, tools, skills, plugins live
KORTIX_WORKSPACE/workspaceWorkspace root
OPENCODE_FILE_ROOT/File explorer shows full filesystem (set in svc-opencode-serve)
OPENCODE_PERMISSION{"*":"allow"}Auto-approve all tool calls (set in docker-compose, not Dockerfile)
BUN_PTY_LIB/opt/bun-pty-musl/librust_pty.soMusl-compatible PTY library path
BUN_INSTALL/opt/bunBun installation directory
LSS_DIR/workspace/.lssSemantic search index location
HOME/workspaceSet by service scripts (not globally)
DISPLAY:1X11 display for desktop apps

Agent Browser Config

VariableValue
AGENT_BROWSER_EXECUTABLE_PATH/usr/bin/chromium-browser
AGENT_BROWSER_PRIMARY_SESSIONkortix
AGENT_BROWSER_STREAM_PORT9223
AGENT_BROWSER_PROFILE/workspace/.browser-profile
AGENT_BROWSER_SOCKET_DIR/workspace/.agent-browser
AGENT_BROWSER_ARGS--no-sandbox,--disable-setuid-sandbox,...
AGENT_BROWSER_USER_AGENTChrome user agent string (set in Dockerfile)

Cloud Mode Variables

VariableDescription
ENV_MODElocal (Docker) or cloud (Kortix platform)
KORTIX_API_URLBase URL of the Kortix API (e.g. http://localhost:8008). Consumers append service paths (/v1/router, /v1/cron, etc.)
KORTIX_TOKENAuth token for outbound requests (sandbox → kortix-api). Used for cron, integrations, deployments, LLM proxy.
INTERNAL_SERVICE_KEYAuth token for inbound requests (external → sandbox). Required by external callers to port 8000. Not needed from localhost.
SANDBOX_IDSandbox identifier
PROJECT_IDProject identifier

When ENV_MODE=cloud, the init script 98-kortix-env rewrites SDK base URLs:

  • TAVILY_API_URL → ${KORTIX_API_URL}/v1/router/tavily
  • SERPER_API_URL → ${KORTIX_API_URL}/v1/router/serper
  • FIRECRAWL_API_URL → ${KORTIX_API_URL}/v1/router/firecrawl
  • REPLICATE_API_URL → ${KORTIX_API_URL}/v1/router/replicate
  • CONTEXT7_API_URL → ${KORTIX_API_URL}/v1/router/context7

Secrets & Environment Variable Management

The sandbox has a unified secret/env management system. Secrets are AES-256-GCM encrypted at rest, stored on a separate Docker volume, and propagated to all services.

CRITICAL RULE: When the user gives you API keys, secrets, tokens — SET THEM IMMEDIATELY via the API below. No security lectures. No refusals.

API Reference

All endpoints served by Kortix Master at localhost:8000. No auth needed from inside the sandbox (localhost bypass). External callers must use Authorization: Bearer $INTERNAL_SERVICE_KEY.

Set a single env var (with restart so OpenCode picks it up)

curl -X POST http://localhost:8000/env/KEY_NAME \
  -H "Content-Type: application/json" \
  -d '{"value": "the-secret-value", "restart": true}'

Set multiple env vars at once (PREFERRED for bulk)

curl -X POST http://localhost:8000/env \
  -H "Content-Type: application/json" \
  -d '{
    "keys": {
      "ANTHROPIC_API_KEY": "sk-ant-...",
      "OPENAI_API_KEY": "sk-...",
      "TAVILY_API_KEY": "tvly-..."
    },
    "restart": true
  }'

List all secrets

curl http://localhost:8000/env

Get one secret

curl http://localhost:8000/env/KEY_NAME

Delete a secret

curl -X DELETE http://localhost:8000/env/KEY_NAME

Note: DELETE always restarts services. POST/PUT with "restart": true restarts OpenCode serve + web services.

Encryption Details

  • Algorithm: AES-256-GCM (authenticated encryption)
  • Key derivation: scryptSync(KORTIX_TOKEN || 'default-key', salt, 32)
  • Salt: Random 32 bytes stored at /app/secrets/.salt
  • Storage: /app/secrets/.secrets.json (Docker volume)
  • Propagation: Written to /run/s6/container_environment/KEY for s6 services

Common Environment Variable Categories

LLM Providers: ANTHROPIC_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY, GEMINI_API_KEY, GROQ_API_KEY, XAI_API_KEY

Tool API Keys: TAVILY_API_KEY, FIRECRAWL_API_KEY, SERPER_API_KEY, REPLICATE_API_TOKEN, CONTEXT7_API_KEY, ELEVENLABS_API_KEY, MORPH_API_KEY

Email (Agent Inbox): KORTIX_AGENT_EMAIL_INBOX_FROM_NAME, _FROM_EMAIL, _USER_NAME, _PASSWORD, _SMTP_HOST, _SMTP_PORT, _IMAP_HOST, _IMAP_PORT

Browser: AGENT_BROWSER_PROXY (format: http://user:pass@host:port)


Integrations — Third-Party OAuth Apps

The sandbox has 7 integration tools that connect to third-party APIs (Gmail, Slack, Google Sheets, GitHub, etc.) via Pipedream OAuth. Auth is handled automatically — the agent never sees tokens.

Architecture

Agent Tool (integration-*.ts)
    │
    ├── integration-list      → GET  /api/integrations/list
    ├── integration-search    → GET  /api/integrations/search-apps?q=...
    ├── integration-connect   → POST /api/integrations/connect
    ├── integration-actions   → GET  /api/integrations/actions?app=...
    ├── integration-run       → POST /api/integrations/run-action
    ├── integration-exec      → POST /api/integrations/proxy (via proxyFetch)
    └── integration-request   → POST /api/integrations/proxy
            │
            ▼
    Kortix Master (/api/integrations/*)
            │
            ▼
    Kortix API (POST /v1/integrations/*)
            │
            ▼
    Pipedream (OAuth token management + action execution)

All tools communicate with Kortix Master at localhost:8000/api/integrations/* (no auth needed — localhost bypass), which then proxies outbound to the Kortix API with KORTIX_TOKEN auth.

Tool Reference

ToolPurposeWhen to Use
integration-listList connected appsCheck what's available before using other tools
integration-searchSearch available apps by keywordFind the correct app slug (e.g., q='gmail' → gmail)
integration-connectGenerate OAuth connect URLReturns a dashboard URL the user clicks to authorize
integration-actionsDiscover actions for an appFind action keys + required params (e.g., gmail-send-email)
integration-runExecute a Pipedream actionRun structured actions without knowing API details
integration-execExecute custom Node.js codeFor custom API calls — use proxyFetch(url, init) instead of fetch()
integration-requestMake raw authenticated HTTP requestDirect API calls with auto-injected OAuth credentials

Workflow

  1. Check what's connected: integration-list
  2. Search for an app if needed: integration-search({ q: "gmail" })
  3. Connect if not linked: integration-connect({ app: "gmail" }) → user clicks URL
  4. Discover actions: integration-actions({ app: "gmail", q: "send" })
  5. Execute: integration-run({ app: "gmail", action_key: "gmail-send-email", props: { to, subject, body } })

For Custom API Calls (integration-exec)

Use proxyFetch(url, init) — it works like fetch() but OAuth credentials are injected automatically by the proxy. Never set Authorization headers manually.

// Example: List Gmail labels
const res = await proxyFetch('https://gmail.googleapis.com/gmail/v1/users/me/labels');
const data = await res.json();
console.log(data);

Deployments

The sandbox has two deployment systems depending on the mode:

Cloud Deployments — Kortix Deployments API (*.style.dev)

In cloud mode, deploy to live URLs via the Kortix Deployments API. The API handles everything server-side — no SDK, no user-facing API keys.

Capabilities:

  • 4 source types: Git repo, inline code, local files, tar URL
  • Auto-detects Next.js, Vite, Expo — TypeScript works out of the box
  • Free *.style.dev subdomains with instant SSL
  • Node.js only — no Python, Ruby, Go
  • Port 3000 — all servers must listen on port 3000

API Call Pattern

curl -X POST "$KORTIX_API_URL/v1/deployments" \
  -H "Authorization: Bearer $KORTIX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ ... }'

Deploy Examples

Git repo:

{
  "source_type": "git",
  "source_ref": "https://github.com/user/repo",
  "domains": ["my-app-x7k2.style.dev"],
  "build": true
}

Inline code (Express API):

{
  "source_type": "code",
  "code": "import express from 'express';\nconst app = express();\napp.get('/', (req, res) => res.json({ status: 'ok' }));\napp.listen(3000);",
  "node_modules": { "express": "^4.18.2" },
  "domains": ["api-e5f6.style.dev"]
}

Local files (pre-built):

import { readFileSync, readdirSync, statSync } from 'fs';
import { join, relative } from 'path';

function readFilesRecursive(dir: string, base?: string): Array<{path: string, content: string, encoding: string}> {
  const result: Array<{path: string, content: string, encoding: string}> = [];
  base = base ?? dir;
  for (const entry of readdirSync(dir)) {
    if (entry === 'node_modules') continue;
    const full = join(dir, entry);
    if (statSync(full).isDirectory()) result.push(...readFilesRecursive(full, base));
    else result.push({ path: relative(base, full), content: readFileSync(full).toString('base64'), encoding: 'base64' });
  }
  return result;
}
// POST body: { source_type: 'files', files: readFilesRecursive('./dist'), entrypoint: 'server.js', domains: ['my-site.style.dev'] }

Cloud Deployments API Request Schema

{
  source_type: 'git' | 'code' | 'files' | 'tar',
  source_ref?: string,       // Git repo URL (git)
  branch?: string,           // Git branch (git)
  root_path?: string,        // Monorepo sub-path (git)
  code?: string,             // Inline JS/TS (code)
  files?: Array<{ path: string, content: string, encoding?: string }>,  // (files)
  tar_url?: string,          // Archive URL (tar)
  domains: string[],         // Required. Use "slug.style.dev"
  build?: boolean | { command?: string, outDir?: string, envVars?: Record<string, string> },
  env_vars?: Record<string, string>,     // Runtime env vars
  node_modules?: Record<string, string>, // Only for 'code' deploys
  entrypoint?: string,
  static_only?: boolean,
  public_dir?: string,
  clean_urls?: boolean,
  framework?: string,        // Hint: 'nextjs', 'vite', 'static'
}

Other Cloud API Endpoints

# List / Get / Logs / Stop / Redeploy / Delete
curl "$KORTIX_API_URL/v1/deployments" -H "Authorization: Bearer $KORTIX_TOKEN"
curl "$KORTIX_API_URL/v1/deployments/{id}" -H "Authorization: Bearer $KORTIX_TOKEN"
curl "$KORTIX_API_URL/v1/deployments/{id}/logs" -H "Authorization: Bearer $KORTIX_TOKEN"
curl -X POST "$KORTIX_API_URL/v1/deployments/{id}/stop" -H "Authorization: Bearer $KORTIX_TOKEN"
curl -X POST "$KORTIX_API_URL/v1/deployments/{id}/redeploy" -H "Authorization: Bearer $KORTIX_TOKEN"
curl -X DELETE "$KORTIX_API_URL/v1/deployments/{id}" -H "Authorization: Bearer $KORTIX_TOKEN"

Cloud Deploy Hard-Won Lessons

  1. Runtime is Node.js: Deno.serve() and app.fire() do NOT work.
  2. Port 3000: All servers must listen on port 3000.
  3. Static sites need a server OR static_only: true.
  4. env_vars are runtime-only: Use build.envVars for build-time variables.
  5. Include your lockfile: Never include node_modules.
  6. Cold starts: First request may take 10-15 seconds.
  7. Next.js requires output: "standalone" and images: { unoptimized: true }.

Local Deployments — Kortix Master Deployer

In local mode (or for preview), the Kortix Master has a built-in deployer at /kortix/deploy. It runs apps as local processes on random ports (10000-60000) inside the container, accessible via the dynamic port proxy.

Capabilities:

  • Auto-detects Next.js, Vite, CRA, Node.js, Python, static HTML
  • Runs locally — no external infrastructure needed
  • Random ports — each deployment gets an available port
  • Accessible via http://localhost:8000/proxy/{port}/

Local Deploy API (no auth needed from inside sandbox)

MASTER_URL="http://localhost:8000"

# Deploy an app (auto-detects framework)
curl -X POST "$MASTER_URL/kortix/deploy" \
  -H "Content-Type: application/json" \
  -d '{
    "deploymentId": "my-app",
    "sourceType": "files",
    "sourcePath": "/workspace/my-app"
  }'
# Returns: { success, port, pid, framework, logs }

# With git source
curl -X POST "$MASTER_URL/kortix/deploy" \
  -H "Content-Type: application/json" \
  -d '{
    "deploymentId": "my-app",
    "sourceType": "git",
    "sourceRef": "https://github.com/user/repo",
    "sourcePath": "/workspace/my-app"
  }'

# List running deployments
curl "$MASTER_URL/kortix/deploy"

# Get status / logs
curl "$MASTER_URL/kortix/deploy/my-app/status"
curl "$MASTER_URL/kortix/deploy/my-app/logs"

# Stop
curl -X POST "$MASTER_URL/kortix/deploy/my-app/stop"

Local Deploy Config

{
  deploymentId: string,      // Required — unique identifier
  sourceType: 'git' | 'code' | 'files' | 'tar',
  sourceRef?: string,        // Git URL (for sourceType: 'git')
  sourcePath: string,        // Path on filesystem (default: /workspace)
  framework?: string,        // Auto-detected if not provided
  envVarKeys?: string[],     // Env var names to pass to the app
  buildConfig?: Record<string, unknown>,
  entrypoint?: string,       // Custom start command
}

Framework Detection (Local)

DetectedInstallBuildStartDefault Port
nextjsnpm installnpm run buildnpm start3000
vitenpm installnpm run buildnpx vite preview --host 0.0.0.0 --port {PORT}4173
cranpm installnpm run buildnpx serve -s build -l {PORT}3000
nodenpm install—npm start3000
pythonpip install -r requirements.txt—python app.py8080
static——npx serve -s . -l {PORT}3000

After deploy, the app is accessible at http://localhost:8000/proxy/{port}/ which Kortix Master proxies to the local port.


Cron Triggers — Scheduled Agent Execution

The Kortix Cron service manages scheduled triggers that fire agents on cron schedules using pg_cron.

Quick Start (Local Mode — No Auth Needed)

SANDBOX_ID="${SANDBOX_ID:-kortix-sandbox}"

curl -X POST "$KORTIX_API_URL/v1/cron/triggers" \
  -H "Content-Type: application/json" \
  -d "{
    \"sandbox_id\": \"$SANDBOX_ID\",
    \"name\": \"Daily Report\",
    \"cron_expr\": \"0 0 9 * * *\",
    \"prompt\": \"Generate the daily status report\"
  }"

Cron Expression Format (6-field with seconds)

second minute hour day month weekday
0      */5    *    *   *     *        ← Every 5 minutes
0      0      9    *   *     *        ← Daily at 9 AM
0      0      8    *   *     1        ← Every Monday at 8 AM

Note: pg_cron strips seconds internally. Minimum resolution is 1 minute.

API Reference

# Create trigger
curl -X POST "$KORTIX_API_URL/v1/cron/triggers" -H "Content-Type: application/json" \
  -d '{"sandbox_id":"...","name":"...","cron_expr":"...","prompt":"..."}'

# List triggers
curl "$KORTIX_API_URL/v1/cron/triggers?sandbox_id=$SANDBOX_ID"

# Get/Update/Delete trigger
curl "$KORTIX_API_URL/v1/cron/triggers/{id}"
curl -X PATCH "$KORTIX_API_URL/v1/cron/triggers/{id}" -d '{"prompt":"new prompt"}'
curl -X DELETE "$KORTIX_API_URL/v1/cron/triggers/{id}"

# Pause/Resume/Fire now
curl -X POST "$KORTIX_API_URL/v1/cron/triggers/{id}/pause"
curl -X POST "$KORTIX_API_URL/v1/cron/triggers/{id}/resume"
curl -X POST "$KORTIX_API_URL/v1/cron/triggers/{id}/run"

# Execution history
curl "$KORTIX_API_URL/v1/cron/executions?limit=20"
curl "$KORTIX_API_URL/v1/cron/executions/by-trigger/{triggerId}"

Trigger Properties

FieldRequiredDescription
sandbox_idYesTarget sandbox UUID
nameYesHuman-readable name
cron_exprYes6-field cron expression
promptYesPrompt sent to agent
timezoneNoIANA timezone (default: UTC)
agent_nameNoTarget agent (e.g., kortix)
model_idNoModel (kortix/basic = Sonnet, kortix/power = Opus)
session_modeNonew (default) or reuse

Semantic Search (lss)

Full semantic search engine powered by lss. Combines BM25 full-text + embedding similarity. Background daemon (lss-sync) auto-indexes file changes in real time.

Quick Reference

# Search everything
lss "your query" -p /workspace -k 10 --json

# Filter by file type
lss "auth logic" -p /workspace -e .py -e .ts -k 10 --json

# Exclude file types
lss "config" -p /workspace -E .json -E .yaml -k 10 --json

# Force re-index
lss index /workspace/important-file.md

# Index stats
lss status

HTTP API (via Kortix Master — no auth needed from inside sandbox)

# Semantic search via HTTP
curl "http://localhost:8000/lss/search?q=auth+logic&k=10&path=/workspace&ext=.ts,.py"

# Index health
curl "http://localhost:8000/lss/status"

Search Filters

FlagDescriptionExample
-e EXTInclude only extensions-e .py -e .ts
-E EXTExclude extensions-E .json -E .yaml
-x REGEXExclude chunks matching regex-x 'test_' -x 'TODO'
-k NNumber of results-k 10
--no-indexSkip re-indexing (faster)

JSON Output

lss "query" -p /workspace --json -k 10
# Returns: [{ "query": "...", "hits": [{ "file_path": "...", "score": 0.03, "snippet": "..." }] }]

When to Use lss vs grep

Use lssUse grep
Conceptual queriesExact strings
Fuzzy matchingVariable names
Cross-file discoveryKnown patterns

Memory & Context Management

For the full memory, context, and filesystem persistence guide, load the memory-context-management skill.

Key rules:

  • The filesystem is forever persistent. /workspace survives restarts, rebuilds, reboots. Write plans and notes to disk for anything that should survive across sessions.
  • kortix-sys-oc-plugin auto-captures observations, consolidates into LTM during compaction, and injects your session ID + relevant LTM on every turn.
  • Four tools: mem_search, mem_save, session_list, session_get — all in one plugin.
  • Both systems reinforce each other: files on disk are ground truth; the memory plugin surfaces relevant knowledge automatically.

Session Search & Management

For the full session search guide (plugin tools, SQL, grep, lss, REST API, workflows), load the session-search skill.

Quick reference below; the session-search skill has the complete decision tree and all query examples.

Key Facts

  • Primary storage: SQLite at /workspace/.local/share/opencode/opencode.db
  • Legacy JSON: /workspace/.local/share/opencode/storage/ (session, message, part, todo)
  • Plugin tools: session_list (browse/filter) + session_get (retrieve with TTC compression)
  • REST API: GET /session, GET /session/:id/message, GET /session/status, DELETE /session/:id (via localhost:8000 or :4096)
  • Direct SQL: sqlite3 /workspace/.local/share/opencode/opencode.db
  • Grep: grep -rl 'keyword' /workspace/.local/share/opencode/storage/part/
  • Semantic: lss "query" -p /workspace/.local/share/opencode/storage/ -k 10 --json

Commands

Slash commands trigger structured workflows. Defined at /opt/opencode/commands/ as markdown files with frontmatter.

CommandFilePurpose
/onboardingonboarding.mdFirst-run gatekeeper — researches the user, builds a profile, demos capabilities, unlocks dashboard

The onboarding command runs automatically on first use. It searches the web for the user, builds a profile, and fires POST /env/ONBOARDING_COMPLETE to unlock the dashboard.


Skill Creation Guide

When creating new skills to extend agent capabilities:

Structure

skill-name/
├── SKILL.md          # Required: YAML frontmatter + markdown instructions
├── scripts/          # Optional: executable code
├── references/       # Optional: supplementary docs
└── assets/           # Optional: templates, files

SKILL.md Format

---
name: my-skill
description: "Comprehensive description with trigger phrases. The agent reads ONLY this to decide when to load the skill."
---

# Instructions loaded when skill triggers

Principles

  1. Concise is key — The context window is shared. Only include what the model doesn't already know.
  2. Description is the trigger — Only name and description are always in context. Make the description comprehensive.
  3. Progressive disclosure — Keep SKILL.md under 500 lines. Use references/ for large docs.
  4. Prefer examples over explanations — Show, don't tell.
  5. Set appropriate freedom — High freedom for flexible tasks, low freedom for fragile operations.

Init Scripts (Boot Order)

ScriptFileWhat It Does
9696-fix-bun-ptyPatches bun-pty .so files for musl compatibility
9797-secrets-to-s6-envSyncs encrypted secrets → s6 environment. Seeds template keys on first run.
9898-kortix-envCloud mode: rewrites SDK base URLs to route through Kortix API proxy
9999-customizeXFCE dark theme, wallpaper, terminal config

Runtimes & Tools

RuntimeLocationNotes
Node.js + npm/usr/bin/nodeSystem install
Bun/opt/bun/bin/bunAlso at /usr/local/bin/bun
Python 3 + pip/usr/bin/python3With virtualenv, uv, numpy, playwright
Bash/bin/bashAlpine default
ToolDescription
opencodeOpenCode CLI — opencode serve (API), opencode web (UI)
agent-browserHeadless browser automation (npm global, uses system Chromium)
lss-syncFile watcher for semantic search indexing
lssSemantic search CLI (BM25 + embeddings)
git, curl, uv, bunStandard tooling

OpenCode Configuration

Main config at /opt/opencode/opencode.jsonc:

  • Default agent: kortix
  • Built-in agents: build, plan, explore, general are available but not default (disable lines are commented out in config)
  • Permission: allow (all tool calls auto-approved)
  • Plugins: opencode-pty, ./plugin/worktree.ts, kortix-sys-oc-plugin (memory + session tools)
  • MCP servers: Context7 (remote, for documentation lookup)
  • Provider: Kortix router (OpenAI-compatible) with two models: kortix/basic and kortix/power
  • Auto-update: enabled (autoupdate: true)

Agents

Located at /opt/opencode/agents/:

AgentFileModeRole
kortixkortix.mdprimaryThe agent. Plans, explores, builds. Self-spawns for parallel work. Loads skills for domain knowledge.

No specialist subagents. Domain knowledge lives in skills loaded on demand via skill().

Custom Tools

Located at /opt/opencode/tools/:

ToolFileDescription
Web Searchweb-search.tsTavily search API
Image Searchimage-search.tsSerper Google Images API
Image Genimage-gen.tsReplicate image generation (Flux)
Video Genvideo-gen.tsReplicate video generation (Seedance)
Scrape Webpagescrape-webpage.tsFirecrawl web scraping
Presentation Genpresentation-gen.tsHTML slide deck creation
Showshow.tsPresent outputs to user UI (images, files, URLs, text, errors)
Cron Triggerscron-triggers.tsScheduled agent execution

| Integration List | integration-list.ts | List connected OAuth apps | | Integration Search | integration-search.ts | Search available apps by keyword | | Integration Connect | integration-connect.ts | Generate OAuth connect URL for user | | Integration Actions | integration-actions.ts | Discover actions for a connected app | | Integration Run | integration-run.ts | Execute a Pipedream action (structured) | | Integration Exec | integration-exec.ts | Execute Node.js code with proxyFetch() | | Integration Request | integration-request.ts | Raw authenticated HTTP request |

Debugging

Check service status

ps aux | grep -E "(opencode|kortix-master|lss-sync|bun)"
ls /run/service/

Restart a service

kill $(pgrep -f "opencode serve")  # s6 auto-restarts longruns

Check health

curl http://localhost:8000/kortix/health
curl http://localhost:8000/lss/status

Common Issues

ProblemFix
opencode not foundPATH="/opt/bun/bin:/usr/local/bin:/usr/bin:/bin"
bun-pty segfaultCheck BUN_PTY_LIB, run 96-fix-bun-pty
Secrets not in envSet via curl localhost:8000/env with restart: true
Cloud SDK calls failCheck KORTIX_API_URL is set
Integration tools failCheck KORTIX_TOKEN is set and integrations are connected
Local deploy failsCheck curl localhost:8000/kortix/deploy for running deploys

Docker Compose

docker compose -f sandbox/docker-compose.yml up --build -d
docker compose -f sandbox/docker-compose.yml logs -f
docker exec -it kortix-sandbox bash
docker exec -it -u abc kortix-sandbox bash

Volumes

VolumeMountPurpose
workspace/workspaceAll persistent data
secrets_data/app/secretsEncrypted secrets

Resource Limits

  • shm_size: 2gb — Required for Chromium
  • cap_add: SYS_ADMIN — Required for PID namespace
  • security_opt: seccomp=unconfined — Required for Chromium sandbox
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.25

Published

Sep 25, 2026

Category

Uncategorized

License

Not specified

Source path

files/skills/kortix-system

Default branch

main

Latest commit

e62346e

Tree SHA

eceaaad