Skip to content

Run a Chart by name ​

Keep a Chart under a name, and run it as a Workflow.

This example is workflows/notes.ts, a Chart wrapper. Its run input has a field chart that is chartSchema. Thus it takes a Chart in its run input, and it keeps Charts under names:

ts
import { assign } from "xstate";
import { z } from "zod";
import { agent, chartSchema, fail, jr2Setup, openBody, type OpenBodyOutput } from "@jr2/orchestrator";

The model of the tab:

ts
const model = "anthropic/claude-haiku-5-5";

The run input and the Machine:

ts
// The run input. The field `chart` is a Chart, so this Machine takes a Chart in its run input,
// and it can keep Charts under names.
const door = z.object({ topic: z.string(), chart: chartSchema });
ts
export const machine = jr2Setup({
  types: {} as { context: Context; input: Input },
  actors: {
    writer: agent({
      model,
      workspace: "none",
      instructions: "You write clear, short text for engineers.",
    }),
    // A Chart can use the Agent `writer`, and `fail` to end the run as a Failure. It cannot add
    // Agents of its own.
    body: openBody({
      lends: { agents: ["writer"], fail: true },
      allows: { models: [], workspace: [] },
      ceilings: { turns: 6, wallClock: "15m", transitions: 200, fanOut: 1, size: 65536 },
    }),
    fail: fail(),
  },
}).createMachine({
  id: "notes",
  input: door,
  context: ({ input }) => ({ ...input }),
  // The fault of `writer` when a Chart does not route it: the Open body sends it here.
  on: { "agent.fault": ".failed" },
  initial: "running",
  states: {
    running: {
      invoke: {
        src: "body",
        // The Chart, and the input that the Chart reads as {{input.topic}}.
        input: ({ context }: { context: Context }) => ({ chart: context.chart, topic: context.topic }),
        onDone: { target: "done", actions: assign({ result: ({ event }) => event.output as OpenBodyOutput }) },
      },
    },
    done: { type: "final" },
    failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
  },
  output: ({ context }) => context.result,
});

This Chart is workflows/notes/summary.chart.json. Its Workflow name is notes/summary:

json
{
  "version": 1,
  "id": "summary",
  "context": { "summary": "", "why": "" },
  "events": {
    "finished": {
      "description": "Give the summary. This ends your work.",
      "input": {
        "type": "object",
        "properties": { "summary": { "type": "string", "description": "The summary, in three sentences." } },
        "required": ["summary"],
        "additionalProperties": false
      }
    }
  },
  "initial": "writing",
  "states": {
    "writing": {
      "invoke": {
        "src": "writer",
        "input": { "prompt": "Write a summary of {{input.topic}} in three sentences. Then pick finished with it." }
      },
      "on": {
        "finished": {
          "target": "done",
          "actions": [{ "type": "set", "path": "summary", "from": "{{event.summary}}" }]
        },
        "agent.fault": {
          "target": "failed",
          "actions": [{ "type": "set", "path": "why", "from": "{{event.reason}}" }]
        }
      }
    },
    "failed": {
      "invoke": {
        "src": "fail",
        "input": { "reason": "the writer could not write the summary", "detail": "{{context.why}}" }
      }
    },
    "done": { "type": "final", "output": { "summary": "{{context.summary}}" } }
  }
}

The Chart invokes the lent Agent writer with a prompt that reads {{input.topic}}. When writer picks finished, the Chart keeps the summary in its context, and ends with it as its output. When the Turn of writer cannot go on, jr2 sends agent.fault. The Chart keeps its reason, and goes to failed. failed invokes fail, which the wrapper lends (fail: true), so the run ends failed, with { reason, detail } as its output. In a Chart the agent.fault route is optional. Without it, the Open body sends the event to this wrapper, and the wrapper's root route goes to its own failed state, which invokes fail too.

Keep a Chart in a file ​

Put the Chart file in a folder that has the name of its wrapper: workflows/<wrapper>/<name>.chart.json. <name> has lower-case letters, digits and -. Then run jr2 up. It checks each Chart file at the door before it changes the cluster, and publishes it in the Assembly.

jr2 runs lists the Chart under its wrapper, on stderr:

sh
jr2 runs
text
…
→ context kind-jr2 / namespace my-instance
workflows:
  notes (machine, Chart wrapper: body "body", one-shot)
    notes/summary (chart file)
  ping (machine)
[]

The first line, …, is the line → .env:, with the keys that the CLI took from .env.

one-shot means that the wrapper also takes a Chart in its run input. jr2 runs --workflows prints the same list as JSON on stdout.

Run the Chart ​

A named Chart supplies the chart field. Give the other fields of the run input:

