Worked examples

25 worked examples, one per extension surface. Each is a real file, checked with kcode's own validator before it is published.

Plugins

Your first plugin

What kcode plugins scaffold gives you: a manifest, a tool, an event hook, and durable storage.

~/.kcode/plugins/hello/

Checked with kcode plugins validate before publishing.

plugin.toml
name = "hello"
version = "0.1.0"
description = "What kcode plugins scaffold gives you: a tool, a hook, durable storage"
kind = "Lua"

[capabilities]
kv = true
hooks = true
main.lua
-- Runs when the plugin loads. Everything registered here joins the session.
kcode.register_tool("hello", "Say hello and count how often you did", function(args)
  local name = args.name or "world"
  local count = tonumber(kcode.kv_get("greetings") or "0") + 1
  kcode.kv_set("greetings", tostring(count))
  return { text = "Hello, " .. name .. "! Greeting number " .. count }
end)

kcode.on_event("core.app.turn-finished", function(ev)
  kcode.log("turn finished (" .. ev.kind .. ")")
end)
commands/hello.md
---
description: Greet the current project by name
---
Use the `hello` tool with the name of the project in the working directory,
then reply with one line.
skills/hello.md
---
name: hello
description: How to greet politely
---
When asked to greet someone, use the `hello` tool once and keep the reply to a single line.
Lua host API · register_tool

A tool in Lua

Give the agent a new tool. It shows up in the registry like a built-in and asks for permission the same way.

~/.kcode/plugins/todo-count/

Checked with kcode plugins validate before publishing.

plugin.toml
name = "todo-count"
version = "0.1.0"
description = "A tool the agent can call: count TODO lines in the project's notes"
kind = "Lua"

[capabilities]
"fs.read" = ["**/*.md", "**/*.txt"]
main.lua
-- The handler receives the arguments the model passed and returns a table.
-- fs_list takes an optional path prefix and returns { path, is_dir } rows; it and fs_read only
-- see the files inside the globs granted under [capabilities].
kcode.register_tool("todo_count", "Count TODO lines in the project's notes", function(args)
  local marker = args.marker or "TODO"
  local total, files, lines = 0, 0, {}
  for _, entry in ipairs(kcode.fs_list(args.prefix)) do
    if not entry.is_dir then
      local text = kcode.fs_read(entry.path)
      if text then
        files = files + 1
        for line in text:gmatch("[^\n]+") do
          if line:find(marker, 1, true) then
            total = total + 1
            if #lines < 50 then lines[#lines + 1] = entry.path .. ": " .. line end
          end
        end
      end
    end
  end
  return { count = total, files = files, marker = marker, lines = lines }
end)
Lua host API · register_command

A command

A slash command that runs your code instead of a prompt.

~/.kcode/plugins/branch-note/

Checked with kcode plugins validate before publishing.

plugin.toml
name = "branch-note"
version = "0.1.0"
description = "A slash command backed by code instead of a prompt"
kind = "Lua"

[capabilities]
kv = true
main.lua
-- /branch-note <text>: the handler gets everything after the command name
-- and returns markdown, which kcode renders in the transcript.
kcode.register_command("branch-note", "Keep a note for this branch", function(text)
  if text == nil or text == "" then
    local saved = kcode.kv_get("note")
    if saved then return "**Branch note:** " .. saved end
    return "No note yet. Try `/branch-note remember to squash before merging`."
  end
  kcode.kv_set("note", text)
  return "Saved: _" .. text .. "_"
end)
Lua host API · on_event

Listening to events

React to the loop: file changes, timer ticks, turn boundaries.

~/.kcode/plugins/turn-log/

Checked with kcode plugins validate before publishing.

plugin.toml
name = "turn-log"
version = "0.1.0"
description = "React to what the loop does: files, timers, turn boundaries"
kind = "Lua"

[capabilities]
hooks = true
notify = true
main.lua
-- Every handler gets {kind, source, intent, data}.
kcode.on_event("core.fs.change", function(ev)
  -- The project watcher, gitignore-filtered.
  kcode.log("changed: " .. tostring(ev.data.path))
end)

kcode.on_event("core.app.turn-started", function(ev)
  kcode.log("turn started from " .. tostring(ev.source))
end)

kcode.on_event("core.app.turn-error", function(ev)
  kcode.notify("kcode turn failed", tostring(ev.data.message))
end)
Triggers · [[triggers.cron]] · [[triggers.every]]

