plan

package
v0.6.1-rc.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 4, 2026 License: Apache-2.0 Imports: 28 Imported by: 0

Documentation

Overview

Package plan builds and revises the task graph for a goal. It executes nothing; the graph is the product.

The graph is generated through a spine of ordered stages and then immediately freed from it. Stages exist for two reasons, neither of which is scheduling. They keep generation cheap, because a stage can be fanned out knowing only the spine, so every stage expands at the same time. And they keep the result acyclic almost for free, because a generated dependency may only point at an earlier stage — or, in the single case where one part changes the material its siblings work on, at another node of the same stage, which is the one edge that is cycle-checked. Once the real edges are known, stage membership gates nothing: a node that needs no input starts immediately, whichever stage produced it.

Four passes, each a single round no matter how large the graph gets:

spine    1 call     ordered stages — the only serial call in the system;
                    each stage names the earlier stages it consumes, and
                    stages that name nothing share a level (levels.go)
fan-out  S calls    every stage split into simultaneous parts, at once
bind     ≤S calls   what each node reads or waits behind, and what duplicates what
audit    S-1 calls  what each node is missing — the counterweight to bind

Bind and audit are deliberately opposed. Bind is written to resist the model's habit of turning any plan into a chain, so it under-connects; audit asks the opposite question and puts back only the edges whose absence would leave a node unable to finish. Neither framing is trustworthy alone.

The positive stopping condition, asked as a question.

Growth used to be bounded only negatively: rounds spent, nodes spliced, dollars burned. Every one of those answers "have we done too much", and none of them answers "is there anything left to do" — which is why a job could spend three legitimate rounds inventing verification of the round before it and be refused only by arithmetic, long after the money was gone.

A criterion makes the positive question askable. Given what the finished job is judged against, what has already landed, and what is in flight together with what each piece is committed to producing, a reader can say whether every condition is covered. That reader is this call.

A task spec is an object, authored once and carried forward.

It used to be prose authored per-scale and re-authored on every retry, which is how a replacement node lost the module name, the filename and the acceptance check its predecessor had been given: the retry path did not re-target a spec, it wrote a new one from failure context, and prose has no field a rewrite can be forbidden from touching. An object does. Instruction and Method may be re-aimed; Done travels verbatim, because the criterion the work is judged against did not change when the worker did.

Nothing here is specific to a kind of work. Kind is "run" or "read" and that is the only structural distinction the spec makes: a condition is either settled by executing something and reading the outcome, or by reading the artifact and finding something present. A prose deliverable's conditions are read conditions, a buildable one's are run conditions, and the orchestrator never inspects which.

Index

Constants

View Source
const (
	PointBehaviour = "behaviour"
	PointAction    = "action"
)

The two kinds a point can be, and the whole reason the field exists.

A BEHAVIOUR IS OF THE FINISHED WORK; AN ACTION IS OF THE RUN. A check can be written for the first and never for the second, and the gate's coverage question — which check would fail if this were absent or wrong — is only askable of something a check could exist for.

The errand that measured this asked for one command to be run and its final line reported, changing no files. Its two points came back as "the command … is run in this workspace" and "the final line it prints is reported", and the gate asked which repository test exercises them. None can, by construction. The run spent more than 97% of its money and 82% of its wall answering a question that had no answer (2026-09-02, deepseek-v4-flash, twice out of two).

View Source
const (
	ConstraintNoWrites  = "no_writes"
	ConstraintPathsOnly = "paths_only"
	ConstraintOther     = "other"
)

The three readings a constraint can carry.

Two of them are mechanical because two of them are answerable from the workspace's own before-and-after list, which the run already takes for other reasons: whether anything changed, and whether what changed is inside a named set. Everything else — do not use the network, do not delete, keep it under two hundred words — is ConstraintOther and is judged by a reader, because arithmetic over a file list cannot settle it and a mechanism that pretended otherwise would fail deliveries for rules it had misread.

View Source
const (
	// EnsembleAuto lets the planner judge, once, from the goal.
	EnsembleAuto = 0

	// EnsembleNever skips the judgment call entirely. It is the setting for a
	// caller that knows its goals are separable work and does not want to pay a
	// call to be told so.
	EnsembleNever = -1

	// DefaultPanelists is three. Two cannot break a tie and gives the merge no
	// way to tell a rare catch from a lone mistake; beyond three the marginal
	// finding per pass falls off well before the cost does.
	DefaultPanelists = 3
)
View Source
const (
	// RefusalUnnamed is the ordinary one and the one the whole burden is built
	// around: nobody could name two pieces, so there is no split to weigh.
	RefusalUnnamed = "no two pieces could be named for it"
	// RefusalWithinReach is the null hypothesis holding: the node is already
	// inside what one worker carries, so division buys no wait it does not
	// already have and costs a briefing and a reassembly it does not pay now.
	RefusalWithinReach = "it is already within one worker's reach"
	// RefusalDepth and RefusalNoRoom are the two arithmetic refusals. They are
	// not judgments about the work and say so, so that a reader does not read
	// the graph's shape as a reading of the material when it was a ceiling.
	RefusalDepth  = "the depth ceiling was reached before it was weighed"
	RefusalNoRoom = "no room was left to carry its pieces"
	// RefusalSameAnswer is the information-gain rule failing: the pieces came
	// back returning the same thing, which is one answer bought twice.
	RefusalSameAnswer = "its pieces would each return the same thing"
	// RefusalOnePiece and RefusalNoSmaller are the restatement failures — a
	// division that gave back the node in different words, or in the same size.
	RefusalOnePiece  = "the division gave back one piece, which is the node again"
	RefusalNoSmaller = "its pieces came back no smaller than it is"
	// RefusalBeyondReach is the measurement, and it is the only one of these
	// that is not a judgment at all. The node names material larger than one
	// worker's window, so it cannot be brought to an end in one sitting — and
	// nobody could name two pieces to divide it into, so it is being handed over
	// whole anyway. It is journaled because a leaf in that state is the exact
	// shape a run fails in, and a person reading the plan afterwards is owed the
	// sentence rather than the symptom.
	RefusalBeyondReach = "its named material exceeds what one worker holds"
	// RefusalNotPaying is the EV-lookahead's verdict: the node could divide,
	// but the measured history says its parts do not buy back the fixed cost a
	// second worker pays before it produces. It is a measured refusal, not a
	// structural one.
	RefusalNotPaying = "the measured history says the split does not pay for itself"
)

The refusals, in the words they are journaled in. They are constants for the same reason the scale gate's routes are: a reader counting them across a week of runs must not have to guess whether two spellings mean one thing.

Each names a way the burden of proof went unmet, and none of them names a domain — the judgment is about the shape of work and never about what the work is about.

View Source
const (
	CheckRun  = "run"
	CheckRead = "read"
)

Check kinds. Two, because there are two ways a person holding a result can settle a question about it, and no third that is not one of these wearing a domain's clothes.

View Source
const ConstraintsHeading = "Rules the person set, never broken"

ConstraintsHeading is what a leaf reads above its own assignment. It is a constant because the leaf's brief, the repair round's input and the spec's own render all write it, and three spellings of one law is three laws.

View Source
const ConstraintsOutrankHeading = "Rules the person set, which outrank anything below"

ConstraintsOutrankHeading is the same list said to a worker that is holding somebody else's finding as well: a repair round is handed a reviewer's gap and told to close it, and the one thing it may not do is close it by breaking a rule. The heading says which of the two wins before the worker reads either.

View Source
const DeliverInMessage = `` /* 1145-byte string literal not displayed */

DeliverInMessage is the default shape, and it is the one that holds unless the ask itself is file-shaped.

It states the split rather than a length, which is what lets it agree with the leaf's own budget instead of fighting it: the answer is never what gets filed, and the working always may be.

View Source
const DeliverToNamedFile = `` /* 401-byte string literal not displayed */

DeliverToNamedFile is the carve-out, and it is the half nothing but the gate used to know.

The wording follows the gate's own, because the gate is what judges the result and a worker held to a different sentence than the one it will be judged by is being set up to fail. Short is not thin here: a message beside an asked-for file is the correct shape, and its length convicts nothing.

View Source
const LinearSubharness = "linear"

sizePrompt asks where a node sits against the anchors, and states the trade-off in the currency that actually applies. The economics matter as much as the ruler: told only "is this the right size", a model optimises for tidiness, but told what splitting costs and what not splitting costs, it optimises for the thing we care about. This is the same lever that made the spine stop inventing stages.

LinearSubharness names the worker. It is written here rather than imported because the ruler is a fact about the worker, and the package that describes the worker is the one that already imports this one; a constant repeated in two packages is cheaper than a cycle.

It is a name, and that is load-bearing. The generalist used to be spelled as the empty string, which made "this node was judged and the answer is the generalist" indistinguishable from "nobody judged this node" — and every reader that fills an unanswered question in from somewhere else, admission's inheritance above all, read the first as the second. A verdict that was made says so out loud; empty is reserved for the verdict nobody made.

View Source
const MaxConditions = 6

MaxConditions caps a criterion's condition count.

The cap is not tidiness. A weaker model handed "state the criterion" will happily produce twelve conditions, and every condition it invents becomes a requirement nobody made — so the ceiling is the structural half of the prompt clause that forbids inventing them.

Variables

View Source
var Criterion = true

Criterion is the wave's rollback switch. On, the brief call returns an instruction and a done-criterion together. Off, it returns today's prose brief and every spec's Done stays empty — which every reader downstream already handles, because an absent criterion has always been legal.

Functions

func Acceptance

func Acceptance(ctx context.Context, client Completer, request string) ([]Point, Usage, error)

Acceptance reads a request and returns the behaviours it states.

It is one call, on the request alone, and it is deliberately not folded into the brief or the criterion pass: those two are written for the WORKER and this is written for the gate, and a checklist assembled in the same breath as the instruction is a checklist the instruction has already seen.

An empty list is a legitimate reading. A request that states no checkable behaviour — a question, a lookup, a piece of prose — has no acceptance checklist, and everything downstream of this behaves exactly as it did before this existed. A CAPABILITY THAT CANNOT WORK IS ABSENT, NOT BROKEN.

func Anchors

func Anchors() string

