ktx
Install and configure ktx, the open-source context layer for data agents.
Use this skill when a user wants an agent to add ktx to a project, connect
data sources, build initial context, install agent integration, or troubleshoot
a local ktx setup.
Operating rules
- Act autonomously when the user asks you to install or configure ktx.
The non-interactive scripted flow below is the canonical path — bare
ktx setup is interactive (clack prompts) and an agent cannot drive it.
- Setup's non-interactive flags are intentionally hidden from
--help. Use the
flags listed below; verify uncommon flags against the docs at
https://docs.kaelio.com/ktx/ or this skill — not against --help output.
- Ask only for values you cannot infer: project directory, connection targets,
credentials, account identifiers, and source selections.
- Prefer
file:/abs/path secret refs over env:VAR_NAME. env: refs are
re-resolved against the process environment on every ktx run, so a var
exported only in the setup shell is gone when ktx ingest or ktx mcp start
runs later — the secret silently resolves to empty and the connection fails.
file: refs read from disk and survive across shells. The same caveat
applies to --*-api-key-env flags: the named var must be present in every
shell that runs ktx, including the ktx mcp daemon's environment.
- A literal database URL is safe to pass —
ktx setup auto-externalizes it
into .ktx/secrets/<id>-url and rewrites ktx.yaml to a file: ref (see
workflow step 2). Source credential refs are not auto-externalized: write
the secret to a file under .ktx/secrets/ (chmod 600) and pass a file:
ref. Never ask the user to paste a secret when a file: or env: ref works.
- Do not commit
.ktx/secrets/*.
- Print each command you run and its result.
- Setup and ingest can run for many minutes (LLM-heavy source ingests take the
longest), and from the outside a slow step looks identical to a stuck one.
Don't go silent: say what's about to run and that it may take a while, then
post brief progress/liveness updates while it runs (see step 4) so the user
never has to wonder whether it stalled — otherwise they may kill it mid-run.
- If a command fails, identify the cause and change something before retrying.
Gather inputs once
Before invoking ktx setup, collect in one round:
- Project directory (default: current working directory).
- LLM backend and key strategy. In
--no-input mode the CLI defaults to
anthropic and requires an API key. When the user is inside Claude
Code, pass --llm-backend claude-code explicitly; otherwise pass
--llm-backend anthropic --anthropic-api-key-env ANTHROPIC_API_KEY.
- Embedding backend (
sentence-transformers is the local default and needs
no key; use openai only if the user already has a key, then pass
--embedding-api-key-env OPENAI_API_KEY).
- Database: driver, connection id, URL (or
env: / file: ref), and one or
more schemas.
- Optional context sources (dbt, Metabase, Looker, LookML, MetricFlow,
Notion). Add each one with a follow-up
ktx setup --source … run (see
Add context sources); use --skip-sources only
when the user has none.
Do not discover these inputs across multiple setup runs.
Install workflow
Detect the install path. If the working directory contains
packages/cli/dist/bin.js or pnpm-workspace.yaml referencing
@kaelio/ktx you are inside the ktx monorepo — build and link the
local CLI with pnpm and do not run npm install -g. Otherwise:
node --version # require >= 22; stop and ask the user if older
ktx --version || npm install -g @kaelio/ktx
Run scripted setup (canonical path):
ktx setup --no-input --yes \
--project-dir <path> \
--llm-backend claude-code \
--embedding-backend sentence-transformers \
--database <driver> --database-connection-id <id> \
--database-url '<raw-url | file:/abs/path>' \
--database-schema <schema> \
--skip-sources \
--skip-agents
--database-schema is required for scope-bearing drivers (Postgres,
MySQL, ClickHouse, SQL Server, BigQuery, Snowflake) in --no-input:
setup fails fast without it unless the connection already has scope in
ktx.yaml. SQLite needs no scope.
- Configure one new database connection per setup invocation. For multiple
connections, rerun setup once per connection.
- Pasting a literal
--database-url is safe: the CLI relocates the URL
into .ktx/secrets/<connection-id>-url and rewrites ktx.yaml to a
file: ref automatically.
ktx setup runs agent integration as its last step. In --no-input
mode with neither --target nor --skip-agents, that step has no input,
prints Run in a TTY, or pass --target <target>., and the command exits
non-zero even though every database/LLM/embedding step succeeded. Pass
--skip-agents to defer agents to step 5 (as above), or --target <agent>
to install them inline and exit 0. Judge data-layer success from
ktx status, not from this exit code.
Resumability and --skip-*. Re-running ktx setup against an existing
project resumes its config. Use --skip-llm, --skip-databases,
--skip-sources, or --skip-embeddings to leave a slice unconfigured but
let the rest complete instead of aborting on the first failure. When
resuming an existing project to change one slice (e.g. only LLM), still
pass the database flags from the previous run — setup validates current
flags, not persisted ktx.yaml state.
Build context if setup did not already complete one:
ktx ingest <connection-id> --no-input
ktx ingest always builds enriched context and requires a configured model
and embeddings (set during setup); a database connection without them fails
with an enrichment-readiness error. Note: ktx ingest rejects --yes
together with --no-input (Choose only one runtime install mode);
ktx setup accepts both. Use --no-input only for ingest.
Ingest one connection at a time. It can run for many minutes with no
stdout until it exits (LLM-heavy sources like Metabase are the slowest), so
don't assume it hung, and don't pipe it through tail/head — that buffers
all output to the end, so run it raw. Tell the user up front that the step is
slow, then keep them posted instead of blocking silently: run the ingest in
the background and poll for liveness every minute or so, reporting a one-line
update each time (which connection, roughly how long it's been running, and
that .ktx files are still changing) so a long run never looks stuck:
find <path>/.ktx/worktrees <path>/.ktx/ingest-transcripts -type f -mmin -3
On success, the Ingest finished summary table shows done in the
Source ingest and Memory update columns with no Failed sources:
section.
Install agent integration:
ktx setup --agents --target <claude-code|claude-desktop|codex|cursor|opencode|universal>
ktx mcp start --project-dir <path>
Agent integration is not usable until ktx mcp start is running. The
--agents step prints this requirement as Required before using agents.
Fall back to bare ktx setup only when a human is at the keyboard —
it uses interactive prompts an agent cannot answer.
Add context sources
Context sources (dbt, Metabase, Looker, LookML, MetricFlow, Notion) are added
one at a time — --source is not repeatable, so run ktx setup once per
source. Source setup is resumable against an existing project: pass
--skip-databases --skip-llm --skip-embeddings --skip-agents so only the source
is configured (the trailing agent step otherwise fails the run — see install
step 2). Map Metabase, Looker, and LookML to an existing database connection
with --source-warehouse-connection-id <db-connection-id> (required for those).
dbt ignores --source-warehouse-connection-id — it maps to the warehouse by
table name — so omit it for dbt. Use file:/abs/path refs for keys and tokens
(see the secrets rule above); env: refs must be exported in every later ktx
shell.
# dbt — pick exactly one of --source-path (local) or --source-git-url (remote).
# No --source-warehouse-connection-id: dbt maps to the warehouse by table name.
ktx setup --no-input --yes --skip-databases --skip-llm --skip-embeddings --skip-agents \
--source dbt --source-connection-id <id> \
--source-git-url <url> --source-branch <branch>
# Metabase
ktx setup --no-input --yes --skip-databases --skip-llm --skip-embeddings --skip-agents \
--source metabase --source-connection-id <id> \
--source-url <url> --source-api-key-ref file:/abs/path/metabase-api-key \
--source-warehouse-connection-id <db-connection-id> \
--metabase-database-id <metabase-db-id>
# Notion
ktx setup --no-input --yes --skip-databases --skip-llm --skip-embeddings --skip-agents \
--source notion --source-connection-id <id> \
--source-auth-token-ref file:/abs/path/notion-token \
--notion-crawl-mode selected_roots --notion-root-page-id <page-id>
Notes:
--metabase-database-id is the numeric id of the warehouse inside
Metabase (not the ktx connection id). Discover it from the Metabase API
(GET /api/database) or UI if the user doesn't know it.
--notion-crawl-mode selected_roots requires at least one
--notion-root-page-id (repeatable); use all_accessible to crawl
everything the token can see.
- After adding sources, ingest each new connection so its context is queryable:
ktx ingest <source-connection-id> --no-input.
Files to inspect
ktx.yaml: project configuration.
.ktx/secrets/*: local secret files. Never commit them.
semantic-layer/<connection-id>/*.yaml: semantic sources for SQL
compilation.
wiki/**/*.md: project context pages for agents.
.claude/skills/ktx/, .agents/skills/ktx/, .cursor/rules/ktx.mdc, and
.opencode/commands/ktx.md: generated agent integration files.
Verification
After setup, run:
ktx connection test <connection-id>
ktx status --json --no-input
ktx sl --output plain # lists compiled semantic sources; `ktx sl` has no --no-input
Judge readiness from ktx status --json fields, not the exit code.
ktx status exits 1 whenever the LLM is none (verdict: "blocked"), even
when embeddings and every database connection are healthy. Treat success as:
verdict: "ready" at the top of the JSON, and
- every
connections[].status === "ok" (other levels: warn, fail,
skipped), and
- every
ktx connection test <id> exited 0, and
- for each ingested source,
localStats.semanticLayer[].sourceCount > 0 and
localStats.wikiPages[].count > 0 — these confirm the source actually
produced context. Do not rely on localStats.ingest.perConnection to
confirm source ingests: it reflects only completed warehouse ingest reports
and under-reports (often lists just the warehouse connection).
If the LLM is intentionally left unconfigured, verdict is blocked and the
exit is non-zero by design — that is still a usable context layer, so report it
as "ready, LLM optional" and judge the data layer by the connection and
localStats fields above rather than retrying setup.
Troubleshooting
For known failure signatures (invalid ELF header,
Native CLI binary for <plat> not found, Missing Anthropic API key,
claude-code probe failure, ktx cannot work without a database on resume,
Run in a TTY, or pass --target <target>. with a misleading exit 1, and a
secret that resolves empty only during ktx ingest/ktx mcp), see
troubleshooting.md.
Final report
End setup work with a concise report:
ktx SETUP COMPLETE
Project: <path>
LLM: <backend> / <model>
Embeddings: <backend> / <model>
Connections: <name> (<driver>) status=<ok|warn|fail>
Sources: <list or none>
Verdict: <ready|needs action>
Next:
1. <copy-pasteable command or action>
2. <copy-pasteable command or action>
RESULT: PASS
1---2name: ktx3description: Installs and configures ktx, the open-source context layer for data agents — runs ktx setup non-interactively with hidden CLI flags, configures database connections and embeddings, installs agent integration, and verifies readiness. Use when the user asks an agent to add ktx to a project, connect data sources, install agent rules, ingest schema, or troubleshoot a local ktx install.4---5
6# ktx
7
8Install and configure **ktx**, the open-source context layer for data agents.
9Use this skill when a user wants an agent to add **ktx** to a project, connect
10data sources, build initial context, install agent integration, or troubleshoot
11a local **ktx** setup.
12
13## Operating rules
14
15- Act autonomously when the user asks you to install or configure **ktx**.
16 The non-interactive scripted flow below is the canonical path — bare
17 `ktx setup` is interactive (clack prompts) and an agent cannot drive it.
18- Setup's non-interactive flags are intentionally hidden from `--help`. Use the
19 flags listed below; verify uncommon flags against the docs at
20 `https://docs.kaelio.com/ktx/` or this skill — not against `--help` output.
21- Ask only for values you cannot infer: project directory, connection targets,
22 credentials, account identifiers, and source selections.
23- Prefer `file:/abs/path` secret refs over `env:VAR_NAME`. `env:` refs are
24 re-resolved against the process environment on **every** `ktx` run, so a var
25 exported only in the setup shell is gone when `ktx ingest` or `ktx mcp start`
26 runs later — the secret silently resolves to empty and the connection fails.
27 `file:` refs read from disk and survive across shells. The same caveat
28 applies to `--*-api-key-env` flags: the named var must be present in every
29 shell that runs `ktx`, including the `ktx mcp` daemon's environment.
30- A literal database URL is safe to pass — `ktx setup` auto-externalizes it
31 into `.ktx/secrets/<id>-url` and rewrites `ktx.yaml` to a `file:` ref (see
32 workflow step 2). Source credential refs are **not** auto-externalized: write
33 the secret to a file under `.ktx/secrets/` (`chmod 600`) and pass a `file:`
34 ref. Never ask the user to paste a secret when a `file:` or `env:` ref works.
35- Do not commit `.ktx/secrets/*`.
36- Print each command you run and its result.
37- Setup and ingest can run for many minutes (LLM-heavy source ingests take the
38 longest), and from the outside a slow step looks identical to a stuck one.
39 Don't go silent: say what's about to run and that it may take a while, then
40 post brief progress/liveness updates while it runs (see step 4) so the user
41 never has to wonder whether it stalled — otherwise they may kill it mid-run.
42- If a command fails, identify the cause and change something before retrying.
43
44## Gather inputs once
45
46Before invoking `ktx setup`, collect in one round:
47
481. Project directory (default: current working directory).
492. LLM backend and key strategy. In `--no-input` mode the CLI defaults to
50 `anthropic` and **requires an API key**. When the user is inside Claude
51 Code, pass `--llm-backend claude-code` explicitly; otherwise pass
52 `--llm-backend anthropic --anthropic-api-key-env ANTHROPIC_API_KEY`.
533. Embedding backend (`sentence-transformers` is the local default and needs
54 no key; use `openai` only if the user already has a key, then pass
55 `--embedding-api-key-env OPENAI_API_KEY`).
564. Database: driver, connection id, URL (or `env:` / `file:` ref), and one or
57 more schemas.
585. Optional context sources (dbt, Metabase, Looker, LookML, MetricFlow,
59 Notion). Add each one with a follow-up `ktx setup --source …` run (see
60 [Add context sources](#add-context-sources)); use `--skip-sources` only
61 when the user has none.
62
63Do not discover these inputs across multiple setup runs.
64
65## Install workflow
66
671. **Detect the install path.** If the working directory contains
68 `packages/cli/dist/bin.js` or `pnpm-workspace.yaml` referencing
69 `@kaelio/ktx` you are inside the **ktx** monorepo — build and link the
70 local CLI with `pnpm` and do **not** run `npm install -g`. Otherwise:
71
72 ```bash
73 node --version # require >= 22; stop and ask the user if older
74 ktx --version || npm install -g @kaelio/ktx
75 ```
76
772. **Run scripted setup** (canonical path):
78
79 ```bash
80 ktx setup --no-input --yes \
81 --project-dir <path> \
82 --llm-backend claude-code \
83 --embedding-backend sentence-transformers \
84 --database <driver> --database-connection-id <id> \
85 --database-url '<raw-url | file:/abs/path>' \
86 --database-schema <schema> \
87 --skip-sources \
88 --skip-agents
89 ```
90
91 - `--database-schema` is required for scope-bearing drivers (Postgres,
92 MySQL, ClickHouse, SQL Server, BigQuery, Snowflake) in `--no-input`:
93 setup fails fast without it unless the connection already has scope in
94 `ktx.yaml`. SQLite needs no scope.
95 - Configure one new database connection per setup invocation. For multiple
96 connections, rerun setup once per connection.
97 - Pasting a literal `--database-url` is safe: the CLI relocates the URL
98 into `.ktx/secrets/<connection-id>-url` and rewrites `ktx.yaml` to a
99 `file:` ref automatically.
100 - `ktx setup` runs agent integration as its **last** step. In `--no-input`
101 mode with neither `--target` nor `--skip-agents`, that step has no input,
102 prints `Run in a TTY, or pass --target <target>.`, and the command exits
103 non-zero **even though every database/LLM/embedding step succeeded**. Pass
104 `--skip-agents` to defer agents to step 5 (as above), or `--target <agent>`
105 to install them inline and exit 0. Judge data-layer success from
106 `ktx status`, not from this exit code.
107
1083. **Resumability and `--skip-*`.** Re-running `ktx setup` against an existing
109 project resumes its config. Use `--skip-llm`, `--skip-databases`,
110 `--skip-sources`, or `--skip-embeddings` to leave a slice unconfigured but
111 let the rest complete instead of aborting on the first failure. **When
112 resuming an existing project to change one slice (e.g. only LLM), still
113 pass the database flags from the previous run** — setup validates current
114 flags, not persisted `ktx.yaml` state.
115
1164. **Build context** if setup did not already complete one:
117
118 ```bash
119 ktx ingest <connection-id> --no-input
120 ```
121
122 `ktx ingest` always builds enriched context and requires a configured model
123 and embeddings (set during setup); a database connection without them fails
124 with an enrichment-readiness error. Note: `ktx ingest` rejects `--yes`
125 together with `--no-input` (*Choose only one runtime install mode*);
126 `ktx setup` accepts both. Use `--no-input` only for ingest.
127
128 Ingest one connection at a time. It can run for many minutes with **no
129 stdout** until it exits (LLM-heavy sources like Metabase are the slowest), so
130 don't assume it hung, and don't pipe it through `tail`/`head` — that buffers
131 all output to the end, so run it raw. Tell the user up front that the step is
132 slow, then keep them posted instead of blocking silently: run the ingest in
133 the background and poll for liveness every minute or so, reporting a one-line
134 update each time (which connection, roughly how long it's been running, and
135 that `.ktx` files are still changing) so a long run never looks stuck:
136
137 ```bash
138 find <path>/.ktx/worktrees <path>/.ktx/ingest-transcripts -type f -mmin -3
139 ```
140
141 On success, the `Ingest finished` summary table shows `done` in the
142 `Source ingest` and `Memory update` columns with no `Failed sources:`
143 section.
144
1455. **Install agent integration:**
146
147 ```bash
148 ktx setup --agents --target <claude-code|claude-desktop|codex|cursor|opencode|universal>
149 ktx mcp start --project-dir <path>
150 ```
151
152 Agent integration is **not usable until `ktx mcp start` is running**. The
153 `--agents` step prints this requirement as `Required before using agents`.
154
1556. **Fall back to bare `ktx setup` only when a human is at the keyboard** —
156 it uses interactive prompts an agent cannot answer.
157
158## Add context sources
159
160Context sources (dbt, Metabase, Looker, LookML, MetricFlow, Notion) are added
161**one at a time** — `--source` is not repeatable, so run `ktx setup` once per
162source. Source setup is resumable against an existing project: pass
163`--skip-databases --skip-llm --skip-embeddings --skip-agents` so only the source
164is configured (the trailing agent step otherwise fails the run — see install
165step 2). Map Metabase, Looker, and LookML to an existing database connection
166with `--source-warehouse-connection-id <db-connection-id>` (required for those).
167**dbt ignores `--source-warehouse-connection-id`** — it maps to the warehouse by
168table name — so omit it for dbt. Use `file:/abs/path` refs for keys and tokens
169(see the secrets rule above); `env:` refs must be exported in every later `ktx`
170shell.
171
172```bash
173# dbt — pick exactly one of --source-path (local) or --source-git-url (remote).
174# No --source-warehouse-connection-id: dbt maps to the warehouse by table name.
175ktx setup --no-input --yes --skip-databases --skip-llm --skip-embeddings --skip-agents \
176 --source dbt --source-connection-id <id> \
177 --source-git-url <url> --source-branch <branch>
178
179# Metabase
180ktx setup --no-input --yes --skip-databases --skip-llm --skip-embeddings --skip-agents \
181 --source metabase --source-connection-id <id> \
182 --source-url <url> --source-api-key-ref file:/abs/path/metabase-api-key \
183 --source-warehouse-connection-id <db-connection-id> \
184 --metabase-database-id <metabase-db-id>
185
186# Notion
187ktx setup --no-input --yes --skip-databases --skip-llm --skip-embeddings --skip-agents \
188 --source notion --source-connection-id <id> \
189 --source-auth-token-ref file:/abs/path/notion-token \
190 --notion-crawl-mode selected_roots --notion-root-page-id <page-id>
191```
192
193Notes:
194
195- `--metabase-database-id` is the **numeric id of the warehouse inside
196 Metabase** (not the ktx connection id). Discover it from the Metabase API
197 (`GET /api/database`) or UI if the user doesn't know it.
198- `--notion-crawl-mode selected_roots` requires at least one
199 `--notion-root-page-id` (repeatable); use `all_accessible` to crawl
200 everything the token can see.
201- After adding sources, ingest each new connection so its context is queryable:
202 `ktx ingest <source-connection-id> --no-input`.
203
204## Files to inspect
205
206- `ktx.yaml`: project configuration.
207- `.ktx/secrets/*`: local secret files. Never commit them.
208- `semantic-layer/<connection-id>/*.yaml`: semantic sources for SQL
209 compilation.
210- `wiki/**/*.md`: project context pages for agents.
211- `.claude/skills/ktx/`, `.agents/skills/ktx/`, `.cursor/rules/ktx.mdc`, and
212 `.opencode/commands/ktx.md`: generated agent integration files.
213
214## Verification
215
216After setup, run:
217
218```bash
219ktx connection test <connection-id>
220ktx status --json --no-input
221ktx sl --output plain # lists compiled semantic sources; `ktx sl` has no --no-input
222```
223
224**Judge readiness from `ktx status --json` fields, not the exit code.**
225`ktx status` exits 1 whenever the LLM is `none` (`verdict: "blocked"`), even
226when embeddings and every database connection are healthy. Treat success as:
227
228- `verdict: "ready"` at the top of the JSON, and
229- every `connections[].status === "ok"` (other levels: `warn`, `fail`,
230 `skipped`), and
231- every `ktx connection test <id>` exited 0, and
232- for each ingested source, `localStats.semanticLayer[].sourceCount > 0` and
233 `localStats.wikiPages[].count > 0` — these confirm the source actually
234 produced context. Do **not** rely on `localStats.ingest.perConnection` to
235 confirm source ingests: it reflects only completed warehouse ingest reports
236 and under-reports (often lists just the warehouse connection).
237
238If the LLM is intentionally left unconfigured, `verdict` is `blocked` and the
239exit is non-zero by design — that is still a usable context layer, so report it
240as "ready, LLM optional" and judge the data layer by the connection and
241`localStats` fields above rather than retrying setup.
242
243## Troubleshooting
244
245For known failure signatures (`invalid ELF header`,
246`Native CLI binary for <plat> not found`, `Missing Anthropic API key`,
247`claude-code` probe failure, `ktx cannot work without a database` on resume,
248`Run in a TTY, or pass --target <target>.` with a misleading exit 1, and a
249secret that resolves empty only during `ktx ingest`/`ktx mcp`), see
250[troubleshooting.md](troubleshooting.md).
251
252## Final report
253
254End setup work with a concise report:
255
256```text
257ktx SETUP COMPLETE
258
259Project: <path>
260LLM: <backend> / <model>
261Embeddings: <backend> / <model>
262Connections: <name> (<driver>) status=<ok|warn|fail>
263Sources: <list or none>
264Verdict: <ready|needs action>
265
266Next:
2671. <copy-pasteable command or action>
2682. <copy-pasteable command or action>
269
270RESULT: PASS
271```