@kindgi/sdk 0.0.0-bootstrap.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +187 -1
  3. package/dist/build.d.ts +10 -0
  4. package/dist/build.d.ts.map +1 -0
  5. package/dist/build.js +11 -0
  6. package/dist/build.js.map +1 -0
  7. package/dist/client.d.ts +33 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +55 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/define.d.ts +25 -0
  12. package/dist/define.d.ts.map +1 -0
  13. package/dist/define.js +37 -0
  14. package/dist/define.js.map +1 -0
  15. package/dist/index.d.ts +17 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +19 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/runtime-config.d.ts +53 -0
  20. package/dist/runtime-config.d.ts.map +1 -0
  21. package/dist/runtime-config.js +114 -0
  22. package/dist/runtime-config.js.map +1 -0
  23. package/dist/types.d.ts +16 -0
  24. package/dist/types.d.ts.map +1 -0
  25. package/dist/types.js +4 -0
  26. package/dist/types.js.map +1 -0
  27. package/dist/webhooks.d.ts +15 -0
  28. package/dist/webhooks.d.ts.map +1 -0
  29. package/dist/webhooks.js +16 -0
  30. package/dist/webhooks.js.map +1 -0
  31. package/package.json +88 -4
  32. package/skills/kindgi-authoring-agents/SKILL.md +252 -0
  33. package/skills/kindgi-authoring-flows/SKILL.md +302 -0
  34. package/skills/kindgi-authoring-guardrails/SKILL.md +297 -0
  35. package/skills/kindgi-authoring-mcp-servers/SKILL.md +289 -0
  36. package/skills/kindgi-authoring-providers/SKILL.md +705 -0
  37. package/skills/kindgi-authoring-tools/SKILL.md +298 -0
  38. package/skills/kindgi-framework-feedback/SKILL.md +211 -0
  39. package/skills/kindgi-getting-started/SKILL.md +189 -0
  40. package/skills/kindgi-python-authoring-agents/SKILL.md +205 -0
  41. package/skills/kindgi-python-authoring-flows/SKILL.md +325 -0
  42. package/skills/kindgi-python-authoring-guardrails/SKILL.md +176 -0
  43. package/skills/kindgi-python-authoring-tools/SKILL.md +305 -0
  44. package/skills/kindgi-python-getting-started/SKILL.md +242 -0
  45. package/src/build.ts +18 -0
  46. package/src/client.ts +177 -0
  47. package/src/define.ts +75 -0
  48. package/src/index.ts +20 -0
  49. package/src/runtime-config.ts +180 -0
  50. package/src/types.ts +86 -0
  51. package/src/webhooks.ts +34 -0
@@ -0,0 +1,305 @@
1
+ ---
2
+ name: kindgi-python-authoring-tools
3
+ description: >
4
+ Covers writing tools for a Kindgi pack in Python (the `kindgi`
5
+ package): the `@tool` decorator, input and output schemas from pydantic
6
+ models / TypedDicts / dataclasses (or JSON Schema), sync and async
7
+ handlers, `ToolContext` and cancellation, reading configuration and
8
+ secrets, errors, tool id and version conventions, unit tests, and
9
+ wiring a tool onto an agent. Load this whenever you are authoring or
10
+ editing code inside a Python pack's tools/ directory (a pack whose
11
+ config is `[tool.kindgi]` in pyproject.toml), defining a tool, or
12
+ wiring one onto an agent. Python agents are covered by
13
+ kindgi-python-authoring-agents, getting started by
14
+ kindgi-python-getting-started.
15
+ type: core
16
+ library: "kindgi (Python)"
17
+ version: "0.1.1"
18
+ sdk_version: "0.0.0"
19
+ pack_languages: [python]
20
+ sources:
21
+ - sdks/python/src/kindgi/pack/define.py
22
+ - sdks/python/src/kindgi/pack/context.py
23
+ - sdks/python/src/kindgi/pack/service.py
24
+ ---
25
+
26
+ # Authoring Kindgi tools in Python
27
+
28
+ > **Running `kindgi`:** a Python pack has no Node project, so the
29
+ > `kindgi` CLI is the one on `PATH`. Python commands run in the pack's
30
+ > environment: `uv run …` (or `.venv/bin/python …`).
31
+
32
+ A **tool** is a unit of work an agent (or a flow step) calls: typed
33
+ input, typed output, your code in between. In a Python pack it is a
34
+ function decorated with `@tool` at module level in a file under
35
+ `tools/`. Kindgi runs it in the pack's own Python process (the pack
36
+ service) and calls it over HTTP; the model sees its id, description and
37
+ input schema.
38
+
39
+ Before writing one, establish what it should **do** — what it computes
40
+ or fetches, what the caller provides, what it returns. "Add a tool" is
41
+ a conversation opener. The pack's sample tools prove the runtime works;
42
+ they are not the shape to copy unless the user asks.
43
+
44
+ ## A tool
45
+
46
+ ```python
47
+ # tools/citations.py
48
+ import os
49
+
50
+ from pydantic import BaseModel, Field
51
+
52
+ from kindgi import ToolContext, tool
53
+
54
+ from ._citator import lookup # a helper module: the leading `_` keeps it out of discovery
55
+
56
+
57
+ class Citation(BaseModel):
58
+ citation: str = Field(min_length=1)
59
+ jurisdiction: str = Field(pattern="^(US|UK|EU)$")
60
+
61
+
62
+ class Verdict(BaseModel):
63
+ found: bool
64
+ canonical_cite: str | None = Field(None, alias="canonicalCite")
65
+
66
+
67
+ @tool(id="acme.verify-citation")
68
+ def verify_citation(citation: Citation, ctx: ToolContext) -> Verdict:
69
+ """Verify a legal citation against the citator; returns whether it resolves and its canonical form."""
70
+ hit = lookup(os.environ["CITATOR_URL"], citation.citation, citation.jurisdiction)
71
+ return Verdict(found=hit is not None, canonicalCite=hit)
72
+ ```
73
+
74
+ - **Id** — `<pack-id>.<tool-name>`, kebab-case, dot-namespaced.
75
+ - **Description** — the docstring, or `description=`. The model reads
76
+ it to decide when to call the tool: say what it does and returns.
77
+ - **Version** — the pack's version, or `version=` (an exact semver).
78
+ - **Schemas** — from the annotations: the first parameter is the input,
79
+ the return annotation the output. A pydantic model, a `TypedDict`, a
80
+ dataclass — anything pydantic understands — or `input=` / `output=`
81
+ (a type, or a JSON Schema dict). **Field aliases are the names on the
82
+ wire** — use them for camelCase (`alias="canonicalCite"`), and
83
+ construct the model with the alias. The input must be an **object**:
84
+ a model calls a tool with an object of arguments.
85
+ - **Handler** — `(input)` or `(input, ctx)`; `def` or `async def`. A
86
+ `def` handler runs in a worker thread, so blocking I/O is fine; an
87
+ `async def` one runs on the event loop — don't block it (use an async
88
+ client, or `asyncio.to_thread`).
89
+ - **Validation** — before your handler runs, the input is checked
90
+ against the schema (JSON Schema defaults filled in) and your model's
91
+ own validators run; what you return is validated against the output
92
+ schema. A bad input comes back as `input-validation-failed` with the
93
+ field's path (a bad output as `output-validation-failed`); the agent's
94
+ `tool_errors` policy decides whether the model gets to fix the call.
95
+ - **Problems in the declaration** (a missing docstring, an
96
+ unannotated parameter, a non-object input) raise `DefinitionError`
97
+ where the tool is declared; the indexer reports it with the file.
98
+
99
+ ## `ToolContext`
100
+
101
+ - `ctx.tenant_id` — the tenant the call is for. Key any per-tenant
102
+ state by it.
103
+ - `ctx.run_id` — the run (an agent turn or a flow step) the call belongs to.
104
+ - `ctx.request_id` — this call, e.g. the model's tool-call id; useful
105
+ for logs and idempotency keys.
106
+ - `ctx.cancellation` — fires when the call's deadline passes or the
107
+ caller disconnects. An `async def` handler is also cancelled at its
108
+ next `await`. A `def` handler keeps running in its thread: check
109
+ `ctx.cancellation.cancelled`, call `ctx.cancellation.raise_if_cancelled()`,
110
+ or wait with `ctx.cancellation.wait(timeout)` between slow steps.
111
+ - `ctx.secrets` — the secrets the tool declares in `needs_spec`,
112
+ resolved for the call's tenant (below).
113
+ - `ctx.env`, `ctx.config` — **reserved, empty today**.
114
+
115
+ ## Configuration and secrets
116
+
117
+ A secret that belongs to the tenant — an API key a customer gives you —
118
+ is declared, and read from `ctx.secrets`:
119
+
120
+ ```python
121
+ @tool(
122
+ id="acme.verify-citation",
123
+ needs_spec={"secrets": {"CITATOR_KEY": {"type": "string", "minLength": 20}}},
124
+ )
125
+ def verify_citation(citation: Citation, ctx: ToolContext) -> Verdict:
126
+ """…"""
127
+ key = ctx.secrets["CITATOR_KEY"]
128
+ ```
129
+
130
+ The runtime resolves every declared secret on every call — for the
131
+ call's tenant, in its env (`KINDGI_ENV`; in `kindgi dev`, `local`: the
132
+ pack's `.env` and `.env.local`) — checks it against its schema, and
133
+ fails the call, naming the secret, when it is missing or doesn't match.
134
+ Every declared secret is required. In a test, pass them:
135
+ `ToolContext.for_test(secrets={"CITATOR_KEY": "…"})`.
136
+
137
+ Everything else comes from the process environment: `os.environ["CITATOR_URL"]`.
138
+ The pack service runs with the pack's environment — in `kindgi dev`
139
+ that is the pack's `.env` and `.env.local` (or `[tool.kindgi.dev]
140
+ envFiles`), restarted when they change; nothing else from your shell
141
+ reaches it except `PATH`, `HOME` and `TMPDIR`. Put a secret there by
142
+ hand or with `kindgi secrets set NAME --env=local --scope=tenant` (a
143
+ no-echo prompt), and keep the env files out of git. `KINDGI_*` names
144
+ are Kindgi's own settings and never reach pack code.
145
+
146
+ ## Errors and output
147
+
148
+ - Raise an exception for a failure: the call fails with
149
+ `handler-throw` and the exception's message. In an agent turn the
150
+ failure goes to the model only when the agent's `tool_errors` policy
151
+ includes `tool-error` — retrying must be safe for that tool.
152
+ - `print()` and `logging` go to the pack service's stdout/stderr
153
+ (`kindgi dev` shows them as `[pack] …`), never into a result.
154
+
155
+ ## Other declarations
156
+
157
+ **`mutating=False`** declares a tool read-only: it changes nothing
158
+ outside itself (a lookup, a search, a calculation). A read-only tool
159
+ runs in a dry run (`kindgi runs start --dry-run`). Leave it out — or
160
+ `mutating=True` — for anything that writes, sends or deletes: such a
161
+ tool stops a dry run.
162
+
163
+ It also sets the tool's approval default. An agent that turns tool
164
+ approval gates on (`conversation_policy={"hitl": {"tools": {...}}}`)
165
+ and has neither an override for the tool nor a `default` doesn't ask
166
+ before a read-only tool, and asks before any other on first use.
167
+
168
+ ```python
169
+ @tool(id="acme.find-citations", mutating=False)
170
+ def find_citations(query: CitationQuery) -> Citations:
171
+ """Searches the citator. Changes nothing."""
172
+ ```
173
+
174
+ `@tool(...)` also takes `effects=` (side effects, e.g.
175
+ `[{"kind": "writes", "resource": "db:ledger"}]`; a dry run also stops at
176
+ a tool with a `writes`, `deletes`, `spawns-run`, `emits-event` or
177
+ `external-side-effect` effect), `needs=` / `needs_spec=`, `sandbox=`,
178
+ `limits=` and `network=`. They are recorded in the pack's index for
179
+ policy and review; declare what the tool really does.
180
+
181
+ ## HTTP tools — one request, no code
182
+
183
+ A tool that is a single HTTP request needs no handler. `http_tool(...)`
184
+ declares the request; the Kindgi runtime makes it (TypeScript's
185
+ `defineTool({ spec: { kind: 'http' } })`):
186
+
187
+ ```python
188
+ # tools/citator.py
189
+ from kindgi import http_tool
190
+
191
+ lookup_case = http_tool(
192
+ id="acme.lookup-case",
193
+ description="Looks a case up in the citator by court and number.",
194
+ input=CaseRef, # pydantic models, as for @tool
195
+ output=CaseRecord,
196
+ method="GET",
197
+ url_template="https://citator.example.com/{court}/{case_number}",
198
+ headers={"Accept": "application/json"},
199
+ authorization={"kind": "bearer", "secretRef": {"envName": "local", "name": "CITATOR_KEY"}},
200
+ )
201
+ ```
202
+
203
+ - `{name}` placeholders in `url_template` are filled from the input's
204
+ fields, URL-encoded; each must be a field of the input model, or the
205
+ indexer reports it.
206
+ - `authorization`: `{"kind": "bearer", "secretRef": …}` or
207
+ `{"kind": "header", "headerName": "X-Api-Key", "secretRef": …}`. The
208
+ runtime resolves the secret on every call — `envName` `local` is the
209
+ pack's `.env` under `kindgi dev` — and fails the call, naming it, when
210
+ it's missing.
211
+ - `request_body`: `{"kind": "json-input"}` (the input's fields the URL
212
+ didn't use, as JSON — the default for POST, PUT and PATCH),
213
+ `{"kind": "input-passthrough"}` (the whole input), or
214
+ `{"kind": "text", "template": "…{field}…"}` (sent as `text/plain`).
215
+ - Also `timeout_ms=` (default 30000), `parse_json=` (default `True`),
216
+ `success_status=(200, 299)`, `effects=`, `version=`, and
217
+ `mutating=False` for a request that changes nothing (a GET, usually).
218
+
219
+ The spec is checked where it's declared, against the same schema as
220
+ TypeScript's. Calling the tool in Python raises: it runs in Kindgi, so
221
+ test it through `kindgi dev` (`kindgi runs start --flow=…`). Anything
222
+ more than one request — paging, retries, shaping the answer — is a
223
+ `@tool` handler with `httpx`.
224
+
225
+ ## Testing
226
+
227
+ A `Tool` is still callable — test the function directly:
228
+
229
+ ```python
230
+ # tests/test_citations.py — the template's pytest config puts the pack root on sys.path
231
+ from kindgi import ToolContext
232
+ from tools.citations import Citation, verify_citation
233
+
234
+
235
+ def test_unknown_citation(monkeypatch):
236
+ monkeypatch.setenv("CITATOR_URL", "http://citator.test")
237
+ out = verify_citation(Citation(citation="1 U.S. 1", jurisdiction="US"), ToolContext.for_test())
238
+ assert out.found is False
239
+ ```
240
+
241
+ `ToolContext.for_test(tenant_id=…, run_id=…)` builds a context.
242
+ `uv run pytest` runs the pack's tests (`test_*.py` files are never
243
+ indexed). `uv run python -m kindgi.pack index --pack-dir .` shows the
244
+ schemas Kindgi derives.
245
+
246
+ ## Wiring the tool onto an agent
247
+
248
+ Pass the `Tool` object — it pins that tool's version:
249
+
250
+ ```python
251
+ from ..tools.citations import verify_citation
252
+
253
+ brief_writer = Agent(..., tools=[verify_citation])
254
+ ```
255
+
256
+ or a ref with a semver **range**, `{"id": "acme.verify-citation",
257
+ "version": "^0.1.0"}`: the highest active version matching it is picked
258
+ at turn start. A bare string is not a tool ref. In a flow, a node's
259
+ `ref` may be the `Tool` object too.
260
+
261
+ ## Iterating
262
+
263
+ Save the file; `kindgi dev` rebuilds and the next call runs the new
264
+ code (a syntax error is reported `file:line:col` and the previous code
265
+ keeps serving). Bump `version` when callers' contract changes — a
266
+ removed field, a narrower type — not on every save.
267
+
268
+ ## Common mistakes
269
+
270
+ 1. **Copying the sample tool's shape without asking what the tool should do.**
271
+ 2. **Reading `ctx.env` / `ctx.config`, or an undeclared `ctx.secrets` name.**
272
+ The first two are empty, and `ctx.secrets` holds only what `needs_spec`
273
+ declares; use `os.environ` for the rest.
274
+ 3. **A non-object input** (`def f(n: int)`): the input must be a model,
275
+ TypedDict, dataclass or object schema.
276
+ 4. **No docstring and no `description=`**, or an unannotated input or
277
+ return: `DefinitionError`.
278
+ 5. **Snake_case on the wire.** Without an alias, the field name *is* the
279
+ wire name; add `alias="camelCase"` (and build models with the alias).
280
+ 6. **Defining the tool inside a function, or a helper module without a
281
+ leading `_`** under `tools/` — the first is never found; the second is
282
+ indexed and must define a primitive.
283
+ 7. **Absolute imports of sibling pack modules inside the pack**
284
+ (`from tools._db import …` in `tools/x.py`): Kindgi imports the pack's
285
+ files as one package, so use relative imports there (`from ._db import
286
+ …`); import your app's own packages by name. (Tests are not pack
287
+ modules — the template's import `tools.x` directly.)
288
+ 8. **Blocking inside `async def`.** Use a `def` handler for blocking I/O.
289
+ 9. **A read-only tool without `mutating=False`.** A dry run stops at it,
290
+ and an agent's tool approval gate asks before it on first use.
291
+ 10. **`mutating=False` on a tool that writes.** A dry run then runs it
292
+ for real.
293
+ 11. **A package a tool imports, only in a dev group.** The deployed pack
294
+ installs without dev dependencies (`uv sync --no-dev`, or Poetry's
295
+ main group only), so the import works under `kindgi dev` and fails in
296
+ the image. Put what tools import in `[project].dependencies` (in a
297
+ Poetry 1 app, `[tool.poetry.dependencies]`); test and build tools stay
298
+ in dev groups.
299
+
300
+ ## When the framework itself is the problem
301
+
302
+ If the bug is in Kindgi or the `kindgi` package (a schema derived wrong,
303
+ a misleading error, the pack service misbehaving) and not in the
304
+ tool's code, load `kindgi-framework-feedback` and file it with
305
+ `kindgi feedback write`.
@@ -0,0 +1,242 @@
1
+ ---
2
+ name: kindgi-python-getting-started
3
+ description: >
4
+ Getting a Python Kindgi pack running: what a pack is, scaffolding one
5
+ (`kindgi init <name> --template=python`) or adding `[tool.kindgi]` to
6
+ an existing Python app, `uv sync`, `kindgi dev`, the first agent run,
7
+ connecting a real model, flows, calling the Kindgi API from Python
8
+ (`kindgi.client`), and building an image. Load this when a Python
9
+ project has no `[tool.kindgi]` table yet and the user asks to "add
10
+ Kindgi", "make an agent", "set up a pack", or when you are orienting
11
+ in a Python pack (pyproject.toml with `[tool.kindgi]`) for the first
12
+ time. Authoring tools, guardrails and agents is covered by
13
+ kindgi-python-authoring-tools, kindgi-python-authoring-guardrails and
14
+ kindgi-python-authoring-agents; models by kindgi-authoring-providers.
15
+ type: core
16
+ library: "kindgi (Python)"
17
+ version: "0.1.1"
18
+ sdk_version: "0.0.0"
19
+ pack_languages: [python]
20
+ sources:
21
+ - sdks/python/README.md
22
+ - sdks/python/src/kindgi/pack/config.py
23
+ - sdks/python/src/kindgi/pack/discovery.py
24
+ ---
25
+
26
+ # Getting started with Kindgi in Python
27
+
28
+ > **Running `kindgi`:** a Python pack has no Node project, so the
29
+ > `kindgi` CLI (a Node 22+ program) is the one on `PATH`. Python
30
+ > commands run in the pack's environment: `uv run …`.
31
+
32
+ ## What a pack is
33
+
34
+ A **pack** is a project Kindgi indexes and runs. Its config is the
35
+ `[tool.kindgi]` table of `pyproject.toml`; its primitives live in four
36
+ folders, one or more per `.py` file, at module level:
37
+
38
+ - **Tools** (`tools/*.py`, `@tool`) — your Python code an agent or flow
39
+ calls.
40
+ - **Guardrails** (`guardrails/*.py`, `@guardrail`) — checks over an
41
+ agent's turn.
42
+ - **Agents** (`agents/*.py`, `Agent(...)`) — data: instructions, tools,
43
+ capabilities. The model runs in Kindgi.
44
+ - **Flows** (`flows/*.py`, `Flow(...)`) — data: steps (tool or agent
45
+ nodes) and the edges between them.
46
+
47
+ Kindgi runs the tools and checks in the pack's own Python process (the
48
+ pack service) and calls them over HTTP; everything else runs in the
49
+ Kindgi runtime. A module whose name starts with `_` is a helper, not a
50
+ primitive; `test_*.py`, `*_test.py` and `conftest.py` are skipped.
51
+
52
+ ## Scaffold a new pack
53
+
54
+ ```sh
55
+ kindgi init my-pack --template=python
56
+ cd my-pack
57
+ uv sync # .venv with the kindgi package
58
+ uv run pytest
59
+ kindgi dev # boots Kindgi locally and runs this pack, reloading on save
60
+ ```
61
+
62
+ `kindgi dev` needs Postgres: it starts one in Docker unless
63
+ `KINDGI_DATABASE_URL` points at yours. It runs the pack with
64
+ `.venv/bin/python` (or `dev.python` in `[tool.kindgi]`, e.g.
65
+ `["uv", "run", "python"]`), checks that interpreter can import
66
+ `kindgi`, and swaps the code on every save.
67
+
68
+ ## Add Kindgi to an existing Python app
69
+
70
+ In the app's directory (where its `pyproject.toml` is):
71
+
72
+ ```sh
73
+ kindgi init # --pack-id=<id> if the app's name doesn't make one
74
+ uv sync # or what it prints for Poetry / pip
75
+ kindgi dev
76
+ ```
77
+
78
+ `kindgi init` edits the app's `pyproject.toml` in place — your layout and
79
+ comments stay — appending the `[tool.kindgi]` tables (the pack id from
80
+ `[project].name`, or `[tool.poetry].name` in a Poetry 1 app; discovery
81
+ under `kindgi/`; in a Poetry app, `dev.python` runs `poetry run python`)
82
+ and adding `kindgi` to `[project].dependencies`; where it can't edit the
83
+ dependencies (Poetry 1, `dynamic`), it prints the command to run. It adds the `kindgi/tools`,
84
+ `guardrails`, `agents` and `flows` folders, these skills and `.gitignore`
85
+ entries. An app with a `package.json` too gets a TypeScript pack unless you
86
+ pass `--template=python`.
87
+
88
+ The pack root is on `sys.path`, so tools import the app's own packages by
89
+ name (`from acme.text import normalize`); inside `kindgi/`, import the pack's
90
+ modules relatively. Don't add an `__init__.py` to `kindgi/` — the folder
91
+ would then shadow the `kindgi` package. `kindgi dev` reads the app's `.env`
92
+ / `.env.local` — keys already there reach the tools as environment
93
+ variables. A package a tool imports must be in the app's main dependencies,
94
+ not a dev group: the deployed pack installs without dev dependencies (see
95
+ `kindgi-python-authoring-tools`).
96
+
97
+ ## Layout of the template
98
+
99
+ ```
100
+ my-pack/
101
+ ├── pyproject.toml # [tool.kindgi] — id, version, (discovery, dev, environments)
102
+ ├── tools/echo.py, tools/greet.py # @tool
103
+ ├── guardrails/response_not_empty.py # @guardrail
104
+ ├── agents/echo_agent.py # Agent(...)
105
+ ├── flows/echo_flow.py # Flow(...)
106
+ ├── tests/test_tools.py # the tools and the check, called directly
107
+ ├── README.md
108
+ └── .claude/skills/ # these skills (`kindgi skills sync` refreshes them)
109
+ ```
110
+
111
+ The `[tool.kindgi]` keys are the ones `kindgi.config.ts` takes —
112
+ `pack`, `discovery`, `dev`, `env`, `environments` — spelled the same.
113
+
114
+ ## First run
115
+
116
+ With `kindgi dev` running, from another terminal in the pack directory:
117
+
118
+ ```sh
119
+ kindgi runs start --agent=my-pack.echo-agent --input='{"userMessage":"Ada"}'
120
+ ```
121
+
122
+ The answer comes from `dev-echo`, a **fallback** provider a new pack
123
+ gets: no model, no key — it calls the first tool and replies "Tool
124
+ responded: …", and the turn carries a `fallback-provider` warning. For
125
+ a real model, put the key in `.env` and register a provider:
126
+
127
+ ```sh
128
+ kindgi secrets set ANTHROPIC_API_KEY --env=local --scope=tenant # no-echo prompt
129
+ kindgi providers register --preset=anthropic
130
+ ```
131
+
132
+ It takes over at the next turn. Details and other providers:
133
+ `kindgi-authoring-providers`.
134
+
135
+ `uv run python -m kindgi.pack index --pack-dir .` prints what Kindgi
136
+ sees (the index); `kindgi dev` reports a broken file with its path and
137
+ keeps serving the rest.
138
+
139
+ ## Flows
140
+
141
+ A flow is data: nodes and edges (`flow.schema.json`). A node's `ref` may
142
+ be the `Tool` object:
143
+
144
+ ```python
145
+ # flows/record.py
146
+ from kindgi import Flow
147
+
148
+ from ..tools.ledger import record_expense
149
+
150
+ record = Flow(
151
+ id="acme.ledger.record-flow",
152
+ version="0.1.0",
153
+ nodes=[{"id": "record", "kind": "tool", "ref": record_expense}],
154
+ edges=[
155
+ {"id": "e-start", "from": "$start", "to": "record"},
156
+ {"id": "e-end", "from": "record", "to": "$end"},
157
+ ],
158
+ )
159
+ ```
160
+
161
+ A node gets its single upstream node's output (the run input after
162
+ `$start`), or what its `inputMapping` says: each key a `{"literal": …}`
163
+ or a `{"path": …}` rooted at `runInput`, `state` or
164
+ `nodeOutputs.<nodeId>`. The indexer checks every flow against the
165
+ schema. Run one with `kindgi runs start --flow=<id> --input='{…}'`;
166
+ branches, loops and agent steps → `kindgi-python-authoring-flows`.
167
+
168
+ ## Calling Kindgi from Python
169
+
170
+ `kindgi.client` covers the whole API:
171
+
172
+ ```python
173
+ from kindgi.client import Kindgi
174
+
175
+ client = Kindgi() # KINDGI_API_URL + KINDGI_API_TOKEN, or Kindgi(url, token=…)
176
+ run = client.runs.start(agent="my-pack.echo-agent", input={"userMessage": "Ada"})
177
+ print(run.status, run.output["response"]["content"])
178
+ for event in client.runs.stream(str(run.id)):
179
+ print(event.kind)
180
+ ```
181
+
182
+ `AsyncKindgi` is the asyncio twin. `kindgi dev` prints the URL and the
183
+ token; `.kindgirc.json` in the pack holds them for the CLI.
184
+
185
+ ## Build an image
186
+
187
+ `kindgi build --env=<name>` (an `[tool.kindgi.environments.<name>]`
188
+ block) builds the pack's image. Its dependencies install from the pack's
189
+ lockfile, frozen, main dependencies only: `uv.lock` (run `uv lock`
190
+ first), or in a Poetry app `poetry.lock` (`poetry lock`; the image brings
191
+ its own pinned Poetry). The index is built in the image; the pack service
192
+ is its entrypoint. An app with only `requirements.txt` can't build yet.
193
+
194
+ Debian packages the code needs (OCR, PDF tools, `libmagic`, …) are
195
+ declared in `pyproject.toml`; the image installs them, for the build and
196
+ at run time:
197
+
198
+ ```toml
199
+ [tool.kindgi.image]
200
+ system-packages = ["tesseract-ocr", "poppler-utils"] # names, or name=version
201
+ ```
202
+
203
+ The variables the code reads from `os.environ` (a database URL, a bucket)
204
+ are declared too, names only. A deployed pack service missing a `required`
205
+ one isn't ready, and its `/readyz` names it:
206
+
207
+ ```toml
208
+ [tool.kindgi.env]
209
+ required = ["DATABASE_URL"]
210
+ optional = ["SENTRY_DSN"]
211
+ ```
212
+
213
+ A `[tool.uv] required-version` that excludes the image's uv stops the
214
+ build before anything uploads, with the range to use.
215
+
216
+ ## Two things need the human
217
+
218
+ - **The pack id and version** in `[tool.kindgi.pack]`. The id prefixes
219
+ every primitive (`<pack-id>.<name>`); pick it once.
220
+ - **Model credentials.** Ask for the key; never invent or hard-code one.
221
+
222
+ ## Next
223
+
224
+ - A tool → `kindgi-python-authoring-tools`.
225
+ - A guardrail → `kindgi-python-authoring-guardrails`.
226
+ - An agent → `kindgi-python-authoring-agents`.
227
+ - A flow → `kindgi-python-authoring-flows`.
228
+ - A real model → `kindgi-authoring-providers`.
229
+ - An external resource (a database) for the coding agent →
230
+ `kindgi-authoring-mcp-servers`.
231
+
232
+ ## Keeping skills up to date
233
+
234
+ `kindgi skills sync` refreshes `.claude/skills/` from the CLI's copy
235
+ (local edits are kept unless `--force`); `kindgi dev` says when they are
236
+ out of date.
237
+
238
+ ## When the framework itself is the problem
239
+
240
+ If the bug is in Kindgi or the `kindgi` package and not in the pack's
241
+ code, load `kindgi-framework-feedback` and file it with
242
+ `kindgi feedback write`.
package/src/build.ts ADDED
@@ -0,0 +1,18 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ /**
5
+ * `@kindgi/sdk/build` — a TypeScript pack image's build extensions, for
6
+ * `image` in `kindgi.config.*` (`systemPackages`, `extensions`,
7
+ * `buildEnv`).
8
+ *
9
+ * Re-exports `@kindgi/handler-runtime/build-extensions` verbatim.
10
+ */
11
+
12
+ export { defineBuildExtension, prisma } from '@kindgi/handler-runtime/build-extensions';
13
+ export type {
14
+ BuildExtension,
15
+ BuildStep,
16
+ ImageConfig,
17
+ PrismaExtensionOptions,
18
+ } from '@kindgi/handler-runtime/build-extensions';