Anchors returns one stable snapshot of the ruler in force.

func AppendCapacityEvidence

func AppendCapacityEvidence(invoice string, evidence CapacityEvidence) string

AppendCapacityEvidence extends an already-rendered invoice with measured capacity, or starts the same measured block when prices have not accumulated yet. An empty observation returns the input byte for byte, which keeps cold start and non-swarm prompts unchanged.

func ComposeSkills

func ComposeSkills(pinned, candidates []string) []string

ComposeSkills builds an ordered list of skill names from pinned names and retrieval candidates, preserving input order. Pinned names come first (in the order given); non-pinned candidates follow in theirs. Duplicates are collapsed to the first occurrence, so a pinned entry always wins over a retrieved one of the same name.

func ComposedBrief

func ComposedBrief(node Node) string

ComposedBrief is a node's instruction written from what the plan already knows about it, for the nodes no model wrote one for.

Two roads reach this and they have to arrive at the same words. A graph planned with briefs turned off is dispatched carrying none, and the scheduler composes one at the moment it hands the job over; a brief call that would not answer twice is composed for here, while the plan is still being built. A second composition written for the second road would be a second answer to "what does a node say when nobody wrote its instruction", which is precisely the drift the one-source-of-truth law is about.

The goal is deliberately absent from it. Every leaf is already handed the goal beside its brief — "This work is part of a larger goal" — and a composition that repeated it would put the same paragraph into one prompt twice.

func ConstraintLines

func ConstraintLines(constraints []Constraint) []string

ConstraintLines is the rules as a person reads them: one line per rule, the text verbatim, and nothing added. Every renderer that shows constraints — the spec, the leaf's brief, the repair round's input — writes these lines, so there is one wording of the law and no surface can paraphrase it.

func ConstraintsBlock

func ConstraintsBlock(heading string, constraints []Constraint) string

ConstraintsBlock is those lines under a heading, or the empty string when there are no rules. Empty renders as nothing at all, which is the emptiness law and also the whole compatibility story: a job whose request stated no rule sends exactly the bytes it sent before this existed.

func DeliveryLaw

func DeliveryLaw(fileShaped bool) string

DeliveryLaw returns the half of the law that applies to an ask of this shape.

It is exported because the bit is not the planner's to compute. Whoever holds the request holds the judgment — the chat session already makes it in the delivery gate, and `codeaf run` can make it from the goal — and everything below this line only needs the answer. See Graph.FileShaped for the wiring.

func Enumerated

func Enumerated(settled []Settlement) bool

Enumerated reports that some variable was bound to more than one value — that the material now carries an enumeration the work is expected to follow. It is the reference the fan-out's acceptance check is keyed on: a stage split under an enumeration owes one unit per part, and a stage split under none owes nothing of the kind.

func ExpandOne

func ExpandOne(ctx context.Context, client Completer, graph *Graph, nodeID int, options Options, claim ClaimContext) (*Graph, Usage, error)

ExpandOne is one node's decomposition, asked for by a caller that is not a build level.

The build asks this question of every candidate at once, at t=0, against the titles of work that has not started. A scheduler asks it of one node, at the moment that node is claimed, when its dependencies have landed and can be read — which is the same two calls against a strictly better picture. What comes back is a sub-graph and nothing else: the caller decides whether to keep it (WorthKeeping), where to put it (Graph.Splice), and what a refusal is called (JournalRefusal), because those are the three things a claim-time caller must do differently from a level loop and the only three.

func FanOut

func FanOut(ctx context.Context, client Completer, premise string, stages []Stage) ([]Node, Usage, error)

FanOut expands every stage at once, with no enumeration in force. It is the call a caller with no grounding to hand over makes.

func FanOutWith

func FanOutWith(ctx context.Context, client Completer, premise string, stages []Stage, settled []Settlement) ([]Node, Usage, error)

FanOutWith expands every stage at once. Each call carries the same frozen prefix — goal plus the whole spine — so the varying part is a single line, which is both the cheapest shape to generate and the friendliest to a prefix cache.

The settlements travel with it because the acceptance check below is keyed on them and on nothing else. A stage split under a bound enumeration owes one unit per part; a stage split under none owes nothing of the kind, and the check does not run. The list is read, never rendered — the premise already carries the settled block, and re-rendering it here would move the prefix.

func Folds

func Folds(node *Node) bool

Folds reports whether this node's whole obligation is to assemble what already fed it.

The inbound count is taken from the plan document here. The dispatcher asks the same question of the durable graph at claim time, where it can also see that every input actually arrived whole; both must agree before a node is run as a fold, and this half is the half that is a fact about the plan.

func GeneralistSubharness

func GeneralistSubharness(name string) bool

GeneralistSubharness reports whether a name is the worker, named. It is not the same question as "is this column filled in": a node whose worker column says "linear" was judged and answered, and an empty one was never asked. Both run the same worker, and only the second may be filled in from elsewhere.

func Ground

func Ground(ctx context.Context, client Completer, goal string) (Grounding, Usage, error)

Ground resolves the goal's free variables. It runs concurrently with the spine — both need only the goal — so it costs no wall clock, and its output joins the prefix every later call already shares, so it costs no cache either.

func GroundWith

func GroundWith(ctx context.Context, client Completer, goal, terrain string, asked []string, named string, recall []store.RecallHit) (Grounding, Usage, error)

GroundWith resolves the goal with the workspace it stands on and optional folded history. Keeping Ground as the bare wrapper is the compatibility boundary: callers with neither send exactly the same prompt bytes they did before either existed.

The terrain matters more here than anywhere else in the package. This pass is the one that binds a goal's free variables by fiat — which three cities, which formats, how many of them — and it was doing so without ever being shown that the answer was sitting in the workspace under four file names. A grounding that settles "the responses" as three regions when four are on disk is the exact failure terrain exists to prevent, and it happens before any graph exists, so the terrain is handed in directly rather than read off one. The usage is the pass's rather than one response's, because a reply that bound nothing buys one free retry and both calls are the plan's to pay for. The measurement of what the goal names travels beside the terrain because it is a reading of the same disk, and every pass that renders the shared block renders the same one. Empty measures nothing and leaves the prompt as it was.

func JournalRefusal

func JournalRefusal(node *Node, reason string)

JournalRefusal writes why a node was left whole, on the node. It is diagnosis and never control: nothing reads the field back to decide anything, and a refusal recorded on a node that is later divided anyway is cleared at the splice, so the field always describes the shape the graph actually has.

Frozen nodes are left alone. Their shape is settled and something else owns them now, so a note written onto them would be both useless and a write into another party's node.

func PinnedSkills

func PinnedSkills(text string, skills []store.Fact) []string

PinnedSkills returns the skills the person's own words name outright: every shelf skill whose name appears in the text. It is simple name-in-text matching — deterministic, no model call — because a person naming a skill is the strongest relevance signal there is, and it is read off the goal, which is the person's proposal in whatever words they used.

func RenderInvoice

func RenderInvoice(invoices ...Invoice) string

RenderInvoice lays out the price lists that have prices, or returns the empty string when none of them do.

The empty string is the whole compatibility story: a fresh machine, a fresh worker, or a profile below the evidence floor renders nothing, every prompt below sends exactly the bytes it has always sent, and no call anywhere costs a token more than it did.

func RenderSkillsBlock

func RenderSkillsBlock(skills []SkillEntry) string

RenderSkillsBlock renders attached skills as doc lines and shelf paths. Each skill produces one line: "- <doc> [<path>]" when both exist, or a shorter form when only one is available; an agentskills folder's line adds "— body in this file" inside the brackets so a worker told to read the path knows it is holding the skill itself. Zero entries returns zero bytes — no header, no placeholder, no blank line. The final line states that earlier-listed skills take precedence in case of conflict.

This renders beside the composition above because the two are one path: the brief pass composes the attachment and the executor renders it into the instruction, and the executor cannot reach a package that itself imports the subharness. A render the worker prompt cannot call is a render that never runs.

func RenderTerrain

func RenderTerrain(dir, goal string) string

RenderTerrain draws what a workspace holds, for the planner to stand on.

It is pure code: no model, no network, and no failure it can pass to its caller. Anything that goes wrong — a missing directory, an unreadable child, no git, no material at all — renders as fewer lines or as the empty string, because a caller with nothing to say about its workspace must produce prompts byte-identical to the ones it produced before terrain existed.

The goal is read only as cues. Directories whose names the goal already says out loud are opened one level further, which is the whole of the "relevance" judgment here — deterministic string matching, so the same goal and the same workspace always draw the same picture.

func RetrieveSkills

func RetrieveSkills(text, workspace string, skills []store.Fact) []string

RetrieveSkills returns the skills retrieval would attach to one leaf: those whose scope or doc line cues against the leaf's own territory — its rendered instruction, and the workspace it runs in. The shape is the chat catalog's window scorer (skillcatalog.go): a scope naming something in front of the leaf outweighs anything, a shared doc word is the weaker cue. One shared word is coincidence — "the" shares with every instruction there is — so only scores a real cue produces come back, most relevant first, capped.

func Revise

func Revise(ctx context.Context, client Completer, graph *Graph, event string) ([]Operation, Usage, error)

Revise asks the sentinel whether the unstarted part of the graph should change in light of an event, and applies whatever it returns that the graph will legally accept.

func RulesAbove

func RulesAbove(constraints []Constraint, body string) string

RulesAbove puts the rules in front of whatever a worker was going to be told, which is the only placement that means anything: a rule read after the assignment it governs is a rule the assignment has already argued with. Returns body unchanged when there are no rules.

func RulesOutranking

func RulesOutranking(constraints []Constraint, body string) string

RulesOutranking is the same placement for the one text where the worker is holding two orders at once: a repair round is handed a reviewer's gap and told to close it, and the heading says which of the two wins before it has read either. Returns body unchanged when there are no rules.

func RunID

func RunID(goal string) string

RunID is a stable identifier for a goal, used to name a run's workspace so that re-running the same goal lands in the same directory.

func Satisfied

func Satisfied(ctx context.Context, client Completer, contextTokens int, criterion Done, landed []Landed, inflight []Spec) (Satisfaction, Usage, error)

Satisfied asks whether a criterion is already covered by work that exists.

