# 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](https://docs.kindgi.com/v0.1.8/start/quickstart-typescript.md), in Python: two tools, an agent that calls them, a guardrail and a flow.

**Before you start**, set up what the [Install page](https://docs.kindgi.com/v0.1.8/start/install.md) describes: Docker, Python 3.11 and uv, and access to the runtime image. No Node: the CLI comes from PyPI. Sign in at [access.kindgi.com](https://access.kindgi.com/) with GitHub for the runtime image's pull credentials, 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
```

- `uvx --from "kindgi-cli>=0.1,<0.2" kindgi` runs the newest 0.1 release of the Kindgi CLI from PyPI, the line these docs describe, without installing it ([Check this machine](https://docs.kindgi.com/v0.1.8/start/install.md#check-this-machine) says how). The range is optional: `uvx --from kindgi-cli kindgi init my-pack --template=python` runs the newest release, which may be on a later line than these docs.
- `init my-pack` makes the pack in a new folder, `my-pack`.
- `--template=python` makes it a Python pack, with examples.

`init` prints what it made. `uv sync` installs the pack's dependencies, and the template's tests pass:

```text excerpt
…
✓ Python pack scaffolded at …/my-pack/
…
Installed 26 packages in 206ms
…
============================== 3 passed in 0.82s ===============================
```

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
└── .agents/skills/                   # the same, where Codex and Gemini CLI look
```

> **Tip: Or ask your coding agent**
>
> The pack already has Kindgi's skills in `.claude/skills/` and `.agents/skills/`. Follow the steps below yourself, or ask your coding agent ("run the pack and try the agent", "add a tool that looks up an order"): the skills tell it which commands to run. [How it knows Kindgi](https://docs.kindgi.com/v0.1.8/start/coding-agents.md).

## 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 next commands 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)
```

To open the console, run `uv run kindgi console`, or go to the banner's `Console` address. Its port is `4000`, or the next free one. On the sign-in page, click **Sign in as seeded user**.

> **Tip**
>
> `kindgi dev --open` opens the console as soon as Kindgi is up.

## 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"}'
```

- `--agent=my-pack.echo-agent` and `--flow=my-pack.echo-flow` name what to run, by its id.
- `--input` is the run's input, as JSON. An agent takes the person's message as `userMessage`.

```text excerpt
  "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. It calls the agent's first tool with `{"message": <your userMessage>}`, 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. 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.

> **Caution: 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](https://docs.kindgi.com/v0.1.8/start/quickstart-python.md#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 in the pack. `--pack-dir .` is the pack's folder, here the current one. A file with an error is reported with its path, and the rest of the pack keeps serving.

## 5. Connect a real model

Store your AI model provider's API key as a secret, then register the provider:

```sh
uv run kindgi secrets set <KEY_NAME> --env=local --scope=tenant
uv run kindgi providers register --preset=<preset>
```

`<KEY_NAME>` and `<preset>` depend on the provider:

| Provider                        | `<KEY_NAME>`         | `<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` |

- `secrets set <KEY_NAME>` asks for the key without showing it, and writes it to Kindgi's own `.kindgi/secrets.env`.
- `--env=local` is the environment the secret belongs to. Under `kindgi dev`, `local` is the pack's env files, that one among them.
- `--scope=tenant` is where the secret lives: the tenant, rather than one org (`org:<orgId>`) or project (`project:<projectId>`).
- `--preset=<preset>` registers the provider's models from Kindgi's built-in preset, which reads the `<KEY_NAME>` secret. `kindgi providers presets` lists every preset.

With Anthropic's key, for example, the two commands print:

```text excerpt
…
  Set ANTHROPIC_API_KEY at tenant in local.
…
✓ Registered anthropic: claude-opus-5-5, claude-sonnet-5-5 (default), claude-haiku-5-5, claude-haiku-4-5 — key ANTHROPIC_API_KEY (env local)
```

The provider 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](https://docs.kindgi.com/v0.1.8/guides/models.md#declare-them-in-your-packs-config).

## Next

- [Add Kindgi to an existing Python app](https://docs.kindgi.com/v0.1.8/start/existing-app-python.md): your app's own modules as tools.
- [Build a support desk](https://docs.kindgi.com/v0.1.8/tutorials/support-desk-python.md) in Python.
- [Guides](https://docs.kindgi.com/v0.1.8/guides.md): one task at a time.
- [Concepts](https://docs.kindgi.com/v0.1.8/concepts.md): packs, runs and the journal, security.
- [The Python SDK reference](https://docs.kindgi.com/v0.1.8/reference/python.md).
- [Set up your coding agent](https://docs.kindgi.com/v0.1.8/start/coding-agents.md): it already has Kindgi's skills.