sh
jr2 run notes/summary --input '{"topic":"the greet CLI"}'
text
…
→ context kind-jr2 / namespace my-instance
run 08a1a07b-b5d8-4e8b-aec5-811eab9289ec (notes/summary) — attached
  → active "running"
  → active "running"
  → done "done"
…

The last line is the status of the run as JSON. Its context.result is the output of the Open body: {"outcome":"done","output":{"summary":"<summary>"}}. Your run id is different, and <summary> is the text of the model. The line → active "running" can show more than one time.

Keep a Chart at run time ​

A runtime Chart needs no jr2 up. Send it to the Orchestrator with PUT /workflows/<wrapper>/charts/<name>, and the Instance token. No jr2 command does this, so use curl, or a tool of your own. Start the port forward of Open the Console. Then, in a second terminal, put the token in a variable:

sh
TOKEN=$(kubectl -n my-instance get secret jr2-instance -o jsonpath='{.data.JR2_INSTANCE_TOKEN}' | base64 -d)

Then send the text of the Chart file under a second name, brief:

sh
curl -X PUT -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" \
  --data @workflows/notes/summary.chart.json http://localhost:4000/workflows/notes/charts/brief
text
{"name":"notes/brief","origin":"runtime","hash":"4685124c248a"}

hash is the hash of the Chart text. The status code is 201 for a new name, and 200 when the Chart replaced a runtime Chart with the same name. jr2 runs then lists notes/brief (chart), and jr2 run notes/brief starts it. A run of the old text keeps its own text.

To remove the Chart:

sh
curl -X DELETE -H "Authorization: Bearer $TOKEN" http://localhost:4000/workflows/notes/charts/brief

The Orchestrator keeps a runtime Chart on its volume, so it stays after a restart. When a publish removes a part that it uses, it stays, and jr2 runs marks it unrunnable with the reason.

Run a Chart one time ​

To run a Chart with no name, put it in the chart field of the run input of the wrapper:

sh
jr2 run notes --input "{\"topic\":\"the greet CLI\",\"chart\":$(cat workflows/notes/summary.chart.json)}"

The run has the Workflow name notes. GET /workflows/notes/chart-schema gives the Chart grammar with the names that this wrapper lends. Give it to your editor as the JSON Schema of your Chart files, so that it checks them while you write.

Rules ​

  • jr2 up refuses a Chart file that the door refuses, and changes nothing in the cluster. It prints one line for each problem: refusing: workflows/<wrapper>/<name>.chart.json: Chart refused: <problem>. See Let an Agent write a Chart for the problems.
  • A Chart file is under a Chart wrapper that the Assembly has: refusing: workflows/<wrapper>/<name>.chart.json: no workflows/<wrapper>.ts registers the Chart wrapper "<wrapper>" this Chart file is under
  • A Chart name has lower-case letters, digits and -: "<name>" is not a Chart name: lower-case letters, digits and "-", starting with a letter or a digit, at most 63 characters
  • jr2 up ignores a Chart file in workflows/, and warns: warning: workflows/<file>: ignored — a Chart file sits in workflows/<wrapper>/<name>.chart.json, below the Chart wrapper it runs under (ADR-0070)
  • The folder of a wrapper holds only Chart files. jr2 up warns about each other file: warning: workflows/<wrapper>/<file>: ignored — a wrapper's directory holds Chart files (<name>.chart.json) and nothing else jr2 reads (ADR-0070)
  • A Chart runs only under a Chart wrapper. The PUT refuses, with the status 409: workflow "<wrapper>" is no Chart wrapper: it declares no Open body (openBody() in its actors), so no Chart runs under it (ADR-0070)
  • Only a wrapper with a chartSchema field keeps a Chart under a name. The PUT refuses, with the status 409: workflow "<wrapper>" has no Chart door: no field of its input is chartSchema, so its Open body binds only Charts its own states supply (ADR-0070)
  • The PUT refuses a wrapper that the Assembly does not have, with the status 404: no workflow "<wrapper>"
  • The PUT refuses the name of a Chart file, with the status 409: "<wrapper>/<name>" is a Chart file of the Assembly (workflows/<wrapper>/<name>.chart.json): change it there and `jr2 up` (ADR-0070)
  • The PUT refuses a Chart that the door refuses, with the status 422: {"error":"refused","report":[<problems>]}
  • The DELETE refuses a Chart file, with the status 409: a Chart file is the Instance's own: remove workflows/<wrapper>/<name>.chart.json and `jr2 up` (ADR-0070)
  • PUT and DELETE need the Instance token. Without it, the status is 401.
  • An unrunnable named Chart does not start. The status is 409: named Chart "<wrapper>/<name>" cannot run: <reason>
  • jr2 run prints the report of a Chart that the door refuses in the run input, and exits with 1: error: the door refused the Chart for "<workflow>", then one line for each problem.

The docs of Kit version 0.5.1. Each page is also at its .md URL.