The message order is the cache shape and is deliberate: the static prompt, then the criterion — fixed for the job's lifetime — then the landed table in admission order, which is append-only so the prefix only ever grows at the tail, and last the in-flight commitments, which are the only part that changes when a job's shape does. This is the one recurring call the growth governor makes, and it is the most cache-friendly shape available to it.

An empty criterion is not a question: nothing is known about what done means, so nothing can be said about whether it is reached, and the caller is told the job is not complete — which admits growth, the direction that cannot truncate. contextTokens is the window of the client being asked; zero is unknown and clips the two tables exactly where they were always clipped.

func SettledLines

func SettledLines(settled []Settlement) []string

SettledLines renders a settlement list the way every prompt in this package reads it. It is the one conversion, so a caller that wants the settled points as text cannot spell them differently from the preamble every planning call shares.

func Spine

func Spine(ctx context.Context, client Completer, goal, terrain string, asked []string, named Measurement, samples int) (*SpineChoice, Usage, error)

Spine runs the planner's one serial call, several times at once, and keeps the most typical answer.

This is a variance fix, not a quality fix. The spine decides the stage count and the framing that every later pass inherits, so a single unlucky sample does not degrade the graph a little — it changes the graph entirely. Measured on one fixed goal, single-sample runs produced 0, 9, 15 and 23 edges, and the 0-edge run came from an outlier spine that no later pass could recover from.

Sampling is close to free here in the only currency that matters. The calls run concurrently, so the wall clock is one call however many we take, and the spine is the cheapest call in the system — three samples cost a fraction of a cent. Selection is done in code rather than by a judge call precisely to keep it that way: a judge would add a serial round to the one path that has no other serial work to hide behind.

The terrain is handed in beside the goal because the stage count is a judgment about the work, and what is already on disk is half of that judgment: a goal whose first stage is "gather the responses" is one stage shorter when the responses are sitting in the workspace already. Like grounding, this runs before there is a graph to read a preamble from, so it takes the snapshot directly. Empty leaves the prompt exactly as it was. The requests the ask was read as containing travel beside the terrain and for the same reason. The stage count is a judgment about the work, and how the person divided their own ask is part of that judgment: several requests that do not feed each other are one stage, and one request written over what the others produce is the second. Nothing here decides which; the block says what was asked and the model reads it. Empty leaves the prompt exactly as it was. The measurement of what the goal names is the third thing handed in, and it is the only one of the three the spine is not asked to judge. Gate 3 above used to ask this call whether the work was too large for one worker to hold — a question about a number lying on the disk, put to a model that cannot see it. Now the number is read and stated, and where it says the named material is larger than one worker's reach a sample answering "one stage" is set aside before the vote rather than argued with inside it. See reach.go and admissible.

func SubharnessChosen

func SubharnessChosen(name string) bool

SubharnessChosen reports whether the column was written at all. Only the empty string means nothing was, and only then may a reader fill it in.

func UseAnchors

func UseAnchors(anchors string)

UseAnchors installs a calibrated ruler. An empty string restores the built-in prior, which is the right fallback whenever a profile is missing or unreadable.

func WorthKeeping

func WorthKeeping(sub *Graph) (bool, string)

WorthKeeping is the acceptance check as a claim-time caller needs it: over a sub-graph alone, because that is all a caller holding one expansion has. The level loop's own call goes through it unchanged.

Types

type BriefJournal

type BriefJournal func(graph *Graph, nodeID int, brief store.NodeBrief)

BriefJournal writes one node's rendered brief as a first-class, queryable event, when the build has a durable home to journal to. The plan package knows the plan node id and the rendered brief; it does not know the store id the node was minted under (that spelling is the caller's — see resident.PlanStoreIDs), so the callback receives the graph and the plan node id and the caller forms the store id. It is best-effort for the same reason RecordPlanGraph is: losing it costs an audit and never the plan.

type CapacityEvidence

type CapacityEvidence struct {
	Worker   string
	Runs     int
	Overruns int
	Rate     float64
}

CapacityEvidence is the measured failure rate that rides beside prices. It belongs in the invoice because it answers the same economic question: what happened here when work was kept as one worker's job.

type Check

type Check struct {
	// Kind is "run" or "read". Universal across harnesses: a condition is
	// either settled by executing something and reading an outcome, or by
	// reading the artifact and finding something present.
	Kind   string `json:"kind"`
	Check  string `json:"check"`
	Expect string `json:"expect"`
}

Check is one condition of a done-criterion.

Every field is required for the check to be settleable by someone who has the result in front of them and did not do the work — which is the whole test of a criterion. Check says what to run or what to look for; Expect says what its outcome must be, or what would make it absent.

type ClaimContext

type ClaimContext struct {
	// Landed is what each finished dependency produced, in admission order.
	Landed []Landed
	// Criterion is what must be true of this node when it is finished.
	Criterion Done
}

ClaimContext is what a node's expansion is worth more for having, and what only exists once the node is claimed rather than merely planned.

At build time a node is decomposed against the *titles* of the work feeding it, because nothing has run yet. At claim time every one of those pieces has landed and said what it produced, so the same question can be asked against what is really there. The criterion travels with it for the other half of the same correction: a division whose parts do not together cover what the node is judged on is a division that loses the node's own answer.

type Completer

type Completer interface {
	CompleteWithMessages(ctx context.Context, messages []ai.Message, options ...ai.Option) (*ai.Response, error)
}

Completer is the slice of the provider adapter this package needs. Depending on the method rather than the concrete client keeps the prompts testable without a network.

type Constraint

type Constraint struct {
	// Text is the person's own words for this rule, verbatim. It is what the
	// leaf is shown, what the gate quotes when it refuses, and the only thing
	// anybody is ever held to.
	Text string `json:"text"`
	// Kind is the mechanical reading: one of the three constants below. An
	// unrecognised kind normalizes to ConstraintOther, which is the weaker
	// claim — a rule nothing mechanical settles is still a rule the leaf reads
	// and the judge is shown.
	Kind string `json:"kind"`
	// Paths are the places a ConstraintPathsOnly rule allows, in the person's
	// own spelling, cleaned to workspace-relative slash paths. Empty on every
	// other kind.
	Paths []string `json:"paths,omitempty"`
}

Constraint is one such rule: the person's words, the mechanical reading of them, and the paths that reading is about when it has any.

func NormalizeConstraints

func NormalizeConstraints(constraints []Constraint) []Constraint

NormalizeConstraints bounds and cleans what a model returned, on the same terms NormalizeDone and NormalizeAcceptance use: a rule that cannot be read is dropped rather than refused, and an unrecognised reading falls back to the weaker one.

A ConstraintPathsOnly rule whose path list is empty after cleaning becomes ConstraintOther, and that is the fail-safe direction rather than tidiness: "only these paths" with no paths reads mechanically as "no paths at all", which would fail every delivery of a job whose model wrote the kind and forgot the list. The rule is still shown to the leaf and to the judge in the person's own words; only the arithmetic is declined.

type ContractPlaybook

type ContractPlaybook func(Node) string

ContractPlaybook returns the earned method bullets relevant to one leaf. Nil and empty results preserve the fixed contract prompt byte for byte.

type Done

type Done struct {
	// Produces names the identifiable outputs by the names they will carry, so
	// a reader holding only the criterion can tell whether they exist.
	Produces   []string `json:"produces,omitempty"`
	Conditions []Check  `json:"conditions,omitempty"`
}

Done is the positive stopping condition: what must be true once the work has landed, as distinct from the steps that get there.

func NormalizeDone

func NormalizeDone(done Done) Done

NormalizeDone bounds and cleans what a model returned. A criterion is only worth carrying if a reader can settle it, so a condition with no check is dropped, an unrecognised kind falls back to read — the weaker claim — and the list is capped.

func (Done) Empty

func (d Done) Empty() bool

Empty reports whether this criterion says anything at all. An absent criterion is legal everywhere and is what every path did before criteria existed.

func (Done) Sentence

func (d Done) Sentence() string

Sentence renders the criterion as the one sufficiency statement a reader holding only the result can test: the produces line and every condition, in one line. It is the form that is journaled as a first-class event per node (see internal/store/planjournal.go), so a run's stopping condition is falsifiable from its own artifacts rather than only as a field inside the plan blob. Empty when the criterion says nothing, which is legal everywhere.

type Graph

