Skip to content

Reading the graph

llms.txtlists every page for an agent
colour means reads as
green managed chant declared it and the estate confirms it
amber foreign it is there, chant did not put it there
blue pending declared, not yet observed as deployed
grey neutral nothing deployed for this component yet

A node behold could not read is unobserved, reported as a hole and never painted as absent. “I did not look” and “it is gone” are different claims, and conflating them is how a viewer talks someone into deleting something.

Drift says whether the account matches the source. Health says whether a declared thing is serving, and behold draws it apart from the colours above. With an environment picked and chant 0.124.33 or newer, behold reads chant lifecycle health <env> for each chant project (each member of a workspace) and marks every card that has a health read: a Route 53 health check, an ECS service, a Container App and a Cloud Run service.

mark under the card means
green healthy the provider says it is serving
red unhealthy, with a solid red ring the provider says it is not
grey health unknown the read worked and the answer decides neither way, such as a service scaled to zero or a rollout in progress
health not read: <reason>, with a dashed grey ring chant could not read it (read-failed, no-credentials, no-binding, unsupported-kind)
grey not deployed the provider says it is not there

A health check chant could not read is a hole, the same as an unobserved card, and is never drawn as unhealthy. The card’s fill stays the drift colour, so a health check can be managed and unhealthy at once.

Rotation is derived from the graph. A Route 53 record whose HealthCheckId names a declared health check is out of rotation while that check is unhealthy: the record and its edges are dimmed and its line reads out of rotation: azureCheck unhealthy. A healthy check puts the record in rotation. When the check is unknown, not read or not deployed, the record’s line says its rotation is unknown and nothing is dimmed, since behold does not guess. Routing beyond that, such as failover pairs or alias targets, is not modelled.

The Model tab counts each state under the drift legend and lists the records out of rotation. Clicking a card shows the provider’s detail line, when it was checked and when chant read it, and for a health check, the records it guards. The health is read once per environment and kept for 30 seconds; “Re-check live” reads it again. Agents read the same answer from GET /api/health?env=<env>.

From widest to narrowest:

  1. Components. The deploy units and the waves they go out in.
  2. Logical. An architecture diagram in the shape the substrate actually has. AWS nests region → VPC → subnet; Azure nests under the resource group; GCP is project → location, with no network containment, because the emulated GCP surface has no VPC to draw.
  3. Composites. The higher-level constructs your source declares.
  4. Resources. Individual resources at their real provider names.
  5. Attributes. The properties on one resource.

On Kubernetes there is one tier further down, below anything you declared:

Runtime. The Pods, ReplicaSets and EndpointSlices the cluster created and maintains through ownerReferences. They are runtime children rather than drift: nothing declared them and nothing should. This tier is why a healthy-looking Deployment in front of a dead Service is visible rather than green.

The API server records, per field, which manager last wrote it. chant compiled the manifest, so it knows which fields it authored. Put together, the inspect pane can say these fields are chant’s, spec.replicas is the HPA’s, and someone edited the image by hand.

Foreign-owned is shown as a distinct state from drifted. A field the HPA legitimately owns is not a problem, and colouring it like drift would recreate the noise that per-field ownership exists to remove.

An estate that spans providers keeps its edges: a managed cluster declared in your cloud lexicon and the workload running on it render connected, in the live view and not only in the source graph.

A project that declares CI (the github, forgejo or gitlab lexicon, chant 0.124.20 or later) draws each workflow or pipeline as a box holding the workflow and its jobs. The box takes the workflow’s name: when it has one (CI Pipeline), and the entity’s key otherwise. A reusable-workflow call runs between two boxes.

Pipeline boxes appear on a project’s resources view, alone or in an estate. In an estate (behold serve a b, or a declared workspace) each member’s box holds its pipeline boxes, one level down, titled the same way without the member name the box around them already shows (release, not api/release). A pipeline goes in the member box that holds most of its cards. When the member boxes are packed into rows, or a wide member’s cards are laid out again, each pipeline box is drawn again around where its cards ended up, so it stays inside its member box and clear of the others.

The composites level has no pipeline boxes, because chant leaves pipeline membership off at detail 1 (chant#3921). When a graph carries deploy waves as well, the wave boxes are drawn instead.

A GitHub Actions, Forgejo or GitLab project draws its edges by the kind chant gives them (chant 0.124.20 or newer). A needs edge is a heavier solid line, calls and triggers have their own colours, and includes and extends are dotted. An edge that leaves its own workflow or pipeline, such as a call to a reusable workflow or a workflow_run trigger, is dashed. The chip on the edge says the kind, with the attribute when that adds something (needs (stage), calls (uses)), as chant’s Mermaid output does, and hovering the edge says what the kind means. The Model tab lists the kinds the graph holds, with a count for each.

Two nodes can be joined by more than one kind, such as a GitLab job that both needs and extends another. Each kind is its own edge, drawn as its own line a little apart from the other, with its own chip, tooltip and highlight.

Three cards carry provenance beside their status, so “is this what we meant to deploy” has an answer on the card rather than in a CI log.

  • A component that the release ledger recorded shows <sha> via <forge>, the commit and the id space of the run that deployed it. The inspect pane’s release section carries the run id, commit, digest, actor and approver. The run is a link only when the record itself carries an address (chant ≥ 0.54); a bare id from an older chant is stated as unlinked rather than guessed at.
  • A Helm chart whose observed release records its render identity (chant ≥ 0.54) gets a deployed from row in the render-diff section: the release was deployed from this project’s render byte for byte, from the same inputs, or from something else. A mismatch repaints the card the drift colour, since a release running someone else’s render is bad news whatever its status. A release no ledger record names reads as unrecorded rather than as a mismatch.
  • A chant entity carved out of Terraform carries carved: <address> and a carved → chant mark on the entity graph and the overlay: the carve manifest names the file it emitted, chant graph names the file that declared the entity, and the join is a byte comparison. The inspect pane’s carved from section gives the Terraform address, stage and estate.

On the ops lens, a step whose contract declares which of its arguments identify an estate entity (httpCheck and its url, chant ≥ 0.54) links to the card it touches: the card is drawn beside the track in an estate box with a dashed edge from the step, and the pane lists the value on both ends. The join is exact: a node id, or a declared attribute value equal to the step’s. A value nothing carries stays on the step as unresolved rather than guessed.

behold