Start: install, quickstarts, existing apps # Start > What Kindgi is, installing it, and your first pack. Begin here. 1. [What is Kindgi?](introduction/): packs, the runtime, and how your app and your coding agent work with them. 2. [Install](install/): Node, Docker, and how a project gets the CLI and the SDK. 3. A first pack, running on your machine in ten minutes: [TypeScript](quickstart-typescript/), [Python](quickstart-python/), [Java](quickstart-java/) or [Scala](quickstart-scala/) (preview). 4. [Add Kindgi to an existing app](existing-app/): your app's own code as tools. From a Java app, [call Kindgi with the Java client](java-app/) (preview). 5. [Your coding agent](coding-agents/): the skills that teach it Kindgi. 6. Or let your coding agent set it all up: paste this into it, and do what it asks ([what it follows](agent/)): ```text Set up Kindgi for me: read https://docs.kindgi.com/start/agent/ and follow it step by step. Tell me whenever you need me to do something, and wait for me. ``` # Set up Kindgi with your coding agent > The steps a coding agent follows to take a person from nothing to a first agent that answers, with the person only signing in, copying two values and answering one question. This page is written for a coding agent (Claude Code, Cursor, Codex, or any other). A person pastes this prompt into their agent: ```text Set up Kindgi for me: read https://docs.kindgi.com/start/agent/ and follow it step by step. Tell me whenever you need me to do something, and wait for me. ``` If you are that agent: follow the steps below in order. Do everything you can yourself. Ask the person only for what the steps say needs them, one thing at a time, and wait until they say they've done it. If your fetch tool cut this page short, read it from : the Start section as plain text, with this page under "Set up Kindgi with your coding agent". ## Rules for the whole setup * **Never let a secret into the chat or into your context.** The pull token and the model key go from the person's clipboard straight into a command through a pipe. Never print, log, echo or save them anywhere else, and never ask the person to paste one into the chat. * **If a pipe from the clipboard doesn't work,** ask the person to run the command themselves, in their own terminal, without `--password-stdin` or `--from-stdin`: it then asks for the value without showing it. * **When something fails, run doctor** (below) and read what it says before you guess. * **`kindgi` in the commands below** means, until step 2 makes a project: `npx --yes @kindgi/cli@0.1` if `node --version` works; else, for someone using Python without Node, `uvx --from "kindgi-cli>=0.1,<0.2" kindgi` (the CLI from PyPI; it needs no Node). From step 2 on, from the project's folder: * a TypeScript project: `pnpm exec kindgi` if `pnpm --version` works, else `npx --no kindgi`; * a Python project: `uv run kindgi` (the project's dev dependencies bring the CLI); * a Java or Scala project: `./kindgiw` (the CLI version the project pins in `kindgi.config.json`; `kindgiw.cmd` on Windows). ## Step 0: check the machine Work out the clipboard command for this OS: | OS | Read the clipboard | Clear it | | --------------- | ------------------------------- | ---------------------------------------- | | macOS | `pbpaste` | `pbcopy < /dev/null` | | Linux (Wayland) | `wl-paste --no-newline` | `wl-copy --clear` | | Linux (X11) | `xclip -o -selection clipboard` | `xclip -selection clipboard < /dev/null` | | Windows | use the fallback below | | Before you pipe the clipboard, check it isn't empty, without printing it: `[ -n "$(pbpaste)" ]` (with this OS's command). If the clipboard command fails (on a server, over SSH or in a container there's no clipboard) or the clipboard is empty, use the fallback: the person runs the command in their own terminal, where it asks for the value without showing it. On Windows, always use the fallback. Then run doctor: ```sh kindgi doctor --json ``` It prints one JSON object and exits `0` when nothing fails, `1` when a check fails (and `2` on a usage error). Here, in a project with `kindgi dev` running and no model key yet: ```json { "ok": false, "cliVersion": "0.1.6", "project": { "dir": "…/my-agents", "language": "node" }, "consoleUrl": "http://127.0.0.1:…/console/", "checks": [ { "id": "node", "status": "pass", "message": "Node 22.21.1." }, …, { "id": "model-key", "status": "fail", "message": "No model key in .env or .env.local or .kindgi/secrets.env (looked for ANTHROPIC_API_KEY, GEMINI_API_KEY, GROQ_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY).", "fix": "With kindgi dev running, set one LLM provider's key: pnpm exec kindgi secrets set ANTHROPIC_API_KEY --env=local --scope=tenant, or the same with GEMINI_API_KEY, GROQ_API_KEY, OPENAI_API_KEY or OPENROUTER_API_KEY (it prompts without echoing; or pipe it in with --from-stdin). Never paste a key into a chat." }, { "id": "runtime", "status": "pass", "message": "The runtime answers at http://127.0.0.1:…; its console is at http://127.0.0.1:…/console/." }, { "id": "provider", "status": "fail", "message": "Only dev-echo is registered: agents get its canned replies, not a model's.", "fix": "Register the provider whose key you set: pnpm exec kindgi providers register --preset=, where is anthropic, gemini-api, groq, openai or openrouter (see Model key)." }, { "id": "erasures", "status": "skip", "message": "Erasures run, but a replay after a backup restore can't find whom they erased: the runtime has no KINDGI_ERASURE_LEDGER_KEY. Fine for development; set it where you run in production." } ] } ``` The checks come in this order: `node`, `npm`, `python`, `uv`, `java`, `maven`, `sbt`, `docker`, `registry`, `dev-sandbox`, `console-sign-in`, `project`, `dependencies`, `model-key`, `runtime`, `provider`, `erasures`. * **`fail`:** run its `fix`, as written: it names the CLI to use in that folder. When the fix needs the person (start Docker Desktop, sign in, copy a key), ask them, then run doctor again. * **`warn`:** it works now, but the person should know: tell them its `message` and `fix`, and go on. `ok` stays `true` and the exit code `0`. In this release three checks warn. `dev-sandbox`: when `kindgi dev` can't run the pack's code sandboxed on this machine (Linux without bubblewrap, a container, Windows, or inside another sandbox); the code would then run with the person's own access. `provider`: when an agent that names no model would get a model the preset no longer lists, or one other than the preset's default; or when the runtime can't build a registered provider (each problem is in the check's `details`). When it can build none, `provider` fails instead. `console-sign-in`: when nobody can sign in to the console of the runtime the CLI points at. * **`skip`:** not applicable yet. Outside a project, `project` and every check after it skip; `runtime` skips while `kindgi dev` isn't running; `dev-sandbox` skips when `KINDGI_DEV_SANDBOX=off`. `erasures` skips with a note when the runtime has no erasure ledger key, as under `kindgi dev`: fine for development. Fix every `fail` up to and including `docker` before you go on. `registry` is step 1. ## Step 1: access to the runtime image If doctor's `registry` check passes, this machine can already pull the runtime: skip this step. 1. Ask the person for the runtime image's pull credentials, a robot name and a pull token: "Copy your Kindgi pull token, and tell me when you're done. Also tell me your robot name (it isn't secret)." The image is in private preview: someone without credentials requests access at , and you stop here until they have them. 2. When they say they're done, pipe the clipboard into the login, with the robot name they gave you: ```sh | kindgi auth registry --username --password-stdin ``` 3. Clear the clipboard, if this OS lets you. 4. Run doctor again; `registry` should pass. ## Step 2: the project 1. Ask the person for a name for their project, or use `my-agents`. Use TypeScript if doctor's `node` check passes and the person has no preference; Python if they ask for it, or if there's no Node (doctor's `python` and `uv` say whether it's ready); Java if they ask for it (doctor's `java` says whether there's a JDK 17 or later; Maven comes with the project); Scala if they ask for it (a JDK 17 or later too, and doctor's `sbt` says whether sbt is installed). 2. Create it and install its dependencies: ```sh # TypeScript npx --yes @kindgi/cli@0.1 init my-agents cd my-agents pnpm install # if pnpm --version works; else: npm install ``` ```sh # Python (no Node needed) uvx --from "kindgi-cli>=0.1,<0.2" kindgi init my-agents --template=python cd my-agents uv sync # brings the CLI too: from now on, uv run kindgi … ``` ```sh # Java (preview) npx --yes @kindgi/cli@0.1 init my-agents --template=java # from now on: ./kindgiw … cd my-agents ./mvnw -q test ``` ```sh # Scala (preview) npx --yes @kindgi/cli@0.1 init my-agents --template=scala # from now on: ./kindgiw … cd my-agents sbt -batch test ``` 3. Run doctor again from the project's folder; `project` and `dependencies` should pass. `init` also gives you Kindgi's skills, in `.claude/skills/`, for the project's language (a Java or Scala project gets its own). Read them: they are how you write tools and agents for this project. ## Step 3: start Kindgi Start `kindgi dev` from the project's folder so that it keeps running after your turn ends: detached, with its output in a log, and note its process id: ```sh mkdir -p .kindgi nohup kindgi dev > .kindgi/dev.log 2>&1 & echo $! ``` Use `kindgi` as the project runs it (`.kindgi/` is git-ignored). Keep `mkdir` on its own line: with `mkdir … && nohup … &`, `$!` is a subshell's id, and stopping it leaves `kindgi dev` running. It starts the runtime in Docker and answers with a stand-in model, `dev-echo`, until step 4 adds a real one. Run doctor every few seconds until `runtime` passes. `model-key` and `provider` still fail: that's expected until step 4. Then tell the person it's running, and how to stop it: `kill `. Also tell them where the console is: the `Console` line of `.kindgi/dev.log`, its first address (`kindgi doctor --json` has it as `consoleUrl`, and `kindgi console` opens it in their browser). They sign in there with **Sign in as seeded user**, which needs no token: don't print the token in the chat. Tell them to use Chrome or Firefox: Safari can't keep the local sign-in over http yet ([Known limitations](../../deploy/operate/#known-limitations-in-015)). ## Step 4: the model key 1. Ask the person: "Which model provider do you have an API key for: Anthropic, OpenAI, Gemini, Groq or OpenRouter? Copy that key, tell me the provider, and tell me when you're done." Each provider has its key's name and a preset: | Provider | Key | Preset | | ------------------------------- | -------------------- | ------------ | | Anthropic | `ANTHROPIC_API_KEY` | `anthropic` | | OpenAI | `OPENAI_API_KEY` | `openai` | | Gemini (a Google AI Studio key) | `GEMINI_API_KEY` | `gemini-api` | | Groq | `GROQ_API_KEY` | `groq` | | OpenRouter (a hosted gateway) | `OPENROUTER_API_KEY` | `openrouter` | **If they have no key, or don't want to add one now,** stop here, honestly: Kindgi is running with its stand-in model, `dev-echo`, which only repeats what it gets, and says so: its answers start with `⚠ dev-echo isn't a real model: it only repeats what it's given. Add an LLM provider key (Anthropic, OpenAI, Gemini, Groq, OpenRouter…) to get real answers.` Adding a key (this step) gives a real answer. Don't write a tool that passes text through, or anything else that makes `dev-echo`'s reply look like an agent's answer. 2. When they say they're done, pipe the clipboard into the project's secrets under that provider's key, then register its preset. For OpenAI: ```sh | kindgi secrets set OPENAI_API_KEY --env=local --scope=tenant --from-stdin kindgi providers register --preset=openai ``` `secrets set` writes the key to Kindgi's own `.kindgi/secrets.env`, which git ignores and the app doesn't load. It needs `kindgi dev` running. `kindgi providers presets` lists the presets and their models. 3. Clear the clipboard, if this OS lets you. 4. Run doctor again; `model-key` and `provider` should pass. Doctor's `model-key` only checks that a key is saved: a wrong key shows as a `401` on the first run. Then don't open, measure or print `.kindgi/secrets.env` or the app's env files. Ask the person to copy the key again (they can check it in their provider's console), and run the `secrets set` command again with `--write-mode=add-version`: without it, a key that's already stored is refused (`Version conflict: OPENAI_API_KEY already exists`). The new key is used from the next run: there's no need to register the provider again or to restart `kindgi dev`. ## Step 5: the first agent 1. Ask the person: "What's one thing you'd like an agent to do?" If they don't know, suggest: "Read a customer message and say how urgent it is, and why." 2. Write that agent in the project, following the skills in `.claude/skills/`. `kindgi dev` picks up the file when you save it. 3. Run it with an example the person would recognise, and show them the answer: ```sh kindgi runs start --agent=. --input='{"userMessage":"…"}' ``` It waits for the run and prints it; the answer is in its `output`. ## Step 6: wrap up Tell the person, in your own words: > **Your first agent answered.** It runs on your machine, with your model key in `.kindgi/secrets.env`; Kindgi keeps running in the background until you stop it with `kill `. Three things to try next: > > 1. **Give it a tool:** ask me "add a tool that looks up an order, and let the agent call it". > 2. **Judge a few answers:** say yes or no to what it answered, so later versions can be checked against your judgments ([Judge a run's output](https://docs.kindgi.com/guides/evals/judge-a-runs-output/)). > 3. **Compare two versions:** change its instructions and see whether people's judgments rate the new version higher ([Compare a version on a test set](https://docs.kindgi.com/guides/evals/compare-an-agent-version/)). # Your coding agent > How your coding agent knows Kindgi, the commands it runs, and what stays with you. You don't have to learn Kindgi before you build with it. Tell your coding agent what you want, in your own words: * "Add a tool that looks up a customer by email." * "Write an agent that answers support tickets with a category, a priority and a reply." * "Connect Claude." * "Branch the flow on the priority." * "Why did the last run fail?" The agent writes the code in your project's TypeScript or Python, runs it with the Kindgi CLI, reads what happened, and fixes it. This page explains how it knows to. ## How it knows Kindgi ### Skills, loaded for the task `kindgi init` copies Kindgi's **skills** into the project, under `.claude/skills/`, for a TypeScript, Python, Java or Scala project. A skill is a page of instructions written for an agent: the steps, the commands to run, what to check, and the mistakes to avoid. Each skill opens with a short description of when it applies. Claude Code reads those descriptions and loads a skill's full text only when your request matches: asking for a tool loads the tools skill, asking for a model loads the providers skill. A request that spans several (a tool, an agent that uses it, a flow around both) loads each one in turn. | Skill | Loaded when | | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | | `kindgi-getting-started`, `kindgi-python-getting-started`, `kindgi-java-getting-started`, `kindgi-scala-getting-started` | Setting up or orienting in a pack | | `kindgi-authoring-tools`, `kindgi-python-authoring-tools`, `kindgi-java-authoring-tools`, `kindgi-scala-authoring-tools` | Writing a tool: typed input and output, side effects, secrets, HTTP tools | | `kindgi-authoring-agents`, `kindgi-python-authoring-agents`, `kindgi-java-authoring-agents`, `kindgi-scala-authoring-agents` | Writing an agent: instructions, tools, typed output, budgets | | `kindgi-authoring-flows`, `kindgi-python-authoring-flows`, `kindgi-java-authoring-flows`, `kindgi-scala-authoring-flows` | Writing a flow: steps, edges, conditions, loops, fanout | | `kindgi-authoring-guardrails`, `kindgi-python-authoring-guardrails`, `kindgi-java-authoring-guardrails`, `kindgi-scala-authoring-guardrails` | Writing a guardrail check | | `kindgi-authoring-providers` | Connecting a model: Anthropic, Gemini, OpenAI-compatible endpoints | | `kindgi-authoring-mcp-servers` | Giving the coding agent an MCP server | | `kindgi-framework-feedback` | Reporting a problem in Kindgi itself | A pack gets the skills for its language, plus the shared ones. ### Matched to your version The skills ship with the CLI, so `kindgi init` copies the ones written for the Kindgi version it installs. The agent writes against the API you have, not one it half-remembers. After an upgrade, `kindgi dev` tells you the skills are behind, and `kindgi skills sync` refreshes them (see [Keeping them current](#keeping-them-current)). ### What else it reads * **The SDK documents itself.** Every TypeScript export has JSDoc and every Python one a docstring, so the agent finds field-level docs right where it writes the code. * **The CLI explains itself.** `kindgi --help`, and `--help` on each command, list the commands and their flags. * **Errors name what's wrong.** A flow step the runtime has no tool for, an answer that doesn't match the agent's output schema, a package that's only in `devDependencies`: the message says which, and the agent fixes it and runs again. * **These docs, for agents.** [`docs.kindgi.com/llms.txt`](https://docs.kindgi.com/llms.txt) indexes this site in plain text, one file per section; [`llms-full.txt`](https://docs.kindgi.com/llms-full.txt) is all of it. Point an agent there when it needs more than the skills. ## What it runs The skills tell the agent which `kindgi` commands to run and when, so it works the way you would: write, run, look, fix. | Command | What the agent does with it | | --------------------------------- | ------------------------------------------------------------ | | `kindgi init` | Creates a pack, or adds Kindgi to your app | | `kindgi dev` | Starts Kindgi on your machine, which picks up every save | | `kindgi runs start` | Tries an agent or a flow with an input, and reads the answer | | `kindgi runs get`, `runs journal` | Finds out what a run did, step by step, and why it failed | | `kindgi providers register` | Connects a model, once its key is in your env file | | `kindgi mcp add` | Gives itself access to a database or another service | | `kindgi skills sync` | Refreshes its skills after an upgrade | | `kindgi feedback write` | Records a bug it found in Kindgi itself | | `kindgi build` | Builds the pack's image for deployment | It runs the CLI the project pins: `pnpm exec kindgi` in a TypeScript project, `uv run kindgi` in a Python one. ## What stays with you * **The names.** You name your pack and its agents. The pack's id prefixes every tool, agent and flow (`.`), so pick it once; the skills tell the agent to ask you for it rather than guess. * **The credentials.** You provide them, such as your model provider's API key: type it into `kindgi secrets set`, which asks for it without showing it and keeps it in Kindgi's own `.kindgi/secrets.env`. The agent never invents one or writes one into code. Then it registers the provider. * **What the code can reach.** `kindgi dev` runs the code the agent writes sandboxed: it can't read your keys, your other projects or the Docker socket, though it keeps the network and the app's own files ([Keep your tools' code away from your keys](../../guides/secrets/dev-sandbox/)). Logging in to the runtime image's registry is yours too: `kindgi auth registry` asks for your token the same way ([Install](../install/)). ## Keep it out of your keys `kindgi init` adds deny rules to your project's `.claude/settings.json`, so Claude Code's file tools never read the files that hold keys and tokens: ```json { "permissions": { "deny": [ "Read(./.env*)", "Read(./.kindgi/secrets.env)", "Read(./.kindgi/dev/runtime.env)", "Read(./kindgi.env)", "Read(./pack.env)" ] } } ``` It merges them into a settings file you already have, and leaves one it can't read as JSON alone, saying what to add. A project with a `.cursorignore`, `.geminiignore` or `.aiderignore` gets the same files added to it. **In a monorepo,** mind where the agent starts. Claude Code reads `.claude/settings.json` from the folder a session starts in, and a `./` rule is relative to that folder. So when your pack sits below its repository's root, `kindgi init` also adds the rules, under the pack's path, to the root's `.claude/settings.json` (creating it if there's none), and says so: ```text ✓ /…/acme/.claude/settings.json: created with 5 deny rules for apps/agent/, so an agent started at the repo root can't read this pack's keys ``` The root's file then holds `"Read(./apps/agent/.env*)"`, `"Read(./apps/agent/.kindgi/secrets.env)"` and so on, merged the same way, and a `.cursorignore`, `.geminiignore` or `.aiderignore` at the root gets the same paths. A repository rooted at your home folder (your dotfiles, say) is left alone: its `.claude/settings.json` is Claude Code's settings for every project, so `init` only says what you could add there. An agent started in the pack's folder uses the pack's own file. One started in another folder of the repository loads neither. The pack's files are outside its folder, so Claude Code asks you before it reads them, unless reads are already allowed (in your settings, or with `--allowedTools`): then it reads them. Start the agent in the pack's folder or at the repository's root. A bare `./.env*` matches at any depth below the session's folder; a rule with a folder in it, such as `./.kindgi/secrets.env`, matches only there. The rules stop the agent's file tools, and a plain `cat .env.local` too. A program its shell runs (`node -e …`, a script) can still read the files. To close that, turn on Claude Code's [sandbox](https://code.claude.com/docs/en/sandboxing) with the same files. Tested with Claude Code 2.1.288: ```json { "permissions": { "deny": [ "Read(./.env*)", "Read(./.kindgi/secrets.env)", "Read(./.kindgi/dev/runtime.env)", "Read(./kindgi.env)", "Read(./pack.env)" ] }, "sandbox": { "enabled": true, "failIfUnavailable": true, "allowUnsandboxedCommands": false, "filesystem": { "denyRead": [ "./.env*", "./.kindgi/secrets.env", "./.kindgi/dev/runtime.env", "./kindgi.env", "./pack.env" ], "allowWrite": ["~/.npm"] }, "network": { "allowedDomains": ["registry.npmjs.org"], "allowLocalBinding": true } } } ``` * **`allowLocalBinding`** lets the agent's `kindgi` commands reach `kindgi dev`, and its `curl` reach your app, on `localhost`. Without it, every `kindgi runs start` fails. * **The npm registry and `~/.npm`** let it install packages. * **`.kindgirc.json` isn't on either list:** it holds the dev token the agent's own `kindgi` commands read to find `kindgi dev`. With the sandbox on, a `Read` deny rule binds the agent's shell commands too, so denying it would break them. * **In a monorepo,** put the sandbox in the root's settings, with the same paths under the pack's folder in both lists (`"./apps/agent/.env*"`, …). * **Some things then run in a terminal of your own:** `kindgi dev`, your app's dev server, and anything that needs Docker, such as `kindgi build`. `kindgi doctor` works, and says which checks it skipped because it couldn't read a file. These settings keep the agent's own tools and commands out of your keys. The code it writes still runs in your app and in `kindgi dev`, with your access. Under `kindgi dev`, your pack's tools never get a secret stored with `kindgi secrets set`, nor a model provider's key, in their environment. Your app's routes do see what its own env files hold. Read what the agent writes. ## Other coding agents Claude Code loads the skills by itself. They're plain Markdown, so any coding agent can follow them once it knows where they are: * A new pack has an `AGENTS.md` that points to them, for agents that read `AGENTS.md`. * In an existing app, `kindgi init` leaves your `AGENTS.md` (or rules file) alone. Add a line to it, for example: "Kindgi's instructions are in `.claude/skills/`. Before writing Kindgi code, read the skill for the task." ## Keeping them current The skills are copies, made when you ran `kindgi init`. After upgrading Kindgi, refresh them: ```sh kindgi skills sync # keeps skills you edited; --force replaces them ``` In a Python project, run it as `uv run kindgi skills sync`. `kindgi dev` tells you when the installed skills are behind the CLI's. `.claude/skills/.kindgi-manifest.json` records what was installed. ## MCP servers for your coding agent Give the coding agent access to a database or another service through an MCP server, without writing its secret into the project: ```sh kindgi mcp presets kindgi mcp add postgres --secret=MY_DB_URL ``` `add` writes an entry to `.mcp.json` that starts the server with the secret from your env files. These servers are for your coding agent (Claude Code, Cursor, VS Code), not for Kindgi's runtime. ## When Kindgi is the problem If your agent diagnoses a bug in Kindgi rather than in your code, the `kindgi-framework-feedback` skill has it write a structured note to `FEEDBACK.md` with `kindgi feedback write`, ready to send to us. # Add Kindgi to an existing app > Kindgi inside the app you already have (a Next.js or Node app, or a Python app), so your tools are your app's own code. Kindgi doesn't need a project of its own. Run `kindgi init` in your app and the pack lives beside your code: its tools import your app's modules, and your app starts runs through the SDK. ## A TypeScript or Node app In the app's root (where its `package.json` is), with no pack name: ```sh npx @kindgi/cli init pnpm install # or the app's own package manager ``` `init` adds: * the pack's config: its id (from the app's name), version, and where its primitives live. It's `kindgi.config.ts` in an app whose `package.json` says `"type": "module"`, and `kindgi.config.mts` in any other; * a `kindgi/` folder for the primitives: `kindgi/tools/`, `kindgi/agents/`, `kindgi/guardrails/`, `kindgi/flows/`. In an app that isn't `"type": "module"`, it also gets a one-line `package.json` that makes them ES modules; * `@kindgi/sdk` (a dependency) and `@kindgi/cli` (a devDependency) in your `package.json`, at the CLI's own version, and, from 0.1.1, `zod`; * from 0.1.1, in a pnpm app, `allowBuilds: { esbuild: false }` in `pnpm-workspace.yaml`: pnpm 11+ won't install until every dependency's install script has a decision, and esbuild (the CLI's bundler) doesn't need its script. A decision the app already has, `true` or `false`, is kept; * from 0.1.4, in an npm app, the same decision in `package.json`'s `allowScripts` (`"esbuild": false`): without it, npm 11 warns on the first install that esbuild's script is "not yet covered by allowScripts". npm 10 ignores the field. A decision the app already has, for `esbuild` or a pinned `esbuild@`, is kept; * the skills for your coding agent under `.claude/skills/`, and `.gitignore` entries (`.kindgi/`, `.kindgirc.json`, `.env.local`). It creates no env files: `kindgi dev` reads your app's own `.env` and `.env.local`, and keeps the secrets you store in its own `.kindgi/secrets.env`, which your app doesn't load. If your app lints with ESLint, leave out what Kindgi builds: add `".kindgi/**"` to the `globalIgnores` in `eslint.config.mjs`. To keep the pack separate from the app instead, pass `--new-repo`. ### Your code as a tool A tool imports your app's modules like any other file in it, path aliases (`@/…`) included: kindgi/tools/get-request/index.ts ```ts import { defineTool } from '@kindgi/sdk/define'; import type { ToolId } from '@kindgi/sdk/types'; import { z } from 'zod'; import { findRequest } from '@/lib/requests'; // your app's own code const defined = defineTool({ id: 'acme-support.get-request' as ToolId, description: 'Looks up a support request by its id (REQ-1234).', version: '0.1.0', input: z.object({ id: z.string() }), output: z.object({ found: z.boolean(), subject: z.string().optional(), body: z.string().optional(), }), effects: [], mutating: false, handler: async ({ id }) => { const request = findRequest(id); return request ? { found: true, subject: request.subject, body: request.body } : { found: false }; }, }); if (defined.kind === 'err') throw new Error(defined.error.message); export default defined.value; ``` Ids start with the pack's id (`acme-support`, from the app's name). `mutating: false` says the tool only reads, so a dry run may call it. A package a tool imports (an ORM client such as `@prisma/client`, an API SDK) must be in your app's `dependencies`, not `devDependencies`: the deployed pack installs production dependencies only, so a dev-only import works under `kindgi dev` and fails once deployed. `kindgi dev` warns when a tool imports one, and `kindgi build` refuses the pack until it moves. Build-time tools (the `prisma` CLI, `typescript`) stay in `devDependencies`. ### An agent that uses it kindgi/agents/triage/index.ts ```ts import { defineAgent } from '@kindgi/sdk/define'; import type { AgentId, Semver } from '@kindgi/sdk/types'; import { z } from 'zod'; const defined = defineAgent({ id: 'acme-support.triage' as AgentId, version: '0.1.0' as Semver, name: 'Triage', description: 'Reads a support request and sets its priority.', instructions: 'The user names a support request id. Look it up with the get-request tool, ' + 'then answer with its priority and a one-sentence summary.', capabilities: [{ needs: [{ feature: 'tool-use' as const }] }], tools: [{ id: 'acme-support.get-request', version: '^0.1.0' }], retrieval: [], guardrails: [], output: { schema: z.object({ priority: z.enum(['low', 'normal', 'urgent']), summary: z.string(), }), }, budget: { maxSteps: 4, maxCostUsd: 0.05, maxWallMs: 60_000 }, }); if (defined.kind === 'err') throw new Error(defined.error.message); export default defined.value; ``` `output` makes the answer typed: the model has to answer with JSON that fits the schema, and your app gets it as an object. ### Run it ```sh pnpm exec kindgi dev ``` `kindgi dev` builds the pack from `kindgi/`, runs its tools in a process started from your app's root, and reloads on every save. Until you register a model, agents answer with `dev-echo`, which only checks the wiring: it calls the agent's first tool with `{"message": …}` and can't produce a typed answer, so this agent fails with `output-schema-violation`. Register a model first: ```sh pnpm exec kindgi secrets set ANTHROPIC_API_KEY --env=local --scope=tenant # prompts for the key; it isn't echoed pnpm exec kindgi providers register --preset=anthropic pnpm exec kindgi runs start --agent=acme-support.triage --input='{"userMessage":"Triage REQ-1002"}' ``` ```text "status": "completed", … "output": { "summary": "User unable to log in due to expired password with non-functional password reset email delivery.", "priority": "urgent" }, ``` To have `kindgi dev` register the model on every boot, in each worktree and after `--reset`, declare it in the pack's config: [Declare them in your pack's config](../../guides/models/#declare-them-in-your-packs-config). ### What the pack's image needs `kindgi build` turns the pack into an image. It installs your app's production dependencies with install scripts off, so your app's own `postinstall` and `prepare` scripts don't run there, and the build says so: ```text ✓ The app's own install scripts don't run in the image: postinstall (`prisma generate`) ``` When the image needs what such a script does, say so under `image` in `kindgi.config.ts`. **Prisma.** A tool that uses Prisma's client needs `prisma generate` in the image. Without it the build fails when it loads the pack: ```text kindgi-index: file-import-failed: Failed to import kindgi/tools/find-order/index.ts: Error: @prisma/client did not initialize yet. Please run "prisma generate" and try to import it again. ``` `prisma()` copies the schema into the image and runs `prisma generate` after the install: ```ts // in kindgi.config.ts import { prisma } from '@kindgi/sdk/build'; const config = { // pack, discovery and environments, as kindgi init wrote them image: { extensions: [prisma({ schema: 'prisma/schema.prisma' })], }, }; ``` With a `prisma.config.ts`, name it too: `prisma({ schema: 'prisma/schema.prisma', config: 'prisma.config.ts' })`. **Also under `image`:** * `systemPackages`: Debian packages the image needs, such as `['tesseract-ocr']`. * `buildEnv`: placeholder variables for the build steps and for loading your modules while building, such as `{ DATABASE_URL: 'postgresql://build-placeholder' }`. Never secrets: they're set while building only, and the final image doesn't have them. * **patch-package:** when a skipped script runs it, `kindgi build` prints the build step to add (`defineBuildExtension`, also from `@kindgi/sdk/build`). With pnpm, `pnpm patch` applies your patches in the install itself. The build ends with the image and a check of what's in it: ```text ✓ Built kindgi-pack/acme-orders:20261008.193804 (sha256:9e102b60f643…) ✓ /app/index.json in the image matches the local index byte for byte ``` [Self-host with Docker](../../deploy/self-host/) pushes, signs and deploys it. ## A Python app In the app's directory (where its `pyproject.toml` is): ```sh uv add --dev "kindgi-cli>=0.1,<0.2" # the CLI, pinned to Kindgi's minor version uv run kindgi init # --pack-id= if the app's name doesn't make one uv sync # or what it prints for Poetry or pip uv run kindgi dev ``` With Poetry, add the CLI with `poetry add --group dev "kindgi-cli>=0.1,<0.2"` and run it as `poetry run kindgi`. `init` edits your `pyproject.toml` in place, keeping its layout and comments: * it adds the `[tool.kindgi]` tables: the pack id from `[project].name`, discovery under `kindgi/`, and, in a Poetry app, `dev.python` set to run `poetry run python`; * it adds `kindgi` to `[project].dependencies`. Where it can't edit them (Poetry 1, or `dynamic` dependencies), it prints the command to run instead. Then the `kindgi/tools`, `guardrails`, `agents` and `flows` folders, the Python skills and `.gitignore` entries. An app with both a `package.json` and a `pyproject.toml` gets a TypeScript pack unless you pass `--template=python`. The app's root is on `sys.path`, so a tool imports your packages by name: kindgi/tools/orders.py ```python from typing import Any from pydantic import BaseModel from acme.orders import find_orders # your app's own code from kindgi import tool class Customer(BaseModel): customer_id: str class Orders(BaseModel): orders: list[dict[str, Any]] @tool(id="acme.customer-orders", mutating=False) def customer_orders(input: Customer) -> Orders: """The customer's orders, newest first.""" return Orders(orders=find_orders(input.customer_id)) ``` `mutating=False` says the tool only reads, so a dry run may call it. A package a tool imports must be in your app's main dependencies (`[project].dependencies`), not a dev group: the deployed pack installs without dev dependencies, so a dev-only import works under `kindgi dev` and fails once deployed. Inside `kindgi/`, import the pack's own modules relatively. Don't add an `__init__.py` to `kindgi/`: the folder would shadow the `kindgi` package. ## A Java app (Maven) Preview **Preview.** Java and Scala support is tested and supported, but its API may still change in 0.1.6 without the usual deprecation period. In the app's directory (where its `pom.xml` is), with a JDK 17 or later and `JAVA_HOME` set: ```sh npx --yes @kindgi/cli@0.1 init # --pack-id= if the app's artifactId doesn't make one ./kindgiw dev ``` `init` writes `kindgi.config.json`: the pack id from the app's `artifactId`, its version, the Kindgi CLI version it pins (`"cli"`, which `./kindgiw` runs, also written), and discovery under `kindgi` packages (`src/main/java/**/kindgi/tools/**/*.java`, and so on), so your app's own `tools` packages are never taken for Kindgi's. It leaves `pom.xml` alone and prints the dependency to add to it: ```xml com.kindgi kindgi-pack … ``` kindgi-pack comes from Maven Central, at the CLI's version. It needs Jackson 2.18 or later in your app (it declares 2.18.11): in a Spring Boot app, Spring Boot 3.4 or later. An app with both a `package.json` and a `pom.xml` gets a TypeScript pack unless you pass `--template=java`. `kindgi dev` runs the app's own Maven (its `mvnw`, else `mvn`; `dev.maven` in `kindgi.config.json` names another, such as `["mvn", "-s", "settings.xml"]`) and its JDK (`JAVA_HOME`, or `dev.javaHome`). `MAVEN_ARGS` and `MAVEN_OPTS` reach Maven, never the pack. A tool is a class in a `kindgi.tools` package under your own, and calls your app's code directly: src/main/java/com/acme/kindgi/tools/CustomerOrders.java ```java package com.acme.kindgi.tools; import com.acme.orders.OrderService; import com.kindgi.pack.Tool; import java.util.List; import java.util.Map; public final class CustomerOrders { public record Input(String customerId) {} public record Output(List> orders) {} public static final Tool TOOL = Tool.define("acme.customer-orders") .description("The customer's orders, newest first.") .input(Input.class) .output(Output.class) .mutating(false) .handler((input, ctx) -> new Output(OrderService.findOrders(input.customerId()))); private CustomerOrders() {} } ``` `mutating(false)` says the tool only reads, so a dry run may call it. A class your tool uses must be on the app's runtime classpath (`compile` or `runtime` scope, not `test` or `provided`): the pack's image copies the runtime dependencies only. Other public classes in a `kindgi.tools` package must define a tool; a helper there is package-private, or a record, an enum or an interface. ## A Scala app (sbt) Preview **Preview.** Java and Scala support is tested and supported, but its API may still change in 0.1.6 without the usual deprecation period. In the app's directory (where its `build.sbt` is), with a JDK 17 or later, `JAVA_HOME` set, and sbt: ```sh npx --yes @kindgi/cli@0.1 init # --pack-id= if the build's name doesn't make one ./kindgiw dev ``` `init` writes `kindgi.config.json`: the pack id from the build's `name`, its version, the Kindgi CLI version it pins (`"cli"`, which `./kindgiw` runs, also written), and discovery under `kindgi` packages (`src/main/scala/**/kindgi/tools/**/*.scala`, and so on), so your app's own `tools` packages are never taken for Kindgi's. It leaves `build.sbt` alone and prints what to add to it: ```scala libraryDependencies += "com.kindgi" %% "kindgi-pack-scala" % "…" ``` kindgi-pack-scala, and kindgi-pack under it, come from Maven Central at the CLI's version. Like kindgi-pack, it needs Jackson 2.18 or later in your app. An app with a `build.sbt` next to a `package.json` or a `pom.xml` gets a TypeScript or Java pack unless you pass `--template=scala`. `kindgi dev` builds through the app's sbt server (`sbt --client`): it starts one when none is running and stops it when it stops, and a server your IDE runs is used and left running. `dev.sbt` in `kindgi.config.json` names another sbt (such as `["sbt", "-mem", "2048"]`) and `dev.javaHome` another JDK. `SBT_OPTS` reaches sbt, never the pack. A tool is a `val` of an object in a `kindgi.tools` package under your own, named like its file, and calls your app's code directly: src/main/scala/com/acme/kindgi/tools/CustomerOrders.scala ```scala package com.acme.kindgi.tools import com.acme.orders.{Order, OrderService} import com.kindgi.pack.scaladsl._ object CustomerOrders { final case class Input(customerId: String) final case class Output(orders: Seq[Order]) val tool: Tool[Input, Output] = Tool[Input, Output]("acme.customer-orders") .description("The customer's orders, newest first.") .readOnly .handler((in, _) => Output(OrderService.findOrders(in.customerId))) } ``` `readOnly` says the tool only reads, so a dry run may call it. A class your tool uses must be on the app's runtime classpath (not `% Test` or `% Provided`): the pack's image ships the runtime classpath only. In a `kindgi.tools` package, a file with no object (a model) or a case class's companion is a helper; an object whose tool is a `def` or a `lazy val` is an error that asks for a `val`. The [Scala quickstart's known limits](../quickstart-scala/#known-limits) apply. ## Starting runs from your app Your app calls Kindgi over HTTP, through the SDK's client. ### Connect your app to the runtime In development, `kindgi dev` is enough: it writes the API's URL and a token to `.kindgirc.json`, and the client finds them there, with a one-time warning to set them. For production, and to silence the warning, give them to your app as `KINDGI_API_URL` and `KINDGI_API_TOKEN` in your env file (`.env` / `.env.local`). `kindgi dev` prints them (`API` and `Token` in its banner): .env.local ```sh KINDGI_API_URL=http://127.0.0.1:4000 KINDGI_API_TOKEN=kgi_bt_… ``` The client looks for them when it's first used, not when you create it. So `const kindgi = createClient()` at the top of a module is safe in a production build that runs without them: `next build`, for one, loads every route's module. If they're still missing when your app first uses the client, that use throws an error that names what to set. Python's `Kindgi()` and `AsyncKindgi()` do the same, so a module-scope `kindgi = Kindgi()` doesn't break a build step that imports your app, such as Django's `collectstatic`. The first request raises `ValueError`, naming what to set. The banner's first line, `Console`, is the console's address (`http://127.0.0.1:4000/console/`; `kindgi console` opens it). Sign in there with **Sign in as seeded user**, or with the same token. Open the console in Chrome or Firefox. Safari can't keep the local sign-in over http yet ([Known limitations](../../deploy/operate/#known-limitations-in-015)). The token stays the same when you restart `kindgi dev`. `kindgi dev --reset` starts the project over, dropping its database ([after asking](../install/#where-kindgi-dev-keeps-its-data)), with a new token: copy the new token after it (the client warns when the token in your env file no longer matches). In production they're your deployment's URL and API token. To pass them yourself instead: `createClient({ apiUrl, auth: { kind: 'apiToken', token } })`, or `Kindgi(url, token=…)` in Python. ### In a CommonJS app An app that isn't `"type": "module"`, or that compiles to CommonJS, loads the client with `require()`: ```js const { createClient } = require('@kindgi/sdk/client'); ``` * **Node 22.12 or later.** The SDK is ES modules, and Node's `require()` loads ES modules from 22.12 on. * **TypeScript that compiles to CommonJS:** TypeScript 5.8 or later, with `"module": "nodenext"`. In an app a bundler builds, `"moduleResolution": "bundler"` works too. With `"moduleResolution": "node10"` (or `"node"`), TypeScript doesn't find the SDK's types. Pack code (`kindgi/`) is ES modules in every app; `init` sets that up. ### Run an agent and read its answer src/lib/kindgi.ts ```ts import { createClient } from '@kindgi/sdk/client'; export const kindgi = createClient(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev ``` src/app/api/requests/\[id]/triage/route.ts ```ts import { KindgiApiError } from '@kindgi/sdk/client'; import { kindgi } from '@/lib/kindgi'; type Triage = { priority: 'low' | 'normal' | 'urgent'; summary: string }; export async function GET(_request: Request, { params }: { params: Promise<{ id: string }> }) { const { id } = await params; try { const run = await kindgi.runs.start({ agent: 'acme-support.triage', input: { userMessage: `Triage ${id}` }, }); const { output } = run.output as { output: Triage }; // the agent's typed answer return Response.json(output); } catch (error) { if (error instanceof KindgiApiError) { const code = error.error.code === 'server' ? error.error.serverCode : error.error.code; return Response.json({ error: code, message: error.error.message }, { status: 502 }); } throw error; } } ``` * An agent's input is `{ userMessage }`. `runs.start` waits for the answer. * `run.output.output` is the typed answer of an agent with an `output`; `run.output.response.content` is the answer as text. A flow's `run.output` is the flow's own output. * When the turn fails, or the API refuses the call, `runs.start` throws a `KindgiApiError`. `error.error.code` says what kind of failure it is (`not-found`, `auth`, `network`, …); for a failure on the server (`server`), `error.error.serverCode` is the server's own code, such as `output-schema-violation`. In Python: ```python from kindgi.client import Kindgi, KindgiApiError kindgi = Kindgi() # KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev try: run = kindgi.runs.start(agent="acme-support.triage", input={"userMessage": "Triage REQ-1002"}) triage = run.output["output"] except KindgiApiError as error: print(error.code, error.server_code, error.message) ``` ### Follow a run as it runs Start the run with `wait: false` and the call returns as soon as the run exists. Its events then arrive as they happen, through to its last one: src/app/api/requests/\[id]/triage/stream/route.ts ```ts import { kindgi } from '@/lib/kindgi'; export async function GET(_request: Request, { params }: { params: Promise<{ id: string }> }) { const { id } = await params; const run = await kindgi.runs.start({ agent: 'acme-support.triage', input: { userMessage: `Triage ${id}` }, options: { wait: false }, // returns as soon as the run exists }); const encoder = new TextEncoder(); const body = new ReadableStream({ async start(controller) { for await (const event of kindgi.runs.follow(run.id)) { controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`)); } controller.close(); }, }); return new Response(body, { headers: { 'content-type': 'text/event-stream' } }); } ``` Each event has a `kind` (`run.started`, `run.step-started`, `run.step-completed`, …, `run.completed`) and a `payload`; the `run.completed` event's `payload.output` is the run's output. In Python, `kindgi.runs.follow(run.id)` is an iterator of the same events, through to the run's end. A browser can also follow a run directly, with a short-lived read-only token, without your API token: see [Follow a run from the browser](../../guides/runs/follow-from-the-browser/). To be told when a run ends instead, have Kindgi send your app a signed webhook: see [Get a webhook when a run finishes](../../guides/webhooks/receive-run-finished/). ### Keep what a run did Store the run's id on your own row (a `kindgi_run_id` column), and read its status, output, steps and sources through the API when your app shows them. Never from Kindgi's database, and never by sending users to Kindgi's console: see [Show runs in your app](../../guides/runs/show-runs-in-your-app/). # Install > What you need on your machine, and how a project gets the Kindgi CLI and SDK. ## What you need * **For a TypeScript pack: Node 22.12 or later.** The `kindgi` CLI and the SDK run on Node. * **Docker** (Docker Desktop, or the Docker engine on Linux). `kindgi dev` runs the Kindgi runtime as a container, and a Postgres container for it: with `docker compose` when it's there, with plain `docker` otherwise. With your own database (Postgres 16 with pgvector, passed as `--database-url`), it starts no Postgres. * **For a Python pack:** Python 3.11 or later, and uv (or Poetry, or pip). The CLI comes from PyPI as `kindgi-cli`, with its own copy of Node, so you install no Node. Nothing else: the runtime is a container image that `kindgi dev` pulls and runs for you. ### Access to the runtime image The runtime image is in private preview: request access at . You get its pull credentials, a robot name and a token. Log in to its registry once with them. The CLI asks for the token without showing it, hands both to `docker login` (Kindgi keeps no copy), and checks that you can pull the image it runs: ```sh npx --yes @kindgi/cli@0.1 auth registry --username # without Node: uvx --from "kindgi-cli>=0.1,<0.2" kindgi auth registry --username ``` ```text ✓ You can pull quay.io/kindgi/runtime:…@sha256:…, the image this CLI runs. ``` The first `kindgi dev` then pulls the image (about 700 MB; `amd64` and `arm64`). Questions or trouble: . ### Check this machine `kindgi doctor` checks what Kindgi needs: Node (or Python and uv), Docker, access to the runtime image and, in a project's folder, the project, its dependencies, its model key, the runtime and a provider. Each check that fails says how to fix it, and it exits `1` until nothing does: ```sh npx --yes @kindgi/cli@0.1 doctor # in a project: pnpm exec kindgi doctor, or npx --no kindgi doctor uvx --from "kindgi-cli>=0.1,<0.2" kindgi doctor # without Node; in a Python project: uv run kindgi doctor ``` Here, with a Docker config whose `credsStore` names a helper Docker can't run: ```text ✓ Docker: Docker is running. ✗ Runtime image: Docker couldn't run its credential helper (docker-credential-kindgi-nope): ERROR: error getting credentials - err: exec: "docker-credential-kindgi-nope": executable file not found in $PATH, out: `` Fix: Docker's config (~/.docker/config.json, or the one in $DOCKER_CONFIG: "credsStore" or "credHelpers") names docker-credential-kindgi-nope, and Docker couldn't run it. Put it on your PATH (Docker Desktop on macOS keeps it in /Applications/Docker.app/Contents/Resources/bin), or remove that entry from the config, then run this again. ``` A `!` line is a warning: it doesn't fail, and things work now, but read it. The Provider check also asks the runtime about each registered provider (runtimes from 0.1.5). A registration the runtime can't build a provider from (one stored before the runtime checked registrations, say) is a warning, and a failure when no working provider is left. Each problem is on a line of its own: ```text ! Provider: 2 providers are registered: anthropic, openai. The runtime can't build openai from its registration, so agents only get the others. ✗ openai: /adapter_config/api: adapter_config.api must be one of responses, chat-completions. Fix: Unregister openai (pnpm exec kindgi providers unregister openai), then register it again: pnpm exec kindgi providers register --preset=openai. ``` `--json` gives the same checks as JSON, for a script or a coding agent ([Set up with a coding agent](../agent/) has an agent read it). A check's problems, such as each provider's above, are in its `details`. ## A TypeScript project The CLI is **project-local**: a project depends on `@kindgi/cli` as a devDependency and runs the version it pins, so everyone on the project runs the same one. `kindgi init` adds it, with `@kindgi/sdk`: ```sh npx @kindgi/cli init my-pack # a new pack; in an existing app: npx @kindgi/cli init ``` From then on, run `kindgi` through the project's package manager: | Package manager | Command | | --------------- | ---------------------- | | pnpm | `pnpm exec kindgi dev` | | npm | `npx --no kindgi dev` | | yarn | `yarn kindgi dev` | | bun | `bun run kindgi dev` | Use the scoped name, `@kindgi/cli`: there is no unscoped `kindgi` package on npm. ## A Python project The CLI is the `kindgi-cli` package on PyPI. Start a new pack with it, pinned to Kindgi's minor version: ```sh uvx --from "kindgi-cli>=0.1,<0.2" kindgi init my-pack --template=python cd my-pack uv sync ``` `init` puts the SDK, the `kindgi` package, in the pack's dependencies and `kindgi-cli` in its dev group, so `uv sync` installs both and everyone on the project runs the CLI it pins. From then on, in a Python project, every `kindgi ` in these docs is `uv run kindgi ` (with Poetry, `poetry run kindgi `). In an existing app, add both yourself: ```sh uv add kindgi # the SDK uv add --dev "kindgi-cli>=0.1,<0.2" # the CLI ``` With Poetry: `poetry add kindgi` and `poetry add --group dev "kindgi-cli>=0.1,<0.2"`. With pip: `pip install kindgi "kindgi-cli>=0.1,<0.2"`. ## Where `kindgi dev` keeps its data `kindgi dev` gives each project its own database in the Postgres it starts: `kindgi_`, and in a git worktree `kindgi___`. The project's name is `project` in the Kindgi config (`kindgi.config.ts`, or `[tool.kindgi]` in `pyproject.toml`); without it, the git repository's, the workspace root's or the pack folder's. It says which when it starts: ```text ✓ Project: acme-desk (the pack's folder name; set `project` in the Kindgi config to name it) ✓ Database: kindgi_acme_desk (created) in the bundled Postgres; to use your own: --database-url ``` * **Starting over:** `kindgi dev --reset` drops the project's database after asking, and makes a new token. `--yes` skips the question, for scripts; without a terminal to ask on and without `--yes`, it refuses. A database you pass with `--database-url` is never dropped. * **Several packs in one project** share its database and tenant, but not each other's tools: under `kindgi dev`, a flow in one pack can't call another pack's tools. * **Coming from Kindgi 0.1.2 or earlier,** where every project shared one database: that database stays as it was, and the project's first start says so once. Register your model providers and reviewers again in each project: ```text This project now has its own database, kindgi_acme_desk. The old shared `kindgi` database is left as it is: projects on an older Kindgi still use it. Providers and reviewers are set up once per project: set them up here again. ``` ## Calling Kindgi from an application An app that only starts runs and reads their results (no pack of its own) needs just the SDK's client: * **TypeScript:** `@kindgi/sdk`, and `createClient` from `@kindgi/sdk/client`. * **Python:** `kindgi`, and `Kindgi` (or `AsyncKindgi`) from `kindgi.client`. ## Next * [Quickstart: TypeScript](../quickstart-typescript/) * [Quickstart: Python](../quickstart-python/) * [Add Kindgi to an existing app](../existing-app/) # What is Kindgi? > Packs, the runtime, and how your app and your coding agent work with them. Kindgi runs the AI parts of your application (tools, agents, flows and guardrails) durably, on infrastructure you choose, and records every step they take. ## Your code, as a pack A **pack** is the Kindgi part of your app, written in the app's own language and living in its repository: * **Tools:** functions the app already has, exposed to agents and flows: a database lookup, a ticket write, an HTTP API. * **Agents:** a model with instructions and tools, answering with a typed result your code can rely on. * **Flows:** your process as steps and branches: an agent triages, a tool writes, an approval waits for a person. * **Guardrails:** checks on what an agent says or does before it counts. You write packs in **TypeScript** (`@kindgi/sdk`) or **Python** (the `kindgi` package). Tools call your app's own modules; nothing is rewritten for Kindgi. ## The runtime The Kindgi runtime runs agent turns and flows **durably**: each step is journaled, a run can be resumed, and long work continues in the background while your app carries on. Models come from providers you register: Anthropic, Gemini, or any OpenAI-compatible endpoint, including an open model served inside your own network. Your app talks to the runtime over HTTP: it starts runs, follows their events as they happen, and receives signed webhooks when they finish. ## Four ways in * **The CLI** (`@kindgi/cli`): create a pack, run it locally with `kindgi dev`, build and deploy it. * **The HTTP API:** every operation, for any language. * **The SDKs:** TypeScript and Python clients for the API. * **The console:** in your browser, at `/console/` on any runtime, and the runtime's own address leads there (with `kindgi dev`, `http://127.0.0.1:4000/console/`; `kindgi console` opens it, in Chrome or Firefox: Safari can't keep the local sign-in over http yet, see [Known limitations](../../deploy/operate/#known-limitations-in-015)). Each run has one page: what the agent was asked, the tools it called and their results, its answer, the model and the cost; its journal, one sentence per step; and where the answer came from. It also has approvals to approve or reject, conversations, and the agents and tools your pack defines. A project's pages show that project's records, with a switch to see all of them. ## Your coding agent `kindgi init` installs skills for Claude Code into the project, so your coding agent writes tools, agents, flows and guardrails the way Kindgi expects, without you learning a new API first. The skills also tell it which commands to run: `kindgi dev` to start Kindgi, `kindgi runs start` to try what it wrote, and the run's journal when something fails. [How it knows Kindgi](../coding-agents/). ## Where it runs * **On your machine:** `kindgi dev` runs the runtime as a container next to your pack's code and picks up every save. (The image is in private preview: request access at .) * **In your own cloud:** you deploy the same runtime image into your project; your data and prompts stay there. * **Kindgi Cloud:** we operate the runtime for you. It's in private preview. ## Status Kindgi is in **preview**: APIs may change between `0.x` releases. These docs are versioned with each release; the version menu shows which one you're reading. The SDKs are Apache-2.0. The runtime is source-available under the Business Source License 1.1. # Call Kindgi from a Java app > Start runs, follow them and read their answers from a Java application, with the Java client (preview). Your Java app starts runs, follows them and reads what they answered, through the Java client: every operation of the API, with typed answers and typed errors. Preview **Preview.** Java and Scala support is tested and supported, but its API may still change in 0.1.6 without the usual deprecation period. **Before you start:** a pack running under `kindgi dev`, such as the one from the [TypeScript quickstart](../quickstart-typescript/) (`my-pack`, with its `echo-agent`). The client finds that runtime by itself. ## 1. Get the client The client is on Maven Central, and needs Java 17 or later. In your app, it's one dependency, at your Kindgi release's version (the CLI's, `kindgi --version`): ```xml com.kindgi kindgi-client 0.1.6 ``` It brings `kindgi-models`, the API's records. It uses its own copy of Jackson, inside its jar, so your app's Jackson (its version and its settings) is left alone; that's checked with Spring Boot 3.2 to 4.1. ## 2. Start a run and follow it To try it, make a small app in a folder inside the pack, `my-pack/hello-java`: `Kindgi.create()` finds the running `kindgi dev` through the nearest `.kindgirc.json` above the folder the app runs in, which is the pack's. Its `pom.xml`: my-pack/hello-java/pom.xml ```xml 4.0.0 com.acme hello-java 1 17 com.acme.StartRun com.kindgi kindgi-client 0.1.6 ``` and this class: src/main/java/com/acme/StartRun.java ```java package com.acme; import com.kindgi.client.EventStream; import com.kindgi.client.Kindgi; import com.kindgi.client.models.Run; import com.kindgi.client.models.RunEvent; import com.kindgi.client.models.StartRunBody; import java.util.Map; public final class StartRun { public static void main(String[] args) { Kindgi client = Kindgi.create(); Run run = client.runs().start(StartRunBody.WithAgent.builder() .agent("my-pack.echo-agent") .input(Map.of("userMessage", "hi")) .build()); System.out.println("started " + run.id()); try (EventStream events = client.runs().follow(run.id())) { for (RunEvent event : events) { System.out.println(event.kind()); } } Run done = client.runs().get(run.id()); Map output = (Map) done.output(); Map reply = (Map) output.get("response"); System.out.println(done.status() + ": " + reply.get("content")); } } ``` Run it, with `kindgi dev` still running. This needs Maven 3.9 or later (`mvn`): ```sh cd my-pack/hello-java mvn -q compile exec:java ``` ```text Oct. 07, 2026 4:54:34 P.M. com.kindgi.client.internal.RuntimeSettings warnOnce WARNING: [kindgi] Using the running kindgi dev from …/my-pack/.kindgirc.json for KINDGI_API_URL and KINDGI_API_TOKEN. Set them in your env file (.env / .env.local), and in production, where there's no .kindgirc.json. started 0f5a3f72-7b3e-464c-9936-7ab4b4898e03 run.started run.step-started run.step-completed … run.completed completed: ⚠ dev-echo isn't a real model: it only repeats what it's given. Add an LLM provider key (Anthropic, OpenAI, Gemini, Groq, OpenRouter…) to get real answers. Tool responded: {"echo":"hi","echoedAt":"2026-10-07T20:54:35.179Z","characterCount":2} ``` The run's events come in as they happen, and `follow` ends with the run. A server ends one stream after 5 minutes; for a longer run, `follow` reconnects and carries on after the last event it read. (`runs().stream(runId)` is the plain operation, which ends when the server closes it.) The answer comes from `dev-echo`, the stand-in a new pack gets until you [connect a model](../quickstart-typescript/#6-connect-a-real-model). A run's `output` is the JSON its agent or flow answered, so the app reads it as maps. `Kindgi.create()` reads `KINDGI_API_URL` and `KINDGI_API_TOKEN`. When they're unset in development, it uses the running `kindgi dev` (the nearest `.kindgirc.json` above the folder it runs in) and logs a warning to put them in your env file, as the first lines show. In production (`KINDGI_ENV` or `NODE_ENV` set to `production`) it never reads `.kindgirc.json`. `Kindgi.builder()` sets the URL, the token, the timeout or your own `HttpClient`. ## 3. Find your way around **The methods follow the API's operations.** The operation `approvals.reviewers.list` is `client.approvals().reviewers().list()`; the [HTTP API reference](../../reference/api/) lists them all. A method takes the path's ids first, then the request body, then the query parameters as one record with a builder: ```java // in src/main/java/com/acme/Lists.java RunCollectionPage page = client.runs().list(RunsListParams.builder().limit(20L).build()); ``` **Every list has a lazy stream of all its pages:** `client.runs().listAll()` fetches the next page as you read on. **Errors are typed.** A refused call throws a `KindgiApiException`: `NotFoundException`, `ConflictException`, `InvalidRequestException`, `AuthException`, `RateLimitedException` and so on, with the server's own code in `serverCode()`: ```java // in src/main/java/com/acme/Lookup.java try { client.runs().get(runId); } catch (NotFoundException e) { System.out.println(e.serverCode()); // run-not-found } ``` A call that's safe to repeat (a read, or a call that takes an idempotency key, such as starting a run: the client adds one for you) is retried, with backoff, after a dropped connection, a timeout, or a 429, 502, 503 or 504 answer. **Async:** `client.async()` has the same methods, answering `CompletableFuture`s. ## 4. What the models do for you The answers are records, a union is a sealed interface you can switch on, and every request body has a builder that checks it against the API's rules before it's sent (a `ValidationException` lists what's wrong). A newer Kindgi runtime may answer with an enum value or a kind of object this client doesn't know yet. The Java client keeps it (`status().value()` is `UNRECOGNIZED`), where the Python client refuses the whole answer. More detail is in the client's [README](https://github.com/kindgi/kindgi-sdk/tree/@kindgi/sdk@0.1.6/sdks/java#readme): streams, updates that clear a property, your app's Jackson. # Quickstart: Java > Create a Java pack with tools, an agent, a guardrail and a flow, run it on your machine, and connect a real model. The same pack as the [TypeScript quickstart](../quickstart-typescript/), in Java: two tools, an agent that calls them, a guardrail and a flow. Its tools are records and lambdas, built with Maven. Preview **Preview.** Java and Scala support is tested and supported, but its API may still change in 0.1.6 without the usual deprecation period. **Before you start**, set up what the [Install page](../install/) describes: Docker, Node 22.12 or later, and access to the runtime image. The image is in private preview: request access at , then log in once with `kindgi auth registry`. You also need a JDK 17 or later, with `JAVA_HOME` set to it. Maven comes with the pack. ## 1. Create the pack ```sh npx --yes @kindgi/cli@0.1 init my-pack --template=java cd my-pack ./mvnw -q test # the template's tests: the tools, called directly ``` Without Node, run `init` from PyPI instead: `uvx --from "kindgi-cli>=0.1,<0.2" kindgi init my-pack --template=java`. The pack is a Maven project. Its config is `kindgi.config.json`; its tools, guardrails, agents and flows are classes in four packages: ```plaintext my-pack/ ├── kindgi.config.json # "language": "java", the pack's id and version ├── pom.xml # com.kindgi:kindgi-pack, Java 17 ├── mvnw, .mvn/ # the Maven wrapper ├── kindgiw, kindgiw.cmd # the Kindgi CLI wrapper (the version kindgi.config.json pins) ├── src/main/java/mypack/ │ ├── tools/Echo.java, tools/Greet.java # Tool.define(...) │ ├── guardrails/ResponseNotEmpty.java # Guardrail.define(...) │ ├── agents/EchoAgent.java # Agent.define(...) │ └── flows/EchoFlow.java # Flow.define(...) └── src/test/java/mypack/ToolsTest.java ``` The package comes from the pack's id (`my-pack` → `mypack`). A tool, a guardrail, an agent or a flow is a `public static final` field of a class in its package. The pack pins the Kindgi CLI it runs with, `"cli"` in `kindgi.config.json`, as Maven's wrapper pins Maven. Run the CLI as `./kindgiw` (`kindgiw.cmd` on Windows): it uses Node's `npx` when Node is installed, else `uvx` (no Node needed). `kindgi upgrade` moves the pin. ## 2. Run it ```sh ./kindgiw dev ``` `kindgi dev` checks the JDK (`JAVA_HOME`'s) and Maven (the pack's `mvnw`), and starts the Kindgi runtime in Docker. Maven compiles the pack, the Java indexer reads it, and its tools and checks run in a pack service. It recompiles on every save. It writes the API's URL and a token to `.kindgirc.json`, so the commands below find the runtime by themselves. Leave it running. ## 3. Run the agent and the flow In a second terminal, in `my-pack`: ```sh ./kindgiw runs start --agent=my-pack.echo-agent --input='{"userMessage":"Ada"}' ./kindgiw runs start --flow=my-pack.echo-flow --input='{"message":"Ada"}' ``` ```text "status": "completed", … ⚠ dev-echo answered, and it isn't a real model: it only repeats what it's given. … … "echo": "Ada", ``` Without a model, the agent's answer comes from `dev-echo`, a stand-in that calls the agent's first tool with `{"message": }` and replies with what it returned, after a first line that says it isn't a real model. The flow runs the `echo` tool on its input and returns what the tool returned. Either way, your Java tool ran: the runtime called it over HTTP in the pack service. dev-echo checks the wiring, nothing more It can't fill in any other tool input, and it can't produce a typed answer (an agent with an `output` fails with `output-schema-violation`). Connect a model ([step 5](#5-connect-a-real-model)) before you write an agent of your own. ## 4. Look at the code A tool is a field: its input and output are records, and their schemas come from the records (a Jakarta Validation constraint becomes its schema keyword). The pack service checks both on every call: src/main/java/mypack/tools/Echo.java ```java package mypack.tools; import com.kindgi.pack.Tool; import jakarta.validation.constraints.PositiveOrZero; import jakarta.validation.constraints.Size; import java.time.Instant; /** my-pack.echo — echoes the message back with a UTC timestamp and its length. */ public final class Echo { public record Input(@Size(min = 1, max = 500) String message) {} public record Output(String echo, Instant echoedAt, @PositiveOrZero int characterCount) {} public static final Tool TOOL = Tool.define("my-pack.echo") .description("Echoes the caller-provided message with a UTC timestamp and character count.") .input(Input.class) .output(Output.class) .mutating(false) .handler((input, ctx) -> new Output(input.message(), Instant.now(), input.message().length())); private Echo() {} } ``` `mutating(false)` says the tool only reads. A dry run may call it, and approval gates skip it. Left unset, a tool counts as one that writes. An agent is data, and it refers to the tools and the guardrail themselves: ```java // in src/main/java/mypack/agents/EchoAgent.java public static final Agent AGENT = Agent.define("my-pack.echo-agent") .version("0.1.0") .name("Echo Agent") .instructions("For each user message: if the user sends a name, greet them with the greet tool. " + "Otherwise echo their message with the echo tool. Quote the tool result verbatim.") .capability(Map.of("needs", List.of(Map.of("feature", "tool-use")))) .tool(Echo.TOOL) .tool(Greet.TOOL) .guardrail(ResponseNotEmpty.GUARDRAIL) .build(); ``` Save a file with a mistake in it, and `kindgi dev` says where, keeping the last good code running: ```text ✗ refresh failed [bundle-failed] The pack's code did not build: src/main/java/mypack/tools/Greet.java:20:36: cannot find symbol ``` Fix it, and the pack reloads: ```text ✓ loaded 5 primitives (2 tools, 1 guardrails, 1 agents, 1 flows) in 640ms ``` ## 5. Connect a real model Store an Anthropic key as a secret (you're prompted for it; it isn't echoed), then register the provider: ```sh ./kindgiw secrets set ANTHROPIC_API_KEY --env=local --scope=tenant ./kindgiw providers register --preset=anthropic ``` It takes over from `dev-echo` at the next turn. OpenAI (`OPENAI_API_KEY`, `--preset=openai`), Gemini (`GEMINI_API_KEY`, `--preset=gemini-api`), Groq (`GROQ_API_KEY`, `--preset=groq`) and OpenRouter (`OPENROUTER_API_KEY`, `--preset=openrouter`) work the same way; `kindgi providers presets` lists them, Gemini on Vertex AI too. Any OpenAI-compatible endpoint registers from a short spec file. The registration is in this project's dev database. To have `kindgi dev` register the model on every boot, in each worktree and after `--reset`, declare it in `kindgi.config.json` (`"providers": [{"preset": "anthropic"}]`): [Declare them in your pack's config](../../guides/models/#declare-them-in-your-packs-config). ## Next * [Add Kindgi to an existing Java app](../existing-app/#a-java-app-maven): your app's own classes as tools. * [Call Kindgi from a Java app](../java-app/): start runs and follow them with the Java client. * [Guides](../../guides/): one task at a time. * [Concepts](../../concepts/): packs, runs and the journal, security. * [kindgi-pack's README](https://github.com/kindgi/kindgi-sdk/tree/@kindgi/sdk@0.1.6/sdks/java/kindgi-pack): schemas from records, guardrails, flows, the call context and cancellation. # Quickstart: Python > Create a Python pack with tools, an agent, a guardrail and a flow, run it on your machine, and connect a real model. The same pack as the [TypeScript quickstart](../quickstart-typescript/), in Python: two tools, an agent that calls them, a guardrail and a flow. **Before you start**, set up what the [Install page](../install/) describes: Docker, Python 3.11 and uv, and access to the runtime image. No Node: the CLI comes from PyPI. The runtime image is in private preview: request access at , then log in once with `kindgi auth registry`. ## 1. Create the pack ```sh uvx --from "kindgi-cli>=0.1,<0.2" kindgi init my-pack --template=python cd my-pack uv sync # a .venv with the kindgi package and the kindgi CLI uv run pytest # the template's tests: the tools and the check, called directly ``` The pack's config is the `[tool.kindgi]` table of its `pyproject.toml`; its tools, guardrails, agents and flows live in four folders: ```plaintext my-pack/ ├── pyproject.toml # [tool.kindgi]: the pack's id, version and folders ├── tools/echo.py, tools/greet.py # @tool ├── guardrails/response_not_empty.py # @guardrail ├── agents/echo_agent.py # Agent(...) ├── flows/echo_flow.py # Flow(...) ├── tests/test_tools.py └── .claude/skills/ # skills for your coding agent ``` ## 2. Run it ```sh uv run kindgi dev ``` `kindgi dev` starts the Kindgi runtime in Docker, indexes the pack with the pack's own Python (`.venv/bin/python`), runs its tools and checks in a pack service, and reloads on every save. It writes the API's URL and a token to `.kindgirc.json`, so the commands below find the runtime by themselves. Leave it running. The first address its banner prints is the console's, where you can see each run: what it was asked, the steps it took and what it answered: ```text Console http://127.0.0.1:4000/console/ (open in your browser) Sign in: "Sign in as seeded user" on the sign-in page (the dev token, below) ``` Open it in your browser (the port is `4000`, or the next free one; the runtime's own address, `http://127.0.0.1:4000/`, leads there too), or run `uv run kindgi console`, which opens it for you. On the sign-in page, click **Sign in as seeded user**. `kindgi dev --open` opens the console as soon as Kindgi is up. Open the console in Chrome or Firefox. Safari can't keep the local sign-in over http yet ([Known limitations](../../deploy/operate/#known-limitations-in-015)). ## 3. Run the agent and the flow In a second terminal, in `my-pack`: ```sh uv run kindgi runs start --agent=my-pack.echo-agent --input='{"userMessage":"Ada"}' uv run kindgi runs start --flow=my-pack.echo-flow --input='{"message":"Ada"}' ``` ```text "status": "completed", … ⚠ dev-echo answered, and it isn't a real model: it only repeats what it's given. … … "echo": "Ada", ``` Without a model, the agent's answer comes from `dev-echo`, a stand-in that calls the agent's first tool with `{"message": }` and replies with what it returned, after a first line that says it isn't a real model (the run carries the `fallback-provider` and `dev-echo-not-a-model` warnings). The flow runs the `echo` tool on its input and returns what the tool returned. Either way, your Python tool ran: the runtime called it over HTTP in the pack service. dev-echo checks the wiring, nothing more It can't fill in any other tool input, and it can't produce a typed answer (an agent with an `output` fails with `output-schema-violation`). Connect a model ([step 5](#5-connect-a-real-model)) before you write an agent of your own. ## 4. Look at the code A tool is a typed Python function. Pydantic models are its input and output, checked on every call: tools/echo.py ```python from datetime import UTC, datetime from pydantic import BaseModel, Field from kindgi import tool class EchoInput(BaseModel): message: str = Field(min_length=1, max_length=500) class Echo(BaseModel): echo: str echoed_at: str = Field(alias="echoedAt") character_count: int = Field(alias="characterCount", ge=0) @tool(id="my-pack.echo") def echo(input: EchoInput) -> Echo: """Echoes the caller-provided message with a UTC timestamp and character count.""" return Echo( echo=input.message, echoedAt=datetime.now(UTC).isoformat(), characterCount=len(input.message), ) ``` An agent is data, and it refers to the tools themselves, not to strings: agents/echo_agent.py ```python from kindgi import Agent from ..guardrails.response_not_empty import response_not_empty from ..tools.echo import echo from ..tools.greet import greet echo_agent = Agent( id="my-pack.echo-agent", version="0.1.0", name="Echo Agent", description="Uses the pack's echo and greet tools; the response-not-empty guardrail guards the output.", instructions=( "For each user message: if the user sends a name, greet them with the greet tool. " "Otherwise echo their message with the echo tool. Quote the tool result verbatim." ), capabilities=[{"needs": [{"feature": "tool-use"}]}], tools=[echo, greet], guardrails=[response_not_empty], conversation_policy={"historyLimit": 10}, budget={"maxSteps": 4, "maxCostUsd": 0.1, "maxWallMs": 60_000}, ) ``` `uv run python -m kindgi.pack index --pack-dir .` prints what Kindgi sees. A file with an error is reported with its path, and the rest of the pack keeps serving. ## 5. Connect a real model Store an Anthropic key as a secret (you're prompted for it; it isn't echoed), then register the provider: ```sh uv run kindgi secrets set ANTHROPIC_API_KEY --env=local --scope=tenant uv run kindgi providers register --preset=anthropic ``` It takes over from `dev-echo` at the next turn. Gemini on Vertex AI has a preset too; any OpenAI-compatible endpoint registers from a short spec file. The registration is in this project's dev database. To have `kindgi dev` register the model on every boot, in each worktree and after `--reset`, declare it in `pyproject.toml`: [Declare them in your pack's config](../../guides/models/#declare-them-in-your-packs-config). ## Next * [Add Kindgi to an existing Python app](../existing-app/#a-python-app): your app's own modules as tools. * [Build a support desk](../../tutorials/support-desk-python/) in Python. * [Guides](../../guides/): one task at a time. * [Concepts](../../concepts/): packs, runs and the journal, security. * [The Python SDK reference](../../reference/python/). * [Set up your coding agent](../coding-agents/): it already has Kindgi's skills. # Quickstart: Scala > Create a Scala pack with tools, an agent, a guardrail and a flow, run it on your machine, and connect a real model. The same pack as the [TypeScript quickstart](../quickstart-typescript/), in Scala: two tools, an agent that calls them, a guardrail and a flow. Its tools are case classes and functions, built with sbt, on Scala 3 (2.13 works too). Preview **Preview.** Java and Scala support is tested and supported, but its API may still change in 0.1.6 without the usual deprecation period. **Before you start**, set up what the [Install page](../install/) describes: Docker, Node 22.12 or later, and access to the runtime image. The image is in private preview: request access at , then log in once with `kindgi auth registry`. You also need a JDK 17 or later, with `JAVA_HOME` set to it, and [sbt](https://www.scala-sbt.org/download) 1.10 or later. ## 1. Create the pack ```sh npx --yes @kindgi/cli@0.1 init my-pack --template=scala cd my-pack sbt -batch test # the template's tests: the tools, called directly ``` Without Node, run `init` from PyPI instead: `uvx --from "kindgi-cli>=0.1,<0.2" kindgi init my-pack --template=scala`. The pack is an sbt project. Its config is `kindgi.config.json`; its tools, guardrails, agents and flows are objects in four packages: ```plaintext my-pack/ ├── kindgi.config.json # "language": "scala", the pack's id and version ├── build.sbt, project/build.properties # kindgi-pack-scala, Scala 3.3, Java 17 ├── kindgiw, kindgiw.cmd # the Kindgi CLI wrapper (the version kindgi.config.json pins) ├── src/main/scala/mypack/ │ ├── tools/Echo.scala, tools/Greet.scala # Tool[Input, Output](...) │ ├── guardrails/ResponseNotEmpty.scala # Guardrail[Config](...) │ ├── agents/EchoAgent.scala # Agent(...) │ └── flows/EchoFlow.scala # Flow(...) └── src/test/scala/mypack/ToolsSuite.scala ``` The package comes from the pack's id (`my-pack` → `mypack`). A tool, a guardrail, an agent or a flow is a `val` of an object named like its file: `Echo.scala` holds `object Echo`. The pack pins the Kindgi CLI it runs with, `"cli"` in `kindgi.config.json`. Run the CLI as `./kindgiw` (`kindgiw.cmd` on Windows): it uses Node's `npx` when Node is installed, else `uvx` (no Node needed). `kindgi upgrade` moves the pin, and kindgi-pack-scala's version in `build.sbt` with it. ## 2. Run it ```sh ./kindgiw dev ``` `kindgi dev` checks the JDK (`JAVA_HOME`'s) and sbt, and starts the Kindgi runtime in Docker. sbt compiles the pack through its server (`sbt --client`), kindgi-pack's indexer reads it, and its tools and checks run in a pack service. The first build starts the server and loads the build; after that, a save compiles in about a second. `kindgi dev` starts that server when none is running and stops it when it stops; a server you already run (your IDE's) is used and left running. It writes the API's URL and a token to `.kindgirc.json`, so the commands below find the runtime by themselves. Leave it running. ## 3. Run the agent and the flow In a second terminal, in `my-pack`: ```sh ./kindgiw runs start --agent=my-pack.echo-agent --input='{"userMessage":"Ada"}' ./kindgiw runs start --flow=my-pack.echo-flow --input='{"message":"Ada"}' ``` ```text "status": "completed", … ⚠ dev-echo answered, and it isn't a real model: it only repeats what it's given. … … "echo": "Ada", ``` Without a model, the agent's answer comes from `dev-echo`, a stand-in that calls the agent's first tool with `{"message": }` and replies with what it returned, after a first line that says it isn't a real model. The flow runs the `echo` tool on its input and returns what the tool returned. Either way, your Scala tool ran: the runtime called it over HTTP in the pack service. dev-echo checks the wiring, nothing more It can't fill in any other tool input, and it can't produce a typed answer (an agent with an `output` fails with `output-schema-violation`). Connect a model ([step 5](#5-connect-a-real-model)) before you write an agent of your own. ## 4. Look at the code A tool is a `val`: its input and output are case classes, and their schemas come from them. `Option` is optional, a parameter's default is the schema's default, and a Jakarta Validation constraint becomes its schema keyword. The pack service checks both on every call: src/main/scala/mypack/tools/Echo.scala ```scala package mypack.tools import com.kindgi.pack.scaladsl._ import jakarta.validation.constraints.{PositiveOrZero, Size} import java.time.Instant /** my-pack.echo — echoes the message back with a UTC timestamp and its length. */ object Echo { final case class Input(@Size(min = 1, max = 500) message: String) final case class Output(echo: String, echoedAt: Instant, @PositiveOrZero characterCount: Int) val tool: Tool[Input, Output] = Tool[Input, Output]("my-pack.echo") .description("Echoes the caller-provided message with a UTC timestamp and character count.") .readOnly .handler((in, _) => Output(in.message, Instant.now(), in.message.length)) } ``` `readOnly` says the tool only reads. A dry run may call it, and approval gates skip it. Left out, a tool counts as one that writes. A tool built on futures uses `handlerAsync`, which takes a `Future`. An agent is data, and it refers to the tools and the guardrail themselves: ```scala // in src/main/scala/mypack/agents/EchoAgent.scala val agent: Agent = Agent("my-pack.echo-agent") .version("0.1.0") .name("Echo Agent") .instructions( "For each user message: if the user sends a name, greet them with the greet tool. " + "Otherwise echo their message with the echo tool. Quote the tool result verbatim.") .capability(Map("needs" -> List(Map("feature" -> "tool-use")))) .tool(Echo.tool) .tool(Greet.tool) .guardrail(ResponseNotEmpty.guardrail) .build() ``` Save a file with a mistake in it, and `kindgi dev` says where, keeping the last good code running: ```text ✗ refresh failed [bundle-failed] The pack's code did not build: src/main/scala/mypack/tools/Greet.scala:16:31: Type Mismatch Error: Found: (42 : Int); Required: String ``` Fix it, and the pack reloads: ```text ✓ loaded 5 primitives (2 tools, 1 guardrails, 1 agents, 1 flows) in 1093ms ``` ### Known limits * **Scala's primitives inside a type parameter** (`Seq[Int]`, `Option[Long]`) are `Object` on the JVM, so the schema allows any JSON value there. Use a case class (`Seq[Item]`), or give the schema yourself with `Tool.json`. * **In Scala 3, name a Java annotation's arguments:** `@Min(value = 0)`, not `@Min(0)`. Scala 3 passes a positional argument to the wrong element. * **A Scala 3 enum whose cases take parameters** is a sealed hierarchy, not a string: describe it with `@JsonTypeInfo` and `@JsonSubTypes`. A simple enum (`enum Color { case Red, Green }`) is a string of its case names. * **jackson-module-scala must match your app's `jackson-databind` minor version**, and refuses another. If your app uses a newer Jackson, depend on the matching `jackson-module-scala` yourself. * **A tool defined as a `def` or a `lazy val`** isn't read without running it: the indexer says so, and asks for a `val`. ## 5. Connect a real model Store an Anthropic key as a secret (you're prompted for it; it isn't echoed), then register the provider: ```sh ./kindgiw secrets set ANTHROPIC_API_KEY --env=local --scope=tenant ./kindgiw providers register --preset=anthropic ``` It takes over from `dev-echo` at the next turn. OpenAI (`OPENAI_API_KEY`, `--preset=openai`), Gemini (`GEMINI_API_KEY`, `--preset=gemini-api`), Groq (`GROQ_API_KEY`, `--preset=groq`) and OpenRouter (`OPENROUTER_API_KEY`, `--preset=openrouter`) work the same way; `kindgi providers presets` lists them, Gemini on Vertex AI too. Any OpenAI-compatible endpoint registers from a short spec file. The registration is in this project's dev database. To have `kindgi dev` register the model on every boot, in each worktree and after `--reset`, declare it in `kindgi.config.json` (`"providers": [{"preset": "anthropic"}]`): [Declare them in your pack's config](../../guides/models/#declare-them-in-your-packs-config). ## Next * [Add Kindgi to an existing Scala app](../existing-app/#a-scala-app-sbt): your app's own classes as tools. * [Guides](../../guides/): one task at a time. * [Concepts](../../concepts/): packs, runs and the journal, security. * [kindgi-pack-scala's README](https://github.com/kindgi/kindgi-sdk/tree/@kindgi/sdk@0.1.6/sdks/scala): schemas from Scala's types, guardrails, flows, futures and the limits. # Quickstart: TypeScript > Create a pack with tools, an agent, a guardrail and a flow, run it on your machine, and connect a real model. In ten minutes: a pack with two tools, an agent that calls them, a guardrail on its answers and a flow, running on your machine. **Before you start**, set up what the [Install page](../install/) describes: Node 22.12, Docker, and access to the runtime image. The image is in private preview: request access at , then log in once with `kindgi auth registry`. ## 1. Create the pack ```sh npx @kindgi/cli init my-pack --template=sample cd my-pack pnpm install ``` `my-pack` is the pack's id: every tool, agent and flow in it is named `my-pack.`. The `sample` template gives you: ```plaintext my-pack/ ├── kindgi.config.ts # the pack's id, version and folders ├── tools/echo/index.ts # a tool: echoes a message ├── tools/greet/index.ts # a tool: greets a name ├── tools/fetch-httpbin/index.ts # a tool that calls an HTTP API ├── agents/echo-agent/index.ts # an agent that calls the tools ├── guardrails/response-not-empty/ # a check on the agent's answers ├── flows/echo-flow/index.ts # a flow: a tool step, then the agent └── .claude/skills/ # skills for your coding agent ``` (The default template, `minimal`, gives you the folders and none of the examples.) ## 2. Run it ```sh pnpm exec kindgi dev ``` `kindgi dev` starts the Kindgi runtime in Docker, indexes the pack, registers every tool, agent, guardrail and flow, and does it again on every save. It prints the API's URL and a token, and writes them to `.kindgirc.json` in the pack, so the commands below find the runtime by themselves. Leave it running. The first address its banner prints is the console's, where you can see each run: what it was asked, the steps it took and what it answered: ```text Console http://127.0.0.1:4000/console/ (open in your browser) Sign in: "Sign in as seeded user" on the sign-in page (the dev token, below) ``` Open it in your browser (the port is `4000`, or the next free one; the runtime's own address, `http://127.0.0.1:4000/`, leads there too), or run `pnpm exec kindgi console`, which opens it for you. On the sign-in page, click **Sign in as seeded user**. `kindgi dev --open` opens the console as soon as Kindgi is up. Open the console in Chrome or Firefox. Safari can't keep the local sign-in over http yet ([Known limitations](../../deploy/operate/#known-limitations-in-015)). ## 3. Run the agent In a second terminal, in `my-pack`: ```sh pnpm exec kindgi runs start --agent=my-pack.echo-agent --input='{"userMessage":"hi"}' ``` ```text "status": "completed", … ⚠ dev-echo answered, and it isn't a real model: it only repeats what it's given. … ``` There's no model yet, so the answer comes from `dev-echo`, a stand-in a new pack gets: it calls the agent's first tool with `{"message": }` and replies with what the tool returned, after a first line that says it isn't a real model. The run carries the `fallback-provider` and `dev-echo-not-a-model` warnings, and the command prints the second on its stderr, with how to add a key. That's enough to see the whole path: the agent's turn, the tool call into your code, the guardrail's check. dev-echo checks the wiring, nothing more It can't fill in any other tool input, and it can't produce a typed answer (an agent with an `output` schema fails with `output-schema-violation`). Connect a model ([step 6](#6-connect-a-real-model)) before you write an agent of your own. ## 4. Run the flow ```sh pnpm exec kindgi runs start --flow=my-pack.echo-flow --input='{"name":"Ada"}' ``` ```text "status": "completed", … "greeting": "Hello, Ada!" ``` The flow greets the name with the `greet` tool, then hands the greeting to the agent and returns both. Add `--dry-run` to see which steps would run without running the tools that change anything. ## 5. Look at the code A tool is a typed function. Its input and output are schemas, checked on every call: tools/greet/index.ts ```ts import { defineTool } from '@kindgi/sdk/define'; import type { ToolId } from '@kindgi/sdk/types'; import { z } from 'zod'; const GreetInput = z.object({ name: z.string().min(1).max(100), greeting: z.string().min(1).max(50).default('Hello'), }); const GreetOutput = z.object({ message: z.string(), }); const defined = defineTool({ id: 'my-pack.greet' as ToolId, description: 'Formats a greeting for the named recipient.', version: '0.1.0', input: GreetInput, output: GreetOutput, effects: [], mutating: false, handler: async (input) => ({ message: `${input.greeting}, ${input.name}!`, }), }); if (defined.kind === 'err') { throw new Error(`my-pack.greet failed to compile: ${defined.error.message}`); } export default defined.value; ``` `mutating: false` says the tool changes nothing, so a dry run calls it and approval gates don't stop it by default. Leave it out for a tool that writes, sends or charges anything: a tool is treated as changing something unless it says otherwise. `effects` names what a tool does outside your code (writes, network calls), for policies and the audit trail. The agent is data: its instructions, the tools it may call, the guardrails on its answers, its budget. Change a file and save; `kindgi dev` picks it up. ## 6. Connect a real model Store an Anthropic key as a secret (you're prompted for it; it isn't echoed, and it goes in Kindgi's own `.kindgi/secrets.env`), then register the provider: ```sh pnpm exec kindgi secrets set ANTHROPIC_API_KEY --env=local --scope=tenant pnpm exec kindgi providers register --preset=anthropic ``` It takes over from `dev-echo` at the next turn. Run the agent again and it answers with a real model, still calling your tools. Gemini on Vertex AI has a preset too (`--preset=gemini --project=`); any OpenAI-compatible endpoint (vLLM, llama.cpp, Ollama, OpenRouter) registers from a short spec file. The registration is in this project's dev database. To have `kindgi dev` register the model on every boot, in each worktree and after `--reset`, declare it in `kindgi.config.ts`: [Declare them in your pack's config](../../guides/models/#declare-them-in-your-packs-config). ## Next * [Build a support desk](../../tutorials/support-desk-typescript/): tools over your own code, a typed answer, a flow that acts on it. * [Guides](../../guides/): one task at a time: tools, agents, models, flows, runs, webhooks, approvals, secrets. * [Add Kindgi to an existing app](../existing-app/): your app's own code as tools, and your app starting runs. * [Concepts](../../concepts/): packs, runs and the journal, security. * [Set up your coding agent](../coding-agents/): it already has Kindgi's skills.