Cron and every

Schedules declared in the manifest fire core.timer.tick with your entry name.

~/.kcode/plugins/heartbeat/

Checked with kcode plugins validate before publishing.

plugin.toml
name = "heartbeat"
version = "0.1.0"
description = "Schedules declared in the manifest; each fires core.timer.tick with its name"
kind = "Lua"

[capabilities]
hooks = true
notify = true

# Six fields, seconds first: every weekday at 09:00.
[[triggers.cron]]
name = "standup"
schedule = "0 0 9 * * 1-5"

# A plain period: 30s, 5m, 2h.
[[triggers.every]]
name = "pulse"
period = "5m"
main.lua
-- One event kind for every schedule; the entry's name tells them apart.
kcode.on_event("core.timer.tick", function(ev)
  if ev.data.name == "standup" then
    kcode.notify("Standup", "Time to write the daily note")
  elseif ev.data.name == "pulse" then
    kcode.log("still here")
  end
end)

-- The same schedule can be registered from code instead of the manifest.
kcode.on_cron("0 0 18 * * 1-5", function()
  kcode.notify("Wrap up", "Commit what you have before you leave")
end)
Lua host API · on_input

Rewriting input

Expand shorthand or veto a prompt before the turn starts.

~/.kcode/plugins/shorthand/

Checked with kcode plugins validate before publishing.

plugin.toml
name = "shorthand"
version = "0.1.0"
description = "Rewrite or veto the prompt before the turn starts"
kind = "Lua"

[capabilities]
"input-transform" = true
main.lua
-- Return nil to pass the text through, a string or {text=} to rewrite it,
-- or {veto="reason"} to stop the turn. Vetoes show in chat, attributed to the plugin.
local expansions = {
  ["!t"] = "Run the test suite and fix every failure you find.",
  ["!r"] = "Review the working diff and list findings by severity.",
}

kcode.on_input(function(input)
  local expanded = expansions[input.text]
  if expanded then return { text = expanded } end
  if input.text:find("DROP TABLE", 1, true) then
    return { veto = "This plugin refuses prompts that mention dropping tables." }
  end
  return nil
end)
Lua host API · on_tool

Gating tools

A blocking check before every tool call. Return nothing to allow, or a reason to block.

~/.kcode/plugins/no-force-push/

Checked with kcode plugins validate before publishing.

plugin.toml
name = "no-force-push"
version = "0.1.0"
description = "A blocking check before every tool call"
kind = "Lua"

[capabilities]
"tool-gate" = true
main.lua
-- Return nil to allow, or {veto="reason"} to block. The gate fails open on
-- error or timeout; the session permission engine remains the boundary.
kcode.on_tool(function(call)
  if call.tool ~= "bash" then return nil end
  local cmd = tostring(call.args.command or "")
  if cmd:find("push%s+.*%-%-force") or cmd:find("push%s+%-f") then
    return { veto = "Force pushes are blocked by the no-force-push plugin." }
  end
  return nil
end)
Lua host API · run_tool · spawn_subagent · inject

Driving the agent

Run tools, start subagents, and wake the session from plugin code.

~/.kcode/plugins/nightly-review/

Checked with kcode plugins validate before publishing.

plugin.toml
name = "nightly-review"
version = "0.1.0"
description = "Run tools, start subagents, and wake the session from plugin code"
kind = "Lua"

[capabilities]
hooks = true
"run-tool" = true
"spawn-subagent" = true
inject = true

[[triggers.cron]]
name = "nightly"
schedule = "0 0 2 * * *"
main.lua
kcode.on_event("core.timer.tick", function(ev)
  if ev.data.name ~= "nightly" then return end

  -- run_tool goes through the permission engine with this plugin as the caller.
  -- It returns the tool's structured result, the same value the model sees.
  local status = kcode.run_tool("bash", { command = "git status --short | wc -l" })
  kcode.log("dirty files: " .. tostring(status.structured))

  -- spawn_subagent returns {name=} at once; completion arrives as an event.
  local child = kcode.spawn_subagent({
    prompt = "Review yesterday's commits for anything risky. Report, do not change files.",
    kind = "security-reviewer",
    name = "nightly",
  })

  -- inject wakes the session with an agent-visible note.
  kcode.inject("Nightly review started as " .. child.name
    .. ". Summarise its findings when it finishes.")
end)
Manifest · shadows · replace-builtin

Replacing a built-in