type Graph struct {
	Goal string `json:"goal"`

	// Asked are the separable requests the person's own ask contained, in
	// their own words, as the call that read the whole ask reported them.
	//
	// They are a reading of the ask and never a layout of the plan. There was
	// once a second road out of that reading — two or more requests laid flat
	// with an assembler behind them, no planner anywhere — and it was a worse
	// planner with a hardcoded shape: the one judgment it could not make was
	// whether one of the requests is written over what the others produce, and
	// the layout it committed to had no way to say so. That is the question
	// this package's passes exist to answer, so the reading is handed to them
	// as evidence and the shape stays theirs.
	//
	// Fewer than two is the ordinary ask and renders nothing at all, which is
	// the whole of the compatibility story: every prompt below sends the bytes
	// it sent before this field existed. Persisted with the graph for the same
	// reason the settled points are — a document revised later is revised
	// against the premises it was built from.
	Asked []string `json:"asked,omitempty"`

	// Settled are the goal's free variables, bound once so that every parallel
	// call works from the same premise. Open are the ones that cannot be bound
	// in advance because they are the answer to the work — they exist to tell
	// the binder what must become a real dependency rather than an assumption.
	//
	// A settlement is the variable and the values together (see Settlement), so
	// that "bound" is a slice length rather than a reading of a sentence. A graph
	// written before that split decodes its strings into the same type and
	// renders them unchanged.
	Settled []Settlement `json:"settled,omitempty"`
	Open    []string     `json:"open,omitempty"`

	// Evidence is the standard of support the goal warrants — reading and
	// citing, running and measuring, or building and demonstrating. It is
	// settled with the scope and for the same reason: left unsaid, each subtree
	// picks its own and the expensive answer wins, which is how a short written
	// report became a benchmarking project.
	Evidence string `json:"evidence,omitempty"`

	// FileShaped says the ask names its own deliverable — a file or document by
	// name, or a change to material that already exists — which is the one case
	// where filing the result is delivery rather than an evasion of it. It rides
	// the graph because it is one fact about the goal and three passes need the
	// same answer: the instruction the deliverable owner receives, the working
	// method written for it, and anything later that judges what came back.
	//
	// False is the safe default and the shape almost every ask has, so a caller
	// that has not made the judgment leaves every prompt exactly as it was.
	//
	// Intended wiring: the chat session sets Options.FileShaped on plan.Build, or
	// graph.FileShaped before plan.Briefs/plan.Contracts, from the same judgment
	// its delivery gate already makes about the request — the bit that decides
	// whether a leaf is offered a workspace path at all (leafOutputHint). Nothing
	// here matches a phrase against the goal.
	FileShaped bool `json:"file_shaped,omitempty"`

	// Continues says this plan is the remainder of work that already happened —
	// a repair, an extension, a continuation — as opposed to a plan for work
	// nobody has started.
	//
	// It is a separate fact from Records and not derivable from it, because the
	// two questions it separates are the ones that were being collapsed. A plan
	// that continues nothing has no record because there is nothing to have a
	// record OF, and telling its method writer that no record was handed in
	// would be answering a question nobody asked. A plan that continues work and
	// still has no readable record is the case that goes wrong: its agents will
	// be asked to state facts about work they cannot see, and the method they are
	// held to has to say so out loud rather than leave them to improvise.
	Continues bool `json:"continues,omitempty"`

	// Records are the files the work this plan continues left behind, which the
	// agents it plans can open and read: the text of a change, a measurement, a
	// transcript. Empty is a plan that continues nothing, which is nearly every
	// plan there is.
	//
	// It rides the graph because the pass that needs it is not the one that
	// receives it. What a remainder can KNOW is settled four calls before any
	// leaf runs, in the pass that writes each leaf's working method, and that
	// pass had no way to tell "the agent will be handed the record and must read
	// it" from "the agent will be handed nothing and must say so". Handed
	// neither, it wrote methods that instructed inference — and one of them
	// offered an illustrative root cause that the leaf then shipped verbatim as
	// a real one, into a person's answer, past a gate holding only file names.
	//
	// It is a roster of what EXISTS, never an instruction about what to write
	// with it. What the method pass makes of a roster, empty or full, is that
	// pass's own business. See contract.go.
	Records []string `json:"records,omitempty"`

	// Terrain is the workspace this run stands on, drawn in code at build start
	// and frozen. It is persisted with the graph for the same reason the settled
	// points are: a graph read back off disk is revised against the premises it
	// was built from, and a reviser that lost the picture would be judging what
	// happened against a workspace it was told about but cannot see.
	Terrain string `json:"terrain,omitempty"`

	// ContextTokens is the window of the model that structures this job: the
	// one that planned it, and the ones that later revise it and judge whether
	// it is finished. Zero means unknown, which is the honest answer for a graph
	// nobody told and never a claim that the model is small.
	//
	// It is persisted for the same reason Terrain is. The passes that read this
	// document after the build — the revision sentinel, the growth gate — are
	// reached through signatures that carry the document and nothing else, and a
	// graph read back off disk that had lost the window would quietly go back to
	// showing a 200k-token reviser 4 KiB of what has happened.
	ContextTokens int `json:"context_tokens,omitempty"`

	// Invoice is the measured price list rendered by the caller before the build
	// starts, and it is what the three passes that judge division are given in
	// place of guessing what a split costs. See invoice.go.
	//
	// It rides the graph rather than the options because expansion and the
	// sizing pass inside it are reached through the document alone, and a
	// sub-planner that lost the prices would be weighing a division against
	// nothing — which is the state this whole block exists to end.
	//
	// Empty is a machine with nothing measured yet, and it is also the whole of
	// the compatibility story: every prompt below renders exactly the bytes it
	// rendered before invoices existed. It is deliberately NOT persisted: prices
	// move every time a leaf lands, and a graph read back off disk a day later
	// carrying yesterday's prices would be quoting a measurement as a fact when
	// the measurement has since changed.
	Invoice string `json:"-"`

	// Workspace is the directory the terrain above was drawn from, and it is
	// here so that the material a node names can be weighed. See reach.go: the
	// sizing pass runs against the document rather than against the options, so
	// a graph that lost the directory would have no way to check a verdict
	// against the material the node it sized actually names — and no way to
	// state the goal's own measurement in the prompt every pass shares.
	//
	// Empty is a run with no workspace, which measures nothing and changes no
	// verdict and no prompt byte anywhere.
	Workspace string `json:"workspace,omitempty"`

	// Named is the frozen measurement of what this GOAL names by name, weighed
	// against what one worker holds and rendered once at build start. It is the
	// whole-plan reading and never a node's: a node's own verdict is
	// Node.BeyondReach above, computed where its siblings are visible.
	//
	// It is frozen for the reason the terrain beside it is: it joins the prefix
	// every pass shares, and workers write into the workspace while passes are
	// still running, so a figure re-read mid-build would move the prefix under
	// calls in flight and leave two of them planning against two different
	// readings of the same disk.
	Named string `json:"named,omitempty"`

	Stages []Stage `json:"stages"`
	Nodes  []Node  `json:"nodes"`
	NextID int     `json:"next_id"`
	Usage  Usage   `json:"usage"`
}

Graph is the whole plan, and the unit that is persisted between a planning run and any later revision of it.

func Build

func Build(ctx context.Context, client Completer, goal string, options Options) (*Graph, error)

Build produces the graph. Four call-rounds, whatever the size of the result.

func Load

func Load(data []byte) (*Graph, error)

Load reads a persisted graph.

func (*Graph) Add

func (g *Graph) Add(node Node) int

Add appends a node, assigning it the next stable ID.

func (*Graph) AddNeed

func (g *Graph) AddNeed(id, need int) error

AddNeed records a dependency, refusing anything that would break the graph. Self-reference, unknown nodes, and edges into a frozen node's inputs are all rejected, and so is any edge that would close a cycle.

func (*Graph) Depth

func (g *Graph) Depth() int

Depth reports how many levels of decomposition the graph has.

func (*Graph) Edges

func (g *Graph) Edges() int

Edges counts declared dependencies.

func (*Graph) JSON

func (g *Graph) JSON() ([]byte, error)

MarshalJSON is provided through a plain method so a graph round-trips to disk between a planning run and a later revision.

func (*Graph) Leaves

func (g *Graph) Leaves() []int

Leaves are the nodes that represent real work — everything the harness would actually hand to an executing agent, excluding the synthesis nodes it owns.

func (*Graph) Node

func (g *Graph) Node(id int) *Node

Node returns the node with the given stable ID.

func (*Graph) Prune

func (g *Graph) Prune()

Prune drops references to nodes that no longer exist. Revision can delete a node that something else still names, and a dangling need would otherwise stall that node forever.

func (*Graph) Remove

func (g *Graph) Remove(id int) error

Remove deletes a pending node and rewires anything that depended on it onto that node's own dependencies. Inheriting the needs rather than dropping them is what keeps the removal from silently freeing a downstream node to run before its real inputs exist.

func (*Graph) Retarget

func (g *Graph) Retarget(from, to int) error

Retarget replaces every reference to one node with a reference to another, then removes the original. It is how a duplicate is folded into the node it duplicates.

func (*Graph) Roots

func (g *Graph) Roots() int

Roots counts nodes that can start immediately.

func (*Graph) SetAcceptance

func (g *Graph) SetAcceptance(points []Point)

SetAcceptance stamps the checklist on the node that DELIVERS, and on no other.

The list is the request's, so it belongs to whoever hands the finished thing over. Every other node in a plan contributes material to that node and was never asked for the whole request's behaviours; holding one of them to the list would be failing a worker for work that was never its. The delivery gate draws the same line from the other end — it judges the node whose parent is the root and nothing else — so the two agree by construction rather than by two readings of one rule.

It answers to deliverableOwner, which is where "who produces the finished thing" is already decided, and it does nothing when the plan has no single owner: a graph with several sinks has not gathered yet, and stamping the list on all of them would buy one repair round per sink for one gap.

func (*Graph) SetConstraints

func (g *Graph) SetConstraints(constraints []Constraint)

SetConstraints stamps the job's rules on EVERY node's spec, and that is the one way it differs from SetAcceptance beside it.

The checklist belongs to whoever hands the finished thing over, because it is the list of behaviours the whole request states and a contributing worker was never asked for those. A CONSTRAINT IS A PROPERTY OF THE JOB AND NOT OF ANY ONE NODE OF IT. The person who said "change no files" said it about the run, so every worker the run ever starts is under it — including the ones spliced later by a repair round or a remainder, which is exactly where #427's files were written.

func (*Graph) Sinks

func (g *Graph) Sinks() []int

Sinks are the nodes nothing else consumes — what the synthesis node reads.

func (*Graph) Splice

func (g *Graph) Splice(parentID int, sub *Graph) error

Splice replaces a node with its own decomposition, in place.

The node does not go away — it becomes the synthesis of the subtree that replaced it. That is what makes recursion safe: the parent keeps its ID, its inbound edges, and its position, so nothing in the outer graph is rewired and nothing that pointed at it has to learn that it was expanded. Expansion is therefore a purely local operation no matter how deep it goes.

before:  A ──▶ X ──▶ B
after:   A ──▶ x1 ┐
         A ──▶ x2 ├──▶ X ──▶ B
         A ──▶ x3 ┘

Children inherit the parent's inputs, because a child cannot know which of its parent's inputs it actually needs and inheriting is the answer that cannot strand it. A sub-binding pass may narrow that later; guessing narrow here would silently starve a node of data it was promised.

func (*Graph) StageWaves

func (g *Graph) StageWaves() [][]int

StageWaves is what execution would look like if stages were barriers — the naive schedule this design exists to beat. It is computed only so the difference can be shown rather than claimed.

func (*Graph) Unresolved

func (g *Graph) Unresolved() int

Unresolved counts leaves that are still judged too big for one agent. They are shipped anyway — a leaf that is too large still gets done, only slowly — but the count is the honest measure of where decomposition ran out of depth or budget, and it should never be silently swallowed.

