@kindgi/sdk 0.1.4 → 0.1.5-rc.0

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/dist/client.d.ts CHANGED
@@ -28,6 +28,8 @@ export { KindgiApiError, fromWire, notImplementedInPreview, notYetWired } from '
28
28
  export type { AuthError, ConflictError, GuardrailViolation, GuardrailViolationError, InvalidRequestError, NetworkError, NotFoundError, NotImplementedInPreviewError, NotYetWiredError, RateLimitedError, ServerError, KindgiError, } from '@kindgi/client';
29
29
  export { readSse, unwrapSseData } from '@kindgi/client';
30
30
  export type { SseEvent, SseReadOptions } from '@kindgi/client';
31
+ export { verifySignedExport } from '@kindgi/client';
32
+ export type { ExportSigningKey, SignedExportEnvelope, SignedExportVerification, VerifySignedExportOptions, } from '@kindgi/client';
31
33
  export { followRun, subscribeToRun } from '@kindgi/client';
32
34
  export type { FollowRunOptions, RunProgress, RunProgressEvent, SubscribeToRunOptions, } from '@kindgi/client';
33
35
  export { comparisonOf } from '@kindgi/client';
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAIlE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,OAAO,CAAC,aAAa,CAAM,GAAG,YAAY,CAE/E;AAGD,YAAY,EACV,qBAAqB,EACrB,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,cAAc,EACd,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,UAAU,EACV,WAAW,EACX,mBAAmB,EACnB,YAAY,EACZ,WAAW,EACX,UAAU,EACV,WAAW,EACX,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,iBAAiB,EACjB,kBAAkB,EAClB,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,EACV,UAAU,EACV,WAAW,EACX,UAAU,EACV,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,qBAAqB,EACrB,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,cAAc,EACd,cAAc,EACd,eAAe,EACf,qBAAqB,EACrB,GAAG,EACH,UAAU,EACV,eAAe,EACf,cAAc,EACd,aAAa,EACb,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,0BAA0B,EAC1B,UAAU,EACV,iBAAiB,EACjB,WAAW,EACX,SAAS,EACT,gBAAgB,EAChB,WAAW,EACX,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,cAAc,EACd,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAChG,YAAY,EACV,SAAS,EACT,aAAa,EACb,kBAAkB,EAClB,uBAAuB,EACvB,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,4BAA4B,EAC5B,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,WAAW,GACZ,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACxD,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAG/D,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC3D,YAAY,EACV,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EACV,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAIlE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,OAAO,CAAC,aAAa,CAAM,GAAG,YAAY,CAE/E;AAGD,YAAY,EACV,qBAAqB,EACrB,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,cAAc,EACd,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,UAAU,EACV,WAAW,EACX,mBAAmB,EACnB,YAAY,EACZ,WAAW,EACX,UAAU,EACV,WAAW,EACX,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,iBAAiB,EACjB,kBAAkB,EAClB,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,EACV,UAAU,EACV,WAAW,EACX,UAAU,EACV,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,qBAAqB,EACrB,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,cAAc,EACd,cAAc,EACd,eAAe,EACf,qBAAqB,EACrB,GAAG,EACH,UAAU,EACV,eAAe,EACf,cAAc,EACd,aAAa,EACb,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,0BAA0B,EAC1B,UAAU,EACV,iBAAiB,EACjB,WAAW,EACX,SAAS,EACT,gBAAgB,EAChB,WAAW,EACX,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,cAAc,EACd,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAChG,YAAY,EACV,SAAS,EACT,aAAa,EACb,kBAAkB,EAClB,uBAAuB,EACvB,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,4BAA4B,EAC5B,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,WAAW,GACZ,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACxD,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAG/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,YAAY,EACV,gBAAgB,EAChB,oBAAoB,EACpB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC3D,YAAY,EACV,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EACV,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,gBAAgB,CAAC"}
package/dist/client.js CHANGED
@@ -50,6 +50,8 @@ export function createClient(options = {}) {
50
50
  export { KindgiApiError, fromWire, notImplementedInPreview, notYetWired } from '@kindgi/client';
51
51
  // ---- Streaming helpers (SSE parser + resume) ----
52
52
  export { readSse, unwrapSseData } from '@kindgi/client';
53
+ // ---- Signed exports: check one where it's read ----
54
+ export { verifySignedExport } from '@kindgi/client';
53
55
  // ---- Following a run (browser-safe: a page follows a run with a public token) ----
54
56
  export { followRun, subscribeToRun } from '@kindgi/client';
55
57
  // ---- Comparisons (a test set compared with a version): the typed result ----
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC;;;;;;;;;;;;;;;;;GAiBG;AAEH,6CAA6C;AAC7C,OAAO,EAAE,YAAY,IAAI,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAI3D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,YAAY,CAAC,UAAkC,EAAE;IAC/D,OAAO,kBAAkB,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC,CAAC;AAC3D,CAAC;AA8FD,0BAA0B;AAC1B,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAgBhG,oDAAoD;AACpD,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGxD,qFAAqF;AACrF,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAQ3D,+EAA+E;AAC/E,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC;;;;;;;;;;;;;;;;;GAiBG;AAEH,6CAA6C;AAC7C,OAAO,EAAE,YAAY,IAAI,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAI3D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,YAAY,CAAC,UAAkC,EAAE;IAC/D,OAAO,kBAAkB,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC,CAAC;AAC3D,CAAC;AA8FD,0BAA0B;AAC1B,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAgBhG,oDAAoD;AACpD,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGxD,sDAAsD;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAQpD,qFAAqF;AACrF,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAQ3D,+EAA+E;AAC/E,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kindgi/sdk",
3
- "version": "0.1.4",
3
+ "version": "0.1.5-rc.0",
4
4
  "description": "@kindgi/sdk — the authoring SDK for Kindgi™. Facade over the individual @kindgi/* packages + @kindgi/client. Unifies pack authoring (defineTool / defineCheck / defineAgent / defineFlow) and client callsites (createClient) behind three sub-paths: /define, /client, /types. Re-export facade; zero behavior.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -55,15 +55,15 @@
55
55
  "README.md"
56
56
  ],