Shadow a built-in tool by name. Safety-critical names need the grant and project trust.

<project>/.kcode/plugins/safe-bash/

Checked with kcode plugins validate before publishing.

plugin.toml
name = "safe-bash"
version = "0.1.0"
description = "Shadow a built-in; safety-critical names need the grant and project trust"
kind = "Lua"

# The plugin's `bash` replaces the built-in for this session.
shadows = ["bash"]

[capabilities]
"replace-builtin" = true
"run-tool" = true
main.lua
-- A wrapper that refuses a few commands and forwards the rest to the real tool.
local refused = { "rm -rf /", "mkfs", ":(){ :|:& };:" }

kcode.register_tool("bash", "Run a shell command (guarded)", function(args)
  local cmd = tostring(args.command or "")
  for _, bad in ipairs(refused) do
    if cmd:find(bad, 1, true) then
      return { error = "safe-bash refused: " .. bad }
    end
  end
  return kcode.run_tool("bash", args)
end)
Lua host API · intercept

Intercept points

Pass, veto, or rewrite at core.* points: provider requests, compaction, continuation.

~/.kcode/plugins/guardrails/

Checked with kcode plugins validate before publishing.

plugin.toml
name = "guardrails"
version = "0.1.0"
description = "Pass, veto, or rewrite at the core.* intercept points"
kind = "Lua"

[capabilities]
hooks = true
main.lua
-- Each handler returns nil (pass), {veto="reason"}, or {rewrite=<payload>}.

-- Before a request leaves for the provider.
kcode.intercept("core.provider.request", function(req)
  if req.max_tokens and req.max_tokens > 8000 then
    req.max_tokens = 8000
    return { rewrite = req }
  end
  return nil
end)

-- Before compaction runs: keep the window as it is during a long tool run.
kcode.intercept("core.compaction.gate", function(gate)
  if gate.tools_in_flight and gate.tools_in_flight > 0 then
    return { veto = "guardrails: compaction deferred while tools run" }
  end
  return nil
end)

-- When the loop decides whether to continue after a round.
kcode.intercept("core.continuation.decide", function(decision)
  if decision.rounds and decision.rounds > 40 then
    return { veto = "guardrails: 40 rounds is enough for one turn" }
  end
  return nil
end)

-- After a tool errors: attach a hint the model will see.
kcode.intercept("core.tool.error", function(err)
  if err.tool == "bash" then
    err.hint = "Try the command with `set -x` to see where it fails."
    return { rewrite = err }
  end
  return nil
end)
Manifest · [settings.<key>]

Plugin settings

Declare typed settings; users set them under [plugins.] in config.toml.

~/.kcode/plugins/journal/

Checked with kcode plugins validate before publishing.

plugin.toml
name = "journal"
version = "0.1.0"
description = "Typed settings declared by the plugin, set by the user in config.toml"
kind = "Lua"

[capabilities]
hooks = true
kv = true

[settings.folder]
type = "string"
default = "notes"
description = "Directory the journal is written to, relative to the project"

[settings.max_entries]
type = "integer"
default = 200
description = "Entries to keep before the oldest are dropped"

[settings.tone]
type = "enum"
options = ["plain", "cheerful"]
default = "plain"
description = "How the summaries read"
main.lua
-- Values come from [plugins.journal] in config.toml, falling back to the defaults above.
local folder = kcode.settings.get("folder")
local limit = kcode.settings.get("max_entries")
local tone = kcode.settings.get("tone")

kcode.on_event("core.app.turn-finished", function(ev)
  local entries = tonumber(kcode.kv_get("entries") or "0") + 1
  if entries > limit then entries = limit end
  kcode.kv_set("entries", tostring(entries))
  kcode.log(string.format("journal: %s/%d entries (%s)", folder, entries, tone))
end)
config.toml
# ~/.kcode/config.toml — the user's side of the same settings
[plugins.journal]
folder = "docs/journal"
max_entries = 50
tone = "cheerful"
Capabilities · net.http · fs.read

Network and files

Scoped grants: which hosts a plugin may fetch, which paths it may read.

~/.kcode/plugins/ci-status/

Checked with kcode plugins validate before publishing.

plugin.toml
name = "ci-status"
version = "0.1.0"
description = "Scoped grants: which hosts a plugin may fetch, which paths it may read"
kind = "Lua"

