craft

package
v0.4.1-rc.1 Latest Latest
Warning

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

Go to latest
Published: Sep 22, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package craft is codeaf's learned know-how: workflows, skills, verifiers, and exemplars, versioned in a git repository the resident owns. A workflow is a reusable job-shape written by the distiller from experience — never by hand into the binary — and refined across versions whose survival is measured like any other belief.

The execution model is deliberately not a second engine. A workflow COMPILES into an ordinary graph subtree: steps are leaves, needs are edges, fan-out unrolls at runtime through the revision sentinel, and a failed verifier earns another bounded round the same way — a sentinel re-splice. Because every step is a normal node in the event-sourced store, persistence, resume after a crash, cost accounting, steering, surgery, the load governor, and the provider rate limiter are all inherited rather than reimplemented. LLM access is the same in-process clients every other leaf uses, sharing one cache lineage per run.

The control layer is data, not code: a workflow file may declare shape and bounds but can never loop unboundedly, recurse into another workflow, or grant itself more budget than the package ceilings below. Intelligence lives in the step briefs; the file only arranges them.

Index

Constants

View Source
const (
	WorkflowDir = "workflows"
	VerifierDir = "verifiers"
	SkillDir    = "skills"
	ExemplarDir = "exemplars"
)

The layout of the craft repository. These names are law rather than convention: Script and Skill are the two fields in a craft file that become an exec call, so the directory they must live under is a constant the validator checks against, not a string the writer chooses.

View Source
const (
	// DefaultHistory is how many versions a history read returns when the
	// caller does not say. A workflow's recent past is what explains its
	// present; its distant past is archaeology and has to be asked for.
	DefaultHistory = 20
	// ScriptMode is how a verifier or skill is written when the caller does
	// not name a mode. Everything in those two directories exists to be
	// executed, so the executable bit is the default rather than the exception.
	ScriptMode = os.FileMode(0o755)
	// MinReasonWords is the shortest message that can count as a reason. A
	// revert with a one-word message ("revert") records that something was
	// undone and loses why, which is the only part a later reader needs.
	MinReasonWords = 4
)
View Source
const (
	// DefaultMatches is how many crafts a request is offered when the caller
	// does not say. Three is a choice; more is a menu, and a menu is what the
	// resident is supposed to spare the user.
	DefaultMatches = 3
	// MatchFloor is the score below which a match is not a match. BM25 hands a
	// small positive score to any shared word, so without a floor an unrelated
	// request would still "find" whichever craft happens to be shortest — the
	// floor is what makes a miss read as a miss instead of a bad suggestion.
	MatchFloor = 1.0
)
View Source
const (
	// DefaultRunBudgetUSD bounds a run whose file declares nothing. It is a
	// backstop and not a budget — the rail pauses-and-asks rather than failing
	// when it is reached — and it was $2.50, which is under the price of the
	// routine run it was written to wave through. A saved shape of work that
	// stops to ask on its ordinary path is a saved shape nobody saves.
	DefaultRunBudgetUSD = 50.0
	// MaxRunBudgetUSD is the most a file may grant itself. Larger ambitions
	// belong to the user, said in chat, not to a file the distiller wrote.
	MaxRunBudgetUSD = 500.0
	// DefaultWallClock bounds a run's total wall time; the sentinel stops
	// opening new rounds or fan-out past it, running leaves finish normally.
	// Half an hour was a wall a real piece of work walked into; six hours is a
	// working day's worth of unattended running, which is what the ceiling is
	// for.
	DefaultWallClock = 6 * time.Hour
	// MaxWallClock is the ceiling a file may declare.
	MaxWallClock = 24 * time.Hour
	// DefaultFanCap bounds one for_each unroll; MaxFanCap is the ceiling a
	// file may declare. Fan-out multiplies cost linearly and silently, so the
	// default stays modest.
	DefaultFanCap = 8
	MaxFanCap     = 24
	// DefaultMaxRounds bounds verify-revise loops when the file is silent;
	// MaxRounds is the ceiling. Two rounds catches most honest misses; past
	// five the verifier is wrong or the task is, and a human should look.
	DefaultMaxRounds = 2
	MaxRounds        = 5
	// MaxSteps refuses pathological files at parse time. A shape bigger than
	// this is a plan, and plans belong to the planner.
	MaxSteps = 24
)