57
57
  "dependencies": {
58
- "@kindgi/agents": "0.1.4",
59
- "@kindgi/client": "0.1.4",
60
- "@kindgi/crypto": "0.1.4",
61
- "@kindgi/flow": "0.1.4",
62
- "@kindgi/guardrails": "0.1.4",
63
- "@kindgi/handler-runtime": "0.1.4",
64
- "@kindgi/schema": "0.1.4",
65
- "@kindgi/tools": "0.1.4",
66
- "@kindgi/types": "0.1.4"
58
+ "@kindgi/agents": "0.1.5-rc.0",
59
+ "@kindgi/client": "0.1.5-rc.0",
60
+ "@kindgi/crypto": "0.1.5-rc.0",
61
+ "@kindgi/flow": "0.1.5-rc.0",
62
+ "@kindgi/guardrails": "0.1.5-rc.0",
63
+ "@kindgi/handler-runtime": "0.1.5-rc.0",
64
+ "@kindgi/schema": "0.1.5-rc.0",
65
+ "@kindgi/tools": "0.1.5-rc.0",
66
+ "@kindgi/types": "0.1.5-rc.0"
67
67
  },
68
68
  "peerDependencies": {
69
69
  "zod": "^4.0.0"
@@ -12,7 +12,7 @@ description: >
12
12
  kindgi-authoring-guardrails.
13
13
  type: core
14
14
  library: "@kindgi/sdk"
15
- version: "0.4.4"
15
+ version: "0.4.5"
16
16
  sdk_version: "0.0.0"
17
17
  pack_languages: [node]
18
18
  sources:
@@ -160,6 +160,30 @@ export default defined.value;
160
160
  retries the turn fails as before, with `toolRetries` on the error. A
161
161
  tenant's `tool-errors` policy can lower these (fewer retries, fewer
162
162
  kinds), never raise them.
163
+ - **`retrieval`** — what the agent reads from memory before each turn.
164
+ Empty = no memory. Each intent is
165
+ `{ types: ['acme.preference'], scope, mode?, limit? }`, with `scope`
166
+ one of `'same-user'`, `'same-conversation'`, `'same-project'`,
167
+ `'tenant'` (always within what the run may see), and `mode` absent
168
+ (newest first), `'keyword'`, `'semantic'` or `'both'`. Retrieved facts
169
+ reach the model as data in a `<memory>` block, never as instructions.
170
+ `semantic`/`both` need embeddings on the runtime
171
+ (`KINDGI_MEMORY_EMBEDDINGS`): without them a `semantic` intent fails
172
+ the turn (`semantic-unavailable`). `{ source: 'conversations', scope:
173
+ 'same-user' }` recalls this agent's earlier conversations: the people's
174
+ own words only, unless `roles: ['user', 'agent']` (its own earlier
175
+ answers come back marked unverified). `same-segment`/`same-project`
176
+ recall other people's conversations, and publishing warns.
177
+ - **`memory`** — `{ remember: { types, scope, keepDays? } }` gives the
178
+ turn the built-in tool `kindgi_remember` (name it exactly so in the
179
+ instructions; built-ins have no dots). The model picks the type, the
180
+ text, an optional `key` and an expiry; never the scope. A fact wider
181
+ than one person, or text that reads like an instruction, waits for a
182
+ person's approval. Remembering is a tool call, so use a model with
183
+ reliable tool calling. `{ instructionTypes: ['acme.policy'] }` turns a
184
+ retrieved, verified fact of those types into an instruction
185
+ ("Policies (verified)"). See
186
+ https://docs.kindgi.com/v0.1/guides/agents/give-an-agent-memory/.
163
187
 
164
188
  ## What a turn receives
165
189
 
@@ -15,7 +15,7 @@ description: >
15
15
  kindgi-authoring-agents.
16
16
  type: core
17
17
  library: "@kindgi/sdk"
18
- version: "0.3.6"
18
+ version: "0.3.8"
19
19
  sdk_version: "0.0.0"
20
20
  pack_languages: [node]
21
21
  sources:
@@ -55,7 +55,11 @@ before the response is stored.
55
55
  - **Built-in checks** (`BUILT_IN_CHECK_IDS` in `@kindgi/guardrails`):
56
56
  `must-cite`, `never-call-tool`, `max-tool-calls`, `output-matches`,
57
57
  `tool-order`, `required-substring`, `forbidden-substring`. A guardrail