func (*Graph) Waves

func (g *Graph) Waves() [][]int

Waves groups nodes by earliest possible start. A node's wave is one past the deepest wave it depends on, so a node needing nothing starts in wave 0 no matter which stage produced it. This is the whole point of binding: stage membership gates nothing, only real data flow does.

func (*Graph) Window

func (g *Graph) Window() int

Window is the graph's context window, asked safely of a graph that may not be there. A caller reaching for a budget usually holds a *Graph that is nil on every path where the job was never planned, and nil is the same answer as unknown: use the fallback.

func (*Graph) WorkDepth

func (g *Graph) WorkDepth() int

WorkDepth is the critical path counting only real work. Splicing leaves a synthesis node behind at every level, and those hops are near-free — they assemble results that already exist rather than going and getting anything. Counting them makes a graph look more serial than it will actually run, which matters because the critical path is the number we are trying to minimise.

type Grounding

type Grounding struct {
	Settled  []Settlement
	Open     []string
	Evidence string
}

Grounding is everything the ground pass binds: the goal's scope variables and the standard of evidence the goal warrants. They travel together because they are the same kind of decision — settled once, upstream, and inherited by every call after it rather than re-answered per subtree.

type Invoice

type Invoice struct {
	// Worker names whose price list this is. Prices are kept per worker because
	// capacity is a property of the executor, and a price list that averaged
	// over several of them would describe none of them.
	Worker string
	// Shapes are the per-size rows, in the order sizes are worth reading.
	Shapes []InvoiceShape
	// Floor is what the cheapest leaf this worker has ever run cost, and it is
	// the closest thing to a measured fixed price of a child there is: whatever
	// a piece costs before it does any work of its own — being told the subject,
	// being told the working method, claiming the node and saying it is done.
	// A split of N ways pays it N times whatever else it buys.
	Floor int
	// FloorKnown separates a measured floor from an unmeasured one. A zero
	// floor is not a free child; it is a worker nobody has watched yet.
	FloorKnown bool
	// JoinTokens is the fan-in tax: what a leaf that consumed earlier results
	// has actually cost, over the runs that recorded how many results they
	// consumed. JoinInputs is the median number of results those runs read.
	// A join re-reads every branch's digest, so this is the price the width
	// itself adds at the far end and it is the one cost a fan-out prompt never
	// sees.
	JoinTokens int
	JoinInputs int
	JoinRuns   int
}

Invoice is one worker's measured price list.

It is deliberately not a model of anything. Each field is a quantile or an extreme of observations that were journaled as they happened; nothing here is fitted, extrapolated or smoothed, so every number in the rendered block is a thing that actually happened to a real leaf.

func InvoiceFor

func InvoiceFor(worker string, records []profile.Record) (Invoice, bool)

InvoiceFor prices one worker from its journaled records.

The evidence floor is profile.MinSamples per row, the same gate the ruler is held to. A shape below it is not rendered at a lower confidence — it is not rendered, because the whole value of this block is that a number in it can be trusted without qualification.

Reflex micro-leaves are excluded outright. They are a different population with an envelope that was never allowed to be large, and a price list that averaged them in would tell a planner that a piece of work costs a fraction of what a piece of work costs.

func (Invoice) Priced

func (i Invoice) Priced() bool

Priced reports whether this invoice says anything at all. An invoice with no shape rows is one nobody has enough evidence for, and it renders to nothing.

type InvoiceShape

type InvoiceShape struct {
	Shape  string
	Runs   int
	Turns  int
	Tokens int
	Cost   float64
}

InvoiceShape is one size bucket's measured price.

type Kind

type Kind string

Kind separates work the plan asked for from work the harness owns.

const (
	KindWork      Kind = "work"
	KindSynthesis Kind = "synthesis"
)

type Landed

type Landed struct {
	Title  string
	Result string
}

Landed is one result a job already has in hand.

It is a title and the result's own words rather than the store's dependency row, because this package must not learn the journal's shape to ask a question about text. The caller that has the graph fills it in.

type Measurement

type Measurement struct {
	Files int
	Bytes int
	Reach int
}

Measurement is one reading: how much the material named in some words weighs, and what one worker holds beside it. It is a value rather than a pair of numbers passed around because the two are only ever meaningful together.

func (Measurement) Exceeds

func (m Measurement) Exceeds() bool

Exceeds is the whole rule in one predicate: the named material is larger than what one worker holds.

func (Measurement) Line

func (m Measurement) Line() string

Line is the measurement in a person's words, for the prompt block every pass that decides shape already shares. Nothing was measured renders nothing at all, down to the newline.

It states both figures and their ratio and stops. There is no instruction in it, and that is deliberate: the passes that read it each have their own rule about what a measurement means for them, written in their own prompts, and a second instruction smuggled in beside the numbers would be that rule stated twice and drifting.

func (Measurement) Taken

func (m Measurement) Taken() bool

Taken reports whether anything was measured. A goal that names no file that exists is not a small goal — it is a goal nothing was measured about — and every consumer below treats the two differently.

type Node

type Node struct {
	ID     int `json:"id"`
	Stage  int `json:"stage"`
	Depth  int `json:"depth"`
	Parent int `json:"parent,omitempty"`

	Title   string `json:"title"`
	Summary string `json:"summary"`

	// Sources are the distinct things this node must touch to be done — pages,
	// documents, datasets, vendors, decisions. They are collected because
	// enumeration is something a model does reliably and effort estimation is
	// not, so they stand in as evidence of size. They are dual-use: the same
	// list is a real hint to whatever eventually executes the node.
	Sources []string `json:"sources,omitempty"`

	// Parts are the pieces this node would break into that could genuinely run
	// at the same time. They are named during sizing, which costs nothing extra,
	// and they are the cheap pre-check on expansion: a node that cannot name two
	// parallel parts is not worth spending a fan-out call on, because whatever
	// comes back will be rejected for not shrinking anything. Naming is the test
	// — a split nobody can describe concretely is a split that does not exist.
	Parts []string `json:"parts,omitempty"`

	// Undivided is why this node was left whole, written at the moment the
	// refusal was made rather than inferred afterwards from the shape that
	// resulted — the shape is the thing being explained. It follows the pattern
	// the scale gate already set for the job-level reading (see
	// store.RecordScaleGate): diagnosis, not control. Nothing reads it back to
	// decide anything, and losing it costs an explanation and nothing else.
	//
	// It answers the question a finished graph otherwise cannot: a node that
	// stayed a leaf because nobody could name two pieces and a node that stayed
	// a leaf because the pieces would have run one after another look identical
	// once the run is over, and the reading that separated them is a model's and
	// does not repeat. Empty means no split was ever considered for this node.
	Undivided string `json:"undivided,omitempty"`

	// BeyondReach is the measurement's finished verdict on this node: the
	// material it will read is larger than one worker's window AND it is not a
	// lane of a division. It is a stored answer rather than a question each
	// reader asks for itself, and THAT IS THE POINT — see correctBeyondReach,
	// which is the one place that computes it.
	//
	// It is here because two seams act on the same fact and only one of them
	// can see the graph. The sizing correction weighs a node's material against
	// its siblings'; the split judgment reads one node and the options and
	// cannot see a sibling at all. While the split judgment measured for itself
	// it reached the opposite verdict on exactly the nodes the exemption exists
	// for: three lanes over one register were spared by the correction, sized
	// atomic, and then journaled "its named material exceeds what one worker
	// holds" by the expansion pass a moment later. THE TWO SEAMS MAY NEVER
	// DISAGREE, so there is one verdict and both read it.
	//
	// False is every graph that was never measured — no workspace, nothing
	// named, nothing weighed — which is every prompt byte and every branch
	// exactly as they were before any of this existed. A graph written to disk
	// before this field existed reads back false and behaves that way too, and
	// a node the sizing pass never reached (frozen: already running, already
	// done) keeps whatever it carried, because it is not a candidate for
	// division any more.
	BeyondReach bool `json:"beyond_reach,omitempty"`

	Needs []int  `json:"needs"`
	Size  Size   `json:"size,omitempty"`
	State State  `json:"state"`
	Kind  Kind   `json:"kind"`
	Brief string `json:"brief,omitempty"`

	// Subharness names the worker that takes this node whole. There is one, so
	// a node built by this harness carries "linear" or nothing; the column
	// stays because a graph written by an older build names what it named, and
	// it travels with the node from the file the graph is persisted to through
	// the splice that admits it to the store.
	//
	// Empty is a different fact from "linear": nobody wrote this column at all,
	// and only that fact lets a reader downstream supply an answer of its own.
	Subharness string `json:"subharness,omitempty"`

	// Contract is the working method for this leaf: how an agent should work
	// this particular kind of job, as distinct from the Brief, which says what
	// the job is. A generic loop with a per-task contract is what lets one
	// executor match a specialised harness on any given leaf without the
	// harness itself changing.
	Contract string `json:"contract,omitempty"`

	// Skills is the ordered list of skill names the brief pass attached to this
	// leaf from the shelf the caller handed the build: skills the goal names
	// outright first, retrieval candidates behind them. Order is precedence —
	// earlier-listed skills win conflicts — and the same order is journaled on
	// the node brief and rendered into the worker's instruction. Empty attaches
	// nothing and renders nothing.
	Skills []string `json:"skills,omitempty"`

	// Spec is the same two facts as an object, plus the one nothing carried
	// before: the criterion this node's work is judged finished against.
	//
	// Brief and Contract stay the source of truth for one release and Spec is
	// written beside them — dual-write, single read — so rolling the wave back
	// is a one-line swap at each reader rather than a migration. What the
	// object buys is the retry path: a replacement node inherits Done verbatim
	// instead of re-authoring a spec from failure context, which is how the
	// module name, the filename and the acceptance check used to disappear the
	// moment a leaf was re-aimed at a different worker.
	Spec Spec `json:"spec,omitzero"`

	// Result is what this node produced and is what its dependents receive. It
	// is the deliverable itself rather than a report about it, so that routing
	// it downstream needs no further interpretation. Artifacts are referenced by
	// path instead of inlined: a large output would otherwise be pasted into
	// every dependent's context at once, which is the exact pollution the
	// dependency list exists to prevent.
	Result    string   `json:"result,omitempty"`
	Artifacts []string `json:"artifacts,omitempty"`
	Turns     int      `json:"turns,omitempty"`

	// Tokens and Cost are recorded per node because a run's total says nothing
	// about where it went. One leaf was 54% of a run's input tokens and that had
	// to be inferred from turn counts afterwards rather than read off, which is
	// exactly the measurement the calibration loop needs.
	Tokens int     `json:"tokens,omitempty"`
	Cost   float64 `json:"cost,omitempty"`
	Stop   string  `json:"stop,omitempty"`

	// Verdict is how the leaf ended, as distinct from State. State answers "may
	// its dependents run", and StateDone answers yes to a leaf that stopped
	// halfway because it ran out of budget — correctly, since the dependents
	// still need whatever it produced. Verdict answers the other question, the
	// one nothing could ask before: was that a success. Anything that learns
	// from a run reads this field and never State.
	Verdict provider.Reading `json:"verdict,omitempty"`

	// FanIn is how many earlier results actually landed in this node, measured
	// by whoever claimed it rather than counted off this document.
	//
	// Needs is the plan's intention and is very nearly the same number; this is
	// what a surface with a live store observed instead, which differs where an
	// edge was spliced in after planning or where a dependency settled without
	// producing anything. Nil means nobody measured, and the reader falls back
	// to len(Needs) — never to Sources, which is the touch-list and was the
	// number the join price was mistakenly read off for as long as it existed.
	FanIn *int `json:"fan_in,omitempty"`

	// Calibration is what the worker said about its own fit for this node. It is
	// carried for one reader: the profile record this node becomes when the run
	// lands, and through it the call that rewrites the ruler. Empty on every
	// node that said nothing about its own fit, which is nearly all of them.
	Calibration []string `json:"calibration,omitempty"`

	// Checked is what this node's own worker ran to check itself, and what each
	// one found — one clause, already composed by whoever observed it.
	//
	// It is here for exactly one reader: the pass that looks at a job's
	// remainder and decides whether anything more is worth adding. A node that
	// changed files and whose suite came back green is finished, and that fact
	// existed only inside the worker — so the reviser, seeing a title and a
	// state, kept proposing children to run the tests again and re-investigate
	// what was already proved. Between 48% and 57% of a run's measured cost went
	// there. This is not a rule telling the reviser what to conclude; it is the
	// evidence it was reasoning without.
	Checked string `json:"checked,omitempty"`

	Failure string `json:"failure,omitempty"`
}

