@kindgi/cli 0.1.4 → 0.1.5

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 (325) hide show
  1. package/README.md +13 -7
  2. package/dist/build/bundle.d.ts +11 -3
  3. package/dist/build/bundle.d.ts.map +1 -1
  4. package/dist/build/bundle.js +17 -3
  5. package/dist/build/bundle.js.map +1 -1
  6. package/dist/build/defaults.d.ts +3 -1
  7. package/dist/build/defaults.d.ts.map +1 -1
  8. package/dist/build/defaults.js +44 -1
  9. package/dist/build/defaults.js.map +1 -1
  10. package/dist/build/java-image.d.ts +31 -0
  11. package/dist/build/java-image.d.ts.map +1 -0
  12. package/dist/build/java-image.js +146 -0
  13. package/dist/build/java-image.js.map +1 -0
  14. package/dist/build/pack-root.d.ts +1 -31
  15. package/dist/build/pack-root.d.ts.map +1 -1
  16. package/dist/build/pack-root.js +22 -2
  17. package/dist/build/pack-root.js.map +1 -1
  18. package/dist/build/runners.d.ts +44 -0
  19. package/dist/build/runners.d.ts.map +1 -1
  20. package/dist/build/scala-image.d.ts +21 -0
  21. package/dist/build/scala-image.d.ts.map +1 -0
  22. package/dist/build/scala-image.js +150 -0
  23. package/dist/build/scala-image.js.map +1 -0
  24. package/dist/cli-pin.d.ts +16 -0
  25. package/dist/cli-pin.d.ts.map +1 -0
  26. package/dist/cli-pin.js +66 -0
  27. package/dist/cli-pin.js.map +1 -0
  28. package/dist/commands/agents.d.ts.map +1 -1
  29. package/dist/commands/agents.js +9 -8
  30. package/dist/commands/agents.js.map +1 -1
  31. package/dist/commands/approvals.d.ts.map +1 -1
  32. package/dist/commands/approvals.js +30 -4
  33. package/dist/commands/approvals.js.map +1 -1
  34. package/dist/commands/artifacts.d.ts.map +1 -1
  35. package/dist/commands/artifacts.js +100 -37
  36. package/dist/commands/artifacts.js.map +1 -1
  37. package/dist/commands/auth.js +2 -2
  38. package/dist/commands/blocks.d.ts.map +1 -1
  39. package/dist/commands/blocks.js +5 -4
  40. package/dist/commands/blocks.js.map +1 -1
  41. package/dist/commands/build.d.ts +10 -2
  42. package/dist/commands/build.d.ts.map +1 -1
  43. package/dist/commands/build.js +109 -16
  44. package/dist/commands/build.js.map +1 -1
  45. package/dist/commands/capabilities.d.ts.map +1 -1
  46. package/dist/commands/capabilities.js +37 -10
  47. package/dist/commands/capabilities.js.map +1 -1
  48. package/dist/commands/console.d.ts +21 -0
  49. package/dist/commands/console.d.ts.map +1 -0
  50. package/dist/commands/console.js +87 -0
  51. package/dist/commands/console.js.map +1 -0
  52. package/dist/commands/conversations.d.ts.map +1 -1
  53. package/dist/commands/conversations.js +13 -2
  54. package/dist/commands/conversations.js.map +1 -1
  55. package/dist/commands/deploy.d.ts.map +1 -1
  56. package/dist/commands/deploy.js +2 -2
  57. package/dist/commands/deploy.js.map +1 -1
  58. package/dist/commands/dev.d.ts +11 -0
  59. package/dist/commands/dev.d.ts.map +1 -1
  60. package/dist/commands/dev.js +212 -40
  61. package/dist/commands/dev.js.map +1 -1
  62. package/dist/commands/doctor.d.ts +14 -2
  63. package/dist/commands/doctor.d.ts.map +1 -1
  64. package/dist/commands/doctor.js +356 -20
  65. package/dist/commands/doctor.js.map +1 -1
  66. package/dist/commands/env-scoped.d.ts +44 -0
  67. package/dist/commands/env-scoped.d.ts.map +1 -0
  68. package/dist/commands/env-scoped.js +222 -0
  69. package/dist/commands/env-scoped.js.map +1 -0
  70. package/dist/commands/env.d.ts.map +1 -1
  71. package/dist/commands/env.js +85 -123
  72. package/dist/commands/env.js.map +1 -1
  73. package/dist/commands/eval-runs.d.ts +9 -0
  74. package/dist/commands/eval-runs.d.ts.map +1 -1
  75. package/dist/commands/eval-runs.js +20 -19
  76. package/dist/commands/eval-runs.js.map +1 -1
  77. package/dist/commands/eval-suites.d.ts.map +1 -1
  78. package/dist/commands/eval-suites.js +16 -8
  79. package/dist/commands/eval-suites.js.map +1 -1
  80. package/dist/commands/exports.d.ts +3 -0
  81. package/dist/commands/exports.d.ts.map +1 -0
  82. package/dist/commands/exports.js +82 -0
  83. package/dist/commands/exports.js.map +1 -0
  84. package/dist/commands/feedback.d.ts.map +1 -1
  85. package/dist/commands/feedback.js +6 -5
  86. package/dist/commands/feedback.js.map +1 -1
  87. package/dist/commands/flows.d.ts.map +1 -1
  88. package/dist/commands/flows.js +2 -1
  89. package/dist/commands/flows.js.map +1 -1
  90. package/dist/commands/gate-policies.d.ts.map +1 -1
  91. package/dist/commands/gate-policies.js +2 -1
  92. package/dist/commands/gate-policies.js.map +1 -1
  93. package/dist/commands/guardrails.d.ts.map +1 -1
  94. package/dist/commands/guardrails.js +3 -2
  95. package/dist/commands/guardrails.js.map +1 -1
  96. package/dist/commands/helpers.js +5 -5
  97. package/dist/commands/helpers.js.map +1 -1
  98. package/dist/commands/index.d.ts.map +1 -1
  99. package/dist/commands/index.js +14 -0
  100. package/dist/commands/index.js.map +1 -1
  101. package/dist/commands/init.d.ts +1 -1
  102. package/dist/commands/init.d.ts.map +1 -1
  103. package/dist/commands/init.js +254 -18
  104. package/dist/commands/init.js.map +1 -1
  105. package/dist/commands/judge-classes.d.ts.map +1 -1
  106. package/dist/commands/judge-classes.js +11 -10
  107. package/dist/commands/judge-classes.js.map +1 -1
  108. package/dist/commands/judgments.d.ts.map +1 -1
  109. package/dist/commands/judgments.js +7 -6
  110. package/dist/commands/judgments.js.map +1 -1
  111. package/dist/commands/key.js +4 -4
  112. package/dist/commands/memory.d.ts.map +1 -1
  113. package/dist/commands/memory.js +277 -25
  114. package/dist/commands/memory.js.map +1 -1
  115. package/dist/commands/people.d.ts +3 -0
  116. package/dist/commands/people.d.ts.map +1 -0
  117. package/dist/commands/people.js +166 -0
  118. package/dist/commands/people.js.map +1 -0
  119. package/dist/commands/proposals.d.ts.map +1 -1
  120. package/dist/commands/proposals.js +390 -69
  121. package/dist/commands/proposals.js.map +1 -1
  122. package/dist/commands/provenance.d.ts.map +1 -1
  123. package/dist/commands/provenance.js +6 -7
  124. package/dist/commands/provenance.js.map +1 -1
  125. package/dist/commands/providers.d.ts.map +1 -1
  126. package/dist/commands/providers.js +7 -5
  127. package/dist/commands/providers.js.map +1 -1
  128. package/dist/commands/reviewers.d.ts.map +1 -1
  129. package/dist/commands/reviewers.js +3 -2
  130. package/dist/commands/reviewers.js.map +1 -1
  131. package/dist/commands/runs.d.ts.map +1 -1
  132. package/dist/commands/runs.js +35 -15
  133. package/dist/commands/runs.js.map +1 -1
  134. package/dist/commands/schedules.d.ts +3 -0
  135. package/dist/commands/schedules.d.ts.map +1 -0
  136. package/dist/commands/schedules.js +275 -0
  137. package/dist/commands/schedules.js.map +1 -0
  138. package/dist/commands/secrets.d.ts.map +1 -1
  139. package/dist/commands/secrets.js +10 -11
  140. package/dist/commands/secrets.js.map +1 -1
  141. package/dist/commands/service-accounts.d.ts +3 -0
  142. package/dist/commands/service-accounts.d.ts.map +1 -0
  143. package/dist/commands/service-accounts.js +175 -0
  144. package/dist/commands/service-accounts.js.map +1 -0
  145. package/dist/commands/skills.d.ts +1 -1
  146. package/dist/commands/skills.d.ts.map +1 -1
  147. package/dist/commands/skills.js +11 -4
  148. package/dist/commands/skills.js.map +1 -1
  149. package/dist/commands/sso.d.ts +6 -0
  150. package/dist/commands/sso.d.ts.map +1 -0
  151. package/dist/commands/sso.js +318 -0
  152. package/dist/commands/sso.js.map +1 -0
  153. package/dist/commands/tokens.d.ts +11 -0
  154. package/dist/commands/tokens.d.ts.map +1 -1
  155. package/dist/commands/tokens.js +144 -11
  156. package/dist/commands/tokens.js.map +1 -1
  157. package/dist/commands/tools.d.ts.map +1 -1
  158. package/dist/commands/tools.js +4 -3
  159. package/dist/commands/tools.js.map +1 -1
  160. package/dist/commands/unwired.d.ts.map +1 -1
  161. package/dist/commands/unwired.js +1 -30
  162. package/dist/commands/unwired.js.map +1 -1
  163. package/dist/commands/upgrade.d.ts +11 -0
  164. package/dist/commands/upgrade.d.ts.map +1 -0
  165. package/dist/commands/upgrade.js +160 -0
  166. package/dist/commands/upgrade.js.map +1 -0
  167. package/dist/context.d.ts +7 -0
  168. package/dist/context.d.ts.map +1 -1
  169. package/dist/context.js +1 -0
  170. package/dist/context.js.map +1 -1
  171. package/dist/dev/defaults.d.ts +28 -11
  172. package/dist/dev/defaults.d.ts.map +1 -1
  173. package/dist/dev/defaults.js +114 -35
  174. package/dist/dev/defaults.js.map +1 -1
  175. package/dist/dev/google-credentials.d.ts +50 -0
  176. package/dist/dev/google-credentials.d.ts.map +1 -0
  177. package/dist/dev/google-credentials.js +157 -0
  178. package/dist/dev/google-credentials.js.map +1 -0
  179. package/dist/dev/java-builder.d.ts +56 -0
  180. package/dist/dev/java-builder.d.ts.map +1 -0
  181. package/dist/dev/java-builder.js +204 -0
  182. package/dist/dev/java-builder.js.map +1 -0
  183. package/dist/dev/jvm-run-files.d.ts +29 -0
  184. package/dist/dev/jvm-run-files.d.ts.map +1 -0
  185. package/dist/dev/jvm-run-files.js +94 -0
  186. package/dist/dev/jvm-run-files.js.map +1 -0
  187. package/dist/dev/lines.d.ts +13 -0
  188. package/dist/dev/lines.d.ts.map +1 -0
  189. package/dist/dev/lines.js +24 -0
  190. package/dist/dev/lines.js.map +1 -0
  191. package/dist/dev/log-view.d.ts +116 -0
  192. package/dist/dev/log-view.d.ts.map +1 -0
  193. package/dist/dev/log-view.js +233 -0
  194. package/dist/dev/log-view.js.map +1 -0
  195. package/dist/dev/pack-code.d.ts +79 -4
  196. package/dist/dev/pack-code.d.ts.map +1 -1
  197. package/dist/dev/pack-code.js +262 -3
  198. package/dist/dev/pack-code.js.map +1 -1
  199. package/dist/dev/pack-env.d.ts.map +1 -1
  200. package/dist/dev/pack-env.js +4 -0
  201. package/dist/dev/pack-env.js.map +1 -1
  202. package/dist/dev/pack-service.d.ts +11 -1
  203. package/dist/dev/pack-service.d.ts.map +1 -1
  204. package/dist/dev/pack-service.js +56 -20
  205. package/dist/dev/pack-service.js.map +1 -1
  206. package/dist/dev/paths.d.ts +6 -0
  207. package/dist/dev/paths.d.ts.map +1 -1
  208. package/dist/dev/paths.js +8 -0
  209. package/dist/dev/paths.js.map +1 -1
  210. package/dist/dev/project-database.js +1 -1
  211. package/dist/dev/project-database.js.map +1 -1
  212. package/dist/dev/runners.d.ts +35 -9
  213. package/dist/dev/runners.d.ts.map +1 -1
  214. package/dist/dev/runtime-container.d.ts +10 -2
  215. package/dist/dev/runtime-container.d.ts.map +1 -1
  216. package/dist/dev/runtime-container.js +48 -20
  217. package/dist/dev/runtime-container.js.map +1 -1
  218. package/dist/dev/runtime-env.d.ts +10 -0
  219. package/dist/dev/runtime-env.d.ts.map +1 -1
  220. package/dist/dev/runtime-env.js +8 -4
  221. package/dist/dev/runtime-env.js.map +1 -1
  222. package/dist/dev/runtime-image.d.ts +1 -1
  223. package/dist/dev/runtime-image.js +1 -1
  224. package/dist/dev/scala-builder.d.ts +72 -0
  225. package/dist/dev/scala-builder.d.ts.map +1 -0
  226. package/dist/dev/scala-builder.js +347 -0
  227. package/dist/dev/scala-builder.js.map +1 -0
  228. package/dist/env/project-env.d.ts.map +1 -1
  229. package/dist/env/project-env.js +3 -1
  230. package/dist/env/project-env.js.map +1 -1
  231. package/dist/errors.d.ts +9 -0
  232. package/dist/errors.d.ts.map +1 -1
  233. package/dist/errors.js +15 -0
  234. package/dist/errors.js.map +1 -1
  235. package/dist/init/augment-scaffolder.js +1 -1
  236. package/dist/init/augment-scaffolder.js.map +1 -1
  237. package/dist/init/dependency-specs.d.ts +26 -0
  238. package/dist/init/dependency-specs.d.ts.map +1 -1
  239. package/dist/init/dependency-specs.js +38 -0
  240. package/dist/init/dependency-specs.js.map +1 -1
  241. package/dist/init/java-augment.d.ts +24 -0
  242. package/dist/init/java-augment.d.ts.map +1 -0
  243. package/dist/init/java-augment.js +198 -0
  244. package/dist/init/java-augment.js.map +1 -0
  245. package/dist/init/mode-detect.d.ts +2 -2
  246. package/dist/init/mode-detect.d.ts.map +1 -1
  247. package/dist/init/mode-detect.js +12 -1
  248. package/dist/init/mode-detect.js.map +1 -1
  249. package/dist/init/python-augment.js +1 -1
  250. package/dist/init/python-augment.js.map +1 -1
  251. package/dist/init/scala-augment.d.ts +23 -0
  252. package/dist/init/scala-augment.d.ts.map +1 -0
  253. package/dist/init/scala-augment.js +213 -0
  254. package/dist/init/scala-augment.js.map +1 -0
  255. package/dist/init/template-files.d.ts +40 -2
  256. package/dist/init/template-files.d.ts.map +1 -1
  257. package/dist/init/template-files.js +56 -5
  258. package/dist/init/template-files.js.map +1 -1
  259. package/dist/main.d.ts +3 -0
  260. package/dist/main.d.ts.map +1 -1
  261. package/dist/main.js +20 -2
  262. package/dist/main.js.map +1 -1
  263. package/dist/open-url.d.ts +15 -0
  264. package/dist/open-url.d.ts.map +1 -0
  265. package/dist/open-url.js +41 -0
  266. package/dist/open-url.js.map +1 -0
  267. package/dist/package-manager.d.ts +10 -3
  268. package/dist/package-manager.d.ts.map +1 -1
  269. package/dist/package-manager.js +19 -1
  270. package/dist/package-manager.js.map +1 -1
  271. package/dist/providers/presets/anthropic.json +11 -3
  272. package/dist/sdk-skills/kindgi-authoring-agents/SKILL.md +30 -2
  273. package/dist/sdk-skills/kindgi-authoring-flows/SKILL.md +1 -1
  274. package/dist/sdk-skills/kindgi-authoring-guardrails/SKILL.md +47 -3
  275. package/dist/sdk-skills/kindgi-authoring-mcp-servers/SKILL.md +7 -5
  276. package/dist/sdk-skills/kindgi-authoring-providers/SKILL.md +40 -15
  277. package/dist/sdk-skills/kindgi-authoring-tools/SKILL.md +66 -2
  278. package/dist/sdk-skills/kindgi-framework-feedback/SKILL.md +5 -4
  279. package/dist/sdk-skills/kindgi-getting-started/SKILL.md +1 -1
  280. package/dist/sdk-skills/kindgi-java-authoring-agents/SKILL.md +220 -0
  281. package/dist/sdk-skills/kindgi-java-authoring-flows/SKILL.md +390 -0
  282. package/dist/sdk-skills/kindgi-java-authoring-guardrails/SKILL.md +209 -0
  283. package/dist/sdk-skills/kindgi-java-authoring-tools/SKILL.md +334 -0
  284. package/dist/sdk-skills/kindgi-java-getting-started/SKILL.md +270 -0
  285. package/dist/sdk-skills/kindgi-python-authoring-agents/SKILL.md +25 -3
  286. package/dist/sdk-skills/kindgi-python-authoring-flows/SKILL.md +1 -1
  287. package/dist/sdk-skills/kindgi-python-authoring-guardrails/SKILL.md +10 -3
  288. package/dist/sdk-skills/kindgi-python-authoring-tools/SKILL.md +45 -6
  289. package/dist/sdk-skills/kindgi-python-getting-started/SKILL.md +2 -2
  290. package/dist/sdk-skills/kindgi-scala-authoring-agents/SKILL.md +217 -0
  291. package/dist/sdk-skills/kindgi-scala-authoring-flows/SKILL.md +357 -0
  292. package/dist/sdk-skills/kindgi-scala-authoring-guardrails/SKILL.md +199 -0
  293. package/dist/sdk-skills/kindgi-scala-authoring-tools/SKILL.md +302 -0
  294. package/dist/sdk-skills/kindgi-scala-getting-started/SKILL.md +302 -0
  295. package/dist/templates/java/.mvn/wrapper/maven-wrapper.properties +3 -0
  296. package/dist/templates/java/AGENTS.md +31 -0
  297. package/dist/templates/java/README.md.tmpl +69 -0
  298. package/dist/templates/java/gitignore +9 -0
  299. package/dist/templates/java/kindgi.config.json.tmpl +8 -0
  300. package/dist/templates/java/kindgiw +33 -0
  301. package/dist/templates/java/kindgiw.cmd +28 -0
  302. package/dist/templates/java/mvnw +295 -0
  303. package/dist/templates/java/pom.xml.tmpl +65 -0
  304. package/dist/templates/java/src/main/java/__PACKAGE__/agents/EchoAgent.java.tmpl +27 -0
  305. package/dist/templates/java/src/main/java/__PACKAGE__/flows/EchoFlow.java.tmpl +18 -0
  306. package/dist/templates/java/src/main/java/__PACKAGE__/guardrails/ResponseNotEmpty.java.tmpl +28 -0
  307. package/dist/templates/java/src/main/java/__PACKAGE__/tools/Echo.java.tmpl +22 -0
  308. package/dist/templates/java/src/main/java/__PACKAGE__/tools/Greet.java.tmpl +23 -0
  309. package/dist/templates/java/src/test/java/__PACKAGE__/ToolsTest.java.tmpl +30 -0
  310. package/dist/templates/minimal/README.md.tmpl +6 -12
  311. package/dist/templates/python/README.md.tmpl +4 -4
  312. package/dist/templates/sample/README.md.tmpl +5 -11
  313. package/dist/templates/scala/AGENTS.md +32 -0
  314. package/dist/templates/scala/README.md.tmpl +75 -0
  315. package/dist/templates/scala/build.sbt.tmpl +16 -0
  316. package/dist/templates/scala/gitignore +13 -0
  317. package/dist/templates/scala/kindgi.config.json.tmpl +8 -0
  318. package/dist/templates/scala/project/build.properties +1 -0
  319. package/dist/templates/scala/src/main/scala/__PACKAGE__/agents/EchoAgent.scala.tmpl +23 -0
  320. package/dist/templates/scala/src/main/scala/__PACKAGE__/flows/EchoFlow.scala.tmpl +16 -0
  321. package/dist/templates/scala/src/main/scala/__PACKAGE__/guardrails/ResponseNotEmpty.scala.tmpl +21 -0
  322. package/dist/templates/scala/src/main/scala/__PACKAGE__/tools/Echo.scala.tmpl +16 -0
  323. package/dist/templates/scala/src/main/scala/__PACKAGE__/tools/Greet.scala.tmpl +17 -0
  324. package/dist/templates/scala/src/test/scala/__PACKAGE__/ToolsSuite.scala.tmpl +23 -0
  325. package/package.json +13 -12