The safety rails. A workflow file may declare tighter bounds than these but never looser: Clamp enforces the ceilings at parse time so a compiled run can trust every number it reads.

View Source
const MaxIDBytes = 48

MaxIDBytes is how long a step id may be once slugged. Node ids are read by people in the rail and parsed back apart by the sentinel; past this length they stop being either.

View Source
const SuggestDistance = 2

SuggestDistance is how far a misspelled reference may sit from a real step or param and still be offered as the intended one. Two edits catches typos and transpositions; three starts naming the wrong step confidently, and a confident wrong suggestion costs a model more than no suggestion at all.

Variables

This section is empty.

Functions

func OnSubject

func OnSubject(workflow *Workflow, request string) bool

OnSubject reports whether a request is about what this workflow is about. It is the gate in front of every unasked-for run: the scorer says the shape fits, this says the subject does, and a craft runs on its own only when both agree.

An unbound workflow is held to a weaker bar rather than to none: the request must still say something the workflow CLAIMS — a word from its name or its description — instead of matching only the vocabulary of its step briefs, which is what every deep-dive in the world has in common. That bar is one a person clears by asking for the thing in their own words ("cut the release notes", "make me a deck"), and it is exactly the bar the decisive score was always documented as standing for.

func Slug

func Slug(name string) string

Slug is the id law, and it lives here because it has to be the same law twice. The compiler mints a node id from a step id by lowercasing it, collapsing every run of anything else into a single dash, and cutting it at MaxIDBytes — and it refuses any step whose id does not survive that trip unchanged. Validate has to refuse exactly the files the compiler refuses, or a workflow saves, commits, announces itself, and then fails to compile forever with nobody watching.

func Subject

func Subject(workflow *Workflow) []string

Subject is what this workflow is BOUND to, in the same stemmed words a request is read into: the words of its own name that describe a subject rather than a shape, plus any proper name its description carries. Empty means unbound — a workflow written to be pointed at anything, which is the ordinary and desirable case for know-how like "release-notes" or a deck workflow with a {{topic}} hole in it.

A word the workflow leaves as a HOLE is never its subject. A craft with a {{topic}} parameter is written to be pointed at any topic; the topic it runs on comes from the request, so the word "topic" in its name or description binds nothing.

func Substitute

func Substitute(text string, values map[string]string) string

Substitute fills {{name}} holes in a brief. An unresolved reference is left standing rather than blanked: Validate already refuses a brief that names an undeclared param, so anything still in braces at runtime is a bug worth seeing in the transcript.

func ValidStepID

func ValidStepID(id string) bool

ValidStepID reports whether an id is already its own slug, which is exactly what the compiler demands of it.

Types

type ForEach

type ForEach struct {
	// Source is "<stepID>" — the items are read from that step's result, one
	// per line of its declared list output.
	Source string
	// Fan caps the unroll; 0 means DefaultFanCap. Clamped to MaxFanCap.
	Fan int
}

ForEach unrolls one step over items discovered at runtime — the compile plants a single planner leaf whose result the sentinel expands into siblings, capped by Fan.

type Limits

type Limits struct {
	CostUSD   float64
	WallClock time.Duration
}

Limits are the file's declared bounds, clamped to the package ceilings. Zero values mean the defaults.

type Param

type Param struct {
	Name        string
	Description string
	Default     string
	Required    bool
}

Param is a named hole in the step briefs, filled at invocation from the user's request. Substitution syntax in briefs is {{name}}.

type Repo

type Repo struct {
	// contains filtered or unexported fields
}

Repo is the craft repository at one directory.

It is never copied — Open hands back a pointer and every caller keeps it — which is what lets the catalogue cache below live on it.

func Open

func Open(dir string) (*Repo, error)

Open prepares the craft repository at dir, creating and initializing it when it is not there yet. It is idempotent: opening an existing repository adds nothing and commits nothing.