[capabilities]
"net.http" = ["api.github.com"]
"fs.read" = [".github/workflows/*.yml"]
notify = true
main.lua
-- fetch is a host-mediated GET; a host outside the grant raises an error.
kcode.register_tool("ci_status", "Latest workflow run status for this repo", function(args)
  local repo = args.repo or "owner/name"
  local url = "https://api.github.com/repos/" .. repo .. "/actions/runs?per_page=1"
  local res = kcode.fetch(url)
  if res.status ~= 200 then
    return { error = "GitHub answered " .. res.status }
  end
  local conclusion = res.body:match('"conclusion"%s*:%s*"([%w_]+)"') or "unknown"
  return { repo = repo, conclusion = conclusion }
end)

-- fs_list takes a path prefix and only sees paths inside the granted globs.
kcode.register_tool("workflows", "List the workflow files in this repo", function()
  local files = {}
  for _, entry in ipairs(kcode.fs_list(".github/workflows")) do
    files[#files + 1] = entry.path
  end
  return { files = files }
end)
Plugin runtimes · Wasm

A Wasm plugin

The same manifest with kind = "Wasm"; the module implements register() and invoke().

~/.kcode/plugins/wasmy/

Checked with a parse pass before publishing.

plugin.toml
name = "wasmy"
version = "0.1.0"
description = "The same manifest with a Wasm entry point"
kind = "Wasm"

[capabilities]
kv = true
notify = true
main.wasm
;; wasmy — a kcode Wasm plugin. This starter is WAT text (kcode compiles
;; WAT or binary .wasm); swap in a compiled artifact from Rust/C/AssemblyScript
;; for real work. Full ABI: libs/plugin-runtime-wasm/spec.md — exports
;; memory/__alloc/__free/register/invoke; imports kcode.register_tool,
;; kcode.set_invoke_result, and the invoke-only kv_set/kv_get/kv_delete/kv_list.
(module
  (import "kcode" "register_tool"
    (func $register_tool (param i32 i32 i32 i32)))
  (import "kcode" "set_invoke_result"
    (func $set_invoke_result (param i32 i32 i32)))

  (memory (export "memory") 1)
  (global $bump (mut i32) (i32.const 1024))

  (data (i32.const 0) "wasmy_echo")
  (data (i32.const 64) "Echoes args back unchanged")

  (func $alloc (export "__alloc") (param $len i32) (result i32)
    (local $ptr i32)
    global.get $bump
    local.set $ptr
    global.get $bump
    local.get $len
    i32.add
    global.set $bump
    local.get $ptr)

  (func (export "__free") (param i32 i32))

  (func (export "register")
    i32.const 0
    i32.const 10
    i32.const 64
    i32.const 26
    call $register_tool)

  (func (export "invoke")
    (param $np i32) (param $nl i32) (param $ap i32) (param $al i32)
    (local $rp i32)
    local.get $al
    call $alloc
    local.set $rp
    local.get $rp
    local.get $ap
    local.get $al
    memory.copy
    local.get $rp
    local.get $al
    i32.const 0
    call $set_invoke_result))
README.txt
kcode plugins scaffold wasmy --kind wasm

writes plugin.toml, main.wasm, and a command and a skill to edit. The starter
main.wasm is WAT text (wasmtime reads text or binary) over the documented
linear-memory ABI; the module exports:

  register()        -> a JSON tool manifest the host reads from linear memory
  invoke(ptr, len)  -> receives the tool call as JSON, returns the result as JSON

Host imports mirror the Lua surface where a grant exists: kcode.kv_get / kv_set /
kv_delete / kv_list, kcode.notify, kcode.fetch, kcode.fs_read, kcode.inject.

Build your own with any toolchain that emits core Wasm (wat2wasm, Rust with
--target wasm32-unknown-unknown, AssemblyScript), replace main.wasm, and validate:

  kcode plugins validate ~/.kcode/plugins/wasmy
Plugin runtimes · MCP

MCP servers

Claude-Code-compatible .mcp.json; each server's tools register as ..

<project>/.mcp.json

Checked with a parse pass before publishing.

.mcp.json
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/code/app"]
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}" }
    }
  }
}
README.txt
Put .mcp.json at the project root (or ~/.kcode/mcp.json for every project).
Each server's tools register as <server>.<tool>, so the filesystem server's
read_file becomes filesystem.read_file in /tools and in permission rules.

Stdio servers connect when the session spawns. From the shell:

  kcode mcp list
  kcode mcp add docs https://mcp.example.com/sse
  kcode mcp reconnect github --session "release"

