Skip to content
Talk to our solutions team

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).

Terminal window
kis ai providers # everything supported + capabilities
kis 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.

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.

FlagMeaning
-p, --providerprovider (default ollama)
-m, --modelmodel id (per-command default otherwise)
-e, --endpointoverride base URL (e.g. -e http://gpubox:11434)
-k, --api-keykey (else env)
--timeoutrequest timeout seconds (default 120 — raise for big local models)
--max-retriesretries on retryable errors (default 2)
-j, --jsonmachine-readable output

Every command works against the platform gateway instead of a direct provider:

Terminal window
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.

Terminal window
kis ai chat "what is 2+2?" # one-shot, default model
kis ai chat -p openai -m gpt-4o "summarize this" # explicit provider/model
echo "translate to French: hello" | kis ai chat # stdin is the prompt
kis ai chat -s "you are terse" "explain monads" # system prompt
kis ai chat --stream "write a haiku about go" # tokens as they arrive
kis ai chat -j "say hi" # full response as JSON
kis ai chat --reasoning high -p anthropic "hard puzzle" # extended thinking
kis ai chat -T 0 "deterministic-ish output" # temperature

Vision (any image-capable model; -I repeatable, path or URL):

Terminal window
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):

Terminal window
kis ai chat -i
kis ai chat -i --session proj-42 # REPL that persists + resumes

File-backed conversations that survive across invocations. Stored under ~/.kisai/ai/sessions (override: --sessions-dir or $KIS_AI_SESSIONS_DIR).

Terminal window
kis ai chat --session proj-42 "remember my name is Sam" # creates if absent
kis ai chat --session proj-42 "what is my name?" # resumes context
kis ai chat --session proj-42 -I diagram.png "explain" # images persist too
kis ai session list # all conversations
kis ai session show proj-42 # transcript
kis ai session send proj-42 "and..." # append a turn (scripting-friendly)
kis ai session new # mint a fresh id

Stop typing long prompts: keep named prompts in YAML and bind variables.

prompts.yaml
summarize:
system: You are a precise summarizer.
user: |
Summarize in {{words}} words:
{{content}}
Terminal window
kis ai chat --prompt-file prompts.yaml --prompt summarize \
--vars words=50 --vars [email protected]
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}}:

prompts.yaml
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}}
Terminal window
kis ai extract --prompt-file prompts.yaml --prompt urla_borrower_section_1e \
--file urla.txt --strip-attrs --schema income.json

Schema-constrained JSON (file path or inline schema). Parse failures retry-with-error (--repair, default 2).

Terminal window
kis ai structured --schema person.json "John Smith, 42, lives in NYC"
echo "$INVOICE" | kis ai structured --schema ./invoice.json -p openai --strict
kis ai structured --schema '{"type":"object","properties":{"sentiment":{"enum":["pos","neg"]}}}' \
"loved it"

Field extraction with optional schema, multi-file input, HTML/hOCR cleanup, section slicing, and N-run majority voting.

Terminal window
# Schema-bound, with consensus voting
kis ai extract --schema loan.json --file ./application.txt --runs 5
echo "$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).

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.

Terminal window
kis ai extract --file note.txt # free-form JSON
kis ai extract --schema fields.json --file note.txt # schema-bound

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.

Terminal window
kis ai extract --schema s.json --file a.txt --file b.txt
kis ai extract --schema s.json --file 'pages/*.txt' # glob
kis ai extract --schema s.json --input @./doc.txt # @ = read file
echo "$DOC" | kis ai extract --schema s.json # stdin

OCR output (hOCR) is mostly bbox/confidence noise. Strip it to save tokens:

Terminal window
# 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 attributes
kis 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.

--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.

Terminal window
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.

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.

Terminal window
kis ai embed "hello world" # vector preview
kis ai embed "a" "b" "c" --compare # pairwise cosine matrix
printf "first\nsecond\n" | kis ai embed --json # stdin lines, JSON out
kis 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.

Split documents the same way the RAG pipeline does (inspect before you ingest):

Terminal window
kis ai chunk README.md --strategy markdown --max-tokens 512
kis ai chunk notes.txt --strategy recursive --overlap 64 --json
kis ai chunk paper.txt --strategy semantic -p ollama -m nomic-embed-text

Strategies: recursive (default) | window | markdown | html | semantic (embeds sentences to find topic boundaries — uses the provider/model flags).