func (*Repo) Dir

func (r *Repo) Dir() string

Dir is where the repository lives, for the surfaces that tell the user where their know-how is kept.

func (*Repo) History

func (r *Repo) History(name string, limit int) ([]Version, error)

History is a workflow's versions, newest first.

func (*Repo) List

func (r *Repo) List() ([]Summary, error)

List is the catalogue. A file that will not parse is skipped and reported rather than fatal: one corrupt workflow must not hide the rest, and the listing is the surface the resident chooses from. The returned error names what was skipped — the summaries are complete either way.

The answer is remembered against the state of the workflows directory, because this is not an occasional call: each summary costs a `git log -1`, which is a process fork of about six milliseconds, and the Self pane asks for the whole catalogue on every poll for as long as it is open. Unchanged files give back the same summaries without touching git at all.

The stamp is every workflow file's name, size and modification time, plus the directory's own — so a save, a revert, an added file and a removed one all invalidate it, since every one of them writes. What it cannot see is a commit made behind the files' backs: committing a workflow by hand, in the craft directory, with git, leaves the listing showing the version it had a moment ago until the file itself next changes. Everything that writes here goes through Save or Revert, both of which write the file first.

func (*Repo) Load

func (r *Repo) Load(name string) (*Workflow, error)

Load reads a workflow at the working tree's version and stamps the version it came from, so anything measured about the run can be attributed to a version rather than to a name.

The working tree is not the same thing as HEAD, and the difference is load-bearing: a run re-reads its workflow by name@commit on every advance and refuses to continue when that reference has moved. Stamping HEAD's hash onto edited bytes would make an uncommitted edit invisible to that guard — the same run, silently finishing on a file nobody committed. A dirty path therefore carries its own content into the version, so editing under a live run moves the reference and the guard fires; and a workflow that was never committed has no version at all, which is a refusal rather than a bare name with the guard switched off.

The answer is remembered against the state it was resolved from, for the same reason the catalogue is: resolving a version costs two forks — a `git log -1` and a `git status --porcelain` — and the Self pane loads every workflow in the catalogue on every poll, for the step count alone. See versionStamp for what "unchanged" has to mean before an answer is reused.

func (*Repo) LoadAt

func (r *Repo) LoadAt(name, commit string) (*Workflow, error)

LoadAt reads a workflow as of one commit. This is how a survival record is re-read: the stats key on name and commit, and the file that earned them may be several revisions behind the working tree.

func (*Repo) Match

func (r *Repo) Match(request string, k int) []Scored

Match ranks the repository's workflows against a request in the user's own words, best first. A file that will not parse is not a candidate; a request that matches nothing returns nothing, which is the answer that lets the caller do the work from scratch instead of forcing a craft onto it.

func (*Repo) Revert

func (r *Repo) Revert(name, toCommit, message string) (string, error)

Revert restores an older version as a NEW commit. History is never rewritten here: the version that failed is part of what the workflow knows about itself, and a repository that can erase its own mistakes cannot be measured. The message has to carry a reason — a revert whose commit says only "revert" throws away the one fact a later reader needs.

func (*Repo) Save

func (r *Repo) Save(w *Workflow, message string) (string, error)

Save writes one version of a workflow. It refuses an invalid file before it touches the working tree — a craft repository that holds a workflow which cannot run is worse than one that is missing it — and it clamps before it marshals, so the file on disk states the numbers the run will actually obey. The commit touches only this workflow's path: a version is one workflow's change and nothing else's.

func (*Repo) WriteSkill

func (r *Repo) WriteSkill(relPath string, content []byte, mode os.FileMode, message string) (string, error)

WriteSkill commits one forged executable, under the same law.

func (*Repo) WriteVerifier

func (r *Repo) WriteVerifier(relPath string, content []byte, mode os.FileMode, message string) (string, error)

WriteVerifier commits one executable check. The path law is the same one the validator enforces on verify.script, applied here so a file can never be written outside the directory a workflow is allowed to point at.

type Scored

type Scored struct {
	Summary
	Score float64
}