An untrusted project contributes no .mcp.json at all.
Plugin runtimes · Content

A content bundle

No code: commands, skills, agents, output styles, and themes in one directory.

~/.kcode/plugins/house-style/

Checked with kcode plugins validate before publishing.

plugin.toml
name = "house-style"
version = "0.1.0"
description = "No code: commands, skills, agents, output styles, and themes in one bundle"
kind = "Content"
agents/docs-writer.md
---
name: docs-writer
description: Writes and edits documentation only
tools: [fs_read, fs_edit, grep]
permission_mode: accept-edits
---
You write documentation. Keep sentences short. Never edit files outside docs/.
commands/standup.md
---
description: Write today's standup note from the git log
---
Read the commits since yesterday morning and write a three-line standup note:
done, doing, blocked.
output-styles/brief.md
---
name: brief
description: Three sentences, then stop
---
Answer in at most three sentences. Prefer a command or a file path over an explanation.
skills/changelog.md
---
name: changelog
description: How this team writes changelog entries
---
One line per change, present tense, user-facing wording, no ticket numbers in the text.
themes/ember.ron
(
  extends: "wingman-dark",
  colors: (
    background: (dark: "#0b0806", light: "#fff8ef"),
  ),
  type_scale: (mono: (size: 13.0)),
)
Project config · [[hooks]]

Project hooks

Automation without a plugin: on a file change, a schedule, or a turn boundary, run a skill or inject a prompt.

<project>/.kcode/config.toml

Checked with a parse pass before publishing.

config.toml
# <project>/.kcode/config.toml
# on: fs:file-changed · timer:cron · app:turn-finished
#     worktree:create · webhook:<name>

[[hooks]]
name = "retest"
on = "fs:file-changed"
filter = "src/**/*.rs"
prompt = "A source file changed. Run its tests and report failures."
concurrency = "replace"   # drop | queue | replace | parallel

[[hooks]]
name = "morning-review"
on = "timer:cron"
schedule = "0 0 9 * * 1-5"
run = "/review"
session = "title:review"  # active | new | title:<t>

[[hooks]]
name = "after-turn"
on = "app:turn-finished"
prompt = "If you changed files this turn, run the formatter."
enabled = true
CLI · kcode trigger · [[hooks]] webhook

Webhooks and kcode trigger

Fire a turn from CI or cron, and POST a fire record to any URL.

<project>/.kcode/config.toml

Checked with a parse pass before publishing.

ci.sh
#!/usr/bin/env bash
# From CI, cron, or a shell: wake the session with a named trigger and some text.
kcode trigger deploy-done "Build 812 is live on staging" \
  --project ~/code/app \
  --session "release"
config.toml
# <project>/.kcode/config.toml
[[hooks]]
name = "deploy-done"
on = "webhook:deploy-done"
prompt = "A deploy finished. Check the health endpoint and summarise the release."
session = "title:release"

# Also POST a fire record to an external URL when this hook runs.
[[hooks]]
name = "notify-chat"
on = "app:turn-finished"
webhook = "https://hooks.example.com/kcode"
System prompt · ~/.kcode/prompt/

Prompt section overrides

Replace one section of the system prompt with your own markdown. No recompile.

~/.kcode/prompt/style.md

Reference text; nothing to validate.

README.txt
Drop a markdown file in ~/.kcode/prompt/ named after the section it replaces:

  identity.md   tools.md   style.md   rich-output.md
  memory.md     recall.md  control-plane.md

The file replaces that section of the built-in system prompt at the next session
start. Remove it to get the default back. No rebuild, no plugin.
style.md
Answer in plain English. One idea per sentence. No filler.
When you are unsure, say what you checked and what you did not.
Prefer a command or a file path over a paragraph.
Themes · extends

A theme

Start from a bundled theme and change only the leaves you want.

~/.kcode/themes/ember.ron

Reference text; nothing to validate.

ember.ron
(
  extends: "wingman-dark",
  colors: (
    background: (dark: "#0b0806", light: "#fff8ef"),
  ),
  type_scale: (mono: (size: 13.0)),
)
README.txt
Save as ~/.kcode/themes/ember.ron (JSON works too), then /theme ember.
`extends` starts from a bundled theme (wingman-dark, wingman-light,
wingman-high-contrast); you override only the leaves you name. The token contract
is on the Themes reference page.
Config · [keymap]

