@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.
- package/README.md +13 -7
- package/dist/build/bundle.d.ts +11 -3
- package/dist/build/bundle.d.ts.map +1 -1
- package/dist/build/bundle.js +17 -3
- package/dist/build/bundle.js.map +1 -1
- package/dist/build/defaults.d.ts +3 -1
- package/dist/build/defaults.d.ts.map +1 -1
- package/dist/build/defaults.js +44 -1
- package/dist/build/defaults.js.map +1 -1
- package/dist/build/java-image.d.ts +31 -0
- package/dist/build/java-image.d.ts.map +1 -0
- package/dist/build/java-image.js +146 -0
- package/dist/build/java-image.js.map +1 -0
- package/dist/build/pack-root.d.ts +1 -31
- package/dist/build/pack-root.d.ts.map +1 -1
- package/dist/build/pack-root.js +22 -2
- package/dist/build/pack-root.js.map +1 -1
- package/dist/build/runners.d.ts +44 -0
- package/dist/build/runners.d.ts.map +1 -1
- package/dist/build/scala-image.d.ts +21 -0
- package/dist/build/scala-image.d.ts.map +1 -0
- package/dist/build/scala-image.js +150 -0
- package/dist/build/scala-image.js.map +1 -0
- package/dist/cli-pin.d.ts +16 -0
- package/dist/cli-pin.d.ts.map +1 -0
- package/dist/cli-pin.js +66 -0
- package/dist/cli-pin.js.map +1 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +9 -8
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/approvals.d.ts.map +1 -1
- package/dist/commands/approvals.js +30 -4
- package/dist/commands/approvals.js.map +1 -1
- package/dist/commands/artifacts.d.ts.map +1 -1
- package/dist/commands/artifacts.js +100 -37
- package/dist/commands/artifacts.js.map +1 -1
- package/dist/commands/auth.js +2 -2
- package/dist/commands/blocks.d.ts.map +1 -1
- package/dist/commands/blocks.js +5 -4
- package/dist/commands/blocks.js.map +1 -1
- package/dist/commands/build.d.ts +10 -2
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +109 -16
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/capabilities.d.ts.map +1 -1
- package/dist/commands/capabilities.js +37 -10
- package/dist/commands/capabilities.js.map +1 -1
- package/dist/commands/console.d.ts +21 -0
- package/dist/commands/console.d.ts.map +1 -0
- package/dist/commands/console.js +87 -0
- package/dist/commands/console.js.map +1 -0
- package/dist/commands/conversations.d.ts.map +1 -1
- package/dist/commands/conversations.js +13 -2
- package/dist/commands/conversations.js.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +2 -2
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts +11 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +212 -40
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts +14 -2
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +356 -20
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/env-scoped.d.ts +44 -0
- package/dist/commands/env-scoped.d.ts.map +1 -0
- package/dist/commands/env-scoped.js +222 -0
- package/dist/commands/env-scoped.js.map +1 -0
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +85 -123
- package/dist/commands/env.js.map +1 -1
- package/dist/commands/eval-runs.d.ts +9 -0
- package/dist/commands/eval-runs.d.ts.map +1 -1
- package/dist/commands/eval-runs.js +20 -19
- package/dist/commands/eval-runs.js.map +1 -1
- package/dist/commands/eval-suites.d.ts.map +1 -1
- package/dist/commands/eval-suites.js +16 -8
- package/dist/commands/eval-suites.js.map +1 -1
- package/dist/commands/exports.d.ts +3 -0
- package/dist/commands/exports.d.ts.map +1 -0
- package/dist/commands/exports.js +82 -0
- package/dist/commands/exports.js.map +1 -0
- package/dist/commands/feedback.d.ts.map +1 -1
- package/dist/commands/feedback.js +6 -5
- package/dist/commands/feedback.js.map +1 -1
- package/dist/commands/flows.d.ts.map +1 -1
- package/dist/commands/flows.js +2 -1
- package/dist/commands/flows.js.map +1 -1
- package/dist/commands/gate-policies.d.ts.map +1 -1
- package/dist/commands/gate-policies.js +2 -1
- package/dist/commands/gate-policies.js.map +1 -1
- package/dist/commands/guardrails.d.ts.map +1 -1
- package/dist/commands/guardrails.js +3 -2
- package/dist/commands/guardrails.js.map +1 -1
- package/dist/commands/helpers.js +5 -5
- package/dist/commands/helpers.js.map +1 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +14 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +254 -18
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/judge-classes.d.ts.map +1 -1
- package/dist/commands/judge-classes.js +11 -10
- package/dist/commands/judge-classes.js.map +1 -1
- package/dist/commands/judgments.d.ts.map +1 -1
- package/dist/commands/judgments.js +7 -6
- package/dist/commands/judgments.js.map +1 -1
- package/dist/commands/key.js +4 -4
- package/dist/commands/memory.d.ts.map +1 -1
- package/dist/commands/memory.js +277 -25
- package/dist/commands/memory.js.map +1 -1
- package/dist/commands/people.d.ts +3 -0
- package/dist/commands/people.d.ts.map +1 -0
- package/dist/commands/people.js +166 -0
- package/dist/commands/people.js.map +1 -0
- package/dist/commands/proposals.d.ts.map +1 -1
- package/dist/commands/proposals.js +390 -69
- package/dist/commands/proposals.js.map +1 -1
- package/dist/commands/provenance.d.ts.map +1 -1
- package/dist/commands/provenance.js +6 -7
- package/dist/commands/provenance.js.map +1 -1
- package/dist/commands/providers.d.ts.map +1 -1
- package/dist/commands/providers.js +7 -5
- package/dist/commands/providers.js.map +1 -1
- package/dist/commands/reviewers.d.ts.map +1 -1
- package/dist/commands/reviewers.js +3 -2
- package/dist/commands/reviewers.js.map +1 -1
- package/dist/commands/runs.d.ts.map +1 -1
- package/dist/commands/runs.js +35 -15
- package/dist/commands/runs.js.map +1 -1
- package/dist/commands/schedules.d.ts +3 -0
- package/dist/commands/schedules.d.ts.map +1 -0
- package/dist/commands/schedules.js +275 -0
- package/dist/commands/schedules.js.map +1 -0
- package/dist/commands/secrets.d.ts.map +1 -1
- package/dist/commands/secrets.js +10 -11
- package/dist/commands/secrets.js.map +1 -1
- package/dist/commands/service-accounts.d.ts +3 -0
- package/dist/commands/service-accounts.d.ts.map +1 -0
- package/dist/commands/service-accounts.js +175 -0
- package/dist/commands/service-accounts.js.map +1 -0
- package/dist/commands/skills.d.ts +1 -1
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +11 -4
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/sso.d.ts +6 -0
- package/dist/commands/sso.d.ts.map +1 -0
- package/dist/commands/sso.js +318 -0
- package/dist/commands/sso.js.map +1 -0
- package/dist/commands/tokens.d.ts +11 -0
- package/dist/commands/tokens.d.ts.map +1 -1
- package/dist/commands/tokens.js +144 -11
- package/dist/commands/tokens.js.map +1 -1
- package/dist/commands/tools.d.ts.map +1 -1
- package/dist/commands/tools.js +4 -3
- package/dist/commands/tools.js.map +1 -1
- package/dist/commands/unwired.d.ts.map +1 -1
- package/dist/commands/unwired.js +1 -30
- package/dist/commands/unwired.js.map +1 -1
- package/dist/commands/upgrade.d.ts +11 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +160 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/context.d.ts +7 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +1 -0
- package/dist/context.js.map +1 -1
- package/dist/dev/defaults.d.ts +28 -11
- package/dist/dev/defaults.d.ts.map +1 -1
- package/dist/dev/defaults.js +114 -35
- package/dist/dev/defaults.js.map +1 -1
- package/dist/dev/google-credentials.d.ts +50 -0
- package/dist/dev/google-credentials.d.ts.map +1 -0
- package/dist/dev/google-credentials.js +157 -0
- package/dist/dev/google-credentials.js.map +1 -0
- package/dist/dev/java-builder.d.ts +56 -0
- package/dist/dev/java-builder.d.ts.map +1 -0
- package/dist/dev/java-builder.js +204 -0
- package/dist/dev/java-builder.js.map +1 -0
- package/dist/dev/jvm-run-files.d.ts +29 -0
- package/dist/dev/jvm-run-files.d.ts.map +1 -0
- package/dist/dev/jvm-run-files.js +94 -0
- package/dist/dev/jvm-run-files.js.map +1 -0
- package/dist/dev/lines.d.ts +13 -0
- package/dist/dev/lines.d.ts.map +1 -0
- package/dist/dev/lines.js +24 -0
- package/dist/dev/lines.js.map +1 -0
- package/dist/dev/log-view.d.ts +116 -0
- package/dist/dev/log-view.d.ts.map +1 -0
- package/dist/dev/log-view.js +233 -0
- package/dist/dev/log-view.js.map +1 -0
- package/dist/dev/pack-code.d.ts +79 -4
- package/dist/dev/pack-code.d.ts.map +1 -1
- package/dist/dev/pack-code.js +262 -3
- package/dist/dev/pack-code.js.map +1 -1
- package/dist/dev/pack-env.d.ts.map +1 -1
- package/dist/dev/pack-env.js +4 -0
- package/dist/dev/pack-env.js.map +1 -1
- package/dist/dev/pack-service.d.ts +11 -1
- package/dist/dev/pack-service.d.ts.map +1 -1
- package/dist/dev/pack-service.js +56 -20
- package/dist/dev/pack-service.js.map +1 -1
- package/dist/dev/paths.d.ts +6 -0
- package/dist/dev/paths.d.ts.map +1 -1
- package/dist/dev/paths.js +8 -0
- package/dist/dev/paths.js.map +1 -1
- package/dist/dev/project-database.js +1 -1
- package/dist/dev/project-database.js.map +1 -1
- package/dist/dev/runners.d.ts +35 -9
- package/dist/dev/runners.d.ts.map +1 -1
- package/dist/dev/runtime-container.d.ts +10 -2
- package/dist/dev/runtime-container.d.ts.map +1 -1
- package/dist/dev/runtime-container.js +48 -20
- package/dist/dev/runtime-container.js.map +1 -1
- package/dist/dev/runtime-env.d.ts +10 -0
- package/dist/dev/runtime-env.d.ts.map +1 -1
- package/dist/dev/runtime-env.js +8 -4
- package/dist/dev/runtime-env.js.map +1 -1
- package/dist/dev/runtime-image.d.ts +1 -1
- package/dist/dev/runtime-image.js +1 -1
- package/dist/dev/scala-builder.d.ts +72 -0
- package/dist/dev/scala-builder.d.ts.map +1 -0
- package/dist/dev/scala-builder.js +347 -0
- package/dist/dev/scala-builder.js.map +1 -0
- package/dist/env/project-env.d.ts.map +1 -1
- package/dist/env/project-env.js +3 -1
- package/dist/env/project-env.js.map +1 -1
- package/dist/errors.d.ts +9 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +15 -0
- package/dist/errors.js.map +1 -1
- package/dist/init/augment-scaffolder.js +1 -1
- package/dist/init/augment-scaffolder.js.map +1 -1
- package/dist/init/dependency-specs.d.ts +26 -0
- package/dist/init/dependency-specs.d.ts.map +1 -1
- package/dist/init/dependency-specs.js +38 -0
- package/dist/init/dependency-specs.js.map +1 -1
- package/dist/init/java-augment.d.ts +24 -0
- package/dist/init/java-augment.d.ts.map +1 -0
- package/dist/init/java-augment.js +198 -0
- package/dist/init/java-augment.js.map +1 -0
- package/dist/init/mode-detect.d.ts +2 -2
- package/dist/init/mode-detect.d.ts.map +1 -1
- package/dist/init/mode-detect.js +12 -1
- package/dist/init/mode-detect.js.map +1 -1
- package/dist/init/python-augment.js +1 -1
- package/dist/init/python-augment.js.map +1 -1
- package/dist/init/scala-augment.d.ts +23 -0
- package/dist/init/scala-augment.d.ts.map +1 -0
- package/dist/init/scala-augment.js +213 -0
- package/dist/init/scala-augment.js.map +1 -0
- package/dist/init/template-files.d.ts +40 -2
- package/dist/init/template-files.d.ts.map +1 -1
- package/dist/init/template-files.js +56 -5
- package/dist/init/template-files.js.map +1 -1
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +20 -2
- package/dist/main.js.map +1 -1
- package/dist/open-url.d.ts +15 -0
- package/dist/open-url.d.ts.map +1 -0
- package/dist/open-url.js +41 -0
- package/dist/open-url.js.map +1 -0
- package/dist/package-manager.d.ts +10 -3
- package/dist/package-manager.d.ts.map +1 -1
- package/dist/package-manager.js +19 -1
- package/dist/package-manager.js.map +1 -1
- package/dist/providers/presets/anthropic.json +11 -3
- package/dist/sdk-skills/kindgi-authoring-agents/SKILL.md +30 -2
- package/dist/sdk-skills/kindgi-authoring-flows/SKILL.md +1 -1
- package/dist/sdk-skills/kindgi-authoring-guardrails/SKILL.md +47 -3
- package/dist/sdk-skills/kindgi-authoring-mcp-servers/SKILL.md +7 -5
- package/dist/sdk-skills/kindgi-authoring-providers/SKILL.md +40 -15
- package/dist/sdk-skills/kindgi-authoring-tools/SKILL.md +66 -2
- package/dist/sdk-skills/kindgi-framework-feedback/SKILL.md +5 -4
- package/dist/sdk-skills/kindgi-getting-started/SKILL.md +1 -1
- package/dist/sdk-skills/kindgi-java-authoring-agents/SKILL.md +220 -0
- package/dist/sdk-skills/kindgi-java-authoring-flows/SKILL.md +390 -0
- package/dist/sdk-skills/kindgi-java-authoring-guardrails/SKILL.md +209 -0
- package/dist/sdk-skills/kindgi-java-authoring-tools/SKILL.md +334 -0
- package/dist/sdk-skills/kindgi-java-getting-started/SKILL.md +270 -0
- package/dist/sdk-skills/kindgi-python-authoring-agents/SKILL.md +25 -3
- package/dist/sdk-skills/kindgi-python-authoring-flows/SKILL.md +1 -1
- package/dist/sdk-skills/kindgi-python-authoring-guardrails/SKILL.md +10 -3
- package/dist/sdk-skills/kindgi-python-authoring-tools/SKILL.md +45 -6
- package/dist/sdk-skills/kindgi-python-getting-started/SKILL.md +2 -2
- package/dist/sdk-skills/kindgi-scala-authoring-agents/SKILL.md +217 -0
- package/dist/sdk-skills/kindgi-scala-authoring-flows/SKILL.md +357 -0
- package/dist/sdk-skills/kindgi-scala-authoring-guardrails/SKILL.md +199 -0
- package/dist/sdk-skills/kindgi-scala-authoring-tools/SKILL.md +302 -0
- package/dist/sdk-skills/kindgi-scala-getting-started/SKILL.md +302 -0
- package/dist/templates/java/.mvn/wrapper/maven-wrapper.properties +3 -0
- package/dist/templates/java/AGENTS.md +31 -0
- package/dist/templates/java/README.md.tmpl +69 -0
- package/dist/templates/java/gitignore +9 -0
- package/dist/templates/java/kindgi.config.json.tmpl +8 -0
- package/dist/templates/java/kindgiw +33 -0
- package/dist/templates/java/kindgiw.cmd +28 -0
- package/dist/templates/java/mvnw +295 -0
- package/dist/templates/java/pom.xml.tmpl +65 -0
- package/dist/templates/java/src/main/java/__PACKAGE__/agents/EchoAgent.java.tmpl +27 -0
- package/dist/templates/java/src/main/java/__PACKAGE__/flows/EchoFlow.java.tmpl +18 -0
- package/dist/templates/java/src/main/java/__PACKAGE__/guardrails/ResponseNotEmpty.java.tmpl +28 -0
- package/dist/templates/java/src/main/java/__PACKAGE__/tools/Echo.java.tmpl +22 -0
- package/dist/templates/java/src/main/java/__PACKAGE__/tools/Greet.java.tmpl +23 -0
- package/dist/templates/java/src/test/java/__PACKAGE__/ToolsTest.java.tmpl +30 -0
- package/dist/templates/minimal/README.md.tmpl +6 -12
- package/dist/templates/python/README.md.tmpl +4 -4
- package/dist/templates/sample/README.md.tmpl +5 -11
- package/dist/templates/scala/AGENTS.md +32 -0
- package/dist/templates/scala/README.md.tmpl +75 -0
- package/dist/templates/scala/build.sbt.tmpl +16 -0
- package/dist/templates/scala/gitignore +13 -0
- package/dist/templates/scala/kindgi.config.json.tmpl +8 -0
- package/dist/templates/scala/project/build.properties +1 -0
- package/dist/templates/scala/src/main/scala/__PACKAGE__/agents/EchoAgent.scala.tmpl +23 -0
- package/dist/templates/scala/src/main/scala/__PACKAGE__/flows/EchoFlow.scala.tmpl +16 -0
- package/dist/templates/scala/src/main/scala/__PACKAGE__/guardrails/ResponseNotEmpty.scala.tmpl +21 -0
- package/dist/templates/scala/src/main/scala/__PACKAGE__/tools/Echo.scala.tmpl +16 -0
- package/dist/templates/scala/src/main/scala/__PACKAGE__/tools/Greet.scala.tmpl +17 -0
- package/dist/templates/scala/src/test/scala/__PACKAGE__/ToolsSuite.scala.tmpl +23 -0
- package/package.json +13 -12
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kindgi-scala-authoring-guardrails
|
|
3
|
+
description: >
|
|
4
|
+
Covers writing guardrails (safety checks on an agent's turn) for a Kindgi
|
|
5
|
+
pack in Scala (`kindgi-pack-scala`, `com.kindgi.pack.scaladsl`):
|
|
6
|
+
`Guardrail[Config](id)` as a `val` of an object named like its file, a
|
|
7
|
+
`(config, trace)` check returning a `CheckResult` (or a Future of one with
|
|
8
|
+
`checkAsync`), `RunTrace`, config case classes and the values a check runs
|
|
9
|
+
with, actions (halt / retry / escalate / log-only / compensate), severity
|
|
10
|
+
and scope, munit tests with `evaluate`, and wiring a guardrail onto an
|
|
11
|
+
agent. Load this whenever you are authoring or editing code in a Scala
|
|
12
|
+
pack's guardrails packages (a pack whose `kindgi.config.json` says
|
|
13
|
+
`"language": "scala"`), defining a check, or wiring a guardrail onto an
|
|
14
|
+
agent. Scala agents are covered by kindgi-scala-authoring-agents, Scala
|
|
15
|
+
tools by kindgi-scala-authoring-tools.
|
|
16
|
+
type: core
|
|
17
|
+
library: "kindgi-pack-scala"
|
|
18
|
+
version: "0.1.0"
|
|
19
|
+
sdk_version: "0.1.5"
|
|
20
|
+
pack_languages: [scala]
|
|
21
|
+
sources:
|
|
22
|
+
- sdks/scala/README.md
|
|
23
|
+
- sdks/scala/kindgi-pack-scala/src/main/scala/com/kindgi/pack/scaladsl/Guardrail.scala
|
|
24
|
+
- sdks/java/kindgi-pack/src/main/java/com/kindgi/pack/RunTrace.java
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# Authoring Kindgi guardrails in Scala
|
|
28
|
+
|
|
29
|
+
> **Running `kindgi`:** the pack pins its CLI (`"cli"` in
|
|
30
|
+
> `kindgi.config.json`), and `./kindgiw` runs that version, so every
|
|
31
|
+
> `kindgi <command>` below runs as `./kindgiw <command>`.
|
|
32
|
+
>
|
|
33
|
+
> Scala support is in preview: tested and supported, but the API may still
|
|
34
|
+
> change in 0.1.6 without the usual deprecation period.
|
|
35
|
+
|
|
36
|
+
A **guardrail** is a rule an agent's turn must satisfy: a **check** (a
|
|
37
|
+
function over the turn's trace) plus an **action** (what happens when it
|
|
38
|
+
fails). In a Scala pack it is a `val` of type `Guardrail[C]` in an object
|
|
39
|
+
named like its file, in a `guardrails` package. For an agent turn, the
|
|
40
|
+
runtime evaluates every guardrail the agent lists once, on the final answer,
|
|
41
|
+
before it is stored.
|
|
42
|
+
|
|
43
|
+
Ask what the rule should catch before writing one. The sample
|
|
44
|
+
`response-not-empty` guardrail is a demonstration, not a template.
|
|
45
|
+
|
|
46
|
+
## A guardrail
|
|
47
|
+
|
|
48
|
+
```scala
|
|
49
|
+
// src/main/scala/acme/guardrails/Citations.scala
|
|
50
|
+
package acme.guardrails
|
|
51
|
+
|
|
52
|
+
import com.kindgi.pack.scaladsl._
|
|
53
|
+
import jakarta.validation.constraints.Min
|
|
54
|
+
import scala.jdk.CollectionConverters._
|
|
55
|
+
|
|
56
|
+
/** acme.no-fabricated-quotes: an answer that quotes case law looked the citations up. */
|
|
57
|
+
object Citations {
|
|
58
|
+
final case class Config(@Min(value = 0) minLookups: Int = 1)
|
|
59
|
+
|
|
60
|
+
val noFabricatedQuotes: Guardrail[Config] = Guardrail[Config]("acme.no-fabricated-quotes")
|
|
61
|
+
.name("No fabricated quotations")
|
|
62
|
+
.onViolation("halt")
|
|
63
|
+
.severity("critical")
|
|
64
|
+
// What the check runs with, keyed as on the wire.
|
|
65
|
+
.set("config", Map("minLookups" -> 2))
|
|
66
|
+
.check { (config, trace) =>
|
|
67
|
+
val lookups = trace.toolCalls.asScala.count {
|
|
68
|
+
case call: java.util.Map[_, _] => call.get("toolName") == "acme.verify-citation"
|
|
69
|
+
case _ => false
|
|
70
|
+
}
|
|
71
|
+
if (lookups >= config.minLookups) CheckResult.pass
|
|
72
|
+
else CheckResult.fail(s"Only $lookups citation lookups (need ${config.minLookups}+).")
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- **The check** is `(config, trace) => CheckResult`: `CheckResult.pass` or
|
|
78
|
+
`CheckResult.fail(reason)`. A failed result's reason is what the violation
|
|
79
|
+
reports, so make it say what was wrong. `checkAsync` takes a
|
|
80
|
+
`(config, trace) => Future[CheckResult]`.
|
|
81
|
+
- **`trace`** is a `RunTrace` (kindgi-pack's), with Java collections:
|
|
82
|
+
- `output`: the final answer's text (`null` when there's none: wrap it in
|
|
83
|
+
`Option`);
|
|
84
|
+
- `userInput`;
|
|
85
|
+
- `toolCalls`: each a map with `toolId`, `toolName` (the tool's dotted
|
|
86
|
+
id), `arguments`;
|
|
87
|
+
- `toolResults`: each with `toolCallId`, `output`;
|
|
88
|
+
- `modelCalls`;
|
|
89
|
+
- `mode`: `runtime` or `ci`;
|
|
90
|
+
- `runId`, `tenantId`;
|
|
91
|
+
- `raw`: the trace as sent.
|
|
92
|
+
- **`Guardrail[Config](id)`** takes the config's case class. Its schema is
|
|
93
|
+
derived as a tool input's is, and a parameter's default is the value when
|
|
94
|
+
none is given. `Guardrail.json(id).configSchema(Map(…))` gives a schema
|
|
95
|
+
instead; the config is then a `Map[String, Any]`.
|
|
96
|
+
- **`set("config", Map(…))`** holds the values the check runs with, keyed as
|
|
97
|
+
on the wire. They go into the index, and the service checks them against
|
|
98
|
+
the schema before every evaluation. Without them the check runs with `{}`
|
|
99
|
+
and the defaults. So give every parameter a default: a required one with
|
|
100
|
+
no value fails every evaluation (`input-validation-failed`).
|
|
101
|
+
- An exception thrown from the check fails the evaluation (`handler-throw`).
|
|
102
|
+
For a rule that isn't met, return `CheckResult.fail(…)`.
|
|
103
|
+
- A check gets no model and no provider: it can't call an LLM. Keep it a
|
|
104
|
+
pure function of the trace (fast, deterministic, free).
|
|
105
|
+
|
|
106
|
+
## `Guardrail[Config](id)`
|
|
107
|
+
|
|
108
|
+
- **`id`:** `<pack-id>.<guardrail-name>`, kebab-case. Name the rule as a
|
|
109
|
+
positive assertion: `no-fabricated-quotes`, `response-not-empty`.
|
|
110
|
+
- **`onViolation(action)`:** `halt`, `retry`, `escalate`, `log-only` or
|
|
111
|
+
`compensate`. For one that needs settings, pass the whole object instead,
|
|
112
|
+
with `action(Map("on-violation" -> "retry", "retry" -> Map("maxAttempts" -> 2)))`.
|
|
113
|
+
The same goes for `escalateTo` and `compensateWith` (a tool id). The
|
|
114
|
+
builder refuses a check with neither.
|
|
115
|
+
- In an agent turn, a failed `halt` guardrail fails the turn
|
|
116
|
+
(`guardrail-violation`), and the answer is not stored.
|
|
117
|
+
- Any other action reports the failure in the turn result's `violations`,
|
|
118
|
+
and the turn completes.
|
|
119
|
+
- In 0.1 the runtime acts only on `halt`: `retry`, `escalate` and
|
|
120
|
+
`compensate` are recorded on the violation, with no second attempt,
|
|
121
|
+
escalation or compensating call.
|
|
122
|
+
- **`severity`:** `info`, `warn`, `error` (default) or `critical`. It's
|
|
123
|
+
independent of the action: dashboards group by severity, and execution
|
|
124
|
+
follows the action.
|
|
125
|
+
- **`set("scope", Map("when" -> "runtime-only"))`:** when the guardrail
|
|
126
|
+
applies: `always`, `ci-only` or `runtime-only`, narrowed by `agents`,
|
|
127
|
+
`flows` and `tenants` lists.
|
|
128
|
+
- **`name`:** a display name. **`checkId`:** defaults to the id.
|
|
129
|
+
- **`kind`:** `zero-llm`, the default: a check over the trace, which is what
|
|
130
|
+
a pack writes.
|
|
131
|
+
- `set("sandbox" | "limits" | "network", …)` are recorded in the index.
|
|
132
|
+
|
|
133
|
+
## Testing
|
|
134
|
+
|
|
135
|
+
`evaluate` runs the check with the config as given, with no schema check
|
|
136
|
+
and no defaults. An async check is awaited.
|
|
137
|
+
|
|
138
|
+
```scala
|
|
139
|
+
// src/test/scala/acme/CitationsSuite.scala
|
|
140
|
+
package acme
|
|
141
|
+
|
|
142
|
+
import acme.guardrails.Citations
|
|
143
|
+
import com.kindgi.pack.RunTrace
|
|
144
|
+
import java.util.{List => JList, Map => JMap}
|
|
145
|
+
|
|
146
|
+
class CitationsSuite extends munit.FunSuite {
|
|
147
|
+
test("no lookups fails") {
|
|
148
|
+
val trace = new RunTrace(JMap.of("output", "As held in Smith v. Jones…"))
|
|
149
|
+
assert(!Citations.noFabricatedQuotes.evaluate(Citations.Config(1), trace).passed)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
test("enough lookups pass") {
|
|
153
|
+
val lookup = JMap.of("toolName", "acme.verify-citation", "arguments", JMap.of())
|
|
154
|
+
val trace = new RunTrace(JMap.of("toolCalls", JList.of(lookup, lookup)))
|
|
155
|
+
assert(Citations.noFabricatedQuotes.evaluate(Citations.Config(2), trace).passed)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`new RunTrace(map)` takes the trace as the runtime sends it: a Java map,
|
|
161
|
+
camelCase keys included.
|
|
162
|
+
|
|
163
|
+
## Wiring onto an agent
|
|
164
|
+
|
|
165
|
+
```scala
|
|
166
|
+
Agent("acme.brief-writer")
|
|
167
|
+
// …
|
|
168
|
+
.guardrail(Citations.noFabricatedQuotes)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Pass the `Guardrail`, or its id (`.guardrail("acme.no-fabricated-quotes")`).
|
|
172
|
+
`kindgi dev` registers the pack's guardrails. An agent that names an id with
|
|
173
|
+
no registered guardrail fails the turn before the model is called
|
|
174
|
+
(`Agent "…" references guardrails not in the registry: <id>`).
|
|
175
|
+
|
|
176
|
+
## Common mistakes
|
|
177
|
+
|
|
178
|
+
1. **A config parameter with no default and no value.** Without
|
|
179
|
+
`set("config", …)`, the check runs with `{}`. Give the parameter a
|
|
180
|
+
default, or the guardrail its values.
|
|
181
|
+
2. **`set("config", …)` keyed by Scala names that differ from the wire.**
|
|
182
|
+
It's keyed by the case class's Jackson names.
|
|
183
|
+
3. **No `onViolation` or `action`.** The builder refuses the check.
|
|
184
|
+
4. **Expecting another attempt.** `halt` stops the turn, and in 0.1 `retry`
|
|
185
|
+
doesn't run the turn again: it's only recorded.
|
|
186
|
+
5. **Calling a model from the check.** That isn't available: keep checks
|
|
187
|
+
pure.
|
|
188
|
+
6. **Throwing for a broken rule.** Return `CheckResult.fail(…)`. An
|
|
189
|
+
exception is an evaluation error, not a violation.
|
|
190
|
+
7. **`trace.output.length` without a null check.** `output` is `null` when
|
|
191
|
+
the turn produced no text: `Option(trace.output).getOrElse("")`.
|
|
192
|
+
8. **A guardrail as a `def` or a `lazy val`.** It's a file error: make it a
|
|
193
|
+
`val`.
|
|
194
|
+
|
|
195
|
+
## When the framework itself is the problem
|
|
196
|
+
|
|
197
|
+
If the bug is in Kindgi, kindgi-pack or the Scala layer (a trace field
|
|
198
|
+
missing, a misleading error) and not in the check, load
|
|
199
|
+
`kindgi-framework-feedback` and file it with `./kindgiw feedback write`.
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kindgi-scala-authoring-tools
|
|
3
|
+
description: >
|
|
4
|
+
Covers writing tools for a Kindgi pack in Scala (`kindgi-pack-scala`,
|
|
5
|
+
`com.kindgi.pack.scaladsl`): `Tool[Input, Output](id)` as a `val` of an
|
|
6
|
+
object named like its file, schemas from case classes (Option, defaults,
|
|
7
|
+
collections, Jakarta constraints) or `Tool.json` with maps, `handler` and
|
|
8
|
+
`handlerAsync` (a Future), `ToolContext` and cancellation, secrets
|
|
9
|
+
(`needsSpec`) and configuration, errors, `readOnly` and effects, munit
|
|
10
|
+
tests with `tool.call`, and wiring a tool onto an agent. Load this
|
|
11
|
+
whenever you are authoring or editing code in a Scala pack's tools
|
|
12
|
+
packages (a pack whose `kindgi.config.json` says `"language": "scala"`),
|
|
13
|
+
defining a tool, or wiring one onto an agent. Scala agents are covered by
|
|
14
|
+
kindgi-scala-authoring-agents, getting started by
|
|
15
|
+
kindgi-scala-getting-started.
|
|
16
|
+
type: core
|
|
17
|
+
library: "kindgi-pack-scala"
|
|
18
|
+
version: "0.1.0"
|
|
19
|
+
sdk_version: "0.1.5"
|
|
20
|
+
pack_languages: [scala]
|
|
21
|
+
sources:
|
|
22
|
+
- sdks/scala/README.md
|
|
23
|
+
- sdks/scala/kindgi-pack-scala/src/main/scala/com/kindgi/pack/scaladsl/Tool.scala
|
|
24
|
+
- sdks/java/kindgi-pack/README.md
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# Authoring Kindgi tools in Scala
|
|
28
|
+
|
|
29
|
+
> **Running `kindgi`:** the pack pins its CLI (`"cli"` in
|
|
30
|
+
> `kindgi.config.json`), and `./kindgiw` runs that version, so every
|
|
31
|
+
> `kindgi <command>` below runs as `./kindgiw <command>`. sbt is the one on
|
|
32
|
+
> your `PATH`.
|
|
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 **tool** is a unit of work an agent (or a flow step) calls: typed input,
|
|
38
|
+
typed output, your code in between. In a Scala pack it is a `val` of type
|
|
39
|
+
`Tool[I, O]` in an object named like its file (`VerifyCitation.scala` holds
|
|
40
|
+
`object VerifyCitation`), in a `tools` package
|
|
41
|
+
(`src/main/scala/**/tools/**/*.scala`; in an app, `**/kindgi/tools/**`).
|
|
42
|
+
Kindgi runs it in the pack's own JVM (the pack service) and calls it over
|
|
43
|
+
HTTP. The model sees its id, description and input schema.
|
|
44
|
+
|
|
45
|
+
Before writing one, establish what it should **do**: what it computes or
|
|
46
|
+
fetches, what the caller provides, what it returns. "Add a tool" is a
|
|
47
|
+
conversation opener. The pack's sample tools prove the runtime works; they
|
|
48
|
+
are not the shape to copy unless the user asks.
|
|
49
|
+
|
|
50
|
+
## A tool
|
|
51
|
+
|
|
52
|
+
```scala
|
|
53
|
+
// src/main/scala/acme/tools/VerifyCitation.scala
|
|
54
|
+
package acme.tools
|
|
55
|
+
|
|
56
|
+
import com.kindgi.pack.scaladsl._
|
|
57
|
+
import jakarta.validation.constraints.{Pattern, Size}
|
|
58
|
+
|
|
59
|
+
/** acme.verify-citation: checks a legal citation against the citator. */
|
|
60
|
+
object VerifyCitation {
|
|
61
|
+
final case class Input(
|
|
62
|
+
@Size(min = 1) citation: String,
|
|
63
|
+
@Pattern(regexp = "^(US|UK|EU)$") jurisdiction: String)
|
|
64
|
+
|
|
65
|
+
final case class Output(found: Boolean, canonicalCite: Option[String])
|
|
66
|
+
|
|
67
|
+
val tool: Tool[Input, Output] = Tool[Input, Output]("acme.verify-citation")
|
|
68
|
+
.description("Verifies a legal citation against the citator; returns whether it resolves and its canonical form.")
|
|
69
|
+
.readOnly
|
|
70
|
+
.set("needsSpec", Map("secrets" -> Map("CITATOR_KEY" -> Map("type" -> "string", "minLength" -> 20))))
|
|
71
|
+
.handler((in, ctx) =>
|
|
72
|
+
verify(in, String.valueOf(ctx.secrets.get("CITATOR_KEY")), sys.env.getOrElse("CITATOR_URL", "")))
|
|
73
|
+
|
|
74
|
+
/** The tool's work, with what it reads from its context and the environment passed in: a test calls it. */
|
|
75
|
+
def verify(in: Input, key: String, citatorUrl: String): Output = {
|
|
76
|
+
val hit = Citator.lookup(citatorUrl, key, in.citation, in.jurisdiction)
|
|
77
|
+
Output(hit.isDefined, hit)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```scala
|
|
83
|
+
// src/main/scala/acme/tools/Citator.scala
|
|
84
|
+
package acme.tools
|
|
85
|
+
|
|
86
|
+
import java.net.URI
|
|
87
|
+
import java.net.URLEncoder
|
|
88
|
+
import java.net.http.{HttpClient, HttpRequest, HttpResponse}
|
|
89
|
+
import java.nio.charset.StandardCharsets
|
|
90
|
+
|
|
91
|
+
/** The citator's API: an object with no tools in it is a helper, not a primitive. */
|
|
92
|
+
object Citator {
|
|
93
|
+
private val http = HttpClient.newHttpClient()
|
|
94
|
+
|
|
95
|
+
def lookup(baseUrl: String, key: String, citation: String, jurisdiction: String): Option[String] = {
|
|
96
|
+
val query = s"q=${URLEncoder.encode(citation, StandardCharsets.UTF_8)}&j=$jurisdiction"
|
|
97
|
+
val request = HttpRequest.newBuilder(URI.create(s"$baseUrl/lookup?$query"))
|
|
98
|
+
.header("Authorization", s"Bearer $key")
|
|
99
|
+
.build()
|
|
100
|
+
val response = http.send(request, HttpResponse.BodyHandlers.ofString())
|
|
101
|
+
if (response.statusCode() == 200) Some(response.body()) else None
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- **Where it lives:** a `val` of the object named like the file. The
|
|
107
|
+
indexer reads the object's vals, and only the primitives that object
|
|
108
|
+
defined. A tool defined as a `def` or a `lazy val` is a file error that
|
|
109
|
+
says to make it a `val`: the indexer can't read one without running it.
|
|
110
|
+
An object with no tools in it is a helper. So is a file with no object
|
|
111
|
+
(a case class, a trait).
|
|
112
|
+
- **Id:** `<pack-id>.<tool-name>`, kebab-case, dot-namespaced.
|
|
113
|
+
- **Description:** what it does and returns. The model reads it to decide
|
|
114
|
+
when to call the tool. It isn't enforced, so never leave it out.
|
|
115
|
+
- **Version:** the pack's (`pack.version`), or `.version("1.2.0")` (an exact
|
|
116
|
+
semver).
|
|
117
|
+
- **Schemas:** from the case classes `Tool[Input, Output]` names. A
|
|
118
|
+
parameter is a property, by its Jackson name
|
|
119
|
+
(`@JsonProperty("canonical_cite")` renames one). The input must be an
|
|
120
|
+
**object**: a model calls a tool with an object of arguments.
|
|
121
|
+
|
|
122
|
+
| In Scala | In the schema |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `String`, `Int`/`Long`, `Double`/`BigDecimal`, `Boolean`; `BigInt` | `string`, `integer`, `number`, `boolean`; `integer` |
|
|
125
|
+
| `Option[T]` | not required; `null` allowed |
|
|
126
|
+
| a parameter's default (`greeting: String = "Hello"`) | `default`, not required; the service fills it in |
|
|
127
|
+
| `Seq[T]`, `List[T]`, `Vector[T]`; `Set[T]` | `array`; `array` with `uniqueItems` |
|
|
128
|
+
| `Map[String, T]` | `object` with `additionalProperties` |
|
|
129
|
+
| a Scala 3 simple enum (`enum Scale { case C, F }`) | `string`, `enum` of its case names |
|
|
130
|
+
| `@Size`, `@Min`, `@Max`, `@Pattern`, `@Email`, `@NotBlank`, `@Positive`, … | the matching keywords |
|
|
131
|
+
|
|
132
|
+
**In Scala 3, name a Java annotation's arguments:** `@Min(value = 0)`, not
|
|
133
|
+
`@Min(0)`. Scala 3 passes a positional argument to the wrong element.
|
|
134
|
+
When a type can't say it, `Tool.json(id).inputSchema(Map(…))` takes the
|
|
135
|
+
schema as Scala maps, and the handler gets a `Map[String, Any]`.
|
|
136
|
+
- **Handler:** `(in, ctx) => output`. It may throw. It runs on a thread of
|
|
137
|
+
its own, so blocking I/O is fine. `handlerAsync((in, ctx) => future)`
|
|
138
|
+
returns a `Future[O]`, and the service awaits it.
|
|
139
|
+
- **Validation:** before the handler runs, the input is checked against the
|
|
140
|
+
schema (its defaults filled in). Then what the handler returns is checked
|
|
141
|
+
against the output schema. A bad input comes back as
|
|
142
|
+
`input-validation-failed`, naming the field (a bad output as
|
|
143
|
+
`output-validation-failed`). The agent's `toolErrors` policy decides
|
|
144
|
+
whether the model gets to fix the call.
|
|
145
|
+
|
|
146
|
+
## `ToolContext`
|
|
147
|
+
|
|
148
|
+
- `ctx.tenantId`: the tenant the call is for. Key per-tenant state by it.
|
|
149
|
+
- `ctx.runId`: the run (an agent turn or a flow step) the call belongs to.
|
|
150
|
+
- `ctx.requestId`: this call, such as the model's tool-call id. Useful for
|
|
151
|
+
logs and idempotency keys.
|
|
152
|
+
- `ctx.projectId`, `ctx.orgId`: the run's project, and its org (`null` when
|
|
153
|
+
it has none). The runtime sets them from the run, never from the input.
|
|
154
|
+
To check an id the input names, compare it with these.
|
|
155
|
+
- `ctx.cancellation` fires when the call's deadline passes (120 s by
|
|
156
|
+
default) or the caller goes away. A blocking handler's thread is
|
|
157
|
+
interrupted too. A loop checks `ctx.cancellation.isCancelled`. A `Future`
|
|
158
|
+
has no way to stop, so stop the work behind it with
|
|
159
|
+
`ctx.cancellation.onCancel(() => …)`.
|
|
160
|
+
- `ctx.secrets`: the secrets the tool declares (below), as a Java map.
|
|
161
|
+
Printing the context shows their names, never their values.
|
|
162
|
+
- `ctx.env`, `ctx.config`: **reserved, empty today**.
|
|
163
|
+
|
|
164
|
+
## Configuration and secrets
|
|
165
|
+
|
|
166
|
+
A secret that belongs to the tenant, such as an API key a customer gives
|
|
167
|
+
you, is declared with `set("needsSpec", …)` and read from `ctx.secrets`, as
|
|
168
|
+
above. The runtime resolves every declared secret on every call, for the
|
|
169
|
+
call's tenant, in its env (`KINDGI_ENV`; under `kindgi dev`, `local`: the
|
|
170
|
+
pack's `.env` and `.env.local`). It checks each against its schema, and fails
|
|
171
|
+
the call, naming the secret, when one is missing or doesn't match. Every
|
|
172
|
+
declared secret is required.
|
|
173
|
+
|
|
174
|
+
Everything else comes from the process environment: `sys.env("CITATOR_URL")`.
|
|
175
|
+
Under `kindgi dev`, the pack service gets the pack's `.env` and `.env.local`,
|
|
176
|
+
and restarts when they change. Nothing else from your shell reaches it
|
|
177
|
+
except `PATH`, `HOME` and `TMPDIR`; `SBT_OPTS` reaches sbt only. Put a secret
|
|
178
|
+
there by hand, or with `./kindgiw secrets set NAME --env=local --scope=tenant`
|
|
179
|
+
(a no-echo prompt), and keep the env files out of git. A deployed service
|
|
180
|
+
names the variables it needs in `kindgi.config.json`:
|
|
181
|
+
`"env": {"required": ["DATABASE_URL"], "optional": ["SENTRY_DSN"]}`. Without
|
|
182
|
+
a required one it isn't ready. `KINDGI_*` names are Kindgi's own and never
|
|
183
|
+
reach pack code.
|
|
184
|
+
|
|
185
|
+
## Errors and output
|
|
186
|
+
|
|
187
|
+
- Throw for a failure, or fail the `Future`: the call fails with
|
|
188
|
+
`handler-throw` and the exception's message. In an agent turn, the model
|
|
189
|
+
sees the failure only when the agent's `toolErrors` policy includes
|
|
190
|
+
`tool-error`, so retrying must be safe for that tool.
|
|
191
|
+
- What the handler logs goes to the pack service's output (`kindgi dev`
|
|
192
|
+
shows it as `[pack] …`), never into a result.
|
|
193
|
+
|
|
194
|
+
## Other declarations
|
|
195
|
+
|
|
196
|
+
**`readOnly`** declares a tool that changes nothing outside itself (a lookup,
|
|
197
|
+
a search, a calculation): it's `mutating(false)`. A dry run
|
|
198
|
+
(`./kindgiw runs start --dry-run`) runs it. Leave it off for anything that
|
|
199
|
+
writes, sends, charges or deletes: such a tool stops a dry run. It's also
|
|
200
|
+
the tool's approval default: an agent that turns approval gates on
|
|
201
|
+
(`conversationPolicy.hitl`) asks before any tool that isn't read-only on its
|
|
202
|
+
first use.
|
|
203
|
+
|
|
204
|
+
`effect(kind, resource)` declares a side effect, such as
|
|
205
|
+
`.effect("writes", "db:ledger")`. A dry run also stops at a tool with a
|
|
206
|
+
`writes`, `deletes`, `spawns-run`, `emits-event` or `external-side-effect`
|
|
207
|
+
effect. `set(field, value)` sets the index entry's other fields (`needs`,
|
|
208
|
+
`needsSpec`, `sandbox`, `limits`, `network`), with Scala maps and lists. They
|
|
209
|
+
are recorded for policy and review, so declare what the tool really does.
|
|
210
|
+
|
|
211
|
+
There is no HTTP-tool form (TypeScript's `kind: 'http'`). A tool that calls
|
|
212
|
+
an HTTP API is a handler with `java.net.http.HttpClient`, as above.
|
|
213
|
+
|
|
214
|
+
## Testing
|
|
215
|
+
|
|
216
|
+
`tool.call(input, ctx)` runs the handler with the input as given (no schema
|
|
217
|
+
check). `ToolContext.forTest()` is a context with a test tenant and run and
|
|
218
|
+
nothing else. A handler that reads the environment and calls a service is
|
|
219
|
+
tested through the method it calls, with a stub server:
|
|
220
|
+
|
|
221
|
+
```scala
|
|
222
|
+
// src/test/scala/acme/VerifyCitationSuite.scala
|
|
223
|
+
package acme
|
|
224
|
+
|
|
225
|
+
import acme.tools.VerifyCitation
|
|
226
|
+
import com.sun.net.httpserver.HttpServer
|
|
227
|
+
import java.net.InetSocketAddress
|
|
228
|
+
|
|
229
|
+
class VerifyCitationSuite extends munit.FunSuite {
|
|
230
|
+
test("an unknown citation is not found") {
|
|
231
|
+
val citator = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0)
|
|
232
|
+
citator.createContext("/lookup", exchange => {
|
|
233
|
+
exchange.sendResponseHeaders(404, -1)
|
|
234
|
+
exchange.close()
|
|
235
|
+
})
|
|
236
|
+
citator.start()
|
|
237
|
+
try {
|
|
238
|
+
val url = s"http://127.0.0.1:${citator.getAddress.getPort}"
|
|
239
|
+
assert(!VerifyCitation.verify(VerifyCitation.Input("1 U.S. 1", "US"), "test-key", url).found)
|
|
240
|
+
} finally citator.stop(0)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
`sbt test` runs the tests (anything under `src/test/` is never indexed). To
|
|
246
|
+
see the schemas Kindgi derives:
|
|
247
|
+
|
|
248
|
+
```sh
|
|
249
|
+
java -cp "$(sbt -batch -error 'export Runtime/fullClasspath')" com.kindgi.pack.Main index --pack-dir .
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Wiring the tool onto an agent
|
|
253
|
+
|
|
254
|
+
Pass the `Tool`, which pins that tool's version:
|
|
255
|
+
|
|
256
|
+
```scala
|
|
257
|
+
Agent("acme.brief-writer")
|
|
258
|
+
// …
|
|
259
|
+
.tool(VerifyCitation.tool)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
A tool of another pack is `.tool("other.lookup", "^1.0.0")`: its id and a
|
|
263
|
+
semver **range**, and the highest active version matching it is picked at
|
|
264
|
+
turn start. In a flow, `toolNode("verify", VerifyCitation.tool)` runs it.
|
|
265
|
+
|
|
266
|
+
## Iterating
|
|
267
|
+
|
|
268
|
+
Save the file. `kindgi dev` recompiles through sbt's server, and the next
|
|
269
|
+
call runs the new code. A compile error is reported as `file:line:col`, and
|
|
270
|
+
the last good code keeps serving. Bump the version when callers' contract
|
|
271
|
+
changes (a removed field, a narrower type), not on every save.
|
|
272
|
+
|
|
273
|
+
## Common mistakes
|
|
274
|
+
|
|
275
|
+
1. **Copying the sample tool's shape without asking what the tool should do.**
|
|
276
|
+
2. **A tool as a `def` or a `lazy val`.** It's a file error: make it a `val`.
|
|
277
|
+
3. **An object not named like its file.** The indexer reads `Greet.scala`'s
|
|
278
|
+
`object Greet`, and a file without one is an error.
|
|
279
|
+
4. **No description.** The model can't tell when to call the tool.
|
|
280
|
+
5. **`@Min(0)` in Scala 3.** Name the argument: `@Min(value = 0)`.
|
|
281
|
+
6. **A primitive inside a type parameter** (`Seq[Int]`, `Option[Long]`). On
|
|
282
|
+
the JVM it's `Object`, so the schema allows any JSON value there. Use a
|
|
283
|
+
case class (`Seq[Item]`), or say the schema with `Tool.json`.
|
|
284
|
+
7. **Reading `ctx.env` or `ctx.config`, or an undeclared secret.** The first
|
|
285
|
+
two are empty, and `ctx.secrets` holds only what `needsSpec` declares.
|
|
286
|
+
Use `sys.env` for the rest.
|
|
287
|
+
8. **A library the tool uses as `% Test` or `% Provided`.** It works under
|
|
288
|
+
`kindgi dev` and fails in the image, which ships the runtime classpath
|
|
289
|
+
only.
|
|
290
|
+
9. **A read-only tool without `readOnly`.** A dry run stops at it, and an
|
|
291
|
+
approval gate asks before it on first use.
|
|
292
|
+
10. **`readOnly` on a tool that writes.** A dry run then runs it for real.
|
|
293
|
+
11. **A jackson-module-scala that doesn't match your app's
|
|
294
|
+
`jackson-databind` minor version.** It refuses to load: depend on the
|
|
295
|
+
matching one.
|
|
296
|
+
|
|
297
|
+
## When the framework itself is the problem
|
|
298
|
+
|
|
299
|
+
If the bug is in Kindgi, kindgi-pack or the Scala layer (a schema derived
|
|
300
|
+
wrong, a misleading error, the pack service misbehaving) and not in the
|
|
301
|
+
tool's code, load `kindgi-framework-feedback` and file it with
|
|
302
|
+
`./kindgiw feedback write`.
|