Scored is one candidate craft and how well it answers the request.

type Step

type Step struct {
	ID    string
	Brief string
	// Needs are step IDs whose results feed this step, compiled to ordinary
	// feeds_into edges.
	Needs []string
	// Model is a slot word (talk/work/boost) or a model word resolved through
	// the existing model-words machinery. Empty means the job's model.
	Model string
	// Skill names an executable in the craft repo's skills/ directory (also on
	// CODEAF_SKILLS_BIN). Advice, not enforcement.
	Skill string
	// ForEach unrolls this step over a list produced upstream.
	ForEach *ForEach
	// Verify makes this a checking step.
	Verify *Verify
}

Step is one leaf-to-be. Exactly one of Brief or Verify drives it: a Brief step is agentic (an ordinary worker with the ordinary tools), a Verify step runs an executable check from the repo. Skill names a forged executable the worker should reach for; it is advice surfaced in the brief, not a harness change.

type Summary

type Summary struct {
	Name        string
	Description string
	Commit      string
	When        time.Time
}

Summary is a workflow as the catalogue sees it: enough to choose by, without parsing the shape.

type UntilPass

type UntilPass struct {
	// Revise names the step IDs to re-run on failure. Empty means the verify
	// step's direct needs.
	Revise []string
	// MaxRounds caps total verify attempts; 0 means DefaultMaxRounds. Clamped
	// to the package MaxRounds.
	MaxRounds int
}

UntilPass is the only loop craft has, and it is not a loop in the graph: on a failed verify the revision sentinel re-splices the named steps once more, carrying the verifier's output as feedback, up to MaxRounds total attempts.

type Verify

type Verify struct {
	// Script is a repo-relative path under verifiers/.
	Script string
	// UntilPass, when set, buys bounded repair rounds on failure.
	UntilPass *UntilPass
}

Verify runs an executable from the repo's verifiers/ directory against the step's inputs. Exit 0 is a pass. Anything else is a fail whose stdout/stderr become revision feedback.

type Version

type Version struct {
	Commit  string
	When    time.Time
	Subject string
	Body    string
}

Version is one commit in a workflow's history.

type Workflow

type Workflow struct {
	Name        string
	Description string
	Params      []Param
	Steps       []Step
	Limits      Limits

	// Commit is the craft-repo version this Workflow was loaded at. Empty for
	// an unsaved draft. Survival stats key on Name+Commit.
	Commit string
}

Workflow is one learned job-shape, parsed from workflows/<name>.yaml in the craft repository. Version identity (git commit) is attached by the Repo at load time, never written in the file.

func Parse

func Parse(data []byte) (*Workflow, error)

Parse reads one craft file. It is strict about unknown fields on purpose: a brief written under `breif:` is a step that silently does nothing, and a step that silently does nothing is the most expensive kind of file to debug. Parse checks shape only — the structural law is Validate's.

func (*Workflow) Clamp

func (w *Workflow) Clamp()

Clamp applies the package ceilings and the defaults for everything the file left at zero, so a compiled run can trust every number it reads without re-deriving the rails. It is idempotent: clamping a clamped workflow is a no-op, which is what lets Save clamp whatever it is handed.

func (*Workflow) Fill

func (w *Workflow) Fill(params map[string]string) (map[string]string, error)

Fill resolves the invocation's params against the declarations: a given value wins, a default fills the silence, and everything still missing is reported in ONE error. A model that has to discover its missing arguments one run at a time will guess the rest, so the error names them all and, when a near-miss was passed instead, says which key it should have been.

func (*Workflow) Marshal

func (w *Workflow) Marshal() ([]byte, error)

Marshal writes the workflow back out in the file shape. Commit is deliberately absent: version identity belongs to the repository, and a file that names its own commit is a file that lies the moment it is edited.

func (*Workflow) Validate

func (w *Workflow) Validate() []error

Validate is the whole structural law, and it reports every breach at once. One error per call would make a model repair a file in as many round trips as it has mistakes; the errors are written to be read together and fixed in one edit, each naming the step it belongs to first.

Jump to

Keyboard shortcuts

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