58
- can name one of these instead of shipping its own check.
58
+ can name one of these (`check: 'forbidden-substring'`) instead of
59
+ shipping its own check, and the runtime runs the built-in. Their ids
60
+ are reserved: a pack that ships its own check under one is refused
61
+ (`reserved-check-id`), so name yours `<pack>.checks.<name>`. See
62
+ "Using a built-in check" below for each one's `config`.
59
63
 
60
64
  `@kindgi/sdk` exports `defineCheck` but no helper for the guardrail
61
65
  itself: a pack file default-exports the declaration as a plain object.
@@ -119,6 +123,46 @@ How the pack tooling reads this file:
119
123
  declares no config gets them all), and a config that doesn't fit
120
124
  refused, naming where.
121
125
 
126
+ ## Using a built-in check
127
+
128
+ Name the built-in as the guardrail's `check`, give its `config`, and ship
129
+ no check implementation:
130
+
131
+ ```ts
132
+ // guardrails/no-guarantees/index.ts
133
+ export default {
134
+ id: 'acme.no-guarantees',
135
+ name: 'Never promise a guarantee',
136
+ kind: 'zero-llm',
137
+ check: 'forbidden-substring',
138
+ config: { patterns: ['guaranteed', 'we promise'] },
139
+ action: { 'on-violation': 'halt' },
140
+ severity: 'error',
141
+ };
142
+ ```
143
+
144
+ Each built-in's `config` (tool lists hold tool ids, as in
145
+ `acme.fetch-precedent`; a setting without `?` is required):
146
+
147
+ | Check | Fails when | `config` |
148
+ | --- | --- | --- |
149
+ | `must-cite` | the answer is empty, or has fewer than `minCitations` matches of `sourcePattern` | `minCitations?: integer ≥ 1` (1), `sourcePattern?: string` (a regex; `[…]`-style citations by default) |
150
+ | `never-call-tool` | the turn called any tool in `tools` | `tools: (string \| { id, version? })[]`, one or more |
151
+ | `max-tool-calls` | the turn made more than `max` tool calls | `max?: integer ≥ 0` (10) |
152
+ | `output-matches` | the answer doesn't match `pattern` (with `negate: true`, it does) | `pattern: string` (a regex), `flags?: string` (letters `dgimsuyv`), `negate?: boolean` |
153
+ | `tool-order` | the tools in `sequence` weren't called in that order (others may come between) | `sequence: string[]`, one or more |
154
+ | `required-substring` | the answer is empty, or lacks any of `patterns` | `patterns: string[]`, one or more non-empty (plain text), `caseSensitive?: boolean` (false) |
155
+ | `forbidden-substring` | the answer contains any of `patterns` | `patterns: string[]`, one or more non-empty (plain text), `caseSensitive?: boolean` (false) |
156
+
157
+ A config the check doesn't take is refused: a required setting left out,
158
+ the wrong type, a setting the check doesn't know, or a regex that doesn't
159
+ compile. Registering it answers `422 guardrail-config-invalid` (each
160
+ problem in `details.issues`); deploying a pack with one fails with
161
+ `deployment-validation-failed`; one that still reaches a turn is a check
162
+ that can't run (`invalid-check-config`), so a `halt` guardrail stops the
163
+ turn. Each built-in's JSON Schema is its registered check's
164
+ `configSchema`.
165
+
122
166
  ## Validating a declaration in-process
123
167
 
124
168
  `defineGuardrail(spec, checks)` from `@kindgi/guardrails` validates a
@@ -17,9 +17,9 @@ description: >
17
17
  `kindgi secrets set` flow.
18
18
  type: core
19
19
  library: "@kindgi/sdk"
20
- version: "0.3.0"
20
+ version: "0.3.2"
21
21
  sdk_version: "0.0.0"
22
- pack_languages: [node, python]
22
+ pack_languages: [node, python, java, scala]
23
23
  ---
24
24
 
25
25
  # Wiring an MCP server for a Kindgi pack
@@ -28,7 +28,8 @@ pack_languages: [node, python]
28
28
  > (`@kindgi/cli`), not a global command. Run it through the project's
29
29
  > package manager — `pnpm exec kindgi …`, `npx --no kindgi …` (npm),
30
30
  > `yarn kindgi …` or `bun run kindgi …`. A Python pack (`[tool.kindgi]` in
31
- > `pyproject.toml`) has no Node project: run the `kindgi` on `PATH`.
31
+ > `pyproject.toml`) has no Node project: run the `kindgi` on `PATH`. A Java
32
+ > or Scala pack (`kindgi.config.json`) runs the CLI it pins: `./kindgiw …`.
32
33
  > Commands below are written `kindgi …` for brevity.
33
34
 