Node is one unit of work.

ID is assigned from a counter and never reused or renumbered. That matters more than it looks: the moment anything can insert a node, a positional identifier silently rewrites every dependency that referred to a later node. Stable IDs are what make the graph safe to revise at all.

Needs carries both meanings the graph has at once. It is the schedule — this node waits for those — and it is the context routing table: the executing agent sees the goal plus exactly those outputs and nothing else. Keeping them one list is deliberate. A dependency that cannot justify a place in the context has not earned the right to delay the node either.

type Operation

type Operation struct {
	Op      string `json:"op"`
	Node    int    `json:"node"`
	Title   string `json:"title"`
	Summary string `json:"summary"`
	Needs   []int  `json:"needs"`
	Reason  string `json:"reason"`
	Applied bool   `json:"applied"`
	Refused string `json:"refused,omitempty"`
}

Operation is one edit the sentinel asked for, together with what actually happened to it. A rejected operation is kept rather than dropped so the caller can see what the model wanted and why the graph refused.

type Options

type Options struct {
	// Recall is folded history relevant to this goal. Empty preserves the
	// pre-memory prompt byte for byte; populated memory is consumed only by the
	// ground pass, before parallel planning can reinterpret the goal.
	Recall []store.RecallHit

	// Terrain is what the run's workspace holds, rendered by the caller with
	// RenderTerrain before the build starts. Empty is the whole of the
	// compatibility story: a caller with no workspace sends the prompt bytes it
	// has always sent.
	//
	// The caller renders it, not this package, and renders it exactly once. This
	// block joins the frozen preamble that every fan-out, bind, size, audit and
	// brief call shares, so re-reading the directory mid-build — where a worker
	// may already be writing into it — would change the prefix under passes that
	// are still running, cost every cache hit behind it, and leave two calls
	// planning from two different pictures of the same workspace. It is a
	// snapshot taken at build start and frozen for the build.
	Terrain string

	// Workspace is the directory the terrain above was drawn from. It is what
	// makes the terrain measurable rather than only readable: the words of the
	// goal and of every node name files, and this is where they are looked up.
	// See reach.go.
	//
	// It is read exactly once, onto the graph at build start, because the pass
	// that weighs a node's material against its siblings' runs off the document
	// and not off these options. See Graph.Workspace and reach.go.
	//
	// Empty measures nothing, renders nothing, and changes no verdict — a
	// caller with no workspace plans exactly as it always did, on the prompt
	// bytes it always sent.
	Workspace string

	// Asked are the separable requests the caller's reading of the ask found in
	// it, in the person's own words. Fewer than two is the ordinary ask and
	// changes no prompt byte anywhere.
	//
	// It is handed to the build rather than acted on by the caller, and that is
	// the whole of this field. A caller that acts on it is a second planner
	// with one shape in it: it can lay the requests side by side, and it cannot
	// answer the one question a flat layout destroys — whether one of them is
	// written over what the others produce. Here the reading reaches the passes
	// whose job that question already is. See Graph.Asked.
	Asked []string

	// SpineSamples is how many spines to draw before choosing one. The spine is
	// the only call whose framing every later pass inherits, so it is the only
	// one worth sampling; the samples run concurrently and cost no wall clock.
	SpineSamples int

	// MaxDepth bounds recursion. It is the guard that matters most, because
	// depth is the only cost of decomposition that is genuinely serial — a
	// level costs four call-rounds however many nodes expand within it.
	MaxDepth int

	// BuildDepth is how many of those levels the *build* runs for itself, for
	// a caller that intends to carry the rest later.
	//
	// Zero means MaxDepth, which is every caller that has ever existed and is
	// the whole of the rollback: a build asked nothing new answers exactly as
	// it did. A scheduler that expands at claim time sets it low and keeps
	// MaxDepth where it was — the difference between the two is not a smaller
	// graph, it is the same depth decided later, against dependencies that have
	// landed instead of against their titles. Every level the build skips is
	// one it would have decided at t=0 with the least information it will ever
	// have, and one whose four serial call-rounds it would have made the person
	// wait through before the first leaf started.
	BuildDepth int

	// NodeBudget is the hard ceiling the model cannot argue with. Every other
	// stop condition is pressure applied through a prompt; this one is
	// arithmetic, and it is what guarantees termination.
	NodeBudget int

	// Briefs turns on per-leaf instruction writing. It is opt-in because it
	// costs one call per leaf and only matters once something is going to
	// execute them.
	Briefs bool

	// Skills is the active shelf, read once by the caller from the store it
	// already holds ([store.SkillFacts] with the active status) and handed in
	// frozen, the way the terrain and the invoice are: the brief pass composes
	// each leaf's attachment from it — skills the goal names outright first,
	// retrieval candidates behind them — and journals the same order on the
	// node brief. Nil is the whole of the compatibility story: a caller with
	// no shelf attaches nothing and changes no prompt byte anywhere.
	Skills []store.Fact

	// FileShaped carries the delivery-law bit onto the graph, for the case where
	// briefs are written inside the build and the caller never sees the graph
	// before they are. See Graph.FileShaped and DeliveryLaw.
	FileShaped bool

	// Continues says this plan is the remainder of work that already happened.
	// See Graph.Continues.
	Continues bool

	// Records are the files the work this plan continues left behind, which the
	// agents it plans can open and read. See Graph.Records.
	Records []string

	// Undivided stops the build at the spine when the spine says there is
	// nothing to divide. It exists for the remainder path and it is opt-in
	// because it is the wrong answer for a fresh project: a one-stage project
	// still has parallel parts inside that stage, and finding them is the
	// point.
	//
	// A remainder is different in kind. It is what is left of one leaf's
	// assignment after a reviewer named a gap, so it is by construction
	// smaller than work one agent was already given. Measured: planning one
	// such remainder — "run pytest and show the output" — cost 13,828 prompt
	// tokens across seven planner passes, and another cost 23,964. That is
	// five to eight times the entire structuring cost of the original job, to
	// produce a graph of one node.
	//
	// The judgement stays the model's and the structure stays the code's, and
	// the judgement is the RULER'S REACH. The spine is asked what has to wait
	// for what, which is advice about the shape of a fresh plan; for a
	// remainder its answer is a list of the steps one worker would take, so
	// however many stages come back they are folded into one node and the ruler
	// is asked whether that node is within one worker. Within reach, this stops
	// there and hands back one worker; past it, the full pipeline runs exactly
	// as before and the stages inform the fan-out as they always did. Nothing
	// matches a phrase against anything, and the stage count divides nothing.
	Undivided bool

	// Ensemble chooses between the two ways of spending parallelism: splitting
	// work by subject, or doing one judgment several times over independently
	// and merging. 0 lets the planner decide from the goal, -1 never asks, and
	// N >= 2 forces a panel of N. See ensemble.go.
	Ensemble int

	// ContextTokens is the window of the model that reads this package's
	// prompts — the planner, and afterwards the reviser and the completion gate
	// that read the same document. Zero means nobody could say, and every
	// budget sized from it then falls back to the literal it always used.
	//
	// It rides onto the graph at build so that the passes which happen later,
	// through signatures that carry a document and not an options struct, size
	// themselves from the same number the build did. See Graph.ContextTokens.
	ContextTokens int

	// Invoice is the measured price list for this model's workers, rendered by
	// the caller with RenderInvoice before the build starts. Empty is a machine
	// with nothing measured yet and leaves every prompt byte for byte as it was.
	//
	// The caller renders it for the same reason it renders the terrain: this
	// package holds no profile directory and no model name, and the block must
	// be one snapshot frozen for the build rather than a figure that moves under
	// passes still running. See Graph.Invoice and invoice.go.
	Invoice string

	// CapacitySamples and CapacityOverrunRate are the journal's measured answer
	// to how often one-worker leaves exceeded their envelope. Zero is no
	// measurement and preserves the old decision exactly; callers only populate
	// them behind the swarm gate. The sample count remains separate because a
	// dramatic rate from one run is an anecdote, not a reason to buy more work.
	CapacitySamples     int
	CapacityOverrunRate float64

	Report Report

	// Progress is called at pass boundaries. Nil keeps planning behavior and
	// output unchanged.
	Progress Progress

	// OnReady fires the instant a node is final and has nothing to wait for.
	// Those nodes are dispatchable at once — a linear harness could be running
	// them while the rest of the graph is still being planned — so the moment
	// we know is the moment worth telling someone. The node is passed by value:
	// the graph is still being appended to, and a pointer into it can go stale.
	OnReady func(node Node, elapsed time.Duration)

	// Journal, when set, writes one node_briefed event per briefed node as the
	// brief pass lands — the rendered instruction, the sufficiency sentence
	// (Spec.Done) — so a run's stopping condition is
	// queryable from its own artifacts rather than only as a field inside the
	// plan blob. The caller forms the store id; see BriefJournal. Nil leaves
	// briefs exactly as durable as they were before this existed, which is the
	// one-shot `codeaf plan` path and every caller with no store to journal to.
	Journal BriefJournal
}