@@ -14,8 +14,8 @@ description: >
14
14
  kindgi-python-getting-started.
15
15
  type: core
16
16
  library: "kindgi (Python)"
17
- version: "0.1.2"
18
- sdk_version: "0.1.4"
17
+ version: "0.1.3"
18
+ sdk_version: "0.1.5"
19
19
  pack_languages: [python]
20
20
  sources:
21
21
  - sdks/python/src/kindgi/pack/define.py
@@ -116,7 +116,19 @@ def verify_citation(citation: Citation, ctx: ToolContext) -> Verdict:
116
116
  or wait with `ctx.cancellation.wait(timeout)` between slow steps.
117
117
  - `ctx.secrets` — the secrets the tool declares in `needs_spec`,
118
118
  resolved for the call's tenant (below).
119
- - `ctx.env`, `ctx.config` — **reserved, empty today**.
119
+ - `ctx.env` — the env values the tool declares in `needs_spec`, resolved
120
+ for the call: its project's value, else its org's, else the tenant's
121
+ (`kindgi env set NAME <value> --scope=project:<id> --env=<env>`).
122
+ Strings, and not secret. Empty from an older runtime.
123
+ - `ctx.config` — **reserved, empty today**.
124
+ - `ctx.log` — a logger bound to the call (its records carry the run's ids,
125
+ the tool's id and the trace id): `ctx.log.info("refund issued",
126
+ {"orderId": order_id, "amountCents": amount_cents})`, or the fields as
127
+ keywords. Values go in the fields, never in the message. Log ids,
128
+ amounts and outcomes, never what a person typed (a refund's reason, a
129
+ message, an address): the log is read by whoever operates the runtime.
130
+ `ToolContext.for_test(…)` logs nothing unless you pass it `log=`. Docs:
131
+ https://docs.kindgi.com/v0.1/guides/tools/write-a-tool/#log-from-a-tool
120
132
 
121
133
  ## Configuration and secrets
122
134
 
@@ -140,6 +152,33 @@ fails the call, naming the secret, when it is missing or doesn't match.
140
152
  Every declared secret is required. In a test, pass them:
141
153
  `ToolContext.for_test(secrets={"CITATOR_KEY": "…"})`.
142
154
 
155
+ 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 `needs_spec`, read from `ctx.env`:
156
+
157
+ ```python
158
+ @tool(
159
+ id="acme.find-order",
160
+ mutating=False,
161
+ needs_spec={
162
+ "env": {
163
+ "ORDERS_BASE_URL": {"type": "string", "pattern": "^https://"},
164
+ "ORDERS_REGION": {"type": "string", "enum": ["eu", "us"], "default": "eu"},
165
+ }
166
+ },
167
+ )
168
+ def find_order(lookup: Lookup, ctx: ToolContext) -> Found:
169
+ """…"""
170
+ return Found(url=f"{ctx.env['ORDERS_BASE_URL']}/{ctx.env['ORDERS_REGION']}/orders/{lookup.order_id}")
171
+ ```
172
+
173
+ - **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.
174
+ - **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`.
175
+ - **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:
176
+ ```text
177
+ 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)
178
+ ```
179
+ - **Not secret:** env values are recorded with each run that uses them and shown in its journal. A credential is a secret, never an env value.
180
+ - **In a test:** `ToolContext.for_test(env={"ORDERS_BASE_URL": "…"})`.
181
+
143
182
  Everything else comes from the process environment: `os.environ["CITATOR_URL"]`.