34
35
  If the user has an external resource (Postgres DB, GitHub org, Notion
@@ -89,7 +90,8 @@ Three moving parts:
89
90
  (`"command": "pnpm", "args": ["exec", "kindgi", "mcp-launch", …]`;
90
91
  npm: `npx --no kindgi …`) — never a global `kindgi`, never a
91
92
  download. A Python pack has no Node project, so its entries run the
92
- `kindgi` on `PATH` (`"command": "kindgi", "args": ["mcp-launch", …]`).
93
+ `kindgi` on `PATH` (`"command": "kindgi", "args": ["mcp-launch", …]`). A
94
+ Java or Scala pack's run the CLI it pins (`"command": "./kindgiw"`).
93
95
  The file is safe to commit — it references secrets by NAME, not
94
96
  value.
95
97
  3. **The launcher** — `kindgi mcp-launch` is what the coding agent
@@ -23,9 +23,9 @@ description: >
23
23
  kindgi-getting-started.
24
24
  type: core
25
25
  library: "@kindgi/sdk"
26
- version: "0.9.8"
26
+ version: "0.9.11"
27
27
  sdk_version: "0.0.0"
28
- pack_languages: [node, python]
28
+ pack_languages: [node, python, java, scala]
29
29
  sources:
30
30
  - packages/adapters/model-anthropic/src/provider.ts
31
31
  - packages/adapters/model-gemini/src/provider.ts
@@ -41,7 +41,8 @@ sources:
41
41
  > (`@kindgi/cli`), not a global command. Run it through the project's
42
42
  > package manager — `pnpm exec kindgi …`, `npx --no kindgi …` (npm),
43
43
  > `yarn kindgi …` or `bun run kindgi …`. A Python pack (`[tool.kindgi]` in
44
- > `pyproject.toml`) has no Node project: run the `kindgi` on `PATH`.
44
+ > `pyproject.toml`) has no Node project: run the `kindgi` on `PATH`. A Java
45
+ > or Scala pack (`kindgi.config.json`) runs the CLI it pins: `./kindgiw …`.
45
46
  > Commands below are written `kindgi …` for brevity.
46
47
 
47
48
  An **agent** is a versioned declaration; it needs a **provider** to run.
@@ -76,8 +77,9 @@ Three moving parts:
76
77
  1. **API key on disk** — in `kindgi dev` (environment `local`) the dotenv
77
78
  secret binding reads the project's own env files: `.env`, then
78
79
  `.env.local` on top (change the list with `dev.envFiles` in
79
- `kindgi.config.ts`, or `envFiles` under `[tool.kindgi.dev]` in a Python
80
- pack's `pyproject.toml`). A key already in the app's `.env` just works.
80
+ `kindgi.config.ts`, `envFiles` under `[tool.kindgi.dev]` in a Python
81
+ pack's `pyproject.toml`, or `dev.envFiles` in a Java or Scala pack's
82
+ `kindgi.config.json`). A key already in the app's `.env` just works.
81
83
  `kindgi secrets set` (interactive, no-echo) writes `.env.local`. For
82
84
  non-sensitive values (log levels, region names, feature flags),
83
85
  `kindgi env set NAME VALUE --env=local` writes the same file with a
@@ -131,8 +133,9 @@ credential on argv.
131
133
  kindgi providers register --preset=anthropic # Opus 5.5, Sonnet 5.5 (default), Haiku 5.5, Haiku 4.5
132
134
  kindgi providers register --preset=anthropic --models=claude-sonnet-5-5 # just one
133
135
  ```
134
- Don't pin `claude-haiku-4-5`: Anthropic retires it on or after 2026-10-15,
135
- and a turn routed to it then fails; `claude-haiku-5-5` replaces it. Each
136
+ Before pinning a Claude model, check its status on Anthropic's model
137
+ deprecations page (https://platform.claude.com/docs/en/about-claude/model-deprecations): a turn routed to a retired model fails. Prefer the
138
+ preset's default. Each
136
139
  preset names a default model (`metadata.defaultModel`, marked `(default)`
137
140
  when it registers), which an agent with no preference gets. A preset
138
141
  registered before 0.1.4 has none: unregister it and register it again.
@@ -164,12 +167,15 @@ providers: [
164
167
  preset = "anthropic"
165
168
  models = ["claude-sonnet-5-5"]
166
169
  ```