Options configures a build.

type Panel

type Panel struct {
	Mode   string `json:"mode"`
	Reason string `json:"reason"`

	PassTitle   string   `json:"pass_title"`
	PassSummary string   `json:"pass_summary"`
	PassSources []string `json:"pass_sources"`

	SetupTitle   string `json:"setup_title"`
	SetupSummary string `json:"setup_summary"`

	Deliverable string `json:"deliverable"`
}

Panel is the verdict and, when the verdict is ensemble, the shape of the panel to build.

func DecidePanel

func DecidePanel(ctx context.Context, client Completer, goal, terrain string, asked []string, named string, stages []Stage, invoice string) (*Panel, *ai.Usage, error)

DecidePanel asks, once, whether this goal wants redundancy instead of decomposition. The spine is passed as evidence rather than as instruction: how many gates the planner found in the goal is the cheapest available signal about whether the work is one body of material or several.

The terrain is part of that evidence and not decoration. The question here is whether the goal is one body of material judged several times or several bodies split up, and what the workspace holds is the most direct answer available to it: one document is a panel, forty are a division. An empty terrain leaves the prompt byte for byte the one this pass has always sent. The invoice is the third piece of evidence and the newest. This decision spends parallelism one of two ways and both of them are bought in the same currency, so what a leaf of this worker has actually cost — and what a merge over N of them has actually cost — is exactly the fact the choice turns on. It rides the tail of the same user message, behind the goal and the spine, for the reason every invoice does: the system prompt above is a constant this process never rewrites, and the prices move whenever a leaf lands.

func (*Panel) Chosen

func (p *Panel) Chosen() bool

Chosen reports whether the judgment came back ensemble.

type Point

type Point struct {
	Behaviour string `json:"behaviour"`
	Quote     string `json:"quote"`
	// Kind is PointBehaviour or PointAction. It is omitempty and normalised to
	// PointBehaviour when it is neither, so a point written before this field
	// existed — a rehydrated plan, a journal row, a caller in another package —
	// is still mapped and still counted, which is the fail-safe direction: the
	// cost of reading an action as a behaviour is one false finding, and the
	// cost of reading a behaviour as an action is a stated requirement nothing
	// ever checks.
	Kind string `json:"kind,omitempty"`
}

Point is one thing the request states, the words of the request it is a reading of, and which of the two kinds it is.

Three fields because the three are used by different readers and none can do another's job. Behaviour is what a repair round is aimed at and what a person reads in the finding; Quote is what the grounding rule weighs, and a point whose quote is not the person's own words is a requirement this system invented for itself and may not hold anybody to; Kind is what decides whether a check can be looked for at all.

func Behaviours

func Behaviours(points []Point) []Point

Behaviours is the half of a checklist a check could exist for, stated once here because two readers in another package need the identical answer: the settlement maps these and the score line counts them.

The other half is not dropped from the checklist — the actions are the person's own words and belong on the record with everything else they asked for — it is only never mapped and never named as uncovered.

func NormalizeAcceptance

func NormalizeAcceptance(request string, points []Point) []Point

NormalizeAcceptance bounds and cleans what a model returned.

THE CAP IS DERIVED FROM THE REQUEST ITSELF AND NOT TYPED. A request cannot state more behaviours than it has CLAUSES: past one point per clause the model has stopped describing the request and started describing the domain, and the checklist has become the thing this whole invariant exists to keep out. It needs no constant, it scales with the ask, and there is no number for a later wave to tune wrongly.

It was one point per LINE, and that was the wrong unit measured twice. A person writes "defaults are threshold = 5, cooldown = 30000, halfOpenMaxRequests = 1" on one line and has stated three behaviours a check either exercises or does not; the s5 sweep's igel run held four points against twenty-four hidden checks and textual five against twenty, because the ceiling and the prompt agreed that a line was a behaviour. A clause is the smallest unit a person writes one behaviour in, so it is the unit the cap counts.

Points are deduplicated on their quote, because two readings of one sentence are one behaviour said twice, and a checklist that counted them twice would buy two repair rounds for one gap.

func (Point) Action

func (p Point) Action() bool

Action reports that this point is something the RUN does rather than something the finished work is, so nothing should look for a check for it.

func (Point) Empty

func (p Point) Empty() bool

Empty reports that this point says nothing that could be checked or grounded.

type Progress

type Progress func(ProgressUpdate)

Progress receives one replaceable snapshot of planning progress.

type ProgressUpdate

type ProgressUpdate struct {
	Phase  string `json:"phase"`
	Done   int    `json:"done"`
	Total  int    `json:"total"`
	Latest string `json:"latest"`
}

ProgressUpdate is the user-facing shape of planning movement. The planner's pass names stay behind this boundary; surfaces receive only calm language, an optional honest count, and real generated content when one just landed.

type Reach

type Reach struct {
	Dir   string
	Bytes int
}

Reach is what one worker holds at a time, and the workspace whose files the words of a goal are measured against.

The two travel together because neither is a measurement on its own: bytes with no workspace has nothing to weigh, and a workspace with no window has nothing to weigh it against.

func ReachFor

func ReachFor(workspace string, contextTokens int) Reach

ReachFor derives one reach from the workspace the terrain was drawn from and the window of the model the work will run on. An empty workspace or an unmeasurable window yields a reach that measures nothing, which is the whole of the compatibility story.

func (Reach) Measure

func (r Reach) Measure(texts ...string) Measurement

Measure weighs the material these words name, once.

It is deliberately stateless. The alternative is a cache on a value that is copied into every pass of the build, which is shared mutable state bought to save a handful of syscalls on paths the operating system has already cached.

type Report

type Report func(pass string, elapsed time.Duration, detail string)

Report exposes the planner's diagnostic pass timings. It is deliberately separate from Progress: reports are operator telemetry, while progress is phrased for the person waiting on the work.

type Satisfaction

type Satisfaction struct {
	Complete  bool        `json:"complete"`
	Uncovered []Uncovered `json:"uncovered,omitempty"`
}

Satisfaction is the verdict. Complete means every condition is met by something landed or committed; the default direction of the prompt is the other one, because a gate that wrongly says complete truncates work that was genuinely unfinished.

type Settlement

type Settlement struct {
	// Variable names what the goal left free — "the three cities", "the vendors
	// compared", "the period covered".
	Variable string `json:"variable"`
	// Values are what it is bound to, by name. Empty means nothing was bound,
	// which is not a settlement however it is worded.
	Values []string `json:"values"`
}

Settlement is one bound scope variable: the thing the goal left free, and the actual names, numbers or values it is now bound to.

The pair is the whole point. The prompt has always demanded that a settled point contain real values and has always been free to return a sentence that merely sounds like one; the enumeration and the thing enumerated were fused into one string, so no reader downstream could tell "the three cities are Berlin, Lisbon and Warsaw" from "the cities are the ones in scope". Separated, the difference is a slice length.

func (Settlement) Bound

func (s Settlement) Bound() bool

Bound reports a settlement that actually settles something.

func (Settlement) Line

func (s Settlement) Line() string

Line is the settlement as one line of a prompt.

It is byte-identical to what the untyped list rendered for a well-formed point: the variable, a colon, the values in the order they were bound. A settlement decoded from a legacy graph carries its whole sentence in Variable and no values, and renders as that sentence unchanged — which is what keeps every plan document written before this schema existed rendering the bytes it always did.

func (*Settlement) UnmarshalJSON

func (s *Settlement) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts the object this schema now emits and the bare string every plan document written before it carries. A graph on disk is a durable artifact that is loaded and re-planned long after it was written, so the older spelling decodes into the same type rather than failing the load.

type Size

type Size string

Size is how a node measures against a linear harness: whether one agent with tools, running serially, is the right thing to hand it to.

const (
	SizeUnknown    Size = ""
	SizeAtomic     Size = "atomic"
	SizeBorderline Size = "borderline"
	SizeOversized  Size = "oversized"
)

type SkillEntry

type SkillEntry struct {
	Name      string
	Doc       string
	ShelfPath string

	// BodyInPath says ShelfPath is the skill's readable body — the SKILL.md of
	// an agentskills folder — rather than a directory holding something to
	// run. The render says so in the line itself, because a worker handed a
	// bare path cannot tell a file it should read from a directory it should
	// run things out of, and `read` refuses a directory outright.
	BodyInPath bool
}

SkillEntry is one attached skill rendered in a worker's instruction block. Name is the skill's shelf name; Doc is the one-line description from its fact; ShelfPath is the path the worker reaches the skill through — the skill's directory for the forge's own executable skills, and the SKILL.md body file itself for an agentskills folder. SkillEntryFromFact is the one construction path that decides which, so the convention is applied there and nowhere else in the render.

func SkillEntryFromFact

func SkillEntryFromFact(fact store.Fact) SkillEntry

