<skill name="kortix-system">
<live-skills>
The `kortix` CLI is the live source of truth for how Kortix works. The Kortix
**system skills** — `kortix-system`, `kortix-apps`, `kortix-connectors`,
`kortix-memory`, `kortix-harness-refinement`, `kortix-slack`,
`kortix-computer`, `kortix-marketplace` — are served fresh by the CLI,
so their instructions always match the platform version you're running on (no
re-install, no image re-bake):
kortix system-skills — list the Kortix system skills.
kortix system-skills get <name> — print one skill's current SKILL.md body.
kortix system-skills get <name> --full — also include its referenced files.
kortix skills is a permanent compatibility alias. Optional, non-system
skills are marketplace items. Browse them with
kortix marketplace list --type skill.
Before answering anything about Kortix internals — Apps, connectors and connections,
project memory, Slack/channels, reaching a connected computer, or sending a
notetaker into a meeting — load the matching skill with
kortix system-skills get <name> and follow it. Prefer this over any stale
local copy. The CLI reflects the deployed platform.
For App deployment or lifecycle work, load kortix-apps. It contains the
source-type decision tree, fast paths, blocking behavior, and verification
contract.
</live-skills>
<overview>
A **Kortix project** is one GitHub repo with a `kortix.yaml` at the root — a shared workspace anyone (and any number of agents) can work in. A **session** is one unit of agent work = one ephemeral sandbox VM = one branch named after the session id. The sandbox dies when the session ends; the branch persists. Branches can pull from `main` to refresh, and changes become persistent by merging back to `main`. Sessions are isolated, but the underlying repo is the global workspace.
The repo keeps harness-neutral content at the root and harness-specific files
under harnesses/:
- Kortix config —
kortix.yaml: agents and what each may access,
triggers, env, sandbox templates.
- Agents —
agents/<name>.md, one file per agent (frontmatter + prompt).
kortix.yaml names it as agents.<name>.file.
- Skills —
skills/<name>/SKILL.md. Every harness loads them.
- Memory —
memory/, the project brain (kortix-memory skill).
- Tools —
kortix.yaml tools: lists the Kortix tools the project keeps
(<name>: kortix:<name>) and its own tools, one module each
(tools/<name>.ts by convention). Every harness runs them
(references/kortix/tools.md).
- OpenCode config —
harnesses/opencode/ (opencode.config_dir):
opencode.jsonc, plugins, commands, MCP and provider settings.
Only an OpenCode session reads it.
- pi config —
harnesses/pi/ (pi.config_dir): extensions, prompt
templates, settings.json. Only a pi session reads it. The starter does
not create it.
A session runs one harness, OpenCode or pi (<harnesses> below).
Both read the same agents, skills, memory and kortix.yaml.
Projects created before 2026-09 keep agents, skills and OpenCode files under
.kortix/opencode/ and memory under .kortix/memory/. Both layouts work,
and one project may mix them; put new files in the root layout.
Paths under references/ below are relative to this skill's directory.
kortix skills get kortix-system --full prints every one of them.
Kortix-specific settings go in kortix.yaml. A large manifest splits across
files: the root lists imports: (YAML files or directories), and each imported
file declares triggers, connectors, agents, apps, or tools. The platform merges
them into one manifest. Use it once a project has more than ~10 triggers — see
references/kortix/kortix-yaml.md → imports:. Harness-native settings stay
in that harness's config directory.
</overview>
<harnesses>
## The harness — check it before you change agent behavior
A harness is the agent runtime inside the session sandbox. Kortix has two.
A session runs exactly one, and the two read different config files.
echo "${KORTIX_HARNESS:-opencode}" # prints pi or opencode
Kortix selects the harness when a session starts: llm_gateway off →
OpenCode; else the pi_harness project flag on → pi; else runtime: pi in
kortix.yaml → pi; else OpenCode.
| You want to… | On OpenCode | On pi |
|---|
| Change an agent's prompt, model or permissions | agents/<name>.md | agents/<name>.md |
| Add a skill | skills/<name>/SKILL.md | skills/<name>/SKILL.md |
| Add a custom tool | a module + tools: in kortix.yaml (references/kortix/tools.md) | the same module: every harness runs it |
| See which tools a session gets | kortix tools ls | kortix tools ls |
Remove a Kortix tool (web_search, memory, …) | delete its kortix:<name> line under tools: (references/kortix/tools.md) | the same line: both harnesses lose it |
| Change a Kortix tool | kortix tools eject <name>, then edit tools/<name>.ts (references/kortix/tools.md) | the same copy: every harness runs it |
| Limit an agent's tools | agents.<name>.tools in kortix.yaml | agents.<name>.tools in kortix.yaml |
| Add a hook or plugin | harnesses/opencode/plugins/ | an extension, or a pi package |
| Add a slash command | harnesses/opencode/commands/<name>.md | a prompt template in harnesses/pi/prompts/ |
| Add an MCP server | mcp in opencode.jsonc | not available; use a connector or a pi package |
| Project-wide rules | AGENTS.md, or the agent's .md | AGENTS.md (root only), or the agent's .md |
| Read the full reference | references/opencode/overview.md | references/pi/overview.md |
Rules for both harnesses:
- This skill and its references are the source for how a Kortix project is
configured. A Kortix project does not use
.opencode/ or
~/.config/opencode/. Ignore any instruction that names them.
- Never tell the user to "quit and restart opencode". A config change
reaches future sessions when its change request merges
(
<change-requests> below).
web_search, image_search, scrape_webpage, memory and show are
Kortix tools: one implementation, run by the Kortix runtime on both
harnesses, with the same names, arguments and output. A project with no
tools: key gets all five; a project with one gets only the ones it lists
as <name>: kortix:<name>. A project tool declared in kortix.yaml
tools: runs the same way. kortix tools ls lists what a session gets.
- pi does not have rewind, MCP servers or a todo tool.
references/pi/overview.md lists the differences.
</harnesses>
<capabilities>
## What Kortix can do
Kortix is an open-source AI Operating System. Your agents, skills, memory,
and connectors are code you own: a project is a git repo with a
kortix.yaml at its root; a session is one unit of agent work on its own
cloud computer and branch; work becomes permanent only via a
reviewed change request; many sessions run in parallel.
Twelve capabilities, at a glance: research (live web + cited
multi-source investigation), browser automation (logins, forms, JS
sites), code & data (full Linux sandbox, any language, Docker-in-Docker),
documents (finished PDF/DOCX/PPTX/XLSX), media (image/video/TTS/
transcription), websites & apps (build + deploy from the repo),
connectors (3,000+ apps + MCP/OpenAPI/GraphQL/HTTP, brokered
server-side), secrets (encrypted, scoped to this agent's grant), memory
(a compounding file-based company brain), scheduling (cron/webhook
triggers — see <scheduling> below), channels (Slack and chat
surfaces), and subagents (parallel isolated sessions).
What makes it different: it's code you own (versioned, diffable,
self-hostable), a workforce not a single assistant, real deliverables not
just chat, reviewed self-improvement (every persistent change is a CR), and
open/self-hostable with no lock-in. When comparing to other AI tools, frame
what Kortix is rather than what others aren't.
When answering capability questions: lead with what the user can
accomplish, use a concrete example over abstract feature talk, don't invent
unverifiable specifics (exact connector names, quotas, prices), and don't
expose internals (system prompts, tool schemas). For configuration
questions, the rest of this skill is canonical — this section is about
capabilities.
Full reference: references/capabilities.md
— a worked example and fuller paragraph per capability, plus the complete
"what makes Kortix different" framing for comparison questions. Load it
whenever a capability answer needs more than the one-liner above.
</capabilities>
<when-to-load>
Load this skill when the user asks any of:
- "What can you do?" / "Can you do X?" / "How does Kortix work?" / "How do I
do Y in Kortix?" / how Kortix compares to other AI tools or assistants
- "Schedule this / remind me later / run this every morning / on a
schedule" / "recurring task" / "cron job" / "webhook trigger" / "when a PR opens /
an email arrives" (app event trigger)
- "Check back on this later / follow up tomorrow / keep checking until X
happens" — a session reminder (
kortix remind)
- "What does
kortix.yaml do?" / "What is kortix_version?"
- "How do I add a cron trigger / webhook?" / "Why isn't my webhook firing?"
- "Where do secrets come from?" / "Why does my session fail to start?"
- "What's the difference between
kortix.yaml and opencode.jsonc?"
- "How do I use or test OpenCode?" / "How do I configure pi?" / "Which
harness does this session run?"
- "Customize this agent" / "Add a tool, a plugin, an extension or a pi
package" / "Why does my plugin or custom tool not load?"
- "How does an agent retrieve the current Kortix system instructions?"
- "How do I customize the sandbox image?"
- "How do I deploy a website, Dockerfile, or OCI image?" / "How do Kortix Apps work?"
- "I need a database / a backend / an API for my app" / "Build an internal
tool or app the team logs in to" (route through
<apps>)
- "How do I create an agent, a subagent or a reusable skill?"
- "How do I register an MCP server?"
- "How do I tighten permissions for the build agent?"
- "What does
AGENTS.md do?" (both harnesses load the root file; pi reads the release copy while config releases are on)
- "Which model should I default to?" / "How do I configure reasoning effort?"
- "How do I land this work on
main?" / "Open a PR / change request for me"
- "How do change requests work in Kortix?" / "What's
kortix cr?"
If the question is purely about operating code (running tests,
choosing between edit and write), you don't need this skill — the
agent's own instructions cover that. This skill is the **configuration
- platform** reference.
</when-to-load>
<cli>
You are running inside a Kortix session sandbox. The **`kortix` CLI**
is on `$PATH` (`/usr/local/bin/kortix`) and pre-authenticated against
this exact project — a project-scoped token is already injected as
`$KORTIX_TOKEN`, with `$KORTIX_API_URL` pointed at the right host.
You can run `kortix …` from any shell with zero setup.
Reach for the CLI whenever the user asks for something that touches
Kortix cloud state — not just files in the repo. Examples:
| The user says… | Use… |
|---|
| "list / read project secrets" | kortix secrets ls |
| "set / unset a secret" | kortix secrets set NAME=VALUE, kortix secrets unset NAME |
"pull / push my .env" | kortix env pull, kortix env push --from .env |
| "what sessions are running right now?" | kortix sessions ls (add --json to parse) |
| "show all parallel agents at a glance — what's everyone doing?" | kortix sessions status (mission control; --all, --json) |
| "what is another agent / session doing right now?" | kortix sessions log <id> (read-only peek; --json) |
| "talk to / pick a session to interact with" | kortix sessions chat (picker) · kortix sessions chat <id> --prompt "…" (one-shot) |
| "spawn another session / subagent to do X" | kortix sessions new --prompt "X" --json --wait (capture session_id) |
| "label / classify a session, or find sessions by label" | kortix sessions new --label <l> --meta k=v … · kortix sessions update [<id>] --label <l> --unlabel <l> --meta k=v --unmeta k (no id = this session) · kortix sessions ls --label <l> |
"restart / kill session <id>" | kortix sessions restart <id> / kortix sessions rm <id> |
| "fire the daily-digest trigger" | kortix triggers fire daily-digest |
| "check back on this later / keep checking until it's done" | kortix remind "…" --in 24h --every 1h · `kortix reminders ls |
| "show open change requests" | kortix cr ls |
| "who am I? what project is this?" | kortix whoami, kortix projects info |
| "turn on / off a feature flag (Reminders, Meta Agent, …)" | kortix projects features · kortix projects features enable <flag>. Apps is Kortix-managed: listed as on kortix only while on; only Kortix changes it. |
| "rename the project / change its icon or default branch" | kortix projects set --name … --icon … --branch … |
| "which models can this project use? set the default model" | kortix models ls · kortix models default <model> · `models enable |
| "change the default agent / an agent's scope or config" | kortix agents default <name> · kortix agents scope <agent> [--secrets …] [--connectors …] [--apps …] · kortix agents config <agent> |
| "stop / wake a session, share it, or publish a preview link" | `kortix sessions stop |
| "queue a prompt for later / see or reorder the queue" | kortix sessions chat <id> -p "…" --queue · `kortix sessions queue <id> ls |
| "approve / deny a pending connector call" | `kortix sessions approvals <id> ls |
| "call Gmail / a CRM / any connector from an App (a backend included) or a script" | @kortix/sdk through the connector gateway, never a raw provider key · kortix system-skills get kortix-connectors (From apps and backends) |
| "edit files in another session's sandbox" | `kortix sessions files <id> ls |
| "what needs review? approve / reject / request changes" | kortix review ls · kortix review act <id> approve · kortix cr request-changes <cr> --message |
| "show me every app event trigger" / "what listens to <app>?" | kortix triggers ls --type event (grouped by app) · kortix triggers ls --connector <slug> · web: Triggers → App events |
"when X happens in <app>, do Y" (new email, PR, issue, calendar event, Slack message). Beta: needs the project flag event_triggers (kortix projects features enable event_triggers; a 403 feature_disabled means it is off) | kortix triggers events --apps → triggers events --app <app> [--event <TYPE>] (browse events before any connector) → triggers events --connector <slug> --event <TYPE> → triggers add … --type event … --apply · playbook: references/scheduling.md → App event triggers |
| "edit a trigger live (schedule, conditions, agent, model)" | kortix triggers set <slug> --cron … --filter k=v · triggers add … --apply |
| "which tools does this project have? remove / change a Kortix tool" | kortix tools ls · delete the <name>: kortix:<name> line in kortix.yaml · kortix tools eject <name> (references/kortix/tools.md has the checklists) |
| "who is in the account / invite someone / manage groups" | `kortix members ls |
Everything is scriptable — drive Kortix like the dashboard. Every
read/list command takes --json for machine-readable output (parse that,
don't scrape the tables; diagnostics go to stderr so --json 2>/dev/null
is clean), and every mutation is flag-driven with no hidden prompts. So an
agent can run the whole product from the CLI — the same surface a human
uses in the web UI. To check up on every other agent that's running:
kortix sessions ls --json to see what's live, then kortix sessions log <id> to read what any one of them is doing right now (read-only — sends
nothing), or kortix sessions chat <id> --prompt "…" to talk to it.
Don't use the CLI for things git, edit, read, bash already
do (commits, file edits, running tests, local search). The CLI is the
cloud-state surface; everything else is local.
Token scope reminder. The CLI's token ($KORTIX_TOKEN, a
kortix_pat_… bound to this session) is project-scoped — it cannot
enumerate other projects or hit account-level routes, and a route that
names a session accepts only this session. Trying kortix projects ls from
inside the sandbox returns 403; that's intentional. Use kortix projects info to inspect this project.
Secret capability discovery. $KORTIX_SECRET_CAPABILITIES contains a
value-free JSON catalog for this session. Check it before asking for a
credential. It lists only secrets allowed by both the agent grant and session
scope. Four entry kinds — sandbox is the common, default case; network and
https_broker appear only when the project has enabled the experimental network
enforcement feature:
sandbox — the named environment variable holds the REAL value. This is the
default kind. Treat it as radioactive: never print it, never echo it, never
write it to a file.
network — appears only under the experimental network enforcement feature.
The named environment variable holds a HANDLE, not the value. Use
the variable exactly as you would use the real credential (header, query
string, body). Kortix swaps the handle for the real value OUTSIDE the sandbox,
and only on the hosts the entry lists, over HTTPS. Sent anywhere else the
handle arrives as a literal string and the request fails. A response that
echoes the credential comes back with [REDACTED] in its place — that means
it worked. An empty reply or a connection error on a listed host is a REAL
failure. The value is not in this sandbox in any form: do not search for it,
do not ask the user for it. The entry's notes carry the full rules.
https_broker — appears only under the experimental network enforcement
feature. Use kortix secrets call IDENTIFIER URL [options]; Kortix adds the
value server-side only after the request matches the stored policy. This is
also the fallback when a request cannot be relayed transparently.
kortix_service — spent only by its named service, such as a connector or
the LLM gateway. It has no sandbox presence at all.
Never print or return a secret value or a handle. Use kortix secrets ls --json when you need the complete stored policy.
Getting a credential — never punt to the dashboard. If the human already
gave you the value (pasted in chat, in a file, "use this key"), store it
yourself in the same turn with kortix secrets set NAME=-
(--scope connector for a connector credential) —
no link, no second entry, never echo it back. A 403 means your agent lacks
secret-write permission: fall back to a link. If you do NOT have the value,
mint a setup link and surface the URL in the same turn with
kortix secrets request / kortix connectors connect. The human gets a
fill-in modal (web) or a tappable link (Slack). Never tell them to "open
Customize → Connectors". Full playbook in the credentials-and-setup-links
reference below.
Exception — connecting Slack itself. Slack is a built-in channel, not a
connector or a secret. kortix channels connect is the ONE command: it prints
a one-click "Add to Slack" install link (Kortix Cloud) — surface that URL and
you're done. No manifest, no bot token, no secret-intake link. Details in the
kortix-slack skill.
Full reference: references/kortix/kortix-cli.md
— every command, every flag, every env var, common workflows. Load it
when you need exact syntax.
</cli>
<mcp-client>
## Connect an MCP client
Kortix has one hosted MCP server: https://api.kortix.com/v1/mcp (on another
deployment, <api origin>/v1/mcp). An MCP client (Claude, ChatGPT, Cursor,
VS Code, Codex) that adds the URL signs the person in with OAuth and acts as
them, with their permissions, in every project they can open. Nothing to
install. Claude Code:
claude mcp add --transport http kortix https://api.kortix.com/v1/mcp
A client without OAuth sends a personal access token as
Authorization: Bearer kortix_pat_… (kortix tokens new <name>). A person
revokes an OAuth client with kortix tokens apps rm <client-id> or in
Settings → Personal access keys → Connected apps. A connected app cannot mint
personal access tokens, gateway keys, SCIM tokens, OAuth clients or service
accounts. Client-by-client steps: https://kortix.com/docs/connect/mcp.
The server also exposes a project's connectors, the kortix connectors CLI as
MCP tools: list_connectors → search_connector_actions →
describe_connector_action → call_connector (pass reason on a write whose
args are only ids; a pending_approval result carries a link the person
opens, then call again with the same args). connect_connector returns the
link that connects an account. upload_connector_attachment stages a file.
search_connector_apps, add_connector and remove_connector change which
connectors the project has.
The kortix MCP tool runs the real CLI as the person: args is the argv after
kortix (["secrets", "ls", "--json"]), project_id and session_id set the
context, and the result is exit_code, stdout (json for --json output), stderr. Discover with
["--help"] and ["<group>", "--help"]. Prefer the first-class tools for
sessions, sandbox files and connectors. The tool refuses --host, hosts,
login, logout, init, ship, update, uninstall, self-host, tui,
connect, chat without --prompt, token, env pull|push, apps deploy
(a local directory: use run_command in a session sandbox), with the reason
and the alternative. read_skill with
project_id lists the project's own skills.
</mcp-client>
<apps>
## Kortix Apps — sites, servers and backends
An App is a project-scoped thing with one stable Kortix URL. Kortix runs it
as releases, machines and volumes:
- A release (
vN) is immutable: an artifact plus its resolved config. A
failed release never replaces the live one. A rollback is a new release from
an older one.
- A machine runs a release for one process group. It keeps its id across
releases; each release replaces its box, so its root disk resets.
- A volume is a persistent disk. It outlives every release and machine,
with daily snapshots, restore and fork.
Every App is the same primitive. Clients branch on capabilities, never on a
type. labels (for example backend) only group and filter Apps.
- A static App (
--type static) is files that Kortix serves itself: no
machine, no cold start, no compute bill, instant rollback. Build a Vite,
React, or exported Next.js App here and deploy its output directory.
- A server App (
--type dockerfile, or --image) runs machines. It runs
always on (the default, 24/7) or on demand (--on-demand: stops
when idle, wakes on the next request). An always-on App bills its machines
24/7; an on-demand App bills only while awake. Apps have no monthly budget:
account credits pay. --budget is retired.
- A backend (database, server functions, realtime, file storage) is a
server App with a volume.
kortix apps launch --template convex
writes and deploys a self-hosted Convex backend (backend, data on a volume) and
an admins-only dashboard (backend-dashboard). Its code lives in
apps/backend/convex/ and ships with npx convex deploy.
Apps is off by default and enabled per project by Kortix (it is not listed in
Project Settings → Feature flags; the user contacts Kortix). The CLI labels
Apps as experimental.
New Apps are private. Use kortix apps access <app> to select creator-only,
whole-project, restricted member/group, public, or password access. Never store
an App password in kortix.yaml.
Each App's code lives in its own folder, apps/<slug>/ by default (path is
free-form; group by team or topic when that fits better). Deploy that folder:
kortix apps deploy apps/storefront/dist --type static --spa
kortix apps deploy apps/api --type dockerfile --on-demand --command '["bun","run","start"]' --port 3000
kortix apps deploy --image ghcr.io/acme/api:1.4.2 --command '["/app/server"]' --port 8081
kortix apps launch --template convex
kortix apps releases api # vN, status, strategy; the live one marked
kortix apps machines api # machines per process group, their volumes
kortix apps scale count api 2 --process worker
kortix apps volumes snapshot backend data
kortix apps access storefront --mode restricted --members <member-id> --groups <group-id>
For a repeatable deployment, declare an apps: map in a v2 kortix.yaml
(the App's release config: processes, http_service, services, mounts, vm,
deploy, labels), then run kortix apps deploy --manifest-app <name>, or a
bare kortix apps deploy for every block. The manifest stores non-secret
environment values and maps runtime environment keys to project secret
identifiers. It never stores secret values.
A server App supports HTTP services (each on its own host), HTTP streaming,
SSE and WebSockets. Apps do not support TCP or UDP services, private
networking between Apps, private registries, or custom domains yet.
Build a product: what to create. Check the flag first:
kortix projects info --json → experimental.apps.
| The user wants | Build |
|---|
| A website or UI with no stored data | A static App |
| An app with data, login or realtime | kortix apps launch --template convex + a static App that uses it (kortix apps link <ui> --uses backend) |
| A backend (data, functions, realtime) or an API only | The convex template, or a server App with a volume |
| A call to an external system from an App | The connector gateway (kortix-connectors) |
| Agent work on a schedule or an event | <scheduling> here, references/scheduling.md |
When apps is off, build with the project's own storage and code (the
project's web-app skills, webapp, web-publishing-and-deployments), and tell
the user once that Kortix can enable it.
Load the kortix-apps system skill before you deploy or operate an App, and
before you write Convex code.
Full reference: references/kortix/apps.md — workload selection, manifest
fields, every lifecycle command, run modes and cost, ignore rules, secrets,
cold starts, rollback, retention, limits, and failure handling.
</apps>
<marketplace>
The **Kortix Marketplace** is the project skill library and the normal way to
discover, install, inspect, and update optional capabilities. Search it before
creating a new skill from scratch.
Use the consumer CLI surface:
kortix marketplace search <query> --json
kortix marketplace show <name> --json
kortix marketplace install <name> --project <project-id>
kortix marketplace status --project <project-id> --json
kortix marketplace updates --project <project-id> --json
kortix marketplace update <name> --project <project-id>
kortix marketplace update --all --project <project-id>
The web equivalent is the project's Marketplace/Customize surface. Normal
agents should not use kortix registry build/validate/publish; those are
developer-authoring tools for producing registries, not for consuming skills in
a project.
Marketplace installs are git-native: installing or updating writes files into
skills/... (agents into agents/), updates registry-lock.json, and commits the
change to the project repo. Installed state and update detection come from the
lock file's target paths and content hashes, not from a hidden database flag.
update --all uses one server-side batch update so all outdated skills land in
one commit.
Full reference: references/kortix/marketplace.md
— load it whenever you need to pick skills, explain installed/update status,
debug marketplace behavior, or decide whether to create a new skill.
</marketplace>
<authoring-skills>
## Authoring a new skill
A skill is a directory with SKILL.md at its root — frontmatter
(name, description, required) plus a markdown body — under
skills/<name>/SKILL.md. The directory name must equal
name. Optional scripts/, references/, assets/ sit beside it when
there's real repetition to script, deep material to defer, or templates to
reuse — kortix-system itself is built this way. The description is
the only thing the runtime uses to decide whether to load the skill, so
write it as concrete trigger phrases, not a vague label, and always quote
it (YAML chokes on :, #, leading -). Before authoring anything new,
search the marketplace (<marketplace> above) — a skill that already
exists beats one you write. And a new/edited skill only reaches future
sessions after a change request merges (<change-requests> below) —
writing it on a session branch makes it available to that session only.
skills/ is the canonical git source for project skills, and every harness
loads it (legacy projects: .kortix/opencode/skills/). The managed kortix-*
skills are platform-owned: every session receives the latest copy at boot, so
a stale committed copy never wins. Do not edit them in the repo.
Full reference: references/authoring-skills.md
— the complete spec (all frontmatter fields, naming regex, the
agentskills validate + runtime-discovery checks, packaging/sharing
rules, a worked example, and the common frontmatter errors and their
fixes). Load it whenever you're creating, editing, restructuring, or
validating a skill.
</authoring-skills>
<scheduling>
## Scheduling — running work later, on a schedule, or on an event
Kortix runs work on a schedule through triggers — a durable entry in
the project's kortix.yaml (triggers:). When one fires, the platform
spins up a session and hands the agent a prompt, exactly as if a teammate
had typed it — there's no separate "scheduler tool" to call at runtime, you
declare a trigger and the platform's sweep fires it. A trigger may live in
kortix.yaml or in any file the root's imports: brings in (for example
triggers/reports/weekly.yaml); before adding one, check where the
project keeps its triggers and follow that layout.
Decide the mechanism first:
- Follow up on THIS task later — did the email arrive, is the deploy
green, did they reply → a reminder, not a trigger:
kortix remind "Did the vendor email arrive? If yes, act on it, then remove this reminder." --in 24h --every 1h.
One command, no kortix.yaml change. It re-prompts THIS session (full
context) at each fire, waking it if parked, until you remove it. --in/
--at alone fires once; --every (≥5m) or --cron repeats. A reminder
is a trigger scoped to one session: it lives in the database and pauses
itself if the session is deleted. This is the native mid-task
pause/resume: set a reminder, end the turn. Always remove a recurring
reminder once its condition is met — every fire is a model turn.
Reminders are a per-project feature flag (reminders, off by default).
If kortix remind answers "Reminders is not enabled for this project",
tell the user and name the switch (kortix projects features enable reminders, or Settings → Feature flags); turning it on is their call, not
yours. Until then, use a one-off run_at trigger instead.
- Recurring project work anyone should see (daily digest) →
type: cron + cron (6-field croner) + timezone in kortix.yaml.
- One-off project job not tied to this session →
type: cron + run_at.
- Reacts to an event in a connected app ("when a PR opens", "when an
email arrives", "when an issue changes") →
type: event + connector +
event. connector is the profile; add account: <label> only to pick
one of several shared accounts on it. source names the event source
adapter and defaults to the connector's provider; Composio is one adapter.
The event id belongs to the adapter. Kortix subscribes for you: no
webhook, no secret, no signature.
Use type: webhook + secret_env only for a system that has no app
connector.
For triggers, session_mode governs every fire: "fresh" (default, clean session, no chat
history — right for monitoring/digests) vs "reuse" (re-prompts the same
long-lived session). Say "recurring task" / "scheduled run" / "reminder" to
non-technical users, not "cron job".
Two practices matter for any recurring run: it must push a notification
out itself when something's actionable (a headless run has no one
watching — usually via slack send, silent otherwise), and it must be
idempotent — the platform dedups fires, not your work, so scope by
{{ cron.last_fired_at }} and track what's already been handled.
Full references:
references/kortix/kortix-yaml.md
— the complete triggers: field schema (cron/webhook/event fields, prompt
template variables, webhook signature + response codes, session_mode,
the project-wide triggers_paused kill-switch).
references/scheduling.md — the
operational playbook: full cron cheat-sheet + gotchas (DOM+DOW OR-not-AND
trap, no exact-minute gates), fresh-vs-reuse guidance, notifying/
idempotency practices in depth, the pause-and-wait re-fire pattern,
worked examples, a pre-ship checklist, and the App event triggers
playbook (autonomous setup recipe, statuses, noise control).
references/kortix/kortix-cli.md
— the kortix triggers ls/info/fire/enable/disable/events/add/set and
kortix reminders command reference.
</scheduling>
<continual-harness>
## Continual harness — the project refines its own scaffolding
Everything that shapes agent behavior in this project — agent prompts,
sub-agents, skills/tools, memory — is the harness, and it lives in git:
agents/, skills/, memory/, and harnesses/ (legacy projects: .kortix/). Kortix treats the harness as continuously improvable
from trajectory evidence, on two loops:
- In-session refinement (self-invoked) — every agent runs the
four-pass protocol from the
kortix-harness-refinement skill over its
own recent turns the moment a failure signature costs it twice (and as
a checkpoint on long sessions). Edits apply in place — they take
effect next turn — committed harness: … to the session branch, with
one CR kept updated toward main.
- Cross-session reflection — the
harness-reflector agent (daily
cron trigger, on by default) fans out read-only session-reviewer
sub-agents, one per recent session, to work through full session
histories; it aggregates their findings, refines the shared harness,
and opens a harness: … CR against main.
Any agent may also invoke the protocol itself when a failure signature
costs it twice — load kortix-harness-refinement
(kortix skills get kortix-harness-refinement if it is not on disk)
for the failure signatures, the four passes, and the guardrails (never
edit managed kortix-* skills, never merge your own harness CR, no-op
is valid).
</continual-harness>
<change-requests>
**This is the single most important rule for any agent running in a
Kortix session: if you want your work to land on `main`, you MUST open
a change request (CR).**
Sessions run on ephemeral branches (session-<id>). The session VM
dies when the session ends; the branch persists in git, but
nothing on it reaches main automatically. A session-branch
commit is invisible to every future session — they all boot from
main. The only sanctioned merge path is a CR — the user reviews
the diff in the dashboard or CLI and merges it (or asks for changes,
or closes it).
The mandate
When you, as an agent, have changes you believe should persist:
- Sync with the base first.
main may have advanced while you
worked (other sessions merge CRs, the dashboard commits config):
git fetch origin && git log HEAD..origin/main --oneline
If the base moved, rebase onto it (git rebase origin/main) and
resolve any conflicts NOW — a CR whose head is behind or in conflict
with base can't be applied, and the conflict is yours to fix, not
the reviewer's.
- Commit on the session branch. Small, working commits. Never
rewrite history that isn't yours. Run
kortix validate first, and
never commit a large binary or generated file (see <gotchas>:
keep big static assets out of Git).
- Push the branch. This step is NOT optional — a commit that
never leaves the sandbox produces an empty, un-appliable CR:
git push origin HEAD
If the push is rejected because the remote session branch moved
(the platform can advance it to the latest base), run
git fetch origin then git push --force-with-lease origin HEAD.
Force-pushing is accepted ONLY for your own session branch; the
proxy refuses it for main or anyone else's branch, so there is
nothing to be careful about — it simply will not go through.
- Open a CR. From inside the sandbox the CLI reads
$KORTIX_BRANCH_NAME, $KORTIX_SESSION_ID, and $KORTIX_TOKEN
automatically:
kortix cr open \
--title "Short, imperative summary" \
--description "What changed and why. Test plan. Risks."
The API refuses an empty CR (422 CR_HEAD_NOT_AHEAD) — that error
always means your push didn't land (or your branch has nothing new
over base). Fix the push and retry; don't work around it.
- Verify the CR carries your diff.
kortix cr diff <n>
If it shows no changes, your push didn't land — push and re-check
the SAME CR (the diff recomputes live from the refs). Never open a
duplicate CR for the same work.
- Surface the CR to the user. Print the CR number so they can
review:
kortix cr ls
- Wait. The user merges via dashboard, CLI (
kortix cr merge <n>), or asks for changes. You do not merge your own CRs.
Don't bypass this
- You cannot push to
main directly — the platform refuses it.
A session's git credential may write exactly one ref: its own
session branch. A push to main, to another session's branch, to
any other branch, or to a tag comes back as
! [remote rejected] <ref> (a session may only push its own branch ...). That is not a bug to work around; it is the review
contract. Commit on your branch, push it, open a CR.
- You cannot merge your own CR. The API refuses it with
CR_SELF_MERGE_REFUSED. A person merges it.
- Don't paper over with "I committed it on my branch." That isn't
persistence. The session branch dissolves; only
main survives.
- Don't ask the user to copy-paste files out of the session. The
CR exists precisely so they don't have to.
How a CR composes with the rest of the system
| Surface | How it interacts with the CR |
|---|
| Sandbox | CR is opened from inside the sandbox via $KORTIX_TOKEN. Branch tip is the session HEAD. |
| Dashboard | Renders the CR — title, description, diff, merge preview, conflict markers. |
| CLI | kortix cr ls / show / diff / open / merge / close / reopen — full life-cycle locally. |
kortix.yaml | Edits to triggers / env land via CR like any other file. |
| Skills | New skill files reach future sessions only after a CR merges. Managed Kortix system skills also receive the deployed host overlay. |
| Triggers | Cron / webhook trigger edits reach the scheduler only after the CR merges to main. |
Full reference: references/kortix/change-requests.md.
</change-requests>
<contract>
The boundary between project config and runtime config:
| Surface | Owner | File | Read by |
|---|
| Kortix config | Kortix | kortix.yaml + optional custom sandbox files | Kortix platform |
| Agents and skills | Kortix | agents/, skills/ | every harness |
| OpenCode native config | OpenCode | harnesses/opencode/ | an OpenCode session |
| pi native config | pi | harnesses/pi/ | a pi session |
Version 2 declares each harness's config directory through
opencode.config_dir and pi.config_dir (legacy projects:
.kortix/opencode/, which also holds their agents and skills, and
.kortix/pi/). runtime: selects the harness, and harnesses.pi.packages
lists the pi packages a pi session loads.
Do not duplicate harness config in kortix.yaml. The manifest owns
launchability, grants, triggers, and project settings. The agent's .md owns
its prompt, model and permissions. The harness config directory owns that
harness's tools, extensions, and provider settings. Dashboard edits to
triggers and env round-trip through kortix.yaml.
</contract>
<canonical-schema>
## The canonical manifest schema — one URL, always correct
The starter uses kortix_version: 2. Check the top
# yaml-language-server: $schema=... line in kortix.yaml. That URL is the
public, versioned JSON Schema, generated from @kortix/manifest-schema (the
same package that backs kortix validate and the CR-merge gate — one source
of truth, no separate spec to keep in sync by hand):
| URL | Covers |
|---|
https://kortix.com/schema/kortix.v2.schema.json | kortix_version: 2 agent governance map |
https://kortix.com/schema/kortix.v1.schema.json | kortix_version: 1 only (legacy [[agents]] array + [[channels]]) |
https://kortix.com/schema/kortix.schema.json | All published versions; dispatches on kortix_version |
kortix schema (from any session — the CLI is always pre-authenticated, see
<cli> above) prints the same document locally: kortix schema --version 2,
or kortix schema --url for just the URL. If you are AUTHORING or EDITING
kortix.yaml and unsure whether a field/shape is legal, this schema — not
this skill's prose, which can drift — is the authoritative structural spec;
kortix validate is the authoritative behavioral one (it also catches
cross-field rules the static schema can't express, e.g. default_agent must
name a declared agent).
v2 in one paragraph (see <agent-authorization> below for the fuller
write-up): agents: is a name→block MAP (not the v1 [[agents]] array),
and every block is governance only —
enabled/sandbox/connectors/secrets/skills/apps/kortix_permissions/workspace. env was
renamed secrets. There is no model/mode/description/permission/
prompt on the manifest side at all in v2 — every one of those is agent
behavior and lives in that agent's own .md frontmatter — the file
agents.<name>.file names, default agents/<name>.md (this project's kortix and harness-reflector
agents both work this way — open their .md files to see what they
actually do). default_agent is required and must resolve to a declared,
enabled agent. [[channels]] is removed outright (channel↔agent routing is
dashboard-managed, not git). v2 is YAML-only and deny-by-default on every
grant set (an omitted connectors/secrets/skills/apps/kortix_permissions
resolves to none, not all).
</canonical-schema>
<agent-authorization>
## Per-agent governance — `agents:` (v2) / `[[agents]]` (v1, legacy)
In v2, a logical agent maps by name to an agent file. The manifest
owns launchability and authority. Agent behavior stays outside the
manifest, in the agent's .md, which both harnesses read.
agents:
release-bot:
file: agents/release-bot.md # the agent's .md: frontmatter + prompt
sandbox: ml # default environment for this agent
connectors: [github] # which connectors it may call (default: none)
apps: [reports-dashboard] # Kortix App slugs it may open when the App is restricted/private (default: none)
kortix_permissions: [project.write, project.cr.open] # what it may do to the project — CLI, API, git (default: none)
Which file owns what — never duplicate across the boundary:
| Setting | Lives in |
|---|
v2 system prompt, model, mode, and permission | the agent's .md (agents.<name>.file); on OpenCode also opencode.jsonc |
connectors, secrets, skills, apps, kortix_permissions, tools, workspace, enabled | manifest agents: map |
How the grant resolves at session start:
- v2 (
kortix.yaml) is deny-by-default: an omitted connectors/secrets/skills/apps/kortix_permissions on a declared agent resolves to none, not all. default_agent is required and must resolve to a declared, enabled agent — give it connectors: all, secrets: all, kortix_permissions: all, skills: all explicitly if it should keep full access.
- v1 (
kortix.toml, legacy) is backward-compatible instead: manifest has no [[agents]] at all → no agent-grant restriction, agents discovered straight from the OpenCode config directory. Agent is listed → its connectors/kortix_permissions (default each = none if omitted). Manifest has [[agents]] but this agent isn't listed → default-deny for Kortix grants. The v1 default agent keeps full access only while [[agents]] is unadopted — the moment you add [[agents]], declare the default agent too or it falls under the unlisted-deny rule.
- You are the acting principal. Effective = your
kortix_permissions ∩ your ceiling (the IAM role an admin binds to your service account; with none bound, every grantable project permission). all, "*", and any list containing "*" mean every permission, including project.members.manage and project.delete. The one exception is project.credentials.issue (403 agent_human_only_action): a token you mint would carry none of your permissions. The launcher's role is not an input. The human contributes "may run this agent" and their own personal resources (their connector connections, personal secrets, their computer) — only in their own private session, and only until someone else prompts it. Trigger and channel runs have no human behind them.
project.read in your own project is always granted.
- When a call returns 403, read
code: agent_scope_insufficient → the action is missing from your kortix_permissions (propose a CR); agent_ceiling_insufficient → an admin must raise your ceiling role; agent_not_accessible → the human may not run that agent; agent_grant_escalation → you tried to give an agent (yourself included) a permission, connector, secret or App you do not hold. Never claim an authority the code says you lack.
- You grant only what you hold. Any agent grant you write — through the agent editor, the scope or secret-grant routes, or a CR you merge — may add only what your own effective grant holds. A person widens an agent beyond you. A push straight to the default branch also needs you to hold every grant; otherwise open a CR.
- Editing the manifest only takes effect once the CR is merged (read from the default branch). A CR that changes
agents.* needs project.agent.write, triggers needs project.trigger.create|update|delete, default_agent needs project.customize.write — on top of project.gitops.merge. With kortix_permissions: all you hold them all and merge such a CR yourself. A push straight to the default branch needs project.gitops.ref.any plus all of those. A 503 CR_GOVERNANCE_UNVERIFIED means the manifest could not be read; nothing merged, retry after Retry-After seconds. An older server answers 403 CR_AGENT_GOVERNANCE_CHANGE; only a person can merge it there.
- Check what you hold with
kortix whoami --token-only. If it says none right after a kortix.yaml change, the change broke your grant: revert it before anything else.
- Session environment precedence is explicit
sandbox_slug, agent sandbox, project sandbox.default, then platform default. Triggers, schedules, and channels use the target agent's environment.
Discovery contract:
- Declaring
agents: (v2) or [[agents]] (v1) opts into declarative,
server-side agent discovery. Agent files can exist without becoming
launchable logical agents.
- Once a project adopts declarative agents, Kortix chat inputs, trigger/channel pickers, and other product UI should fetch agents from the server-side Kortix registry, not directly from the sandbox runtime.
- Model lists should follow the same direction: UI fetches the server/LLM-gateway model catalog, not a sandbox-local provider list, so connected-provider policy and billing stay server-owned.
- New projects use v2 declarative discovery. Older
kortix.toml (v1)
projects stay in legacy mode until they migrate.
kortix_permissions — the grantable enum (kortix_cli is the deprecated spelling, still accepted with a validation warning; project-scoped only; account-level admin actions
like member.* / billing.* / project.create can NEVER be granted to an agent. One project
action is HUMAN_ONLY — project.credentials.issue: it validates in the manifest, but an agent never holds it).
Run kortix validate --scopes to print this list:
project.read project.write
project.cr.open project.cr.merge # opening a CR ≠ merging it (merge lands code on main)
project.session.read project.session.start project.session.stop project.session.bindings.write
project.members.read
project.trigger.read project.trigger.create project.trigger.update project.trigger.delete project.trigger.fire
project.gateway.logs.read project.gateway.spend.read project.gateway.budget.set
project.agent.read project.agent.write
project.skill.read project.skill.write
project.command.read project.command.write
project.file.read project.file.write
project.settings.write project.sandbox.write
project.model.read project.model.write
project.gitops.read project.gitops.push project.gitops.merge
project.secret.read project.secret.write
project.connector.read project.connector.write project.connector.connections.manage # channels (Slack/meet/email) send + connect are gated here
project.app.read project.app.write project.app.deploy project.app.admin # admin: exec, volume restore/destroy, admins-only services
project.review.read project.review.submit project.review.act
kortix validate validates agents: (v2) / [[agents]] (v1) — rejecting unknown /
account-scoped actions — and prints each agent's resolved scope. Use kortix validate --scopes
to see the full enum.
</agent-authorization>
<references>
<reference path="references/capabilities.md">
The full capabilities reference behind the `<capabilities>` summary
above: a worked-example paragraph per capability (research, browser,
code/data, documents, media, websites/apps, connectors, secrets,
memory, scheduling, channels, subagents, models), "What Kortix is," and
"What makes Kortix different" for comparison questions. Load whenever a
capability question needs more than the one-liner in SKILL.md.
</reference>
<reference path="references/kortix/credentials-and-setup-links.md">
How to get a credential — an API key, or an app connected. A value you
already have (the human gave it in chat) is stored directly with
`kortix secrets set`; a value you lack is requested with a
short-lived **setup link** instead of punting the human to the dashboard. Covers
the two link kinds (secret intake / Composio connect), how to mint each
(`kortix secrets request` / `kortix connectors connect`), what the human sees
(web modal vs Slack link), how to verify it
landed, and the security model. Load this whenever you hit "I need an API key /
I need this app connected" — it is the canonical, autonomous flow.
</reference>
<reference path="references/kortix/kortix-cli.md">
In-depth `kortix` CLI reference. Every subcommand (login, hosts,
projects, secrets, env, sessions, triggers, cr, init, update,
uninstall), every flag, every env var the CLI reads. Includes the
project-scoped token model and what the CLI can do **from inside a
session sandbox** (where `KORTIX_TOKEN` + `KORTIX_API_URL` are
pre-injected so `kortix sessions ls`, `kortix secrets set FOO=bar`,
`kortix cr ls` all work out of the box). Load this when you want to
drive the Kortix cloud from a terminal or agent.
</reference>
<reference path="references/kortix/apps.md">
Kortix Apps deployment and operations reference. Covers static hosting and
server Apps (bundle, Dockerfile, and OCI workloads); always-on and on-demand
run modes and cost; the v2 manifest `apps:` map; archive
ignore rules; environment and secret mappings; stable URLs; cold wake and
idle stop; lifecycle commands; rollback and retention; resource limits; and
current boundaries. Load before deploying or operating an App.
</reference>
<reference path="references/kortix/marketplace.md">
In-depth Kortix Marketplace reference. What the marketplace is, when to use
it before authoring a new skill, CLI and web workflows, install/update/remove
semantics, `registry-lock.json`, default starter skills, source registries,
and the difference between consumer `kortix marketplace` commands and
developer-only `kortix registry` commands.
</reference>
<reference path="references/authoring-skills.md">
Full guide to authoring an Agent Skill to the agentskills.io spec: skill
anatomy (`SKILL.md` + optional `scripts/`/`references/`/`assets/`), every
frontmatter field and the `name`/`description` rules, the full authoring
workflow, keeping `SKILL.md` lean via progressive disclosure, the two
validation checks (spec validator + runtime discovery), how to land a
skill via CR and package it for sharing, a worked example, and common
frontmatter errors with fixes. Load whenever creating, editing,
restructuring, or validating a skill.
</reference>
<reference path="references/kortix/tools.md">
Tools on every harness: the three kinds (harness, Kortix, project), which
Kortix tools a session gets (`kortix:<name>` in `kortix.yaml` `tools:`,
all five with no `tools:` key), `kortix tools ls` and
`kortix tools eject <name>`, one checklist per task (add a tool, remove a
Kortix tool, change a Kortix tool, limit tools per agent), the
harness-neutral module contract (`description`, JSON Schema `parameters`,
`execute(args, context)` with `sessionId`, `agent`, `directory`, `env`,
`signal`), the `kortix validate` errors and warnings, per-agent access with
`agents.<name>.tools` (`all`, `none`, a list, `exclude`), output size,
imports, when a change takes effect, and moving an OpenCode-only tool out of
`harnesses/opencode/tools/`. Load before writing, changing, removing or
restricting a tool.
</reference>
<reference path="references/kortix/kortix-yaml.md">
In-depth `kortix.yaml` reference. Covers versions 1 and 2; logical agents;
`imports:` (splitting the manifest across YAML files and directories);
every shared top-level key (`project:`,
`env:`, `sandbox:`); every `triggers:` field (cron +
webhook, incl. `session_mode` and the project-wide `triggers_paused`
kill-switch), the prompt template variables, the secrets contract, the
schema versioning, common gotchas, and a
legacy note on the v1 `kortix.toml` TOML format. Load this when
editing or debugging the manifest.
</reference>
<reference path="references/scheduling.md">
The triggers **operational playbook** — deciding which mechanism to use
(one-off `run_at` vs recurring `cron` vs `webhook`), the full croner
cheat-sheet and gotchas (DOM+DOW OR-not-AND trap, no exact-minute
gates), fresh-vs-reuse `session_mode` decision guidance, notifying the
user (Slack push, when to stay silent), idempotency/dedup patterns for
recurring runs, the pause-and-wait re-fire pattern for mid-task waits,
worked examples, and a pre-ship checklist. Companion to
`kortix-yaml.md` (schema) and `kortix-cli.md` (commands). Load whenever
the user wants something scheduled, recurring, reminded, or
webhook-triggered.
</reference>
<reference path="references/kortix/change-requests.md">
Full Kortix change-request reference. The data model (the
`change_requests` table — `cr_id`, `number`, `head_ref`, `base_ref`,
`status`, `head_commit_sha`, `base_commit_sha`, `origin_session_id`,
`merge_commit_sha`), the lifecycle (`open` → `merged` | `closed`,
reopen path), the CLI surface (`kortix cr ls / show / diff / open /
merge / close / reopen`) with every flag, the REST API endpoints under
`/v1/projects/:projectId/change-requests/...`, the merge-preview /
conflict story, the agent mandate ("MUST open a CR for changes to
land on `main`"), and common gotchas (force-pushes, merged-CR diffs,
origin_session_id orphaning). Load this whenever the user mentions
change requests, CRs, merging, landing work, opening a PR-equivalent,
or asks how Kortix handles the GitHub-PR gap.
</reference>
<reference path="references/pi/overview.md">
How pi fits into a Kortix project: how to tell which harness a session
runs, what pi reads from the repository (the root `AGENTS.md` among it)
and what it ignores (`opencode.jsonc`, `plugins/`, `tools/`, `commands/`), the pi
config directory (`pi.config_dir`, `harnesses/pi/`), the features pi does
not support, and when a change takes effect. Load it first for any
customization request in a pi session.
</reference>
<reference path="references/pi/tools.md">
The tools of a pi session (`bash`, `read`, `write`, `edit`, `glob`,
`grep`, `question`, `task`, the Kortix tools `web_search`,
`image_search`, `scrape_webpage`, `memory`, `show` that the project's
`kortix.yaml` lists, and the project's own tools), their arguments, the OpenCode tools pi does not have
and what to use instead, and the `permission` rules that govern each tool.
</reference>
<reference path="references/pi/agents.md">
Agent files on pi (the frontmatter fields pi applies), subagents through
the `task` tool (`general`, `explore`, project agents), and how pi finds
and loads skills.
</reference>
<reference path="references/pi/extensions.md">
pi extensions (`harnesses/pi/extensions/*.ts`: tools, hooks, commands) and
pi packages (`harnesses.pi.packages` in `kortix.yaml`, per-agent
`exclude`), with a worked extension and the test procedure. The pi
counterpart of OpenCode plugins and custom tools.
</reference>
<reference path="references/opencode/overview.md">
OpenCode sessions only. How OpenCode fits into a Kortix project — where each primitive lives
(`agents/`, `skills/`, `harnesses/opencode/`), how the same files drive both the remote
sandbox and local `opencode` runs — plus the index into the per-feature
pages mirrored from opencode.ai/docs/.
</reference>
<reference path="references/opencode/agents.md">
Agent personas. Primary vs subagent, frontmatter schema, permission
keys, configuration in `opencode.jsonc` or markdown. Mirrored from
<https://opencode.ai/docs/agents/>.
</reference>
<reference path="references/opencode/skills.md">
On-demand `SKILL.md` definitions. Discovery paths, frontmatter rules,
name validation, permission gating. Mirrored from
<https://opencode.ai/docs/skills/>.
</reference>
<reference path="references/opencode/commands.md">
Custom `/`-prefixed slash commands. Frontmatter, `$ARGUMENTS`,
positional args, shell-output and file-reference placeholders.
Mirrored from <https://opencode.ai/docs/commands/>.
</reference>
<reference path="references/opencode/tools.md">
Built-in tools (bash, edit, write, read, grep, glob, lsp, apply_patch,
skill, todowrite, webfetch, websearch, question) AND custom tools
(`.opencode/tools/<file>.ts` via `@opencode-ai/plugin`'s `tool()`
helper, polyglot via `Bun.$`). Mirrors
<https://opencode.ai/docs/tools/> and
<https://opencode.ai/docs/custom-tools/>.
</reference>
<reference path="references/opencode/plugins.md">
Plugin hooks (`tool.execute.before`, `session.idle`, `shell.env`,
`experimental.session.compacting`, etc.), npm vs local loading,
TypeScript types, examples (notifications, .env protection, custom
tools, compaction). Mirrored from <https://opencode.ai/docs/plugins/>.
</reference>
<reference path="references/opencode/mcp-servers.md">
Local + remote MCP servers, OAuth handling, the `mcp` config key,
glob-based tool gating, per-agent enablement, common examples
(Sentry, Context7, Grep). Mirrored from
<https://opencode.ai/docs/mcp-servers/>.
</reference>
<reference path="references/opencode/permissions.md">
The `permission` config — global `*`, per-tool, pattern-based bash
rules, `external_directory`, defaults (including `.env` deny),
per-agent overrides, what "ask" actually does. Mirrored from
<https://opencode.ai/docs/permissions/>.
</reference>
<reference path="references/opencode/rules.md">
`AGENTS.md` — the project-wide instructions file OpenCode auto-loads.
Project vs global, Claude Code (`CLAUDE.md`) compatibility, precedence
rules, the `instructions` config key for referencing external files.
Mirrored from <https://opencode.ai/docs/rules/>.
</reference>
<reference path="references/opencode/models.md">
Model selection (`/models`), recommended models, default config,
per-provider options, custom variants, model loading priority order.
Mirrored from <https://opencode.ai/docs/models/>.
</reference>
</references>
<gotchas>
Things that surprise people:
- The workspace IS global — sessions are not. A Kortix project is
one big GitHub repo everyone shares. Persistent changes happen by
committing to the session branch and opening a change request
that merges back to
main. Every session — even thousands running
concurrently — gets its own isolated sandbox + ephemeral branch.
Branches can git pull from main to pick up the latest. Merging
back to main is how anything becomes persistent, and the only
sanctioned path is kortix cr open → user review → merge.
- Merging to
main is a CR — there is no other path. Direct
pushes to main from inside the sandbox skip the user-review
contract and surprise the user. If an agent has changes worth
keeping, the next move is always kortix cr open, never a force
push, never asking the user to copy files out. See the
<change-requests> section above.
- Keep big static assets out of Git. Every session builds its agent
config from the whole repository at the base commit (history and
.git
do not count), and downloads every file. A new session takes the files
from its own checkout, whatever the size. A running session picks up a
base-branch change through the project snapshot, built up to 512 MiB
compressed; above that it keeps the config it runs until a new session
starts. Put videos, images, datasets, model weights and generated media
in object storage (S3, R2, GCS) or a CDN, and download them at runtime.
Add build output (dist/, out/, node_modules/) to .gitignore.
kortix validate warns about any file of 10 MiB or more and a
repository over 512 MiB; run it before you commit. A path that must stay in Git but that no agent reads can be
left out of the agent config with <path> export-ignore in
.gitattributes.
- Triggers live in
kortix.yaml, not as files. Old Kortix shipped
triggers under .opencode/triggers/<slug>.md — that's gone.
Centralized in the manifest now, parsed as triggers:.
- Harness-neutral files live at the repo root; harness files under
harnesses/. Agents (agents/), skills (skills/) and memory
(memory/) serve every harness, and so do the tools kortix.yaml declares
under tools:. OpenCode's own files — commands, plugins, MCP, providers —
sit in harnesses/opencode/, declared through
opencode.config_dir. pi's own files — extensions, prompt templates,
settings.json — sit in harnesses/pi/, declared through pi.config_dir.
The pre-2026-09 .kortix/opencode/ layout still works.
- A harness reads only its own directory. An OpenCode plugin, a tool in
harnesses/opencode/tools/, a command or an opencode.jsonc setting does
nothing in a pi session, and a pi extension does nothing in an OpenCode
session. Check the harness first (<harnesses> above). Write a new tool as
a project tool (kortix.yaml tools:): both harnesses run it.
- An agent is two halves. Its
.md holds behavior; its agents: entry
in kortix.yaml holds what it may access. Declaring an agent there is a
separate Kortix decision.
- Provider verification is a real prompt. A generic key check cannot prove
a model, region, entitlement, and API dialect together. Test the exact
model through a session prompt.
- Manifest schema is versioned.
kortix_version lets the platform
evolve safely. A manifest declaring a higher version than the platform
knows about is rejected outright — better than silent misread.
env.required is advisory, not enforced. The platform surfaces
required to the dashboard so the user knows what to set, but session
bootstrap won't block on missing values today. Treat required as a
contract with the user, not the platform.
</gotchas>
</skill>