AI Recipes
Everything below is backed by lib-ai. All commands share one client surface: pick a provider, pick a model (or take the default), pass text via args or stdin, get text on stdout (errors and “side” info on stderr, so pipes stay clean).
Providers
Section titled “Providers”kis ai providers # everything supported + capabilitieskis ai models -p anthropic # a provider's model catalog (+ --json)Providers: ollama (default) | openai | anthropic | gemini | groq |
xai | vllm | llamacpp | tei | whispercpp | deepgram | stability |
cohere | gateway (AI Gateway).
Local engines (ollama, vllm, llamacpp) have no static catalog — pass -m
or rely on the per-command defaults.
Credentials
Section titled “Credentials”Resolution order: --api-key flag → provider env var (OPENAI_API_KEY,
ANTHROPIC_API_KEY, …) → generic KIS_AI_API_KEY. Local providers (ollama,
vllm, llamacpp, tei, whispercpp) need no key.
Common flags (every command)
Section titled “Common flags (every command)”| Flag | Meaning |
|---|---|
-p, --provider | provider (default ollama) |
-m, --model | model id (per-command default otherwise) |
-e, --endpoint | override base URL (e.g. -e http://gpubox:11434) |
-k, --api-key | key (else env) |
--timeout | request timeout seconds (default 120 — raise for big local models) |
--max-retries | retries on retryable errors (default 2) |
-j, --json | machine-readable output |
Through the AI Gateway gateway
Section titled “Through the AI Gateway gateway”Every command works against the platform gateway instead of a direct provider:
kis ai chat -p gateway -e https://ai-gateway.internal/v1 -m fast-chat "hello"Aliases, budgets, caching, fallback and metering then apply server-side.
kis ai chat "what is 2+2?" # one-shot, default modelkis ai chat -p openai -m gpt-4o "summarize this" # explicit provider/modelecho "translate to French: hello" | kis ai chat # stdin is the promptkis ai chat -s "you are terse" "explain monads" # system promptkis ai chat --stream "write a haiku about go" # tokens as they arrivekis ai chat -j "say hi" # full response as JSONkis ai chat --reasoning high -p anthropic "hard puzzle" # extended thinkingkis ai chat -T 0 "deterministic-ish output" # temperatureVision (any image-capable model; -I repeatable, path or URL):
kis ai chat -p ollama -m qwen3-vl:8b -I photo.jpg "what is in this image?"kis ai chat -p openai -m gpt-4o -I a.png -I b.png "compare these"Interactive REPL (streamed, multi-turn):
kis ai chat -ikis ai chat -i --session proj-42 # REPL that persists + resumesSessions
Section titled “Sessions”File-backed conversations that survive across invocations. Stored under
~/.kisai/ai/sessions (override: --sessions-dir or $KIS_AI_SESSIONS_DIR).
kis ai chat --session proj-42 "remember my name is Sam" # creates if absentkis ai chat --session proj-42 "what is my name?" # resumes contextkis ai chat --session proj-42 -I diagram.png "explain" # images persist too
kis ai session list # all conversationskis ai session show proj-42 # transcriptkis ai session send proj-42 "and..." # append a turn (scripting-friendly)kis ai session new # mint a fresh idPrompt files
Section titled “Prompt files”Stop typing long prompts: keep named prompts in YAML and bind variables.
summarize: system: You are a precise summarizer. user: | Summarize in {{words}} words: {{content}}kis ai chat --prompt-file prompts.yaml --prompt summarize \cat report.txt | kis ai chat --prompt-file prompts.yaml --prompt summarize \ --vars words=50 --vars content=@- # @- = read stdin{{var}} placeholders resolve from --vars k=v (@file reads a file, @-
stdin). Unresolved vars error unless --allow-unset. Works identically on
chat, structured, and extract.
Format: the root is a map of name → {system, user} (not a list). The
document gathered from --file/--input/stdin is auto-offered under {{input}}
and {{content}}; an entry with no user: falls back to using the document as
the user message.
Extract-only fields — an entry may carry start:/end: section markers
(emerald-style); extract then sends only the sliced region as {{content}}:
urla_borrower_section_1e: system: Extract the 1e income table. Return JSON only. start: - "1e. Income from Other Sources" end: - "Section 2: Financial Information Assets and Liabilities" user: | Extract every income row from:
{{content}}kis ai extract --prompt-file prompts.yaml --prompt urla_borrower_section_1e \ --file urla.txt --strip-attrs --schema income.jsonStructured output
Section titled “Structured output”Schema-constrained JSON (file path or inline schema). Parse failures
retry-with-error (--repair, default 2).
kis ai structured --schema person.json "John Smith, 42, lives in NYC"echo "$INVOICE" | kis ai structured --schema ./invoice.json -p openai --strictkis ai structured --schema '{"type":"object","properties":{"sentiment":{"enum":["pos","neg"]}}}' \ "loved it"Extraction with consensus
Section titled “Extraction with consensus”Field extraction with optional schema, multi-file input, HTML/hOCR cleanup, section slicing, and N-run majority voting.
# Schema-bound, with consensus votingkis ai extract --schema loan.json --file ./application.txt --runs 5echo "$DOC" | kis ai extract --schema fields.json -p openai --runs 3 --json--runs N extracts independently N times and decides each field by per-field
majority vote, reporting agreement — the cheap way to harden extraction on
small/local models. --json includes the per-field agreement counts.
Temperature defaults to 0.4 when runs > 1 (diversity for the vote).
Schema is optional
Section titled “Schema is optional”With --schema the output conforms to that JSON Schema (provider-native
structured output + validate/repair). Without it, the model returns a
free-form JSON object and the vote runs over whatever keys it emits.
kis ai extract --file note.txt # free-form JSONkis ai extract --schema fields.json --file note.txt # schema-boundInput: files, globs, literal, stdin
Section titled “Input: files, globs, literal, stdin”Input is assembled, in order, from --file (paths/globs, repeatable),
--input (literal text, or @path), positional args, then stdin. Multiple
files are concatenated with a per-file banner.
kis ai extract --schema s.json --file a.txt --file b.txtkis ai extract --schema s.json --file 'pages/*.txt' # globkis ai extract --schema s.json --input @./doc.txt # @ = read fileecho "$DOC" | kis ai extract --schema s.json # stdinClean HTML / hOCR before sending
Section titled “Clean HTML / hOCR before sending”OCR output (hOCR) is mostly bbox/confidence noise. Strip it to save tokens:
# Flatten to plain text (drop ALL tags + attributes)kis ai extract --schema s.json --file 'pages/*.hocr' --strip-html
# Keep the tags (line/word/paragraph structure) but drop noisy attributeskis ai extract --schema s.json --file 'pages/*.hocr' --strip-attrs--strip-html wins if both are set. Stripping only touches input that looks
like markup; plain text passes through untouched.
Section slicing — send only one region
Section titled “Section slicing — send only one region”--start/--end (repeatable, first match wins) send only the text between the
first matching start marker and the following end marker — to disambiguate a
single section of a large document. Case-insensitive; start included, end
excluded; runs after --strip-html/--strip-attrs. An unmatched marker
degrades to doc start/end with a stderr warning.
kis ai extract --file urla.txt \ --start "1e. Income from Other Sources" \ --end "Section 2: Financial Information"The same markers can live on a prompt-file entry as start:/end: (see
Prompt files); --start/--end override them.
Stats footer
Section titled “Stats footer”Like every model-backed command, extract prints a timing + token footer to
stderr (suppress with --stats=false) — handy for confirming stripping/slicing
actually cut the input tokens.
Embeddings
Section titled “Embeddings”kis ai embed "hello world" # vector previewkis ai embed "a" "b" "c" --compare # pairwise cosine matrixprintf "first\nsecond\n" | kis ai embed --json # stdin lines, JSON outkis ai embed -p openai -m text-embedding-3-small "query"Default embed models: ollama nomic-embed-text, openai
text-embedding-3-small, gemini text-embedding-004, cohere embed-v4.0.
Chunking
Section titled “Chunking”Split documents the same way the RAG pipeline does (inspect before you ingest):
kis ai chunk README.md --strategy markdown --max-tokens 512kis ai chunk notes.txt --strategy recursive --overlap 64 --jsonkis ai chunk paper.txt --strategy semantic -p ollama -m nomic-embed-textStrategies: recursive (default) | window | markdown | html |
semantic (embeds sentences to find topic boundaries — uses the
provider/model flags).
Tokens
Section titled “Tokens”kis ai tokens "how many tokens is this?" # heuristic (safe over-estimate)kis ai tokens --file ./prompt.txt -p openai -m gpt-4okis ai tokens --exact -p llamacpp -e http://localhost:8080/v1 --file big.txt--exact uses a llama.cpp server’s native /tokenize for a true count.
Ingest documents into a vector store, then ask questions grounded in them.
The store is picked by --dsn:
| DSN | Backend | Notes |
|---|---|---|
memory: | in-process | default; lives only for ONE command — not for ingest-then-answer |
duckdb:///abs/path.db | DuckDB file | serverless, single file, HNSW-indexed; ideal for local KBs |
pgvector://user:pass@host:5432/db?sslmode=disable | Postgres | production-shaped, shared |
chroma://host:8000 | Chroma | remote |
qdrant://host:6333 | Qdrant | remote |
The local knowledge-base recipe (folder of docs → DuckDB → answers):
# 1) ingest (re-run any time the docs change — replaced + index compacted)kis ai rag ingest --dsn "duckdb:///Users/me/kb.db" --collection docs \ -p ollama --embed-model nomic-embed-text \ --chunk-tokens 512 --overlap 64 \ ~/notes/*.md
# 2) inspect retrieval (no generation)kis ai rag retrieve --dsn "duckdb:///Users/me/kb.db" --collection docs \ -p ollama --embed-model nomic-embed-text --top-k 5 "what did we decide about X?"
# 3) grounded answer (+ cited chunks on stderr)kis ai rag answer --dsn "duckdb:///Users/me/kb.db" --collection docs \ -p ollama --embed-model nomic-embed-text -m qwen3-coder:30b \ --top-k 5 --sources "what did we decide about X?"
# scripting: {answer, sources} as JSONkis ai rag answer ... --json "question" | jq .answerRules that matter:
- Same
--embed-modelfor ingest and query — vectors must come from the same embedder to be comparable. - Absolute paths in duckdb DSNs (
duckdb:///Users/..., three slashes). --dsn="value"or--dsn "value"— a bare=with spaces is a flag parsing trap (the CLI now catches it with an actionable error).- Text formats only (md/txt/csv/code). PDFs/DOCX are read as raw bytes — extract them to text first.
- With no file args,
rag ingestreads one document from stdin. - Index upkeep is automatic: inserts index incrementally; re-ingest/delete trigger HNSW compaction (DuckDB).
Reranking
Section titled “Reranking”Reorder candidate documents by relevance (cohere/tei rerank models):
kis ai rerank -p cohere -m rerank-english-v3.0 \ "best feline pet" "cats are independent" "dogs are loyal" "stocks rose"Reasoning strategies
Section titled “Reasoning strategies”kis ai reason --mode <strategy> — prompt-shaping strategies (one model call)
and iterative algorithms (many calls) from lib-ai:
| Mode | What happens | Key flags |
|---|---|---|
cot (default) | chain-of-thought (“think step by step”) | --system |
structured-cot | phased CoT | --phases "understand,plan,solve,verify" |
plan-and-solve | plan first, then execute | |
tot | Tree-of-Thoughts: branch, judge, beam-search | --width 3 --depth 3 --beam 1 --judge |
reflexion | generate → critique → revise loop | --rounds 3, --judge (critic) |
self-refine | reflexion with the model as its own critic | --rounds |
debate | N models argue, judge decides | --models "p:m,p:m", --judge, --rounds |
kis ai reason --mode cot "If a train travels 60km in 1.5h, its speed?"kis ai reason --mode tot --width 3 --depth 3 --trace "Make 24 from 4 6 7 8"kis ai reason --mode reflexion --rounds 3 "Define overfitting precisely"kis ai reason --mode debate --models "openai:gpt-4o,anthropic:claude-sonnet-4-6" "Is P=NP likely?"--trace prints the reasoning trace (thoughts/critiques/votes) to stderr;
the answer goes to stdout. --judge / --models take provider:model refs.
Sampling & ensembles
Section titled “Sampling & ensembles”kis ai sample --mode <strategy> — multi-run/multi-model robustness:
| Mode | What happens |
|---|---|
self-consistency (default) | one model, N runs, majority vote (-n 5) |
best-of-n | N candidates, an LLM judge picks (-n, --judge) |
cross-provider | several models answer, judge picks (--models) |
race | several models, first to finish wins (--models) |
kis ai sample --mode self-consistency -n 5 "tricky reasoning question"kis ai sample --mode race --models "groq:llama-3.3-70b-versatile,openai:gpt-4o-mini" "quick one"The deterministic path: the model writes a small JavaScript program; the the platform scripting runtime engine executes it; you get the program’s result — no arithmetic hallucinations. The model plans, the engine computes.
kis ai solve "how many r's are there in strawberry"kis ai solve --allowed file "mean, median and mode of the salary column in /tmp/pay.csv"kis ai solve --allowed file,db "join users.csv and orders.csv on id; top 5 by total"cat numbers.txt | kis ai solve "median of the numbers in this list" # stdin → input var "compute the total of the amount column in the data variable, applying the tax rate in the rate variable"--var k=v values become script globals (the model is told each one’s
name, size, and a quoted preview); @file reads a file, @- reads stdin.
Security model (engine-enforced, not prompt-enforced):
- Default = pure sandbox: plain JavaScript only; no file/network/shell.
--allowed file,db,http,…opts namespaces in; the script engine refuses everything else regardless of what the model writes.--allowed allopens everything (don’t, unless you mean it).- ~40 namespaces are available (file, db — incl. DuckDB SQL, http, jq, glob, hash, id, text, validate, vector, rag, shell, ssh, k8s, …); each one’s functions are documented to the model via skills embedded in the binary.
Execution posture:
- Default: the generated program is printed (syntax-highlighted) and you
confirm
y/Nbefore it runs. --exec— run without asking (code still shown).--silent— no code, no ask.--dry-run— print the program, never execute.--attempts N(default 1): on script errors the failure is fed back to the model to fix its own code and retry. Guards catch non-programs, NaN results (e.g. wrong CSV column), and{error:…}returns.--script-timeoutms (default 30000).--show-promptreveals the exact composed system prompt.
Model auto-selection (ollama): prefers qwen3-coder:30b; if not
installed, picks the largest local code-specialized model (note on stderr);
else falls back to the chat default with an ollama pull hint. -m overrides.
Harness
Section titled “Harness”solve is one model call writing one program. A harness is a graph —
model calls, tool dispatches, evaluation gates, and control flow, including
explicit loops: the agentic core. You write the behavior in YAML
(the flow format, extended), the embedded engine runs it.
kis ai harness run -f agent-loop.yaml -v goal="find the answer"kis ai harness run -f flows.yaml -n triage --input '{"claim": {...}}'kis ai harness validate -f loop.yaml # graph + cycle-boundedness checkkis ai harness graph -f loop.yaml --expanded # the desugared graphkis ai harness resume --snapshot run.json --signal approved -v approver=anandThe agent loop, as sugar — a model that reasons, calls tools, and stops when done, bounded by gates so it can’t run forever:
name: agent-loopoutput: "{{ result.content }}"tasks: - name: act agent: model: "anthropic:claude-sonnet-4-6" # provider:model, or bare ref → -p/-m input: "{{ goal }}" tools: [search, tasks] # `tasks` = built-in plan tool gates: - max_turns: 20 - max_cost_usd: 2.00 - no_progress: { window: 3 } output: result next: { go: done } - name: done print: "{{ result.content }}"Node kinds (kind = which field is present, flow-style): model (a call
via lib-ai), tool (dispatch), gate (lib-ai-eval budget/safety check),
agent (the loop, sugar for model→route→tools→gate→exit), script
(JS/script), plus everything the flow engine has — shell, print, assign, choice,
map/foreach, wait, subworkflow, fail/succeed. Routing is
next:/error:; templating is Liquid {{ }}.
Bounded loops, by construction. A back-edge (next.go to an earlier
node) is a loop. The compiler rejects any cycle without a budget gate
(max_turns/max_cost_usd/max_tokens/max_duration) or a bound: N
annotation — termination is checked at validate, not hoped for at runtime.
Suspend & resume. A wait: node or a gate pause decision suspends the
run; the CLI writes a snapshot to ~/.kisai/ai/harness/snapshots/<run-id>.json
and prints the resume line. Ctrl-C interrupts the same way (node effects
are atomic — fully applied or absent), so long runs are restartable.
Dev model resolution: provider:model refs build that provider from the
shared kis ai flags + env keys (ANTHROPIC_API_KEY, …, KIS_LOKI_KEY for
gateway:); a bare ref uses -p/--provider and -m/--model. Progress
streams on stderr, the result is on stdout (--json for the full envelope:
output, vars, tasks, usage, cost, trajectory id). Raise --node-timeout
(seconds) for big local models — the default 60s per call can cut a 30B
model off mid-generation.
Editing code — the bug-fixing agent. --allow-edit registers five
dir-scoped tools (list_files, read_file, edit_file, write_file,
run) so the agent reads files, makes precise line edits
(edit_file does exact old→new replacement, not whole-file rewrites),
runs the build/tests, and loops until green. examples/fix-bugs.yaml
is that loop:
kis ai harness run -f fix-bugs.yaml --allow-edit \ -v dir=/path/to/project \ -v task="the tests panic, fix the bug" \ -v verify="go build ./... && go test ./..." \ --node-timeout 300Propose → approve → apply (engine-enforced, not prompt-enforced): the
agent edits a shadow copy of dir, so your files are never touched
during the loop. After it finishes, the CLI shows the diff (shadow vs
original) and asks before copying changes back. --yes applies without
asking (for automation). Use a tool-capable model (qwen3-coder, claude,
gpt-4o); the loop is driven by tool calls. Run on a git-clean tree or a
throwaway copy so you can review and revert.
A real loop — review the code in a folder: examples/gbu-review.yaml
gathers a folder’s source, asks for a Good/Bad/Ugly review, writes
gbu.md, and loops until the file has all three sections each with a
bullet — feeding the model what’s missing on each retry, with a max_turns
gate as the safety cap.
kis ai harness run -f gbu-review.yaml -v dir=/path/to/projectkis ai harness run -f gbu-review.yaml -v dir=. -v model=anthropic:claude-sonnet-4-6# default model: ollama:qwen3-coder:30b (local, no key)Try it with no keys: examples/smoke.yaml is a model-free self-test —
every engine mechanism asserts the previous one:
kis ai harness run -f examples/smoke.yaml# → smoke ok: loop=3 shell=smoke-3 fan=3 child=33 tasks=2 completedAsking the user — REPL / questions / approvals. Harnesses pause for
input three ways: an ask: node (the author scripts a question), the
ask_user tool (the model asks mid-loop — list it in tools:), or a
wait: node (external events). -i answers them inline at the terminal:
kis ai harness run -f interview.yaml -i # a question wizard, no modelexamples/interview.yaml is a no-keys intake wizard. Without -i, the same
harness runs durably — it suspends, snapshots, and you (or a chat service)
answer with a resume:
kis ai harness run -f interview.yaml # suspends at the first askkis ai harness resume --snapshot <f> --signal elicit_answer -v answer="a CLI tool"Each resume advances one question and snapshots at the next — the same
authoring drives a terminal REPL or a multi-message chat session, depending
only on whether the caller answers inline or resumes. An agent can ask too:
give it tools: [ask_user] and it pauses the loop to ask you a question,
then continues with your answer.
For services, not the CLI: the harness is a Go library — Yama, Brahma,
and AI Bot embed it directly, injecting AI Gateway models, real tool routers, a
Prompter (or durable resume), and Smriti-backed recorders. The CLI is the
dev/CI surface.
Modalities
Section titled “Modalities”Images (openai, stability):
kis ai image -p openai "a watercolor fox" -o fox.pngkis ai image -p stability "an isometric office" --width 1024 --height 1024 --n 2 --negative "text, blur"Speech (TTS) (openai, deepgram):
kis ai tts -p openai "hello there" --voice alloy -o hello.mp3echo "good morning" | kis ai tts -p deepgram -o gm.mp3 --format wavTranscription (STT) (openai, groq, deepgram, whispercpp):
kis ai transcribe -p openai call.mp3kis ai transcribe -p groq -m whisper-large-v3 interview.m4a --timestampskis ai transcribe -p whispercpp -e http://localhost:8080/v1 audio.wav --jsonOutput & scripting
Section titled “Output & scripting”- stdout = the answer, stderr = everything else (generated code, sources,
traces, notes).
kis ai rag answer ... | jqstyle pipelines are safe. - Markdown rendering: chat (non-streamed), rag answers and reason answers render as terminal markdown — headings, lists, syntax-highlighted code blocks — only when stdout is a TTY. Piped/redirected output is the raw model text, byte-for-byte.
- Code highlighting: solve’s generated programs highlight on a TTY.
- Disable styling:
NO_COLOR=1env or--no-color. --jsonexists on chat, embed, chunk, tokens, extract, rag, models, providers, transcribe, solve — prefer it for machine consumption.- Non-zero exit on failure everywhere;
solvefailures explain whether the sandbox blocked something (--allowedhint) and how to enable retries.
Defaults appendix
Section titled “Defaults appendix”Per-provider default chat models: ollama llama3.2:3b · openai gpt-4o-mini
· anthropic claude-haiku-4-5 · gemini gemini-2.0-flash · groq
llama-3.3-70b-versatile · xai grok-3-mini.
solve overrides ollama’s default to qwen3-coder:30b (auto-discovery above).
Environment variables: provider keys (OPENAI_API_KEY, ANTHROPIC_API_KEY,
…), KIS_AI_API_KEY (generic fallback), KIS_AI_SESSIONS_DIR (session store),
NO_COLOR (disable rendering).
Timeout tip for big local models: first-token latency on 20GB+ models can
exceed the 120s default — pass --timeout 300.