Skip to content

Charts ​

A Chart is a Machine that is written as data, in JSON, with no code. An Agent writes it while a run runs, or you keep it in a file. A Chart runs only under a Chart wrapper: a Machine that declares an Open body. The Open body names the parts that a Chart can use.

Why Charts exist ​

Some work has a shape that nobody knows before the run. A planner Agent reads the task and decides the steps: first a coder, then a reviewer, then a person. You cannot write that Machine before the run.

An Agent cannot write code for the Orchestrator. Only jr2 up publishes code, because the Orchestrator holds the credentials of the Instance. Code that an Agent wrote could reach them. A Chart is data: the Orchestrator reads it, but it cannot reach the process of the Orchestrator or its credentials. It can only name parts that your code gives it, and jr2 checks each name before the Chart runs. Thus an Agent can make a Machine while a run runs, and it can do only what the Open body lets it do. A Chart becomes a Workflow only when it is kept under a name (see Four ways to run a Chart). This is true also when the model is wrong or someone gives it bad text.

The Open body ​

openBody() makes the actor slot that a Chart binds. It is the one Open part that a run binds, not a composer. Its declaration has three parts:

  • lends: the parts of the Chart wrapper that a Chart can use, by their slot keys. These are Agents, Effects, Machines, guards, actions and delays. gate: true lends the Gate, so that a Chart can wait for a person. fail: true lends fail(), so that a Chart can end its run as a Failure with invoke: { src: "fail", input: { reason } }.
  • allows: what the Agents that a Chart adds can have. models lists the models, and workspace lists the Workspace accesses. With no model, a Chart cannot add an Agent.
  • ceilings: the limits of one Chart: the Agent Turns, the wall clock, the transitions, the children at one time, and the size of the Chart in bytes.

A Chart gets nothing else. A lent Effect keeps its url, its argv and its secrets, so a Chart fills only its input. A Chart has no url, no secret, no image and no Repo of its own. The Open body also decides where each Turn of the Chart runs: in the Sandbox of its workspace(), or on the Instance Harness.

What a Chart holds ​

A Chart is xstate's own JSON config, with these limits:

  • States, transitions, after delays, and nested and parallel states. A Chart has 200 states at most, 8 levels deep.
  • Each invoke.src is a lent slot key or an Agent of the Chart. Each guard, action and delay is a lent name. Each Chart also has the guard jr2.failure, which is true on an onError when the error is a Failure.
  • A state that invokes an Agent can route agent.fault in its on, but it does not have to. When no transition of the Chart takes agent.fault, the Open body sends it, unchanged, to the Chart wrapper. The wrapper's own agent.fault route takes it, as for an Agent of the wrapper, so one route on the wrapper's root is the route for each Agent of each Chart.
  • Its events, with a JSON Schema for the input of each. An Agent of the Chart picks these events.
  • Its own Agents, under agents, each with a model that the Open body allows.
  • Its context, and the output of its final state. A Chart reads that output. A Machine that you write in TypeScript does not: it needs a root output. createMachine refuses an output on a final state directly below the root when the root has none (see Write a Workflow).

A Chart moves data with a Reference: a path that jr2 reads when the state starts. {{input.topic}} reads the input of the Chart, {{context.notes}} reads its context, and {{event.summary}} reads the pick that a transition handles. { "$ref": "event.summary" } is the same. A Reference is a path, not an expression: {{context.round + 1}} is refused. Four actions write the context: set, append, increment and clear.

A placeholder in data stays text. When a ticket holds {{context.notes}}, the prompt gets those characters, not the notes.

The door ​

jr2 checks each Chart before it runs. The door reads the whole Chart, and gives one line for each problem:

  • each name that is not lent, or not declared by the Chart
  • each event that no state handles, and each payload schema that uses a part that jr2 does not check
  • each model that the Open body does not allow
  • each loop with no guard, and each Agent state that nothing can end
  • each Reference that nothing supplies, such as {{context.summary}} when no set writes summary

A refusal is the output of the Open body: { outcome: "refused", report }. The Chart wrapper routes it. Usually it goes back to the planner as the answer to its pick, and the planner sends a corrected Chart. The other outputs are { outcome: "done", output } when the Chart ends, and { outcome: "ceiling", ceiling } when it gets to a ceiling.

The pick of a planner carries the Chart as its parameter. Its schema is chartSchemaFor(declaration): the Chart grammar with the lent names of this Open body. Thus the model gets the grammar as the parameters of its tool. On each Turn where the pick is an Allowed pick, the Briefing also tells the Agent how a Chart runs.

Four ways to run a Chart ​

WhoOne timeBy name
You, a tool or a CLIThe Chart in the run input of the Chart wrapperA Chart file, or a runtime Chart, run as jr2 run <wrapper>/<name>
An AgentIts pick carries the Chart, and a state invokes the Open bodyThe Open body gets { chart: { name } }

A Chart wrapper takes a Chart in its run input when a field of its input is chartSchema. Only such a wrapper keeps Charts under a name. A named Chart is a Workflow, with the name <wrapper>/<name>, and it runs under that wrapper only. It comes from one of two places:

  • A Chart file in the Assembly: workflows/<wrapper>/<name>.chart.json. jr2 up checks it at the door before it converges, and refuses a Chart file that the door refuses.
  • A runtime Chart: PUT /workflows/<wrapper>/charts/<name>, with the Instance token. The Orchestrator keeps it on its volume. DELETE on the same path removes it.

jr2 runs lists each named Chart under its wrapper. When a publish removes a part that a runtime Chart uses, the Chart stays, and it is unrunnable until a publish gives the part back.

A run keeps its Chart ​

The run keeps the text of its Chart in its snapshot. Thus a run continues when its named Chart changes or goes. After a restart, jr2 checks the text at the door again. A Chart that the door refuses then makes the run drifted.

The Console draws the Machine that the run binds, with its Chart in it. Thus you see the states of the Chart while it runs.

Rules ​

  • A Chart runs only under a Chart wrapper.
  • A Chart names only the parts that the Open body lends, its own Agents, and the four generic actions.
  • An Agent of a Chart has a model that the Open body allows, and a Workspace access that it allows.
  • A Reference starts at context, event or input, and something must supply it.
  • The door refuses the whole Chart when it finds one problem, and it lists each problem.
  • A Chart cannot go past a ceiling. The Open body then stops it.
  • Only a wrapper with a chartSchema field in its input takes a Chart in the run input, and keeps named Charts.
  • A Chart file and a runtime Chart with the same name: the Chart file wins, and the runtime Chart is unrunnable.

Let an Agent write a Chart and Run a Chart by name give the refusal text of each rule.

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