@kindgi/cli 0.1.4-rc.5 → 0.1.5-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -10
- 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 +211 -39
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts +18 -5
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +421 -21
- 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 +46 -7
- 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 +24 -14
- 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/help.d.ts +14 -0
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +55 -0
- package/dist/help.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/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 +30 -4
- 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/preset-loader.d.ts.map +1 -1
- package/dist/providers/preset-loader.js +9 -1
- package/dist/providers/preset-loader.js.map +1 -1
- package/dist/providers/presets/anthropic.json +35 -4
- package/dist/providers/presets/gemini-api.json +3 -0
- package/dist/providers/presets/gemini.json +3 -0
- package/dist/providers/presets/groq.json +1 -0
- package/dist/providers/presets/openai.json +36 -4
- package/dist/providers/presets/openrouter.json +6 -2
- package/dist/sdk-skills/kindgi-authoring-agents/SKILL.md +28 -4
- 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 +81 -52
- package/dist/sdk-skills/kindgi-authoring-tools/SKILL.md +27 -1
- 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 +23 -5
- 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 +36 -5
- 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 +8 -14
- package/dist/templates/python/README.md.tmpl +6 -6
- package/dist/templates/sample/README.md.tmpl +7 -13
- 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,390 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kindgi-java-authoring-flows
|
|
3
|
+
description: >
|
|
4
|
+
Covers writing flows for a Kindgi pack in Java (`com.kindgi:kindgi-pack`):
|
|
5
|
+
`Flow.define(id)` as a `public static final` field, tool and agent steps
|
|
6
|
+
(`toolNode`, `agentNode`, or a node map with `inputMapping` and `config`),
|
|
7
|
+
edges and their `when` conditions and `policy`, branches that join again,
|
|
8
|
+
inputMapping from runInput / nodeOutputs, typed agent output in a flow,
|
|
9
|
+
the flow's declared output, loops and fanout, and running a flow (runs
|
|
10
|
+
start --flow, in the background, as a dry run) and reading its journal.
|
|
11
|
+
Load this whenever you are authoring or editing code in a Java pack's
|
|
12
|
+
flows packages (a pack whose `kindgi.config.json` says
|
|
13
|
+
`"language": "java"`), defining a flow, or when the user asks to add,
|
|
14
|
+
change or debug one. Java tools are covered by
|
|
15
|
+
kindgi-java-authoring-tools, Java agents by kindgi-java-authoring-agents.
|
|
16
|
+
type: core
|
|
17
|
+
library: "kindgi-pack (Java)"
|
|
18
|
+
version: "0.1.0"
|
|
19
|
+
sdk_version: "0.1.5-rc.0"
|
|
20
|
+
pack_languages: [java]
|
|
21
|
+
sources:
|
|
22
|
+
- sdks/java/kindgi-pack/README.md
|
|
23
|
+
- sdks/java/kindgi-pack/src/main/java/com/kindgi/pack/Flow.java
|
|
24
|
+
- packages/specs/schemas/flow.schema.json
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# Authoring Kindgi flows in Java
|
|
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>`. Maven runs as
|
|
32
|
+
> `./mvnw`.
|
|
33
|
+
>
|
|
34
|
+
> Java 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 `public static final Flow` field of a class in a
|
|
40
|
+
`flows` package. The runtime runs it step by step, journals every step, and
|
|
41
|
+
can resume a run that was interrupted. A run pins the flow version it
|
|
42
|
+
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
|
+
```java
|
|
65
|
+
// src/main/java/acme/tools/Tickets.java
|
|
66
|
+
package acme.tools;
|
|
67
|
+
|
|
68
|
+
import com.kindgi.pack.Tool;
|
|
69
|
+
import org.jspecify.annotations.Nullable;
|
|
70
|
+
|
|
71
|
+
/** The ticket tools: parse one, look its invoice up, draft the reply. */
|
|
72
|
+
public final class Tickets {
|
|
73
|
+
public record Ticket(String customerId, String body) {}
|
|
74
|
+
|
|
75
|
+
public record ParseInput(Ticket ticket) {}
|
|
76
|
+
|
|
77
|
+
public record Parsed(String text) {}
|
|
78
|
+
|
|
79
|
+
public record InvoiceInput(String customerId) {}
|
|
80
|
+
|
|
81
|
+
public record Invoice(String number, double amount) {}
|
|
82
|
+
|
|
83
|
+
public record Found(Invoice invoice) {}
|
|
84
|
+
|
|
85
|
+
/** `invoice` is absent when the billing step didn't run: it may be null. */
|
|
86
|
+
public record ReplyInput(String category, @Nullable Invoice invoice) {}
|
|
87
|
+
|
|
88
|
+
public record Reply(String text) {}
|
|
89
|
+
|
|
90
|
+
public static final Tool<ParseInput, Parsed> PARSE = Tool.define("acme.parse-ticket")
|
|
91
|
+
.description("Extracts a ticket's text.")
|
|
92
|
+
.input(ParseInput.class)
|
|
93
|
+
.output(Parsed.class)
|
|
94
|
+
.mutating(false)
|
|
95
|
+
.handler((input, ctx) -> new Parsed(input.ticket().body().strip()));
|
|
96
|
+
|
|
97
|
+
public static final Tool<InvoiceInput, Found> LOOKUP_INVOICE = Tool.define("acme.lookup-invoice")
|
|
98
|
+
.description("The customer's latest invoice.")
|
|
99
|
+
.input(InvoiceInput.class)
|
|
100
|
+
.output(Found.class)
|
|
101
|
+
.mutating(false)
|
|
102
|
+
.handler((input, ctx) -> new Found(new Invoice("INV-1", 42.0)));
|
|
103
|
+
|
|
104
|
+
public static final Tool<ReplyInput, Reply> DRAFT_REPLY = Tool.define("acme.draft-reply")
|
|
105
|
+
.description("Drafts a reply for the ticket's category.")
|
|
106
|
+
.input(ReplyInput.class)
|
|
107
|
+
.output(Reply.class)
|
|
108
|
+
.mutating(false)
|
|
109
|
+
.handler((input, ctx) -> new Reply(input.invoice() == null
|
|
110
|
+
? "Thanks, we're on it (" + input.category() + ")."
|
|
111
|
+
: "Invoice " + input.invoice().number() + " is " + input.invoice().amount() + "."));
|
|
112
|
+
|
|
113
|
+
private Tickets() {}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```java
|
|
118
|
+
// src/main/java/acme/agents/TicketClassifier.java
|
|
119
|
+
package acme.agents;
|
|
120
|
+
|
|
121
|
+
import com.kindgi.pack.Agent;
|
|
122
|
+
import java.util.List;
|
|
123
|
+
import java.util.Map;
|
|
124
|
+
|
|
125
|
+
/** acme.ticket-classifier: one word for a ticket's category. */
|
|
126
|
+
public final class TicketClassifier {
|
|
127
|
+
public record Category(String category) {}
|
|
128
|
+
|
|
129
|
+
public static final Agent AGENT = Agent.define("acme.ticket-classifier")
|
|
130
|
+
.version("0.1.0")
|
|
131
|
+
.name("Ticket classifier")
|
|
132
|
+
.instructions("Classify the support ticket for {{ product }}: answer billing, bug or other, "
|
|
133
|
+
+ "as JSON with one field, category.")
|
|
134
|
+
.capability(Map.of("needs", List.of(Map.of("feature", "tool-use"))))
|
|
135
|
+
.set("parameters", List.of(Map.of("name", "product", "type", "string", "required", true)))
|
|
136
|
+
.output(Category.class)
|
|
137
|
+
.build();
|
|
138
|
+
|
|
139
|
+
private TicketClassifier() {}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The flow:
|
|
144
|
+
|
|
145
|
+
```java
|
|
146
|
+
// src/main/java/acme/flows/TriageTicket.java
|
|
147
|
+
package acme.flows;
|
|
148
|
+
|
|
149
|
+
import acme.agents.TicketClassifier;
|
|
150
|
+
import acme.tools.Tickets;
|
|
151
|
+
import com.kindgi.pack.Flow;
|
|
152
|
+
import java.util.List;
|
|
153
|
+
import java.util.Map;
|
|
154
|
+
|
|
155
|
+
/** acme.triage-ticket: parse, classify, look billing up when needed, reply. */
|
|
156
|
+
public final class TriageTicket {
|
|
157
|
+
static final Map<String, Object> IS_BILLING = Map.of(
|
|
158
|
+
"op", "eq",
|
|
159
|
+
"left", Map.of("path", "nodeOutputs.classify.output.category"),
|
|
160
|
+
"right", Map.of("literal", "billing"));
|
|
161
|
+
|
|
162
|
+
public static final Flow FLOW = Flow.define("acme.triage-ticket")
|
|
163
|
+
.version("0.1.0")
|
|
164
|
+
.set("name", "Triage a support ticket")
|
|
165
|
+
.set("description", "Parses a ticket, classifies it, looks billing up when needed, drafts a reply.")
|
|
166
|
+
.node(Map.of("id", "parse", "kind", "tool", "ref", Tickets.PARSE,
|
|
167
|
+
"inputMapping", Map.of("ticket", Map.of("path", "runInput.ticket"))))
|
|
168
|
+
.node(Map.of("id", "classify", "kind", "agent", "ref", TicketClassifier.AGENT,
|
|
169
|
+
"inputMapping", Map.of("text", Map.of("path", "nodeOutputs.parse.text")),
|
|
170
|
+
"config", Map.of("parameters", Map.of("product", "acme-cloud"))))
|
|
171
|
+
.node(Map.of("id", "billing", "kind", "tool", "ref", Tickets.LOOKUP_INVOICE,
|
|
172
|
+
"inputMapping", Map.of("customerId", Map.of("path", "runInput.ticket.customerId"))))
|
|
173
|
+
.node(Map.of("id", "reply", "kind", "tool", "ref", Tickets.DRAFT_REPLY,
|
|
174
|
+
"inputMapping", Map.of(
|
|
175
|
+
"category", Map.of("path", "nodeOutputs.classify.output.category"),
|
|
176
|
+
"invoice", Map.of("path", "nodeOutputs.billing.invoice"))))
|
|
177
|
+
.edge("e0", "$start", "parse")
|
|
178
|
+
.edge("e1", "parse", "classify")
|
|
179
|
+
.edge(Map.of("id", "e2", "from", "classify", "to", "billing", "when", IS_BILLING))
|
|
180
|
+
.edge(Map.of("id", "e3", "from", "classify", "to", "reply", "when", Map.of("op", "not", "child", IS_BILLING)))
|
|
181
|
+
.edge("e4", "billing", "reply")
|
|
182
|
+
.edge("e5", "reply", "$end")
|
|
183
|
+
.set("output", Map.of(
|
|
184
|
+
"mapping", Map.of(
|
|
185
|
+
"category", Map.of("path", "nodeOutputs.classify.output.category"),
|
|
186
|
+
"reply", Map.of("path", "nodeOutputs.reply.text")),
|
|
187
|
+
"schema", Map.of(
|
|
188
|
+
"type", "object",
|
|
189
|
+
"properties", Map.of("category", Map.of("type", "string"), "reply", Map.of("type", "string")),
|
|
190
|
+
"required", List.of("category", "reply"))))
|
|
191
|
+
.build();
|
|
192
|
+
|
|
193
|
+
private TriageTicket() {}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
- **Steps:** `toolNode(id, Tool)` and `agentNode(id, Agent)` are a step with
|
|
198
|
+
nothing more. A step with an `inputMapping`, a `config`, a loop or a fanout
|
|
199
|
+
is a map, `node(Map.of(…))`, as `flow.schema.json` describes it. In a
|
|
200
|
+
node's map, a `Tool`, `Agent` or `Flow` (loop bodies and fanout branches
|
|
201
|
+
included) becomes its id. A primitive of another pack is its id string.
|
|
202
|
+
- **Edges:** `edge(id, from, to)` joins two steps. An edge with a condition
|
|
203
|
+
(`when`) or a `policy` is a map, `edge(Map.of(…))`.
|
|
204
|
+
- **Other fields:** `set(field, value)` takes `name`, `description`,
|
|
205
|
+
`output`, `maxParallelism` and `metadata`. `build()` needs a version.
|
|
206
|
+
- **The maps are the wire's shape,** so their keys are camelCase
|
|
207
|
+
(`inputMapping`, `loopKind`, `maxIterations`).
|
|
208
|
+
- **Where it's checked:** the indexer checks every flow against
|
|
209
|
+
`flow.schema.json` (version, node and edge shapes, `$start` / `$end`).
|
|
210
|
+
`kindgi dev` reports a mistake as a file error that says what and where,
|
|
211
|
+
while the pack's other primitives keep serving. Whether a step's tool or
|
|
212
|
+
agent exists is checked when a run starts (see "Running a flow").
|
|
213
|
+
- `Map.of` takes up to ten pairs. For a bigger map, use `Map.ofEntries` or a
|
|
214
|
+
`LinkedHashMap`.
|
|
215
|
+
|
|
216
|
+
## Nodes
|
|
217
|
+
|
|
218
|
+
- **A tool step** (`"kind": "tool"`) runs the tool `ref`.
|
|
219
|
+
- Its input is what the node's `inputMapping` builds; else, the output of
|
|
220
|
+
the node's single upstream node (the run input after `$start`).
|
|
221
|
+
- The input is checked against the tool's input schema, so a mismatch
|
|
222
|
+
fails the step with `input-validation-failed`.
|
|
223
|
+
- The node's output is what the tool returned, as JSON (its Jackson names).
|
|
224
|
+
- **An agent step** (`"kind": "agent"`) runs one turn of the agent `ref`, as a
|
|
225
|
+
child run of the flow run.
|
|
226
|
+
- The agent gets the node's input two ways: as structured input
|
|
227
|
+
(`{{ input.text }}` in its instructions), and as its user message (the
|
|
228
|
+
input as JSON).
|
|
229
|
+
- `"config": {"parameters": {…}}` fills the agent's `parameters` (string,
|
|
230
|
+
number or boolean values). `"config": {"version": "1.2.0"}` pins an agent
|
|
231
|
+
version; without it, the latest active version runs.
|
|
232
|
+
- The node's output: `output` is the agent's typed answer (its
|
|
233
|
+
`output(Type.class)`); `text` is the answer as text; `runId` and
|
|
234
|
+
`conversationId` are the child run's. Read a field as
|
|
235
|
+
`nodeOutputs.<node>.output.<field>`.
|
|
236
|
+
- An answer that doesn't fit the agent's output, after its repairs, fails
|
|
237
|
+
the step with `output-schema-violation`. An approval inside the agent's
|
|
238
|
+
turn parks the flow until it's decided.
|
|
239
|
+
- **A loop** (`"kind": "loop"`) repeats a body.
|
|
240
|
+
- `"loopKind": "foreach"` runs it once per element of `iterateOver`
|
|
241
|
+
(`concurrency` up to 32 in parallel). `"loopKind": "while"` runs it until
|
|
242
|
+
`exitCondition`.
|
|
243
|
+
- The body (`"body": {"nodes": […], "edges": […]}`) has its own nodes and
|
|
244
|
+
edges, with `$loop-start` and `$loop-end`. The element is the body's
|
|
245
|
+
input.
|
|
246
|
+
- `maxIterations` and `outputSchema` are required.
|
|
247
|
+
- The loop's output is `finalOutput`, plus `outputs` with
|
|
248
|
+
`"collectAllIterations": true`.
|
|
249
|
+
- Node ids must be unique across the whole flow, bodies included.
|
|
250
|
+
- **A fanout** (`"kind": "fanout"`) runs several handlers on the same input
|
|
251
|
+
at once. Each is a branch (`branchId`, `handler`: a `Tool` or an id, and
|
|
252
|
+
`outputSchema`). `convergence` decides the result: `all-succeed`,
|
|
253
|
+
`any-succeed` (the first success wins), or `settle-all` (wait for every
|
|
254
|
+
branch and report each).
|
|
255
|
+
- **A sub-flow** (`"kind": "subgraph"`) is part of the flow schema, but a run
|
|
256
|
+
refuses it today (`flow-unbound`). Inline the steps instead.
|
|
257
|
+
|
|
258
|
+
## Edges and conditions
|
|
259
|
+
|
|
260
|
+
An edge goes from a node (or `$start`) to a node (or `$end`). Without `when`
|
|
261
|
+
it fires when its source completes. With `when`, it fires only if the
|
|
262
|
+
condition is true. Conditions are maps:
|
|
263
|
+
|
|
264
|
+
| Operator | Shape |
|
|
265
|
+
|---|---|
|
|
266
|
+
| `eq` `ne` `lt` `lte` `gt` `gte` | `op`, `left`, `right` |
|
|
267
|
+
| `in` `notIn` | `op`, `value`, `set` |
|
|
268
|
+
| `exists` `notExists` `truthy` `falsy` | `op`, `value` |
|
|
269
|
+
| `and` `or` | `op`, `children` (a list) |
|
|
270
|
+
| `not` | `op`, `child` |
|
|
271
|
+
|
|
272
|
+
Each operand is `Map.of("literal", …)` or `Map.of("path", …)`. When a path
|
|
273
|
+
doesn't resolve, `eq`, `lt`, `lte`, `gt` and `gte` are false, and `ne` is
|
|
274
|
+
true. So for the "otherwise" branch, wrap the condition in `not` (as above)
|
|
275
|
+
rather than writing a second comparison: it covers exactly what the first
|
|
276
|
+
edge doesn't. A condition used twice is easiest as a constant (`IS_BILLING`).
|
|
277
|
+
|
|
278
|
+
**Joining branches.** A node with several incoming edges runs once every one
|
|
279
|
+
of them is decided and at least one fired. Above, `reply` runs after
|
|
280
|
+
`billing` on the billing branch, and straight after `classify` otherwise. A
|
|
281
|
+
node none of whose incoming edges fired is skipped, and so is everything
|
|
282
|
+
only it leads to.
|
|
283
|
+
|
|
284
|
+
**Edge policy** (`"policy"` on the edge into a node with a single incoming
|
|
285
|
+
edge; a node with several ignores it):
|
|
286
|
+
- `"retry": {"maxAttempts", "delayMs"?, "backoff"?, "maxDelayMs"?}`, up to 10
|
|
287
|
+
attempts in all;
|
|
288
|
+
- `"timeoutMs"`: a step that takes longer fails with `reason: timeout`;
|
|
289
|
+
- `"concurrencyKey"`: at most one such step at a time in the tenant;
|
|
290
|
+
- `"priority"`: −100 to 100.
|
|
291
|
+
|
|
292
|
+
## Inputs and the output
|
|
293
|
+
|
|
294
|
+
`inputMapping` maps each key to a `literal` or a `path`. Its keys are the
|
|
295
|
+
tool's input **as it travels**: the record's Jackson names (a component
|
|
296
|
+
`customerId` is the key `customerId`; with
|
|
297
|
+
`@JsonProperty("customer_id")`, it's `customer_id`). Paths are dot-separated
|
|
298
|
+
(a number segment indexes an array: `items.0.sku`), rooted at:
|
|
299
|
+
- `runInput.…`: the input the run started with;
|
|
300
|
+
- `nodeOutputs.<nodeId>.…`: a step's output. For an agent step, add
|
|
301
|
+
`.output.<field>` to read its typed answer;
|
|
302
|
+
- `state.…`: values the runtime's own handlers write. Pack tools don't
|
|
303
|
+
write it, so use `nodeOutputs`.
|
|
304
|
+
|
|
305
|
+
A path that doesn't resolve leaves its key out. So a step after a branch
|
|
306
|
+
that didn't run gets no `invoice` key at all, rather than `null`. Make that
|
|
307
|
+
component optional in the tool's input (`@Nullable Invoice invoice`, as
|
|
308
|
+
above).
|
|
309
|
+
|
|
310
|
+
`set("output", …)` is what the run returns: a `mapping` resolved when the
|
|
311
|
+
run finishes, checked against `schema` if you give one. A run whose output
|
|
312
|
+
doesn't match fails. Without an output, the run returns the output of the
|
|
313
|
+
step that reached `$end`.
|
|
314
|
+
|
|
315
|
+
## Running a flow
|
|
316
|
+
|
|
317
|
+
From another terminal in the pack directory, while `kindgi dev` runs:
|
|
318
|
+
|
|
319
|
+
```sh
|
|
320
|
+
./kindgiw runs start --flow=acme.triage-ticket --input='{"ticket":{"customerId":"c-1","body":"Charged twice"}}'
|
|
321
|
+
./kindgiw runs start --flow=acme.triage-ticket --input=@ticket.json --no-wait # the run id now; it finishes in the background
|
|
322
|
+
./kindgiw runs start --flow=acme.triage-ticket --input=@ticket.json --dry-run # stops before a tool that may write
|
|
323
|
+
./kindgiw runs get <run-id> # status, output, failureMessage
|
|
324
|
+
./kindgiw runs journal <run-id> # every step.started / step.completed / edge.evaluated
|
|
325
|
+
./kindgiw runs stream <run-id> # follow a running one
|
|
326
|
+
./kindgiw runs cancel <run-id>
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
From a Java app, the client starts one the same way:
|
|
330
|
+
`client.runs().start(StartRunBody.WithFlow.builder().flow("acme.triage-ticket").input(…).build())`.
|
|
331
|
+
|
|
332
|
+
- **A refusal before the run exists:** `422 flow-unbound` names the nodes a
|
|
333
|
+
run can't bind: a tool or agent id the tenant doesn't have, or a
|
|
334
|
+
sub-flow. Fix the ids; nothing ran.
|
|
335
|
+
- **A failed step fails the run**, and `failureMessage` says which step and
|
|
336
|
+
why. Retry it on its edge with `policy.retry`, but only if running the
|
|
337
|
+
step twice is safe.
|
|
338
|
+
- **`--no-wait`** is how an application starts runs
|
|
339
|
+
(`"options": {"wait": false}`). It answers with the run id at once; poll
|
|
340
|
+
the run or follow its stream.
|
|
341
|
+
- **`--dry-run`** runs a tool only if it's declared read-only:
|
|
342
|
+
`mutating(false)` and no `writes`, `deletes`, `spawns-run`, `emits-event`
|
|
343
|
+
or `external-side-effect` effect. The first other tool stops the run with
|
|
344
|
+
`dry-run-effectful-tool`, and everything before it really ran. That's
|
|
345
|
+
useful for checking the wiring without the writes.
|
|
346
|
+
|
|
347
|
+
## Iterating on a flow
|
|
348
|
+
|
|
349
|
+
Save the file, and `kindgi dev` recompiles and re-indexes. The next run uses
|
|
350
|
+
the new definition, with no restart. A run already in flight keeps the
|
|
351
|
+
version it started on. Bump `version` when callers' contract changes (the
|
|
352
|
+
input or the output), not on every save.
|
|
353
|
+
|
|
354
|
+
## Common mistakes
|
|
355
|
+
|
|
356
|
+
1. **Building a flow without asking what goes in and comes out.** The pack's
|
|
357
|
+
`EchoFlow` proves the runtime works. It isn't a template for the user's
|
|
358
|
+
flow.
|
|
359
|
+
2. **A second comparison for "otherwise".** On a path that may be missing,
|
|
360
|
+
`eq` is false and `ne` is true, and `lt`/`gt` are both false, so a
|
|
361
|
+
hand-written opposite can miss a case or overlap. Wrap the positive
|
|
362
|
+
condition in `not`: it covers exactly what the first edge doesn't.
|
|
363
|
+
3. **Reading an agent step's answer at `nodeOutputs.<step>.<field>`.** The
|
|
364
|
+
typed answer is under `.output`: `nodeOutputs.<step>.output.<field>`. An
|
|
365
|
+
agent without an output type has only `text`.
|
|
366
|
+
4. **A required input component fed by a branch that may not run.** The key
|
|
367
|
+
is left out, the tool's input check fails, and so does the step. Make it
|
|
368
|
+
`@Nullable`.
|
|
369
|
+
5. **`inputMapping` keys in the wrong spelling.** They're the input's wire
|
|
370
|
+
names: `customer_id` doesn't fill a `customerId` component that has no
|
|
371
|
+
`@JsonProperty("customer_id")`.
|
|
372
|
+
6. **A `when` given through `set("edges", …)`.** `build()` writes the edges
|
|
373
|
+
you added with `edge(…)`; use `edge(Map.of(…))` for an edge with a
|
|
374
|
+
condition or a policy.
|
|
375
|
+
7. **A sub-flow node.** A run refuses it (`flow-unbound`) until sub-flows
|
|
376
|
+
are supported.
|
|
377
|
+
8. **Duplicate node ids inside a loop body.** Ids are unique across the
|
|
378
|
+
whole flow, bodies included.
|
|
379
|
+
9. **`mutating(false)` on a tool that writes.** A dry run then runs it for
|
|
380
|
+
real, and an approval gate (when it falls back on `mutating`) won't ask
|
|
381
|
+
before it.
|
|
382
|
+
10. **A read-only tool without `mutating(false)`.** A dry run stops at it,
|
|
383
|
+
and an approval gate asks before it on first use.
|
|
384
|
+
|
|
385
|
+
## When the framework itself is the problem
|
|
386
|
+
|
|
387
|
+
If the bug is in Kindgi or kindgi-pack (a step's output missing a field, a
|
|
388
|
+
condition that evaluates wrongly, a misleading error) and not in the pack's
|
|
389
|
+
code, load `kindgi-framework-feedback` and file it with
|
|
390
|
+
`./kindgiw feedback write`.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kindgi-java-authoring-guardrails
|
|
3
|
+
description: >
|
|
4
|
+
Covers writing guardrails (safety checks on an agent's turn) for a Kindgi
|
|
5
|
+
pack in Java (`com.kindgi:kindgi-pack`): `Guardrail.define(id)` as a
|
|
6
|
+
`public static final` field, a `(config, trace)` check returning a
|
|
7
|
+
`CheckResult`, `RunTrace`, config records and the values a check runs
|
|
8
|
+
with, actions (halt / retry / escalate / log-only / compensate), severity
|
|
9
|
+
and scope, unit tests with `evaluate`, and wiring a guardrail onto an
|
|
10
|
+
agent. Load this whenever you are authoring or editing code in a Java
|
|
11
|
+
pack's guardrails packages (a pack whose `kindgi.config.json` says
|
|
12
|
+
`"language": "java"`), defining a check, or wiring a guardrail onto an
|
|
13
|
+
agent. Java agents are covered by kindgi-java-authoring-agents, Java tools
|
|
14
|
+
by kindgi-java-authoring-tools.
|
|
15
|
+
type: core
|
|
16
|
+
library: "kindgi-pack (Java)"
|
|
17
|
+
version: "0.1.0"
|
|
18
|
+
sdk_version: "0.1.5-rc.0"
|
|
19
|
+
pack_languages: [java]
|
|
20
|
+
sources:
|
|
21
|
+
- sdks/java/kindgi-pack/README.md
|
|
22
|
+
- sdks/java/kindgi-pack/src/main/java/com/kindgi/pack/Guardrail.java
|
|
23
|
+
- sdks/java/kindgi-pack/src/main/java/com/kindgi/pack/RunTrace.java
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
# Authoring Kindgi guardrails in Java
|
|
27
|
+
|
|
28
|
+
> **Running `kindgi`:** the pack pins its CLI (`"cli"` in
|
|
29
|
+
> `kindgi.config.json`), and `./kindgiw` runs that version, so every
|
|
30
|
+
> `kindgi <command>` below runs as `./kindgiw <command>`. Maven runs as
|
|
31
|
+
> `./mvnw`.
|
|
32
|
+
>
|
|
33
|
+
> Java 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 Java pack it is a `public static final Guardrail<C>` field of a
|
|
39
|
+
class in a `guardrails` package. For an agent turn, the runtime evaluates
|
|
40
|
+
every guardrail the agent lists once, on the final answer, before it is
|
|
41
|
+
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
|
+
```java
|
|
49
|
+
// src/main/java/acme/guardrails/Citations.java
|
|
50
|
+
package acme.guardrails;
|
|
51
|
+
|
|
52
|
+
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
53
|
+
import com.kindgi.pack.CheckResult;
|
|
54
|
+
import com.kindgi.pack.Guardrail;
|
|
55
|
+
import jakarta.validation.constraints.Min;
|
|
56
|
+
import java.util.Map;
|
|
57
|
+
|
|
58
|
+
/** acme.no-fabricated-quotes: an answer that quotes case law looked the citations up. */
|
|
59
|
+
public final class Citations {
|
|
60
|
+
public record Config(@JsonProperty(defaultValue = "1") @Min(0) int minLookups) {}
|
|
61
|
+
|
|
62
|
+
public static final Guardrail<Config> NO_FABRICATED_QUOTES = Guardrail.define("acme.no-fabricated-quotes")
|
|
63
|
+
.name("No fabricated quotations")
|
|
64
|
+
.onViolation("halt")
|
|
65
|
+
.severity("critical")
|
|
66
|
+
.config(Config.class)
|
|
67
|
+
// What the check runs with, keyed as on the wire.
|
|
68
|
+
.set("config", Map.of("minLookups", 2))
|
|
69
|
+
.check((config, trace) -> {
|
|
70
|
+
long lookups = trace.toolCalls().stream()
|
|
71
|
+
.map(call -> (Map<?, ?>) call)
|
|
72
|
+
.filter(call -> "acme.verify-citation".equals(call.get("toolName")))
|
|
73
|
+
.count();
|
|
74
|
+
return lookups >= config.minLookups()
|
|
75
|
+
? CheckResult.pass()
|
|
76
|
+
: CheckResult.fail("Only " + lookups + " citation lookups (need " + config.minLookups() + "+).");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
private Citations() {}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- **The check** is `(config, trace) -> CheckResult`: `CheckResult.pass()`
|
|
84
|
+
or `CheckResult.fail(reason)`. A failed result's reason is what the
|
|
85
|
+
violation reports, so make it say what was wrong. A check built on futures
|
|
86
|
+
uses `asyncCheck` and returns a `CompletionStage<CheckResult>`.
|
|
87
|
+
- **`trace`** is a `RunTrace`:
|
|
88
|
+
- `output()`: the final answer's text;
|
|
89
|
+
- `userInput()`;
|
|
90
|
+
- `toolCalls()`: each a map with `toolId`, `toolName`, `arguments`;
|
|
91
|
+
- `toolResults()`: each with `toolCallId`, `output`;
|
|
92
|
+
- `modelCalls()`;
|
|
93
|
+
- `mode()`: `runtime` or `ci`;
|
|
94
|
+
- `runId()`, `tenantId()`;
|
|
95
|
+
- `raw()`: the trace as sent, with any field a newer runtime adds.
|
|
96
|
+
- **`config(Type.class)`** is the config's type. Its schema is derived as a
|
|
97
|
+
tool input's is, and the service binds the values to it. A
|
|
98
|
+
`@JsonProperty(defaultValue = …)` is the value when none is given.
|
|
99
|
+
`configSchema(Map)` gives a schema instead; the config is then a `Map`.
|
|
100
|
+
- **`set("config", Map.of(…))`** holds the values the check runs with,
|
|
101
|
+
keyed as on the wire. They go into the index, and the service checks them
|
|
102
|
+
against the schema before every evaluation. Without them the check runs
|
|
103
|
+
with `{}` and the defaults. So give every field a default: a required
|
|
104
|
+
field with no value fails every evaluation (`input-validation-failed`).
|
|
105
|
+
- An exception thrown from the check fails the evaluation (`handler-throw`).
|
|
106
|
+
For a rule that isn't met, return `CheckResult.fail(…)`.
|
|
107
|
+
- A check gets no model and no provider: it can't call an LLM. Keep it a
|
|
108
|
+
pure function of the trace (fast, deterministic, free).
|
|
109
|
+
|
|
110
|
+
## `Guardrail.define(id)`
|
|
111
|
+
|
|
112
|
+
- **`id`:** `<pack-id>.<guardrail-name>`, kebab-case. Name the rule as a
|
|
113
|
+
positive assertion: `no-fabricated-quotes`, `response-not-empty`.
|
|
114
|
+
- **`onViolation(action)`:** `halt`, `retry`, `escalate`, `log-only` or
|
|
115
|
+
`compensate`. For one that needs settings, pass the whole object instead,
|
|
116
|
+
with `action(Map.of("on-violation", "retry", "retry", Map.of("maxAttempts", 2)))`.
|
|
117
|
+
The same goes for `escalateTo` and `compensateWith` (a tool id). The
|
|
118
|
+
builder refuses a check with neither.
|
|
119
|
+
- In an agent turn, a failed `halt` guardrail fails the turn
|
|
120
|
+
(`guardrail-violation`), and the answer is not stored.
|
|
121
|
+
- Any other action reports the failure in the turn result's `violations`,
|
|
122
|
+
and the turn completes.
|
|
123
|
+
- In 0.1 the runtime acts only on `halt`: `retry`, `escalate` and
|
|
124
|
+
`compensate` are recorded on the violation, with no second attempt,
|
|
125
|
+
escalation or compensating call.
|
|
126
|
+
- **`severity`:** `info`, `warn`, `error` (default) or `critical`. It's
|
|
127
|
+
independent of the action: dashboards group by severity, and execution
|
|
128
|
+
follows the action.
|
|
129
|
+
- **`set("scope", Map.of("when", "runtime-only"))`:** when the guardrail
|
|
130
|
+
applies: `always`, `ci-only` or `runtime-only`, narrowed by `agents`,
|
|
131
|
+
`flows` and `tenants` lists.
|
|
132
|
+
- **`name`:** a display name. **`checkId`:** defaults to the id.
|
|
133
|
+
- **`kind`:** `zero-llm`, the default: a check over the trace, which is what
|
|
134
|
+
a pack writes.
|
|
135
|
+
- `set("sandbox" | "limits" | "network", …)` are recorded in the index.
|
|
136
|
+
|
|
137
|
+
## Testing
|
|
138
|
+
|
|
139
|
+
`evaluate` runs the check with the config as given, with no schema check
|
|
140
|
+
and no defaults. An async check is awaited.
|
|
141
|
+
|
|
142
|
+
```java
|
|
143
|
+
// src/test/java/acme/CitationsTest.java
|
|
144
|
+
package acme;
|
|
145
|
+
|
|
146
|
+
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
147
|
+
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
148
|
+
|
|
149
|
+
import acme.guardrails.Citations;
|
|
150
|
+
import com.kindgi.pack.RunTrace;
|
|
151
|
+
import java.util.List;
|
|
152
|
+
import java.util.Map;
|
|
153
|
+
import org.junit.jupiter.api.Test;
|
|
154
|
+
|
|
155
|
+
class CitationsTest {
|
|
156
|
+
@Test
|
|
157
|
+
void noLookupsFails() throws Exception {
|
|
158
|
+
RunTrace trace = new RunTrace(Map.of("output", "As held in Smith v. Jones…"));
|
|
159
|
+
assertFalse(Citations.NO_FABRICATED_QUOTES.evaluate(new Citations.Config(1), trace).passed());
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@Test
|
|
163
|
+
void enoughLookupsPass() throws Exception {
|
|
164
|
+
Map<String, Object> lookup = Map.of("toolName", "acme.verify-citation", "arguments", Map.of());
|
|
165
|
+
RunTrace trace = new RunTrace(Map.of("toolCalls", List.of(lookup, lookup)));
|
|
166
|
+
assertTrue(Citations.NO_FABRICATED_QUOTES.evaluate(new Citations.Config(2), trace).passed());
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`new RunTrace(Map)` takes the trace as the runtime sends it, camelCase keys
|
|
172
|
+
included.
|
|
173
|
+
|
|
174
|
+
## Wiring onto an agent
|
|
175
|
+
|
|
176
|
+
```java
|
|
177
|
+
Agent.define("acme.brief-writer")
|
|
178
|
+
// …
|
|
179
|
+
.guardrail(Citations.NO_FABRICATED_QUOTES)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Pass the `Guardrail`, or its id (`.guardrail("acme.no-fabricated-quotes")`).
|
|
183
|
+
`kindgi dev` registers the pack's guardrails. An agent that names an id with
|
|
184
|
+
no registered guardrail fails the turn before the model is called
|
|
185
|
+
(`Agent "…" references guardrails not in the registry: <id>`).
|
|
186
|
+
|
|
187
|
+
## Common mistakes
|
|
188
|
+
|
|
189
|
+
1. **A required config field with no value.** Without `set("config", …)`,
|
|
190
|
+
the check runs with `{}`. Give the field a default
|
|
191
|
+
(`@JsonProperty(defaultValue = "1")`), or the guardrail its values.
|
|
192
|
+
2. **`set("config", …)` keyed by Java names.** It's keyed like the wire: the
|
|
193
|
+
record's Jackson names.
|
|
194
|
+
3. **No `onViolation` or `action`.** The builder refuses the check.
|
|
195
|
+
4. **Expecting another attempt.** `halt` stops the turn, and in 0.1 `retry`
|
|
196
|
+
doesn't run the turn again: it's only recorded.
|
|
197
|
+
5. **Calling a model from the check.** That isn't available: keep checks
|
|
198
|
+
pure.
|
|
199
|
+
6. **Throwing for a broken rule.** Return `CheckResult.fail(…)`. An
|
|
200
|
+
exception is an evaluation error, not a violation.
|
|
201
|
+
7. **A guardrail that isn't a `static final` field**, or a public class in
|
|
202
|
+
a `guardrails` package that defines none (a file error). A helper there
|
|
203
|
+
is package-private, or a record, an enum or an interface.
|
|
204
|
+
|
|
205
|
+
## When the framework itself is the problem
|
|
206
|
+
|
|
207
|
+
If the bug is in Kindgi or kindgi-pack (a trace field missing, a misleading
|
|
208
|
+
error) and not in the check, load `kindgi-framework-feedback` and file it
|
|
209
|
+
with `./kindgiw feedback write`.
|