170
+ In a Java or Scala pack's `kindgi.config.json`, the same keys:
171
+ `"providers": [{"preset": "anthropic", "models": ["claude-sonnet-5-5"]}]`.
167
172
  - A preset entry takes `models`, `project`, `secret` (the key's name, in place
168
- of the preset's) and `maxOutputTokens`, spelled the same in `pyproject.toml`;
173
+ of the preset's) and `maxOutputTokens`, spelled the same in `pyproject.toml`
174
+ and `kindgi.config.json`;
169
175
  a `spec` entry is a `--spec` body. A
170
176
  key is always a secret's name (`secret_ref`); a credential in
171
177
  `adapter_config` is refused.
172
- - Each boot prints `Providers from kindgi.config.ts:` with one line each:
178
+ - Each boot prints `Providers from kindgi.config.ts:` (the pack's config file) with one line each:
173
179
  `registered`, `unchanged`, `registered again (changed in kindgi.config.ts)`,
174
180
  `unregistered (no longer in kindgi.config.ts)`, or ⚠ `not registered: <KEY>
175
181
  is not in .env, .env.local` (set the key, then restart: the config isn't
@@ -535,7 +541,8 @@ Or skip step 2: `kindgi providers register --preset=gemini --project=<your-gcp-p
535
541
  registers both models above.
536
542
  Then pin it from an agent with `preferredProvider: 'gemini'` (and a model
537
543
  with `preferredModel`; in Python, `preferred_provider="gemini"` and
538
- `preferred_model=…`), or let the router pick by capability.
544
+ `preferred_model=…`; in Java, `.set("preferredProvider", "gemini")`), or let
545
+ the router pick by capability.
539
546
 
540
547
  ## How the router picks between multiple providers + models
541
548
 
@@ -550,7 +557,8 @@ tenant policy), then sorts survivors in this order:
550
557
  - Only `preferredModel` set → promote any provider exposing that model.
551
558
  - Only `preferredProvider` set → promote every model of that provider.
552
559
  `defineAgent` takes both (`preferredProvider`, `preferredModel`), and
553
- so does a Python `Agent` (`preferred_provider=`, `preferred_model=`).
560
+ so does a Python `Agent` (`preferred_provider=`, `preferred_model=`) and
561
+ a Java `Agent.define(…)` (`set("preferredProvider", …)`).
554
562
  2. **`capability.prefer[]` weights.** If the agent's capability
555
563
  declares `prefer: [{feature: 'thinking', weight: 3}, ...]`, tuples
556
564
  with matching model features (or provider attributes) get higher
@@ -649,7 +657,8 @@ defineAgent({
649
657
  be the FULL npm package name of the adapter — `"@kindgi/adapter-model-anthropic"`,
650
658
  NOT `"anthropic"`. Adapters are registered with the runtime under
651
659
  their full package names, and a short name matches none of them, so
652
- the registration fails. The model adapters are
660
+ registering is refused: the runtime has no adapter by that name
661
+ (`✗ /adapter_id: …`). The model adapters are
653
662
  `@kindgi/adapter-model-anthropic`, `@kindgi/adapter-model-gemini`,
654
663
  `@kindgi/adapter-model-openai-compat` and
655
664
  `@kindgi/adapter-model-in-process`; `kindgi providers presets` shows
@@ -686,8 +695,9 @@ defineAgent({
686
695
  Then re-register.
687
696
 
688
697
  6. **Key not found by the runtime.** `kindgi dev` reads the env files
689
- at the PACK ROOT (the directory with `kindgi.config.ts`, or a Python
690
- pack's `pyproject.toml` with `[tool.kindgi]`) — `.env` and
698
+ at the PACK ROOT (the directory with `kindgi.config.ts`, a Python
699
+ pack's `pyproject.toml` with `[tool.kindgi]`, or a Java or Scala pack's
700
+ `kindgi.config.json`) — `.env` and
691
701
  `.env.local`, or whatever `dev.envFiles` lists; the boot log prints
692
702
  which files it found. A `KINDGI_`-prefixed name is Kindgi runtime
693
703
  config and never resolves as a secret. Outside `kindgi dev`, the
@@ -716,6 +726,21 @@ defineAgent({
716
726
  that names nothing registered, and the tenant's policy. `kindgi
717
727
  providers list` shows what is registered.
718
728
 
729
+ 10. **A setting the adapter can't use.** Registering checks the spec
730
+ against its adapter (no network call, no key read) and refuses what
731
+ it can't use: `422 provider-config-invalid`, nothing stored, one line
732
+ per problem with its JSON-pointer path:
733
+ ```text
734
+ Error [invalid-request]: Provider "ollama" doesn't fit adapter @kindgi/adapter-model-openai-compat: adapter_config.api must be one of responses, chat-completions.
735
+ ✗ /adapter_config/api: adapter_config.api must be one of responses, chat-completions.
736
+ ```
737
+ Fix each `✗` line's setting and register again. A key the adapter
738
+ needs is checked too (`✗ /secret_ref: …`); whether the key works, or
739
+ the endpoint answers, isn't (the first turn finds out). A
740
+ registration stored before 0.1.5 wasn't checked: `kindgi doctor`
741
+ names its problems (`GET /v1/providers/<id>/check`); unregister it
742
+ and register it again.
743
+
719
744
  ## Verifying end-to-end
720
745
 
721
746
  ```sh
@@ -132,6 +132,32 @@ const defined = defineTool({
132
132
 
133
133
  The runtime resolves every declared secret on every call, for the call's tenant, in its env (`KINDGI_ENV`; in `kindgi dev`, `local`: the pack's `.env` and `.env.local`). It checks each value against its schema, and fails the call, naming the secret, when one is missing or doesn't match. Every declared secret is required, so in a runtime call `ctx.secrets` holds them all; it's optional in the type because a unit test builds its own context and passes `secrets: { CITATOR_KEY: '…' }`.
134
134
 
135
+ A value that differs per tenant, org or project but isn't secret (a base URL, a region, an account id) is an **env value**: declared in `needsSpec.env`, read from `ctx.env`:
136
+
137
+ ```ts
138
+ const defined = defineTool({
139
+ // …id, description, version, input, output, effects…
140
+ needsSpec: {
141
+ env: {
142
+ ORDERS_BASE_URL: { type: 'string', pattern: '^https://' },
143
+ ORDERS_REGION: { type: 'string', enum: ['eu', 'us'], default: 'eu' },
144
+ },
145
+ },
146
+ handler: async ({ orderId }, ctx) => ({
147
+ url: `${ctx.env?.ORDERS_BASE_URL}/${ctx.env?.ORDERS_REGION}/orders/${orderId}`,
148
+ }),
149
+ });
150
+ ```
151
+
152
+ - **Which value a call gets:** its project's, else its org's, else the tenant's, in the runtime's env; a schema `default` makes a name optional. The values a call used are recorded with it, so a retry or a resume sees the same ones.
153
+ - **Setting them:** `kindgi env set ORDERS_REGION us --scope=project:<project-id> --env=local` (or `--scope=tenant`, for every project). Changing a value that's already set takes `--force`.
154
+ - **A declared value nobody set** stops the call before the tool runs. For a tool `acme-orders.needs-account` that declares `ACME_ACCOUNT_ID`, the message reads:
155
+ ```text
156
+ precondition-failed: Tool "acme-orders.needs-account" was not run: env-value-missing: tool "acme-orders.needs-account" needs env value "ACME_ACCOUNT_ID" in env "local", and none is set for project e889c1f5-eae7-45dc-8669-5bd029a5d85c, its org, or the tenant. Set it: kindgi env set ACME_ACCOUNT_ID <value> --scope=project:e889c1f5-eae7-45dc-8669-5bd029a5d85c --env=local (or --scope=tenant, for every project)
157
+ ```
158
+ - **Not secret:** env values are recorded with each run that uses them and shown in its journal. A credential is a secret (`needsSpec.secrets`), never an env value.
159
+ - **In a unit test:** pass `env: { … }` in the context `invokeTool` gets.
160
+
135
161
  Everything else comes from the process environment: `process.env.CITATOR_URL`. The pack service runs with the pack's env files in `kindgi dev`, and with the container's environment in an image. Declare the names your code reads in `kindgi.config.ts`, `env: { required: ['CITATOR_URL'], optional: [...] }`: a deployment injects exactly those, a pack service missing a required one isn't ready and says which, and `kindgi dev` warns about it. Values per environment go in `environments.<name>.env`, secrets only as references.
136
162
 
137
163
  ## Declarative HTTP spec
@@ -14,9 +14,9 @@ description: >
14
14
  diagnostic output into durable input for framework improvement.
15
15
  type: core
16
16
  library: "@kindgi/sdk"
17
- version: "0.4.0"
17
+ version: "0.4.2"
18
18
  sdk_version: "0.0.0"
19
- pack_languages: [node, python]
19
+ pack_languages: [node, python, java, scala]
20
20
  ---
21
21
 
22
22
  # Capturing framework feedback
@@ -25,7 +25,8 @@ pack_languages: [node, python]
25
25
  > (`@kindgi/cli`), not a global command. Run it through the project's
26
26
  > package manager — `pnpm exec kindgi …`, `npx --no kindgi …` (npm),
27
27
  > `yarn kindgi …` or `bun run kindgi …`. A Python pack (`[tool.kindgi]` in
28
- > `pyproject.toml`) has no Node project: run the `kindgi` on `PATH`.
28
+ > `pyproject.toml`) has no Node project: run the `kindgi` on `PATH`. A Java
29
+ > or Scala pack (`kindgi.config.json`) runs the CLI it pins: `./kindgiw …`.
29
30
  > Commands below are written `kindgi …` for brevity.
30
31
 
31
32
  You just spent time diagnosing a Kindgi-framework issue. That diagnostic
@@ -0,0 +1,220 @@
1
+ ---
2
+ name: kindgi-java-authoring-agents
3
+ description: >
4
+ Covers writing agents for a Kindgi pack in Java (`com.kindgi:kindgi-pack`):
5
+ `Agent.define(id)` as a `public static final` field, wiring tools (`Tool`
6
+ objects or an id with a version range) and guardrails, capabilities and
7
+ model choice (preferredProvider / preferredModel), conversation policy,
8
+ turn budgets, prompt parameters, a typed answer from a record, and
9
+ tool-error retries, all as data. Load this whenever you are authoring or
10
+ editing code in a Java pack's agents packages (a pack whose
11
+ `kindgi.config.json` says `"language": "java"`), defining an agent, or
12
+ when the user asks to add, change or refactor one. Java tools are covered
13
+ by kindgi-java-authoring-tools, Java guardrails by
14
+ kindgi-java-authoring-guardrails, connecting a real model by
15
+ kindgi-authoring-providers.
16
+ type: core
17
+ library: "kindgi-pack (Java)"
18
+ version: "0.1.0"
19
+ sdk_version: "0.0.0"
20
+ pack_languages: [java]
21
+ sources:
22
+ - sdks/java/kindgi-pack/README.md
23
+ - sdks/java/kindgi-pack/src/main/java/com/kindgi/pack/Agent.java
24
+ - packages/specs/schemas/agent.schema.json
25
+ - packages/specs/schemas/pack-index.schema.json
26
+ ---
27
+
28
+ # Authoring Kindgi agents in Java
29
+
30
+ > **Running `kindgi`:** the pack pins its CLI (`"cli"` in
31
+ > `kindgi.config.json`), and `./kindgiw` runs that version, so every
32
+ > `kindgi <command>` below runs as `./kindgiw <command>`. Maven runs as
33
+ > `./mvnw`.
34
+ >
35
+ > Java support is in preview: tested and supported, but the API may still
36
+ > change in 0.1.6 without the usual deprecation period.
37
+
38
+ An **agent** is a versioned, model-driven orchestrator. It's made of:
39
+ - instructions (a prompt template);
40
+ - the tools it may call;
41
+ - the capabilities its model needs;
42
+ - guardrails that gate its answer;
43
+ - optionally, a conversation policy.
44
+
45
+ In a Java pack it is **data**: a `public static final Agent` field of a class
46
+ in an `agents` package. The model runs in the Kindgi runtime, not in your
47
+ JVM. Your Java code runs only inside the agent's tools and guardrail checks.
48
+
49
+ ## Ask before building
50
+
51
+ "Add an agent" is a conversation opener, not a ticket. Before writing a
52
+ file, ask:
53
+
54
+ - **What should the agent do?** The purpose drives everything else.
55
+ - **Which tools does it need?** New ones, or existing ones?
56
+ - **Multi-turn or one-shot?** History changes the shape.
57
+ - **Any rules it must respect?** Those become guardrails.
58
+
59
+ The pack's sample agent proves the runtime works end to end. It is not the
60
+ shape to imitate unless the user asks for that.
61
+
62
+ ## An agent
63
+
64
+ ```java
65
+ // src/main/java/acme/agents/BriefWriter.java
66
+ package acme.agents;
67
+
68
+ import acme.guardrails.ResponseNotEmpty;
69
+ import acme.tools.Echo;
70
+ import com.kindgi.pack.Agent;
71
+ import java.util.List;
72
+ import java.util.Map;
73
+
74
+ /** acme.brief-writer: drafts a brief's argument from the case facts. */
75
+ public final class BriefWriter {
76
+ /** The typed answer: the final message must be JSON of this shape. */
77
+ public record Brief(String argument, List<String> citations) {}
78
+
79
+ public static final Agent AGENT = Agent.define("acme.brief-writer")
80
+ .version("0.1.0")
81
+ .name("Brief Writer")
82
+ .description("Drafts appellate briefs from a case file; cites precedents.")
83
+ .instructions("You are drafting a brief in {{ jurisdiction }}. The user gives the case facts; you write "
84
+ + "a Section IV argument citing at least two precedents. Check every cite with the echo tool "
85
+ + "before using it. Never invent one.")
86
+ .capability(Map.of("needs", List.of(Map.of("feature", "tool-use"))))
87
+ .tool(Echo.TOOL)
88
+ .guardrail(ResponseNotEmpty.GUARDRAIL)
89
+ .set("parameters", List.of(Map.of("name", "jurisdiction", "type", "string", "required", true)))
90
+ .set("conversationPolicy", Map.of("historyLimit", 20))
91
+ .set("budget", Map.of("maxSteps", 8, "maxCostUsd", 0.5, "maxWallMs", 60_000))
92
+ .set("toolErrors", Map.of("maxRetries", 1, "retryOn", List.of("invalid-arguments", "unknown-tool")))
93
+ .output(Brief.class)
94
+ .build();
95
+
96
+ private BriefWriter() {}
97
+ }
98
+ ```
99
+
100
+ - Tools and guardrails are the pack's own fields, imported like any class:
101
+ `.tool(Echo.TOOL)`, `.guardrail(ResponseNotEmpty.GUARDRAIL)`.
102
+ - **`build()` needs a version, a name and instructions.** Anything missing
103
+ is an error where the agent is defined, and the indexer reports it with
104
+ the file.
105
+ - **Every other field is `set(field, value)`, keyed as on the wire:** the
106
+ field and its maps are camelCase (`conversationPolicy`, `maxSteps`,
107
+ `historyLimit`), as `agent.schema.json` names them. The indexer checks
108
+ each agent against the pack index's schema, and `kindgi dev` reports a
109
+ mistake with its file.
110
+ - A class may hold several agents, each in its own `static final` field.
111
+
112
+ ## Field by field
113
+
114
+ - **`id`:** `<pack-id>.<agent-name>`, kebab-case, dot-namespaced.
115
+ - **`version`:** an exact semver. Conversations pin the version they started
116
+ on.
117
+ - **`name`, `description`**, and `set("tags", List.of(…))`: for people and
118
+ listings. The model never sees the description.
119
+ - **`instructions`:** a LiquidJS template. `{{ variable }}` comes from
120
+ `parameters` or the runtime's own variables (`today`, `now`, `agent.*`,
121
+ `conversation.*`). It's rendered strictly: an unknown variable fails the
122
+ turn. Write it as a brief for a capable colleague: what to do, which tools
123
+ to prefer, what to refuse, the quality bar. Name a tool by what it does
124
+ ("the verify-citation tool"), never by its dotted id. The model sees ids in
125
+ its provider's form (`acme__verify-citation` for Anthropic and
126
+ OpenAI-compatible models), and a dotted id in the instructions can make it
127
+ call a name it wasn't given. `instructions(Map.of("prompt", …, "version", …))`
128
+ references a registered prompt block instead.
129
+ - **`capability(Map)`:** what the model must support, such as
130
+ `Map.of("needs", List.of(Map.of("feature", "tool-use")))`. Call it once per
131
+ capability. The turn routes its first capability to pick a provider and
132
+ model; with none declared, the turn fails.
133
+ - **`tool(Tool)`:** pins that tool's version (its own, or the pack's).
134
+ **`tool(id, range)`** is a tool of another pack, with a semver **range**
135
+ (`"^1.0.0"`); the highest active matching version is picked at turn
136
+ start. An agent with no tools is chat-only.
137
+ - **`guardrail(Guardrail)`** or **`guardrail(id)`:** evaluated once per turn
138
+ on the final answer, before it is stored. An id with no registered
139
+ guardrail fails the turn.
140
+ - **`set("parameters", List.of(Map.of("name", …, "type", …, "required", …)))`:**
141
+ inputs the caller supplies per run. They fill `{{ … }}` in the
142
+ instructions.
143
+ - **`set("preferredProvider", "anthropic")`, `set("preferredModel", "claude-haiku-4-5")`:**
144
+ soft hints. The router prefers them when they satisfy the capabilities.
145
+ To *require* a model, put it in the capability:
146
+ `Map.of("needs", List.of(Map.of("feature", "tool-use"), Map.of("models", Map.of("allow", List.of("claude-haiku-4-5")))))`.
147
+ - **`set("conversationPolicy", …)`:** `historyLimit` caps the prior messages
148
+ loaded, and `hitl` configures approval gates. Absent, the turn loads the
149
+ full history with no gates. A tenant's `hitl` policy can tighten the gates
150
+ (a shorter timeout, a higher reviewer role, stricter per tool), never
151
+ loosen them.
152
+ - **`set("budget", …)`:** per turn. `maxSteps` counts model calls (default
153
+ 8); `maxCostUsd`; `maxWallMs` (default 120 000). Exceeding the steps or the
154
+ cost fails the turn (`budget-exceeded`); running out of wall time aborts
155
+ it. Leave room for real models: a turn with tool calls can take tens of
156
+ seconds.
157
+ - **`output(Type.class)`:** a typed answer, with its schema derived from the
158
+ record. The final answer must be JSON matching it. A wrong one goes back
159
+ to the model with the problems (`maxRepairs`, default 1), and then the
160
+ turn fails (`output-schema-violation`). For `maxRepairs` or a schema of
161
+ your own, use `set("output", Map.of("schema", …, "maxRepairs", 2))`. The
162
+ parsed answer is the turn result's `output`. In a flow it is
163
+ `nodeOutputs.<step>.output.<field>`.
164
+ - **`set("toolErrors", …)`:** `maxRetries` and `retryOn`. A failed tool call
165
+ goes back to the model as the call's result, so it can fix the call. The
166
+ default kinds are `invalid-arguments` and `unknown-tool` (nothing ran). Add
167
+ `tool-error` only when retrying the tool is safe. Each retry costs a step.
168
+
169
+ ## Which model answers
170
+
171
+ Agents run on a registered model provider: the router picks one whose
172
+ models satisfy the capabilities. `kindgi dev` gives a new pack `dev-echo`, a
173
+ **fallback** that answers only while no other provider fits. It calls the
174
+ first tool and replies "⚠ dev-echo isn't a real model: …", then "Tool
175
+ responded: …". The turn carries the `fallback-provider` and
176
+ `dev-echo-not-a-model` warnings. dev-echo can't fill in any other tool
177
+ input, and can't give a typed answer: an agent with `output` fails with
178
+ `output-schema-violation` until a real model is registered. To register one,
179
+ see `kindgi-authoring-providers` (`./kindgiw providers register --preset=anthropic`,
180
+ or `"providers": [{"preset": "anthropic"}]` in `kindgi.config.json`).
181
+
182
+ ## Iterating
183
+
184
+ Save the file: `kindgi dev` recompiles, re-indexes, and the next run uses
185
+ it, with no restart. Bump `version` when you break what callers rely on (a
186
+ removed parameter, an incompatible output), not on every save.
187
+
188
+ Run an agent from another terminal in the pack directory:
189
+
190
+ ```sh
191
+ ./kindgiw runs start --agent=acme.brief-writer --input='{"userMessage":"…","parameters":{"jurisdiction":"US"}}'
192
+ ```
193
+
194
+ or from a Java app with the client (`kindgi-java-getting-started`).
195
+
196
+ ## Common mistakes
197
+
198
+ 1. **Building an agent without asking what it should do.** Copying the
199
+ sample's shape answers the wrong question.
200
+ 2. **No `capability(…)`.** The turn can't pick a model.
201
+ 3. **Java names inside the maps.** `set("budget", Map.of("max_steps", 4))`
202
+ isn't a budget: the keys are the wire's camelCase (`maxSteps`,
203
+ `historyLimit`, `maxRetries`).
204
+ 4. **An unregistered guardrail id.** Pass the `Guardrail` field, or make sure
205
+ the id is one the tenant has.
206
+ 5. **`{{ variable }}` not in `parameters`.** The turn fails when the
207
+ instructions render.
208
+ 6. **An agent that isn't a `static final` field.** Only static fields are
209
+ indexed.
210
+ 7. **A tight `maxWallMs` with a real model.** 15 s aborts real turns under
211
+ load; 60 s is a safer start.
212
+ 8. **Expecting dev-echo to give a typed answer.** It can't: register a real
213
+ model first.
214
+
215
+ ## When the framework itself is the problem
216
+
217
+ If the bug is in Kindgi or kindgi-pack (the index dropping a field, a
218
+ misleading error, the router picking the wrong model) and not in the pack's
219
+ code, load `kindgi-framework-feedback` and file it with
220
+ `./kindgiw feedback write`.