Rebinding keys

Every app shortcut is data. Rebind, add a second chord, or unbind.

~/.kcode/config.toml

Checked with a parse pass before publishing.

config.toml
# ~/.kcode/config.toml — every app chord is data.
[keymap]
new_session = "Cmd+Shift+N"             # rebind
command_palette = ["Cmd+P", "Cmd+K"]    # two chords
mission_control = "Cmd+M"
jump_to_session_1 = "Cmd+1"
notifications = false                   # unbind
text_size_increase = "Cmd+Equal"
text_size_decrease = "Cmd+Minus"
text_size_reset = "Cmd+0"
Project config · policy.toml

Project policy

Pin which plugins a project refuses and which it requires.

<project>/.kcode/policy.toml

Checked with a parse pass before publishing.

policy.toml
# <project>/.kcode/policy.toml — committed with the repo.
[plugins]
# Refused on every load path, including their bundled commands, skills, and themes.
blocklist = ["safe-bash", "shorthand"]
# Loading fails loudly when one of these is missing.
required = ["no-force-push"]
Config · KCODE_* · --config

Config layering

Defaults, user, project, a launch profile, then environment variables. Print the whole surface.

shell

Checked with a parse pass before publishing.

config.toml
# Layers, lowest first:
#   built-in defaults
#   ~/.kcode/config.toml            (this file)
#   <project>/.kcode/config.toml
#   --config <path>                 (one launch)
#   KCODE_* environment variables
#   command-line flags
default_model = "claude-sonnet-5"
default_effort = "medium"

[session_presenter]
grid_max = 9
env.sh
# Environment variables map to keys; a double underscore descends into a table.
export KCODE_DEFAULT_MODEL="claude-opus-5"
export KCODE_SESSION_PRESENTER__GRID_MAX=6

# A launch profile on top of user + project config, for one run:
kcode --config ./profiles/ci.toml

# Print the whole surface with defaults, to see what a key is called:
kcode config defaults
CLI · kcode -p

Headless with ndjson

One prompt, one process, one JSON line per event. Built for scripts and CI.

shell

Reference text; nothing to validate.

functions.json
{
  "type": "object",
  "required": ["functions"],
  "properties": {
    "functions": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["name", "line"],
        "properties": {
          "name": { "type": "string" },
          "line": { "type": "integer" },
          "doc": { "type": "string" }
        }
      }
    }
  }
}
run.sh
# One prompt, one process. text | json | ndjson.
kcode -p "List the failing tests and the file each one lives in" \
  --output-format ndjson \
  --permission-mode plan

# Constrain the reply to a JSON schema.
kcode -p "Extract the public functions in src/lib.rs" \
  --output-format json \
  --json-schema ./schemas/functions.json

# Continue the most recent session without the window.
kcode -c --print
Config · curated keys

Settings that change behavior

The handful of keys that change how kcode looks, asks, remembers, and updates.

~/.kcode/config.toml

Checked with a parse pass before publishing.

config.toml
# ~/.kcode/config.toml — the keys that change how kcode looks, asks,
# remembers, and updates.
theme = "wingman-dark"
default_output_style = "terse"
default_effort = "high"

[session_presenter]
ui_scale = 1.1
default_layout = "grid"
grid_max = 6

[session_presenter.shader]
backdrop = "shader"
background = "dot-matrix"
wordmark = "kcode"
reactive = true
per_tile = false

[session_presenter.approach]
enabled = true
lift_pct = 4
glow_pct = 40

[session_presenter.peek]
enabled = true
dwell_ms = 900

[compaction]
auto_compact_tokens = 120000

[memory]
auto_recall = true

[memory_consolidator]
enabled = true

[judge_orchestration]
enabled = false

[subagent]
advisor_enabled = true

[updater]
default_channel = "stable"

[session_daemon]
keep_awake = true
CLI · kcode plugins validate

Validate and ship

The same checks a session load runs, as a command you can put in CI.

shell

Reference text; nothing to validate.

ship.sh
# The same checks a session load runs: manifest shape, capability keys, the
# retired flat `permissions` list, an entry point that loads.
kcode plugins validate ~/.kcode/plugins/nightly-review

# Project-scoped plugins travel with the repo; validate them in CI.
for dir in .kcode/plugins/*/; do
  kcode plugins validate "$dir" || exit 1
done

# See what a session actually loaded, and each plugin's grants.
#   /plugins