144
183
  The pack service runs with the pack's environment — in `kindgi dev`
145
184
  that is the pack's `.env` and `.env.local` (or `[tool.kindgi.dev]
@@ -274,9 +313,9 @@ removed field, a narrower type — not on every save.
274
313
  ## Common mistakes
275
314
 
276
315
  1. **Copying the sample tool's shape without asking what the tool should do.**
277
- 2. **Reading `ctx.env` / `ctx.config`, or an undeclared `ctx.secrets` name.**
278
- The first two are empty, and `ctx.secrets` holds only what `needs_spec`
279
- declares; use `os.environ` for the rest.
316
+ 2. **Reading `ctx.config`, or an undeclared `ctx.env` or `ctx.secrets` name.**
317
+ `ctx.config` is empty, and `ctx.env` and `ctx.secrets` hold only what
318
+ `needs_spec` declares; use `os.environ` for the rest.
280
319
  3. **A non-object input** (`def f(n: int)`): the input must be a model,
281
320
  TypedDict, dataclass or object schema.
282
321
  4. **No docstring and no `description=`**, or an unannotated input or
@@ -15,7 +15,7 @@ description: >
15
15
  type: core
16
16
  library: "kindgi (Python)"
17
17
  version: "0.1.7"
18
- sdk_version: "0.1.4"
18
+ sdk_version: "0.1.5"
19
19
  pack_languages: [python]