SkillEntryFromFact builds the one entry the brief pass attaches for a shelf fact — the single construction path, so the agentskills convention is applied here or nowhere. A fact whose artifact directory holds a top-level SKILL.md is an agentskills folder: its content is that FILE, and the directory itself is what `read` refuses, so the entry carries the SKILL.md path and the render marks it as the body. Every other fact keeps the exact entry this pass has always built — the artifact directory itself, whatever the fact's trust tier says, because the convention keys on the folder and never on how the skill arrived.

type Spec

type Spec struct {
	Instruction string   `json:"instruction,omitempty"`
	Method      string   `json:"method,omitempty"`
	Done        Done     `json:"done,omitzero"`
	Sources     []string `json:"sources,omitempty"`
	// Accept is the acceptance checklist: the behaviours the REQUEST states,
	// read from the request before any work existed. Done is the criterion the
	// planner wrote about what the work will produce; this is what the person
	// asked for, and the two are different objects because they answer to
	// different authors. See accept.go.
	//
	// It travels here rather than beside the gate for the reason Done does: this
	// is the one object in the system carried forward verbatim through a retry
	// (RetargetSpec), so a repair round is judged against the same checklist its
	// predecessor was. Render deliberately omits it — the worker is never shown
	// the list it will be checked on.
	Accept []Point `json:"accept,omitempty"`
	// Constraints are the rules the person stated about what the run may or may
	// not DO. Unlike Accept, which belongs to the node that delivers, these are
	// stamped on every node of the job: the person said it about the run, so
	// every worker the run starts is under it. See constraint.go, and Render
	// below, which puts them FIRST.
	Constraints []Constraint `json:"constraints,omitempty"`
}

Spec is what a worker is handed: one object, authored once, carried forward.

Instruction and Method are the two prose halves that already existed as Node.Brief and Node.Contract, named here for what they are. Done is new and is the reason the object exists at all — without a positive criterion, growth can only ever be bounded negatively, by counting rounds.

func (Spec) Empty

func (s Spec) Empty() bool

Empty reports whether this spec carries nothing. An empty spec renders to the empty string, and every reader downstream falls back to the fields it read before the spec existed — which is what makes the whole wave a one-line read swap to roll back.

func (Spec) Render

func (s Spec) Render(limit int) string

Render writes the spec as the text a worker or a foreign engine reads.

THE RULES THE PERSON SET COME FIRST, ABOVE EVERYTHING. A rule about what the run may or may not do is not one consideration among several: it is the boundary the rest of the spec is written inside, and a worker that reads its assignment before the boundary has already decided what to do by the time it meets the rule. It costs nothing at the cache seam either — constraints are stamped once per job and are the most stable bytes in the whole object.

Field order past them is fixed and Method comes next: it is the half that is stable for the life of a job, so it belongs at the front of a string that will be a cache prefix once this render reaches an engine of its own. Done comes last because it is what a retry rewrites least and an instruction rewrites most — the order puts the churn where a prefix match has already been spent.

limit bounds the whole render in bytes; zero or less is unbounded. An empty spec renders to "".

type SpineChoice

type SpineChoice struct {
	Stages  []Stage
	Samples int
	Drawn   []int // stage count of each sample as written, ascending
	Spread  []int // stage count of each sample after levelling, ascending
	Agreed  bool  // every sample proposed the same number of stages
}

SpineChoice records what the sampling saw, so the spread is reportable rather than hidden. The spread is the honest measure of how much of the final graph was decided by luck.

Drawn is how many stages each sample wrote and Spread is how many it has once the stages' stated needs are read (see levelled). The two differ exactly when a model laid independent work end to end, which is worth seeing in a report: it is the difference between a plan that ran one worker and a plan that ran seven.

type SplitVerdict

type SplitVerdict struct {
	Divide bool
	Reason string
}

SplitVerdict is the answer to one question asked of one node: may this node be divided, and if the answer is no, in what words.

Reason is filled on refusal and empty on admission, because a node that is about to be divided has nothing to explain — the shape it ends up with is the explanation. It is prose rather than a code because the only reader is a person asking why a graph looks the way it does.

func JudgeSplit

func JudgeSplit(node *Node, options Options) SplitVerdict

JudgeSplit is the burden of proof as a predicate over one node.

Atomic-and-run is the null hypothesis: this returns a refusal unless something about the node actually argues for dividing it, and the arguments are exactly the two the sizing prompt names — pieces that could genuinely run at the same time, which is what a named part list is evidence of, or a node that does not fit inside what one worker can carry, which is what a size beyond atomic says. Everything else is a refusal with its reason attached.

Measured capacity sharpens only the last boundary. When enough journaled leaves show that atomic-sized work often overruns, an atomic node that has already cleared every refusal above and named independent parts may divide. This does not rescue an unnamed split or invert the null hypothesis. Zero measurements — including every non-swarm caller — therefore take the old branches byte for byte.

It is a free function over a node and the options rather than a step inside the level loop, and that shape is the point: the same question has to be asked again later, when a worker claims a leaf and the graph has moved on since planning. A claim-time caller reads the node it is about to hand out, asks this, and either divides it or records the refusal exactly as the level loop does. That wiring is deliberately not done here — this package still decides nothing at claim time — but the predicate is written so that doing it is a call and not a refactor.

The budget is not asked about here. It is a fact about the whole graph rather than about this node, and folding it in would make a per-node question answer differently depending on who else is in the graph.

type Stage

type Stage struct {
	Title   string `json:"title"`
	Summary string `json:"summary"`
	Needs   []int  `json:"needs,omitempty"`
}

Stage is one position in the generation spine. Stages are scaffolding, not schedule: they exist so the graph can be produced cheaply and acyclically, and they gate nothing once the real edges are known.

Needs is what the spine model said this stage consumes from earlier stages, by 1-based position, and it is read exactly once — by levelled, which turns the list the model wrote into the levels the planner fans out. A stage the planner holds has no Needs left: its position is its level.

type State

type State string

State is what has happened to a node. It exists to make the graph editable safely: an edit is legal or illegal depending on state, and nothing else.

const (
	StatePending State = "pending"
	StateRunning State = "running"
	StateDone    State = "done"
	StateFailed  State = "failed"

	// StateBlocked is a node whose input failed. It is distinct from failed
	// because nothing was wrong with it — it never got the chance to run — and
	// a report that conflates the two makes a single upstream failure look like
	// a collapse.
	StateBlocked State = "blocked"
)

func (State) Frozen

func (s State) Frozen() bool

Frozen reports whether a node may still be changed. Once work has started, its output may already be someone else's input, so the past is not editable — a revision to a started node has to be expressed as new work appended after it, never as a rewrite of it.

type Uncovered

type Uncovered struct {
	Condition string `json:"condition"`
	Missing   string `json:"missing"`
}

Uncovered names one condition of the criterion nothing covers, and what is missing from it. Naming the gap is the whole answer: the call never proposes work, because proposing work is what the planner is for and what the round counter exists to bound.

type Usage

type Usage struct {
	Calls            int     `json:"calls"`
	PromptTokens     int     `json:"prompt_tokens"`
	CompletionTokens int     `json:"completion_tokens"`
	CachedTokens     int     `json:"cached_tokens"`
	Cost             float64 `json:"cost"`
}

Usage is the running cost of a plan across every pass.

func Bind

func Bind(ctx context.Context, client Completer, graph *Graph) (Usage, error)

Bind resolves dependencies for every stage, all at once. Stage 1 is asked only when it holds more than one node: the only edge it can produce is the mutation ordering between two siblings, and a stage of one has no siblings.

It is split into a gather phase and an apply phase so a concurrent pass can share the graph. Gathering only reads and calls; every write waits for bindApply, which the builder runs serially — the sizing pass copies nodes while binding is in flight, and interleaved writes were a data race.

func Briefs

func Briefs(ctx context.Context, client Completer, graph *Graph, callbacks ...Progress) (Usage, error)

Briefs writes an instruction for every leaf that lacks one, all at once. The planner writes briefs in the background as nodes settle; this is the batch form, for a graph that was planned without them and is about to be executed.

func Contracts

func Contracts(ctx context.Context, client Completer, graph *Graph, playbook ContractPlaybook, callbacks ...Progress) (Usage, error)

Contracts writes a working method for every leaf that lacks one, all leaves at once. Like Briefs, it is the batch form used at run time; the wall-clock cost is one call however many leaves there are.

func Ensemble

func Ensemble(ctx context.Context, client Completer, graph *Graph, panel Panel, panelists int) (Usage, error)

Ensemble rewrites a graph as a panel: an optional shared setup, N independent panelists, and one synthesis node that merges them.

The graph keeps its goal and its grounding — those were settled before this choice was made and are just as true for a panel — and loses its stages and nodes, because decomposition and redundancy are alternatives, not layers.

Nothing structural is new. Panelists are ordinary work nodes and the merge is an ordinary synthesis node, so nothing downstream of the planner has to learn what an ensemble is; a graph that ran through the normal executor before still does.

func ExpandLevel

func ExpandLevel(ctx context.Context, client Completer, graph *Graph, options Options) (int, Usage, error)

ExpandLevel decomposes every node worth decomposing, all at the same time, and returns how many were actually spliced in.

The parallelism is the point. Each expansion is a complete four-pass build, so a level costs four call-rounds no matter how many nodes expand — the cost of recursion is measured in depth, never in width. Depth is the only thing here that is genuinely serial, which is why the depth cap is the guard that matters most.

func Recalibrate

func Recalibrate(ctx context.Context, client Completer, store *profile.Profile) (string, string, Usage, error)

Recalibrate rewrites the ruler from measured work, returning the new anchors and whether anything changed. It is a no-op unless the profile both has enough evidence and disagrees with the ruler in force.

func SizeNodes

func SizeNodes(ctx context.Context, client Completer, graph *Graph) (Usage, error)

SizeNodes judges every node in the graph, one call per stage, all at once. It reads nothing that bind writes, so it is run concurrently with binding rather than as a pass of its own — the judgment is free in wall clock.

Like Bind, it is split into gather and apply: the calls run while another pass reads the same graph, so no write may happen until the builder runs sizeApply serially.

func (*Usage) Add

func (u *Usage) Add(usage *ai.Usage)

Add folds one response's accounting in. A nil usage still counts the call: pretending a call did not happen because the provider was quiet about it would understate the plan.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL