@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.
- package/LICENSE +201 -0
- package/README.md +187 -1
- package/dist/build.d.ts +10 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +11 -0
- package/dist/build.js.map +1 -0
- package/dist/client.d.ts +33 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +55 -0
- package/dist/client.js.map +1 -0
- package/dist/define.d.ts +25 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/define.js +37 -0
- package/dist/define.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime-config.d.ts +53 -0
- package/dist/runtime-config.d.ts.map +1 -0
- package/dist/runtime-config.js +114 -0
- package/dist/runtime-config.js.map +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/dist/webhooks.d.ts +15 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/dist/webhooks.js +16 -0
- package/dist/webhooks.js.map +1 -0
- package/package.json +88 -4
- package/skills/kindgi-authoring-agents/SKILL.md +252 -0
- package/skills/kindgi-authoring-flows/SKILL.md +302 -0
- package/skills/kindgi-authoring-guardrails/SKILL.md +297 -0
- package/skills/kindgi-authoring-mcp-servers/SKILL.md +289 -0
- package/skills/kindgi-authoring-providers/SKILL.md +705 -0
- package/skills/kindgi-authoring-tools/SKILL.md +298 -0
- package/skills/kindgi-framework-feedback/SKILL.md +211 -0
- package/skills/kindgi-getting-started/SKILL.md +189 -0
- package/skills/kindgi-python-authoring-agents/SKILL.md +205 -0
- package/skills/kindgi-python-authoring-flows/SKILL.md +325 -0
- package/skills/kindgi-python-authoring-guardrails/SKILL.md +176 -0
- package/skills/kindgi-python-authoring-tools/SKILL.md +305 -0
- package/skills/kindgi-python-getting-started/SKILL.md +242 -0
- package/src/build.ts +18 -0
- package/src/client.ts +177 -0
- package/src/define.ts +75 -0
- package/src/index.ts +20 -0
- package/src/runtime-config.ts +180 -0
- package/src/types.ts +86 -0
- 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';
|