20
20
  sources:
21
21
  - sdks/python/README.md
@@ -189,7 +189,7 @@ from kindgi.client import Kindgi
189
189
  client = Kindgi() # KINDGI_API_URL + KINDGI_API_TOKEN, or Kindgi(url, token=…)
190
190
  run = client.runs.start(agent="my-pack.echo-agent", input={"userMessage": "Ada"})
191
191
  print(run.status, run.output["response"]["content"])
192
- for event in client.runs.stream(str(run.id)):
192
+ for event in client.runs.follow(run.id): # to the run's end, reconnecting
193
193
  print(event.kind)
194
194
  ```
195
195
 
@@ -0,0 +1,217 @@
1
+ ---
2
+ name: kindgi-scala-authoring-agents
3
+ description: >
4
+ Covers writing agents for a Kindgi pack in Scala (`kindgi-pack-scala`,
5
+ `com.kindgi.pack.scaladsl`): `Agent(id)` as a `val` of an object named like
6
+ its file, wiring tools (`Tool` vals or an id with a version range) and
7
+ guardrails, capabilities and model choice (preferredProvider /
8
+ preferredModel), conversation policy, turn budgets, prompt parameters, a
9
+ typed answer from a case class (`output[T]`), and tool-error retries, all
10
+ as data with Scala maps. Load this whenever you are authoring or editing
11
+ code in a Scala pack's agents packages (a pack whose `kindgi.config.json`
12
+ says `"language": "scala"`), defining an agent, or when the user asks to
13
+ add, change or refactor one. Scala tools are covered by
14
+ kindgi-scala-authoring-tools, Scala guardrails by
15
+ kindgi-scala-authoring-guardrails, connecting a real model by
16
+ kindgi-authoring-providers.
17
+ type: core
18
+ library: "kindgi-pack-scala"
19
+ version: "0.1.0"
20
+ sdk_version: "0.1.5"
21
+ pack_languages: [scala]
22
+ sources:
23
+ - sdks/scala/README.md
24
+ - sdks/scala/kindgi-pack-scala/src/main/scala/com/kindgi/pack/scaladsl/Agent.scala
25
+ - packages/specs/schemas/agent.schema.json
26
+ - packages/specs/schemas/pack-index.schema.json
27
+ ---
28
+
29
+ # Authoring Kindgi agents in Scala
30
+
31
+ > **Running `kindgi`:** the pack pins its CLI (`"cli"` in
32
+ > `kindgi.config.json`), and `./kindgiw` runs that version, so every
33
+ > `kindgi <command>` below runs as `./kindgiw <command>`.
34
+ >
35
+ > Scala 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 Scala pack it is **data**: a `val` of type `Agent` in an object named
46
+ like its file, in an `agents` package. The model runs in the Kindgi runtime,
47
+ not in your JVM. Your Scala code runs only inside the agent's tools and
48
+ guardrail checks.
49
+
50
+ ## Ask before building
51
+
52
+ "Add an agent" is a conversation opener, not a ticket. Before writing a
53
+ file, ask:
54
+
55
+ - **What should the agent do?** The purpose drives everything else.
56
+ - **Which tools does it need?** New ones, or existing ones?
57
+ - **Multi-turn or one-shot?** History changes the shape.
58
+ - **Any rules it must respect?** Those become guardrails.
59
+
60
+ The pack's sample agent proves the runtime works end to end. It is not the
61
+ shape to imitate unless the user asks for that.
62
+
63
+ ## An agent
64
+
65
+ ```scala
66
+ // src/main/scala/acme/agents/BriefWriter.scala
67
+ package acme.agents
68
+
69
+ import acme.guardrails.ResponseNotEmpty
70
+ import acme.tools.Echo
71
+ import com.kindgi.pack.scaladsl._
72
+
73
+ /** acme.brief-writer: drafts a brief's argument from the case facts. */
74
+ object BriefWriter {
75
+ /** The typed answer: the final message must be JSON of this shape. */
76
+ final case class Brief(argument: String, citations: List[String])
77
+
78
+ val agent: Agent = Agent("acme.brief-writer")
79
+ .version("0.1.0")
80
+ .name("Brief Writer")
81
+ .description("Drafts appellate briefs from a case file; cites precedents.")
82
+ .instructions(
83
+ "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("needs" -> List(Map("feature" -> "tool-use"))))
87
+ .tool(Echo.tool)
88
+ .guardrail(ResponseNotEmpty.guardrail)
89
+ .set("parameters", List(Map("name" -> "jurisdiction", "type" -> "string", "required" -> true)))
90
+ .set("conversationPolicy", Map("historyLimit" -> 20))
91
+ .set("budget", Map("maxSteps" -> 8, "maxCostUsd" -> 0.5, "maxWallMs" -> 60000))
92
+ .set("toolErrors", Map("maxRetries" -> 1, "retryOn" -> List("invalid-arguments", "unknown-tool")))
93
+ .output[Brief]
94
+ .build()
95
+ }
96
+ ```
97
+
98
+ - Tools and guardrails are the pack's own vals, imported like any member:
99
+ `.tool(Echo.tool)`, `.guardrail(ResponseNotEmpty.guardrail)`.
100
+ - **`build()` needs a version, a name and instructions.** Anything missing
101
+ is an error where the agent is defined, and the indexer reports it with
102
+ the file.
103
+ - **Every other field is `set(field, value)`, keyed as on the wire,** with
104
+ Scala maps and lists: the field and its maps are camelCase
105
+ (`conversationPolicy`, `maxSteps`, `historyLimit`), as `agent.schema.json`
106
+ names them. The indexer checks each agent against the pack index's
107
+ schema, and `kindgi dev` reports a mistake with its file.
108
+ - An object may hold several agents, each in its own `val`.
109
+
110
+ ## Field by field
111
+
112
+ - **`id`:** `<pack-id>.<agent-name>`, kebab-case, dot-namespaced.
113
+ - **`version`:** an exact semver. Conversations pin the version they started
114
+ on.
115
+ - **`name`, `description`**, and `set("tags", List(…))`: for people and
116
+ listings. The model never sees the description.
117
+ - **`instructions`:** a LiquidJS template. `{{ variable }}` comes from
118
+ `parameters` or the runtime's own variables (`today`, `now`, `agent.*`,
119
+ `conversation.*`). It's rendered strictly: an unknown variable fails the
120
+ turn. Write it as a brief for a capable colleague: what to do, which tools
121
+ to prefer, what to refuse, the quality bar. Name a tool by what it does
122
+ ("the verify-citation tool"), never by its dotted id. The model sees ids in
123
+ its provider's form (`acme__verify-citation` for Anthropic and
124
+ OpenAI-compatible models), and a dotted id in the instructions can make it
125
+ call a name it wasn't given. `instructions(Map("prompt" -> …, "version" -> …))`
126
+ references a registered prompt block instead.
127
+ - **`capability(Map(…))`:** what the model must support, such as
128
+ `Map("needs" -> List(Map("feature" -> "tool-use")))`. Call it once per
129
+ capability. The turn routes its first capability to pick a provider and
130
+ model; with none declared, the turn fails.
131
+ - **`tool(tool)`:** pins that tool's version (its own, or the pack's).
132
+ **`tool(id, range)`** is a tool of another pack, with a semver **range**
133
+ (`"^1.0.0"`); the highest active matching version is picked at turn
134
+ start. An agent with no tools is chat-only.
135
+ - **`guardrail(guardrail)`** or **`guardrail(id)`:** evaluated once per turn
136
+ on the final answer, before it is stored. An id with no registered
137
+ guardrail fails the turn.
138
+ - **`set("parameters", List(Map("name" -> …, "type" -> …, "required" -> …)))`:**
139
+ inputs the caller supplies per run. They fill `{{ … }}` in the
140
+ instructions.
141
+ - **`set("preferredProvider", "anthropic")`, `set("preferredModel", "claude-haiku-4-5")`:**
142
+ soft hints. The router prefers them when they satisfy the capabilities.
143
+ To *require* a model, put it in the capability:
144
+ `Map("needs" -> List(Map("feature" -> "tool-use"), Map("models" -> Map("allow" -> List("claude-haiku-4-5")))))`.
145
+ - **`set("conversationPolicy", …)`:** `historyLimit` caps the prior messages
146
+ loaded, and `hitl` configures approval gates. Absent, the turn loads the
147
+ full history with no gates. A tenant's `hitl` policy can tighten the gates
148
+ (a shorter timeout, a higher reviewer role, stricter per tool), never
149
+ loosen them.
150
+ - **`set("budget", …)`:** per turn. `maxSteps` counts model calls (default
151
+ 8); `maxCostUsd`; `maxWallMs` (default 120 000). Exceeding the steps or the
152
+ cost fails the turn (`budget-exceeded`); running out of wall time aborts
153
+ it. Leave room for real models: a turn with tool calls can take tens of
154
+ seconds.
155
+ - **`output[T]`:** a typed answer, with its schema derived from the case
156
+ class. The final answer must be JSON matching it. A wrong one goes back to
157
+ the model with the problems (`maxRepairs`, default 1), and then the turn
158
+ fails (`output-schema-violation`). For `maxRepairs` or a schema of your
159
+ own, use `set("output", Map("schema" -> …, "maxRepairs" -> 2))`. The parsed
160
+ answer is the turn result's `output`. In a flow it is
161
+ `nodeOutputs.<step>.output.<field>`.
162
+ - **`set("toolErrors", …)`:** `maxRetries` and `retryOn`. A failed tool call
163
+ goes back to the model as the call's result, so it can fix the call. The
164
+ default kinds are `invalid-arguments` and `unknown-tool` (nothing ran). Add
165
+ `tool-error` only when retrying the tool is safe. Each retry costs a step.
166
+
167
+ ## Which model answers
168
+
169
+ Agents run on a registered model provider: the router picks one whose
170
+ models satisfy the capabilities. `kindgi dev` gives a new pack `dev-echo`, a
171
+ **fallback** that answers only while no other provider fits. It calls the
172
+ first tool and replies "⚠ dev-echo isn't a real model: …", then "Tool
173
+ responded: …". The turn carries the `fallback-provider` and
174
+ `dev-echo-not-a-model` warnings. dev-echo can't fill in any other tool
175
+ input, and can't give a typed answer: an agent with an output type fails
176
+ with `output-schema-violation` until a real model is registered. To register
177
+ one, see `kindgi-authoring-providers` (`./kindgiw providers register --preset=anthropic`,
178
+ or `"providers": [{"preset": "anthropic"}]` in `kindgi.config.json`).
179
+
180
+ ## Iterating
181
+
182
+ Save the file: `kindgi dev` recompiles through sbt's server, re-indexes, and
183
+ the next run uses it, with no restart. Bump `version` when you break what
184
+ callers rely on (a removed parameter, an incompatible output), not on every
185
+ save.
186
+
187
+ Run an agent from another terminal in the pack directory:
188
+
189
+ ```sh
190
+ ./kindgiw runs start --agent=acme.brief-writer --input='{"userMessage":"…","parameters":{"jurisdiction":"US"}}'
191
+ ```
192
+
193
+ ## Common mistakes
194
+
195
+ 1. **Building an agent without asking what it should do.** Copying the
196
+ sample's shape answers the wrong question.
197
+ 2. **No `capability(…)`.** The turn can't pick a model.
198
+ 3. **Scala names inside the maps.** `set("budget", Map("max_steps" -> 4))`
199
+ isn't a budget: the keys are the wire's camelCase (`maxSteps`,
200
+ `historyLimit`, `maxRetries`).
201
+ 4. **An unregistered guardrail id.** Pass the `Guardrail` val, or make sure
202
+ the id is one the tenant has.
203
+ 5. **`{{ variable }}` not in `parameters`.** The turn fails when the
204
+ instructions render.
205
+ 6. **An agent as a `def` or a `lazy val`.** It's a file error: make it a
206
+ `val`.
207
+ 7. **A tight `maxWallMs` with a real model.** 15 s aborts real turns under
208
+ load; 60 s is a safer start.
209
+ 8. **Expecting dev-echo to give a typed answer.** It can't: register a real
210
+ model first.
211
+
212
+ ## When the framework itself is the problem
213
+
214
+ If the bug is in Kindgi, kindgi-pack or the Scala layer (the index dropping
215
+ a field, a misleading error, the router picking the wrong model) and not in
216
+ the pack's code, load `kindgi-framework-feedback` and file it with
217
+ `./kindgiw feedback write`.
@@ -0,0 +1,357 @@
1
+ ---
2
+ name: kindgi-scala-authoring-flows
3
+ description: >
4
+ Covers writing flows for a Kindgi pack in Scala (`kindgi-pack-scala`,
5
+ `com.kindgi.pack.scaladsl`): `Flow(id)` as a `val` of an object named like
6
+ its file, tool and agent steps (`toolNode`, `agentNode`, or a node map
7
+ with `inputMapping` and `config`), edges and their `when` conditions and
8
+ `policy` (`edge(Map(…))`), branches that join again, inputMapping from
9
+ runInput / nodeOutputs, typed agent output in a flow, the flow's declared
10
+ output, loops and fanout, and running a flow (runs start --flow, in the
11
+ background, as a dry run) and reading its journal. Load this whenever you
12
+ are authoring or editing code in a Scala pack's flows packages (a pack
13
+ whose `kindgi.config.json` says `"language": "scala"`), defining a flow,
14
+ or when the user asks to add, change or debug one. Scala tools are
15
+ covered by kindgi-scala-authoring-tools, Scala agents by
16
+ kindgi-scala-authoring-agents.
17
+ type: core
18
+ library: "kindgi-pack-scala"
19
+ version: "0.1.0"
20
+ sdk_version: "0.1.5"
21
+ pack_languages: [scala]
22
+ sources:
23
+ - sdks/scala/README.md
24
+ - sdks/scala/kindgi-pack-scala/src/main/scala/com/kindgi/pack/scaladsl/Agent.scala
25
+ - packages/specs/schemas/flow.schema.json
26
+ ---
27
+
28
+ # Authoring Kindgi flows in Scala
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>`.
33
+ >
34
+ > Scala support is in preview: tested and supported, but the API may still
35
+ > change in 0.1.6 without the usual deprecation period.
36
+
37
+ A **flow** is a versioned, durable graph of steps: tools (your code) and
38
+ agents (a model's judgment), joined by edges that can carry conditions. It
39
+ is **data**, not code: a `val` of type `Flow` in an object named like its
40
+ file, in a `flows` package. The runtime runs it step by step, journals every
41
+ step, and can resume a run that was interrupted. A run pins the flow version
42
+ it started on.
43
+
44
+ Use a flow when the order of the work is known: parse, then classify, then
45
+ branch, then write. Use a single agent when the model should decide the
46
+ order.
47
+
48
+ ## Ask before building
49
+
50
+ - **What goes in, and what comes out?** The run input's shape and the
51
+ output the caller reads. They become `runInput.*` paths and `output`.
52
+ - **Which steps are code, which are judgment?** Deterministic work (parse,
53
+ rank, look up, write) is a tool. Judgment (classify, draft, summarize) is
54
+ an agent with a typed output.
55
+ - **Where does it branch?** Every branch needs a condition, and the steps
56
+ after a branch must cope with the branch that didn't run.
57
+ - **What does it change outside Kindgi?** Know which tools write: a dry run
58
+ stops before them (see "Running a flow").
59
+
60
+ ## A flow
61
+
62
+ The tools and the agent it runs:
63
+
64
+ ```scala
65
+ // src/main/scala/acme/tools/Tickets.scala
66
+ package acme.tools
67
+
68
+ import com.kindgi.pack.scaladsl._
69
+
70
+ /** The ticket tools: parse one, look its invoice up, draft the reply. */
71
+ object Tickets {
72
+ final case class Ticket(customerId: String, body: String)
73
+ final case class ParseInput(ticket: Ticket)
74
+ final case class Parsed(text: String)
75
+ final case class InvoiceInput(customerId: String)
76
+ final case class Invoice(number: String, amount: BigDecimal)
77
+ final case class Found(invoice: Invoice)
78
+ /** `invoice` is absent when the billing step didn't run: an Option. */
79
+ final case class ReplyInput(category: String, invoice: Option[Invoice])
80
+ final case class Reply(text: String)
81
+
82
+ val parse: Tool[ParseInput, Parsed] = Tool[ParseInput, Parsed]("acme.parse-ticket")
83
+ .description("Extracts a ticket's text.")
84
+ .readOnly
85
+ .handler((in, _) => Parsed(in.ticket.body.strip))
86
+
87
+ val lookupInvoice: Tool[InvoiceInput, Found] = Tool[InvoiceInput, Found]("acme.lookup-invoice")
88
+ .description("The customer's latest invoice.")
89
+ .readOnly
90
+ .handler((_, _) => Found(Invoice("INV-1", BigDecimal("42.00"))))
91
+
92
+ val draftReply: Tool[ReplyInput, Reply] = Tool[ReplyInput, Reply]("acme.draft-reply")
93
+ .description("Drafts a reply for the ticket's category.")
94
+ .readOnly
95
+ .handler((in, _) =>
96
+ Reply(in.invoice match {
97
+ case Some(invoice) => s"Invoice ${invoice.number} is ${invoice.amount}."
98
+ case None => s"Thanks, we're on it (${in.category})."
99
+ }))
100
+ }
101
+ ```
102
+
103
+ ```scala
104
+ // src/main/scala/acme/agents/TicketClassifier.scala
105
+ package acme.agents
106
+
107
+ import com.kindgi.pack.scaladsl._
108
+
109
+ /** acme.ticket-classifier: one word for a ticket's category. */
110
+ object TicketClassifier {
111
+ final case class Category(category: String)
112
+
113
+ val agent: Agent = Agent("acme.ticket-classifier")
114
+ .version("0.1.0")
115
+ .name("Ticket classifier")
116
+ .instructions(
117
+ "Classify the support ticket for {{ product }}: answer billing, bug or other, " +
118
+ "as JSON with one field, category.")
119
+ .capability(Map("needs" -> List(Map("feature" -> "tool-use"))))
120
+ .set("parameters", List(Map("name" -> "product", "type" -> "string", "required" -> true)))
121
+ .output[Category]
122
+ .build()
123
+ }
124
+ ```
125
+
126
+ The flow:
127
+
128
+ ```scala
129
+ // src/main/scala/acme/flows/TriageTicket.scala
130
+ package acme.flows
131
+
132
+ import acme.agents.TicketClassifier
133
+ import acme.tools.Tickets
134
+ import com.kindgi.pack.scaladsl._
135
+
136
+ /** acme.triage-ticket: parse, classify, look billing up when needed, reply. */
137
+ object TriageTicket {
138
+ private val isBilling = Map(
139
+ "op" -> "eq",
140
+ "left" -> Map("path" -> "nodeOutputs.classify.output.category"),
141
+ "right" -> Map("literal" -> "billing"))
142
+
143
+ val flow: Flow = Flow("acme.triage-ticket")
144
+ .version("0.1.0")
145
+ .set("name", "Triage a support ticket")
146
+ .set("description", "Parses a ticket, classifies it, looks billing up when needed, drafts a reply.")
147
+ .node(Map("id" -> "parse", "kind" -> "tool", "ref" -> Tickets.parse,
148
+ "inputMapping" -> Map("ticket" -> Map("path" -> "runInput.ticket"))))
149
+ .node(Map("id" -> "classify", "kind" -> "agent", "ref" -> TicketClassifier.agent,
150
+ "inputMapping" -> Map("text" -> Map("path" -> "nodeOutputs.parse.text")),
151
+ "config" -> Map("parameters" -> Map("product" -> "acme-cloud"))))
152
+ .node(Map("id" -> "billing", "kind" -> "tool", "ref" -> Tickets.lookupInvoice,
153
+ "inputMapping" -> Map("customerId" -> Map("path" -> "runInput.ticket.customerId"))))
154
+ .node(Map("id" -> "reply", "kind" -> "tool", "ref" -> Tickets.draftReply,
155
+ "inputMapping" -> Map(
156
+ "category" -> Map("path" -> "nodeOutputs.classify.output.category"),
157
+ "invoice" -> Map("path" -> "nodeOutputs.billing.invoice"))))
158
+ .edge("e0", "$start", "parse")
159
+ .edge("e1", "parse", "classify")
160
+ .edge(Map("id" -> "e2", "from" -> "classify", "to" -> "billing", "when" -> isBilling))
161
+ .edge(Map("id" -> "e3", "from" -> "classify", "to" -> "reply", "when" -> Map("op" -> "not", "child" -> isBilling)))
162
+ .edge("e4", "billing", "reply")
163
+ .edge("e5", "reply", "$end")
164
+ .set("output", Map(
165
+ "mapping" -> Map(
166
+ "category" -> Map("path" -> "nodeOutputs.classify.output.category"),
167
+ "reply" -> Map("path" -> "nodeOutputs.reply.text")),
168
+ "schema" -> Map(
169
+ "type" -> "object",
170
+ "properties" -> Map("category" -> Map("type" -> "string"), "reply" -> Map("type" -> "string")),
171
+ "required" -> List("category", "reply"))))
172
+ .build()
173
+ }
174
+ ```
175
+
176
+ - **Steps:** `toolNode(id, tool)` and `agentNode(id, agent)` are a step with
177
+ nothing more. A step with an `inputMapping`, a `config`, a loop or a fanout
178
+ is a map, `node(Map(…))`, as `flow.schema.json` describes it. In a node's
179
+ map, a `Tool`, `Agent` or `Flow` (loop bodies and fanout branches included)
180
+ becomes its id. A primitive of another pack is its id string.
181
+ - **Edges:** `edge(id, from, to)` joins two steps. An edge with a condition
182
+ (`when`) or a `policy` is a map, `edge(Map(…))`.
183
+ - **Other fields:** `set(field, value)` takes `name`, `description`,
184
+ `output`, `maxParallelism` and `metadata`. `build()` needs a version.
185
+ - **The maps are the wire's shape,** in Scala maps and lists, so their keys
186
+ are camelCase (`inputMapping`, `loopKind`, `maxIterations`).
187
+ - **Where it's checked:** the indexer checks every flow against
188
+ `flow.schema.json` (version, node and edge shapes, `$start` / `$end`).
189
+ `kindgi dev` reports a mistake as a file error that says what and where,
190
+ while the pack's other primitives keep serving. Whether a step's tool or
191
+ agent exists is checked when a run starts (see "Running a flow").
192
+
193
+ ## Nodes
194
+
195
+ - **A tool step** (`"kind" -> "tool"`) runs the tool `ref`.
196
+ - Its input is what the node's `inputMapping` builds; else, the output of
197
+ the node's single upstream node (the run input after `$start`).
198
+ - The input is checked against the tool's input schema, so a mismatch
199
+ fails the step with `input-validation-failed`.
200
+ - The node's output is what the tool returned, as JSON.
201
+ - **An agent step** (`"kind" -> "agent"`) runs one turn of the agent `ref`,
202
+ as a child run of the flow run.
203
+ - The agent gets the node's input two ways: as structured input
204
+ (`{{ input.text }}` in its instructions), and as its user message (the
205
+ input as JSON).
206
+ - `"config" -> Map("parameters" -> Map(…))` fills the agent's `parameters`
207
+ (string, number or boolean values). `"config" -> Map("version" -> "1.2.0")`
208
+ pins an agent version; without it, the latest active version runs.
209
+ - The node's output: `output` is the agent's typed answer (its
210
+ `output[T]`); `text` is the answer as text; `runId` and `conversationId`
211
+ are the child run's. Read a field as `nodeOutputs.<node>.output.<field>`.
212
+ - An answer that doesn't fit the agent's output, after its repairs, fails
213
+ the step with `output-schema-violation`. An approval inside the agent's
214
+ turn parks the flow until it's decided.
215
+ - **A loop** (`"kind" -> "loop"`) repeats a body.
216
+ - `"loopKind" -> "foreach"` runs it once per element of `iterateOver`
217
+ (`concurrency` up to 32 in parallel). `"loopKind" -> "while"` runs it
218
+ until `exitCondition`.
219
+ - The body (`"body" -> Map("nodes" -> List(…), "edges" -> List(…))`) has
220
+ its own nodes and edges, with `$loop-start` and `$loop-end`. The element
221
+ is the body's input.
222
+ - `maxIterations` and `outputSchema` are required.
223
+ - The loop's output is `finalOutput`, plus `outputs` with
224
+ `"collectAllIterations" -> true`.
225
+ - Node ids must be unique across the whole flow, bodies included.
226
+ - **A fanout** (`"kind" -> "fanout"`) runs several handlers on the same input
227
+ at once. Each is a branch (`branchId`, `handler`: a `Tool` or an id, and
228
+ `outputSchema`). `convergence` decides the result: `all-succeed`,
229
+ `any-succeed` (the first success wins), or `settle-all` (wait for every
230
+ branch and report each).
231
+ - **A sub-flow** (`"kind" -> "subgraph"`) is part of the flow schema, but a
232
+ run refuses it today (`flow-unbound`). Inline the steps instead.
233
+
234
+ ## Edges and conditions
235
+
236
+ An edge goes from a node (or `$start`) to a node (or `$end`). Without `when`
237
+ it fires when its source completes. With `when`, it fires only if the
238
+ condition is true. Conditions are maps:
239
+
240
+ | Operator | Shape |
241
+ |---|---|
242
+ | `eq` `ne` `lt` `lte` `gt` `gte` | `op`, `left`, `right` |
243
+ | `in` `notIn` | `op`, `value`, `set` |
244
+ | `exists` `notExists` `truthy` `falsy` | `op`, `value` |
245
+ | `and` `or` | `op`, `children` (a list) |
246
+ | `not` | `op`, `child` |
247
+
248
+ Each operand is `Map("literal" -> …)` or `Map("path" -> …)`. When a path
249
+ doesn't resolve, `eq`, `lt`, `lte`, `gt` and `gte` are false, and `ne` is
250
+ true. So for the "otherwise" branch, wrap the condition in `not` (as above)
251
+ rather than writing a second comparison: it covers exactly what the first
252
+ edge doesn't. A condition used twice is easiest as a `val` (`isBilling`).
253
+
254
+ **Joining branches.** A node with several incoming edges runs once every one
255
+ of them is decided and at least one fired. Above, `reply` runs after
256
+ `billing` on the billing branch, and straight after `classify` otherwise. A
257
+ node none of whose incoming edges fired is skipped, and so is everything
258
+ only it leads to.
259
+
260
+ **Edge policy** (`"policy"` on the edge into a node with a single incoming
261
+ edge; a node with several ignores it):
262
+ - `"retry" -> Map("maxAttempts" -> …, "delayMs" -> …)`, with `backoff` and
263
+ `maxDelayMs` optional; up to 10 attempts in all;
264
+ - `"timeoutMs"`: a step that takes longer fails with `reason: timeout`;
265
+ - `"concurrencyKey"`: at most one such step at a time in the tenant;
266
+ - `"priority"`: −100 to 100.
267
+
268
+ ## Inputs and the output
269
+
270
+ `inputMapping` maps each key to a `literal` or a `path`. Its keys are the
271
+ tool's input **as it travels**: the case class's Jackson names (a parameter
272
+ `customerId` is the key `customerId`; with `@JsonProperty("customer_id")`,
273
+ it's `customer_id`). Paths are dot-separated (a number segment indexes an
274
+ array: `items.0.sku`), rooted at:
275
+ - `runInput.…`: the input the run started with;
276
+ - `nodeOutputs.<nodeId>.…`: a step's output. For an agent step, add
277
+ `.output.<field>` to read its typed answer;
278
+ - `state.…`: values the runtime's own handlers write. Pack tools don't
279
+ write it, so use `nodeOutputs`.
280
+
281
+ A path that doesn't resolve leaves its key out. So a step after a branch
282
+ that didn't run gets no `invoice` key at all. Make that parameter an
283
+ `Option` (as above), or give it a default.
284
+
285
+ `set("output", …)` is what the run returns: a `mapping` resolved when the
286
+ run finishes, checked against `schema` if you give one. A run whose output
287
+ doesn't match fails. Without an output, the run returns the output of the
288
+ step that reached `$end`.
289
+
290
+ ## Running a flow
291
+
292
+ From another terminal in the pack directory, while `kindgi dev` runs:
293
+
294
+ ```sh
295
+ ./kindgiw runs start --flow=acme.triage-ticket --input='{"ticket":{"customerId":"c-1","body":"Charged twice"}}'
296
+ ./kindgiw runs start --flow=acme.triage-ticket --input=@ticket.json --no-wait # the run id now; it finishes in the background
297
+ ./kindgiw runs start --flow=acme.triage-ticket --input=@ticket.json --dry-run # stops before a tool that may write
298
+ ./kindgiw runs get <run-id> # status, output, failureMessage
299
+ ./kindgiw runs journal <run-id> # every step.started / step.completed / edge.evaluated
300
+ ./kindgiw runs stream <run-id> # follow a running one
301
+ ./kindgiw runs cancel <run-id>
302
+ ```
303
+
304
+ - **A refusal before the run exists:** `422 flow-unbound` names the nodes a
305
+ run can't bind: a tool or agent id the tenant doesn't have, or a
306
+ sub-flow. Fix the ids; nothing ran.
307
+ - **A failed step fails the run**, and `failureMessage` says which step and
308
+ why. Retry it on its edge with `policy.retry`, but only if running the
309
+ step twice is safe.
310
+ - **`--no-wait`** is how an application starts runs
311
+ (`"options": {"wait": false}`). It answers with the run id at once; poll
312
+ the run or follow its stream.
313
+ - **`--dry-run`** runs a tool only if it's declared read-only (`readOnly`)
314
+ and has no `writes`, `deletes`, `spawns-run`, `emits-event` or
315
+ `external-side-effect` effect. The first other tool stops the run with
316
+ `dry-run-effectful-tool`, and everything before it really ran.
317
+
318
+ ## Iterating on a flow
319
+
320
+ Save the file, and `kindgi dev` recompiles and re-indexes. The next run uses
321
+ the new definition, with no restart. A run already in flight keeps the
322
+ version it started on. Bump `version` when callers' contract changes (the
323
+ input or the output), not on every save.
324
+
325
+ ## Common mistakes
326
+
327
+ 1. **Building a flow without asking what goes in and comes out.** The pack's
328
+ `EchoFlow` proves the runtime works. It isn't a template for the user's
329
+ flow.
330
+ 2. **A second comparison for "otherwise".** On a path that may be missing,
331
+ `eq` is false and `ne` is true, and `lt`/`gt` are both false, so a
332
+ hand-written opposite can miss a case or overlap. Wrap the positive
333
+ condition in `not`.
334
+ 3. **Reading an agent step's answer at `nodeOutputs.<step>.<field>`.** The
335
+ typed answer is under `.output`. An agent without an output type has
336
+ only `text`.
337
+ 4. **A required input parameter fed by a branch that may not run.** The key
338
+ is left out, the tool's input check fails, and so does the step. Make it
339
+ an `Option`, or give it a default.
340
+ 5. **`inputMapping` keys in the wrong spelling.** They're the input's wire
341
+ names.
342
+ 6. **A `when` given through `set("edges", …)`.** `build()` writes the edges
343
+ added with `edge(…)`; use `edge(Map(…))` for an edge with a condition or
344
+ a policy.
345
+ 7. **A sub-flow node.** A run refuses it (`flow-unbound`).
346
+ 8. **Duplicate node ids inside a loop body.** Ids are unique across the
347
+ whole flow, bodies included.
348
+ 9. **`readOnly` on a tool that writes.** A dry run then runs it for real.
349
+ 10. **A flow as a `def` or a `lazy val`.** It's a file error: make it a
350
+ `val`.
351
+
352
+ ## When the framework itself is the problem
353
+
354
+ If the bug is in Kindgi, kindgi-pack or the Scala layer (a step's output
355
+ missing a field, a condition that evaluates wrongly, a misleading error) and
356
+ not in the pack's code, load `kindgi-framework-feedback` and file it with
357
+ `./kindgiw feedback write`.