Terminal window
kis ai tokens "how many tokens is this?" # heuristic (safe over-estimate)
kis ai tokens --file ./prompt.txt -p openai -m gpt-4o
kis 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:

DSNBackendNotes
memory:in-processdefault; lives only for ONE command — not for ingest-then-answer
duckdb:///abs/path.dbDuckDB fileserverless, single file, HNSW-indexed; ideal for local KBs
pgvector://user:pass@host:5432/db?sslmode=disablePostgresproduction-shaped, shared
chroma://host:8000Chromaremote
qdrant://host:6333Qdrantremote

The local knowledge-base recipe (folder of docs → DuckDB → answers):

Terminal window
# 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 JSON
kis ai rag answer ... --json "question" | jq .answer

Rules that matter:

  • Same --embed-model for 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 ingest reads one document from stdin.
  • Index upkeep is automatic: inserts index incrementally; re-ingest/delete trigger HNSW compaction (DuckDB).

Reorder candidate documents by relevance (cohere/tei rerank models):

Terminal window
kis ai rerank -p cohere -m rerank-english-v3.0 \
"best feline pet" "cats are independent" "dogs are loyal" "stocks rose"

kis ai reason --mode <strategy> — prompt-shaping strategies (one model call) and iterative algorithms (many calls) from lib-ai:

ModeWhat happensKey flags
cot (default)chain-of-thought (“think step by step”)--system
structured-cotphased CoT--phases "understand,plan,solve,verify"
plan-and-solveplan first, then execute
totTree-of-Thoughts: branch, judge, beam-search--width 3 --depth 3 --beam 1 --judge
reflexiongenerate → critique → revise loop--rounds 3, --judge (critic)
self-refinereflexion with the model as its own critic--rounds
debateN models argue, judge decides--models "p:m,p:m", --judge, --rounds
Terminal window
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.

kis ai sample --mode <strategy> — multi-run/multi-model robustness:

ModeWhat happens
self-consistency (default)one model, N runs, majority vote (-n 5)
best-of-nN candidates, an LLM judge picks (-n, --judge)
cross-providerseveral models answer, judge picks (--models)
raceseveral models, first to finish wins (--models)
Terminal window
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.

Terminal window
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
kis ai solve --var rate=0.08 --var [email protected] \
"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 all opens 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/N before 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-timeout ms (default 30000). --show-prompt reveals 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.

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.

Terminal window
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 check
kis ai harness graph -f loop.yaml --expanded # the desugared graph
kis ai harness resume --snapshot run.json --signal approved -v approver=anand

The 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-loop
output: "{{ 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:

Terminal window
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 300

Propose → 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.

Terminal window
kis ai harness run -f gbu-review.yaml -v dir=/path/to/project
kis 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:

Terminal window
kis ai harness run -f examples/smoke.yaml
# → smoke ok: loop=3 shell=smoke-3 fan=3 child=33 tasks=2 completed

Asking 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:

Terminal window
kis ai harness run -f interview.yaml -i # a question wizard, no model

examples/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:

Terminal window
kis ai harness run -f interview.yaml # suspends at the first ask
kis 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.

Images (openai, stability):

Terminal window
kis ai image -p openai "a watercolor fox" -o fox.png
kis ai image -p stability "an isometric office" --width 1024 --height 1024 --n 2 --negative "text, blur"

Speech (TTS) (openai, deepgram):

Terminal window
kis ai tts -p openai "hello there" --voice alloy -o hello.mp3
echo "good morning" | kis ai tts -p deepgram -o gm.mp3 --format wav

Transcription (STT) (openai, groq, deepgram, whispercpp):

Terminal window
kis ai transcribe -p openai call.mp3
kis ai transcribe -p groq -m whisper-large-v3 interview.m4a --timestamps
kis ai transcribe -p whispercpp -e http://localhost:8080/v1 audio.wav --json
  • stdout = the answer, stderr = everything else (generated code, sources, traces, notes). kis ai rag answer ... | jq style 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=1 env or --no-color.
  • --json exists on chat, embed, chunk, tokens, extract, rag, models, providers, transcribe, solve — prefer it for machine consumption.
  • Non-zero exit on failure everywhere; solve failures explain whether the sandbox blocked something (--allowed hint) and how to enable retries.

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.