# Authoring Graphs Start from the stable work model, not from a list of commands: 1. Identify the Facts the host can actually observe. 2. Define the legal Actions and their effects. 3. State what proves each Action complete. 4. Add user Gates where authority matters. 5. Make failure, partial work, verification and recovery explicit. 6. Attach long instructions only to the Routes that need them. 7. Write graph tests for the important states and choices. 8. Bind one or more thin Skills to the Graph and Entry. Do not turn every paragraph of an old Skill into a node. A node is a state transition that is independently actionable, gated, recoverable or testable; supporting explanation stays a Resource. Module names, document IDs, dates and queue entries stay Facts — a stable Host Action resolves the current target instead of the Provider generating a node per item. ## Project layout Directory names are conventions, not protocol. All references are relative to the Provider manifest, and the loader rejects any path that escapes the Provider root. ```text provider.yaml Provider ID, version, Graph list, optional Code Catalog graphs/ Graph definitions actions/ Action contracts skills/ Agent Skills used by agent Actions resources/ Procedures, templates, schemas, dynamic view descriptors schemas/ Action input and output schemas tests/ Graph tests (not shipped in the bundle) ``` ## Worked example: a weekly report The example drafts a report, asks for approval, publishes it and checks the publication when the publish step claims success without evidence. It validates and its seven tests pass with `@c4a/agent-graph@0.3.0`. ```mermaid flowchart LR draft["draft (agent)"] -->|"completed"| approve["approve (gate)"] draft -->|"partial: repeat"| draft approve -->|"gatedBy"| publish["publish (command, external)"] publish -->|"completed"| done["done (terminal)"] publish -->|"unverified"| check["check-publication (host, read)"] check -->|"completed"| done ``` ```yaml schema: agent-graph.graph.v1 id: report entrypoints: default: draft nodes: - id: draft kind: action reasonCode: route.report.draft action: actions/draft.yaml resources: required: [resources/procedure.md] satisfiedBy: - path: report.digest exists: true - id: approve kind: gate reasonCode: route.report.approval-required gate: id: report-approval prompt: Publish this report? authority: report.publish delegatable: true satisfiedBy: - path: report.approved equals: true - id: publish kind: action reasonCode: route.report.publish action: actions/publish.yaml satisfiedBy: - path: publication.url exists: true - id: check-publication kind: action reasonCode: route.report.check-publication action: actions/check-publication.yaml - id: done kind: terminal join: any terminalOutcome: completed edges: - { from: draft, to: draft, kind: repeat, outcomes: [partial] } - { from: draft, to: approve } - { from: approve, to: publish, kind: gatedBy } - { from: publish, to: done } - { from: publish, to: check-publication, outcomes: [unverified] } - { from: check-publication, to: done } ``` Every Action and Gate has a `reasonCode` beginning with `route.`; because the Provider declares a Code Catalog, each of those codes must also appear in `codes.yaml`, or validation fails with `route-reason-code-missing`. `done` uses `join: any` because two alternative paths reach it. ## Actions ```yaml # actions/publish.yaml schema: agent-graph.action.v1 id: publish runner: command effect: external command: ./scripts/publish-report.sh ``` | Runner | Required field | What the Route returns | | --- | --- | --- | | `command` | `command` | The exact shell command | | `script` | `entry`, `runtime`, optional `args` | The runtime and the absolute installed entry path | | `agent` | `skill` | The Skill as a required file resource | | `host` | `handler` | A host-defined handler identity | Runner fields are mutually exclusive, so a host cannot read the same Action two ways. `effect` is mandatory: `read` (should not change lifecycle Facts), `write` (local mutation) or `external` (outside state may change — normally behind a Gate). `cwd` is `workspace` by default or `provider`; `inputSchema`, `outputSchema` and `files` are packaged with the Action. Agent Graph resolves Actions but does not execute them automatically; the host does, then refreshes Facts and evaluates again. ## Facts, not history `satisfiedBy` is how a node is proven complete. Checks are a restricted data model: a dotted `path` (numeric segments index arrays, as in `artifacts.0.digest`) and exactly one of `exists` or `equals` with a scalar value. There is no expression language. - Matching Facts complete a node even with no Run record — the example's `draft` is skipped when `report.digest` already exists. - A recorded `completed` without matching Facts becomes `unverified`; the example routes that to `check-publication`. - `requiresFacts` makes a node legal only under current external state; use it, not `priority`, to decide which option applies. When evidence has a lifecycle, keep it in ordinary nested Facts such as `{digest, sourceRevision, available, fresh}` and check only the fields that make it trustworthy. Every Fact feeds the Evaluation revision, so the host should publish stable states such as `fresh: false` after applying its own clock or lease — not a constantly changing poll timestamp. Keep `missing`, `unavailable`, `stale` and an explicit `false` distinct when they need different recovery Routes. To stop a producer from verifying its own work, add a separate `effect: read` host Action whose `satisfiedBy` requires a verification receipt; the host binds it to an independently authorized verifier. ## Edges and loops An edge matches explicit Outcomes; omitting `outcomes` means `[completed]`. The edge kind records intent and does not change reachability: | Kind | Use it when | | --- | --- | | `flow` | An ordinary transition (the default) | | `consumes` | The target consumes something the source produced | | `requires` | The source is a prerequisite of the target | | `gatedBy` | A Gate resolves the target's policy boundary; must start at a Gate | | `repeat` | A matching Outcome rearms an Action or Gate | Edges carry no data. Persist outputs through the host and publish references, digests or receipts as Facts. Ordinary edges must be acyclic: adding `publish → draft` on `failed` to the example is rejected with `graph-flow-cycle` and the message "use a repeat edge". A `repeat` edge is the explicit loop — the Agent does one round, records `partial`, evaluates again and gets the same Action until it records something else. Fan-out and fan-in describe reachability only; nothing runs in parallel. ## Gates and delegation Without the named authority, a Gate Route is `waiting-user` with an empty `commandPlan`. With a `delegatable` authority in the current input or Run, it becomes actionable with `resolution: session-authority`. Authority never satisfies Facts; in the example, `report.approved` must still be observed before `publish` becomes legal. A Gate can add three optional contracts: ```yaml inspectionAction: actions/inspect-review.yaml # effect: read; prepares evidence, safe before confirmation resolutionAction: actions/apply-review.yaml # effect: write or external; persists the decision after confirmation delegated: inspection: skip # only with a delegatable authority and an inspection Action resolutionAction: actions/accept-review.yaml # replaces resolution on the delegated Route only resources: { required: [], recommended: [] } # replaces the Gate's resources on the delegated Route ``` Keep the Gate prompt short and put structured decision input on the resolution Action's `inputSchema`. A host must not run the resolution while the Route still requires the user. The repository's `examples/review-gate` shows both the waiting and delegated cases. ## Resources Put long instructions in Markdown with frontmatter, and declare them on the node that needs them: ```yaml --- id: procedure.prepare kind: procedure media-type: text/markdown --- ``` Non-Markdown files such as JSON Schemas use an `agent-graph.resource.file.v1` descriptor with `id`, `kind`, `mediaType` and `path`. Dynamic context views use `agent-graph.resource.graph-action.v1` with a read-only command or script materializer, and host-produced content uses `agent-graph.resource.host-action.v1`. An agent Action's Skill is loaded only at the Action that uses it; a Gate's inspection or resolution Skill stays on that nested Action until that phase is selected. Details are in [Skills and Providers](). ## Subgraphs and ranking A `subgraph` node calls a named entry of another Graph in the same Provider. The child Route keeps its own Graph and node identity plus the parent call path, and the child's terminal Outcome flows back to the parent edge. Recursion into an ancestor is rejected statically and at runtime, and cross-Provider Subgraphs are not supported in version 1. When several Routes are legal at once, `priority` ranks them within an availability class; it never decides which option is semantically right. Every Graph needs at least one Terminal, and reaching Terminals with conflicting Outcomes is an error (`terminal-outcome-ambiguous`), never resolved by declaration order. Next, pin each of these behaviours with a test: [Testing and Publishing](). --- ## Sources - [agent-graph/docs/en/adoption-paths.md L29–L44 · 9e8b98a]() - [agent-graph/docs/en/adoption-paths.md L17–L21 · 9e8b98a]() - [agent-graph/docs/en/authoring.md L1–L17 · 9e8b98a]() - [agent-graph/docs/en/specification.md L63–L82 · 9e8b98a]() - [agent-graph/docs/en/authoring.md L19–L30 · 9e8b98a]() - [agent-graph/examples/facts-recovery/graphs/build.yaml L1–L30 · 9e8b98a]() - [agent-graph/docs/en/specification.md L172–L202 · 9e8b98a]() - [agent-graph/docs/en/specification.md L84–L105 · 9e8b98a]() - [agent-graph/docs/en/authoring.md L59–L96 · 9e8b98a]() - [agent-graph/docs/en/specification.md L137–L164 · 9e8b98a]() - [agent-graph/docs/en/authoring.md L34–L57 · 9e8b98a]() - [agent-graph/docs/en/specification.md L107–L135 · 9e8b98a]() - [agent-graph/docs/en/authoring.md L98–L122 · 9e8b98a]() - [agent-graph/examples/review-gate/graphs/review.yaml L1–L35 · 9e8b98a]() - [agent-graph/docs/en/authoring.md L124–L154 · 9e8b98a]() - [agent-graph/docs/en/authoring.md L32–L32 · 9e8b98a]() - [agent-graph/docs/en/specification.md L166–L170 · 9e8b98a]() - [agent-graph/docs/en/specification.md L56–L61 · 9e8b98a]() - [agent-graph/docs/en/authoring.md L156–L158 · 9e8b98a]()