head

package
v0.7.1 Latest Latest
Warning

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

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

Documentation

Overview

Package head turns durable thread messages into immediate conversational replies and asynchronous graph commands. It never plans or executes work; the thread remains responsive while the rest of codeaf changes the graph.

Index

Constants

View Source
const (
	// ClassSetNameLimit is how many titles a set receipt names before it counts
	// the remainder. Three is a sentence; ten is a list.
	ClassSetNameLimit = 3
	// ClassFallbackFloor is the score a lone content match must clear before it
	// may act on a message that also named a status class. It is redirection's
	// anchor floor — roughly one solid content word against a job's own brief —
	// because the doubt is the same one: does this sentence really point here?
	ClassFallbackFloor = RedirectAnchorScore
)
View Source
const (
	ScaleLookup  = "lookup"
	ScaleTask    = "task"
	ScaleProject = "project"
)

Scale values the compiler may emit. ScaleTask is also the degradation target for anything unrecognised: the safe default shape is one worker.

View Source
const (
	StructureEnumerates  = "enumerates"
	StructureStratifies  = "stratifies"
	StructureOneJudgment = "one_judgement"
	StructureSingleAct   = "single_act"
)

Structure values, the structural reading scale must follow from.

View Source
const (
	// ModelSlotBoost is the slot a boost word resolves to, and ModelSlotWork is
	// the candidacy filter a named model is resolved inside — the same two
	// slots the model palette uses.
	ModelSlotBoost = "boost"
	ModelSlotWork  = "work"
	// MaxModelCandidates bounds one ambiguity question. More options than this
	// is a list, not a choice.
	MaxModelCandidates = 4
)

Model words are read deterministically, exactly like standing and service intent: which model runs a job is the user's decision, not a provider's.

Two shapes exist. A SLOT word ("with the better model", "use the boost model", "with model 2") names the boost slot and always resolves, because the slot always has a model. A NAME word ("use gemini", "with the opus model") names a model and is resolved against the live catalog by the surface; only a name the ask marked explicitly — it said "model" — earns a receipt line when nothing matches, because a bare word that resolves to nothing was probably never a model word at all.

View Source
const (
	// RestartModelPrefix marks the head's reading of the model words on a
	// restart. It is matched rather than reconstructed, so a change to the
	// wording makes the reader return nothing — today's behaviour — instead of
	// a wrong model.
	RestartModelPrefix = "Run this restart on:"
	// RestartModelBoost is what the prefix carries when the ask named the boost
	// slot rather than a model. The slot always resolves, so this always does.
	RestartModelBoost = "the boost model"
)

RecognizeModelWords had exactly one call site — inside Compiler.Compile, which only a fresh splice reaches — so model words could create a new job and could never move an existing one. The two natural phrasings diverged completely: "redo that with the better model" fell through to the router and became a brand-new job in a brand-new workspace, while "rerun that with the better model" matched the restart cue, was handled deterministically, and re-ran the failure on the default slot. A restart is where escalation actually belongs — the work exists, the user watched it go wrong, and they are asking for a stronger hand on the same job.

store.Command has no model field and adding one is a store change this seam does not need: the instruction is a durable payload and the model words are already in it, verbatim. What the head adds is its own deterministic READING of them, on one marked line, in exactly the idiom CompilerAnswerPrefix already uses. The head cannot resolve a name — the catalog lives with the surface that owns the slots — so it says what it read and leaves resolution where the resolver is.

View Source
const (
	// RedirectAnchorScore is the BM25 floor a live job must clear before the
	// user's words are read as steering for it. It sits at roughly one solid
	// content-word match against a job's own brief: below that the overlap is
	// as likely to be a coincidence of vocabulary as a reference to the work,
	// and a wrong redirection edits a plan the user never meant to touch.
	RedirectAnchorScore = 0.4
	// RedirectCandidateLimit bounds the disambiguation question. More than a
	// handful of choices is not a question, it is a list.
	RedirectCandidateLimit = 4
	// AdjacencyMessageWindow is how far back the thread is read for the job
	// that spoke last. It is the floor of the router's own thread window — the
	// count the prompt is guaranteed to still carry whatever the big-step
	// truncation has done — because a line the model still carries is a line
	// the user can still be answering, and anything further back is history.
	AdjacencyMessageWindow = threadWindowKeep
	// AdjacencyQuiet is how long a job's own line stays the thing being
	// answered. A running job speaks on a two-minute heartbeat, so two of them
	// is the span in which nothing newer has been said; past that the thread
	// has moved on and mere position proves nothing about what the user means.
	AdjacencyQuiet = 4 * time.Minute
)
View Source
const (
	// SurgerySpendGateUSD is recorded spend above which cancel/restart needs
	// explicit consent.
	SurgerySpendGateUSD = store.SurgerySpendGateUSD
	// SurgeryRuntimeGate is live runtime above which cancellation needs consent.
	SurgeryRuntimeGate = store.SurgeryRuntimeGate
	// SurgeryCascadeGateNodes gates every operation that affects a larger tree.
	SurgeryCascadeGateNodes = store.SurgeryCascadeGateNodes
)
View Source
const AsideStub = "▸ asked → answered"

AsideStub opens the collapsed line. It is a constant so a surface can recognize the row it must draw folded, and so the stub reads the same in the journal, in a search result and on screen.

View Source
const (
	// BoardRowCap bounds every board read. A board longer than this is a log,
	// not a board: the model reads it to choose a target, and a dozen live jobs
	// is already more than a person holds in their head at once.
	//
	// It is the floor rather than the cap now. The board's rows are what the
	// board's bytes are spent on, so a head whose board block grew gets rows in
	// the same proportion (budget.go) — and a head that was told nothing about
	// its window gets exactly this dozen, which is what every board rendered
	// before the window was a fact anybody here could reach.
	BoardRowCap = 12
)
View Source
const CompilerAnswerPrefix = "Answer to compiler question:"

CompilerAnswerPrefix marks the user's answer where the head splices it back onto the instruction that raised the question. It is the rail's durable record that an ask was already put to the user: every deterministic recognizer reads the last answer as authoritative and none may raise that question again, because a recognizer that re-reads the older words asks the same question forever.

View Source
const (
	// CorrectionPrefix marks the block a correction splice carries. The
	// resident reads it to know that this splice is a revision of its target
	// rather than a new job that merely follows one.
	CorrectionPrefix = "Correcting delivered work:"
)
View Source
const CorrectionQuiet = 20 * time.Minute

CorrectionQuiet bounds adjacency once more than one delivered job is in the window. With a single settled job, position needs no clock: a result is the last word until the user answers it, however long they take to read it, and that argument is still exactly right. With four, the last node-anchored line is as likely to be some other job's heartbeat as the deliverable being corrected — so stale position stops being evidence and becomes a question.

View Source
const ForkedContextPrefix = store.ForkedContextPrefix

ForkedContextPrefix opens the inherited block in a pre-split instruction. It is the store's constant because the store is what reads old journal rows, and it stays named here because this is where the fence was minted for as long as there was a fence to mint.

NOTHING WRITES IT ANY MORE. The two halves travel as two typed fields on the command (store.Command.Instruction and .Context), which is the whole of this fix: a fence in one string is only a fence to a reader that looks for it, and every reader downstream was reading the string as the person's own words. The composition still exists — store.Command.Brief — but it exists for the ONE consumer that is entitled to both halves, the compiler.

View Source
const HeadInterruptKind = store.CommandHeadInterrupt

HeadInterruptKind is the command kind a journaled turn-cancel carries. It is an alias rather than a second spelling: the store owns the kind list, and two constants holding one string is how a kind quietly becomes two kinds.

View Source
const NoGlossNote = "The compiler supplied no reading of its own, so your request stands as the goal, word for word."

NoGlossNote is the receipt line for a compile whose goal came back blank. It is a constant so the surfaces that show it and the tests that pin it read one spelling.

View Source
const TaskModelPrefix = "Run this on:"

TaskModelPrefix marks the model a task tool's own `model` argument named. It is the RestartModelPrefix idiom for the commissioning half: store.Command has no model column, the instruction is the durable payload, and the head cannot resolve a name because the catalog lives with the surface that owns the slots. So the head writes down what was asked for and leaves resolution there.

Variables

View Source
var ErrAsideEmpty = errors.New("head aside: nothing was asked")

ErrAsideEmpty says the aside carried no question.

View Source
var ErrInterruptUnreachable = errors.New("head interrupt: no route to the turn in flight")

ErrInterruptUnreachable is returned when neither route exists.

Functions

func AbsorbStreamSession

func AbsorbStreamSession(sessionID string) string

AbsorbStreamSession is the stream key the absorption turn's deltas carry.

func AsideStreamSession

func AsideStreamSession(sessionID string) string

AsideStreamSession is the stream key an ephemeral turn's deltas carry.

func IsCorrection

func IsCorrection(instruction string) bool

IsCorrection reports that a splice is a revision of the work it targets. Exported for the resident half, which owns what happens next.

func LastCompilerAnswer

func LastCompilerAnswer(instruction string) (string, bool)

LastCompilerAnswer returns the most recent answer spliced onto instruction. Answers accumulate in order, so the last one is the live decision.

func MarkRestartModel

func MarkRestartModel(instruction string) string

MarkRestartModel appends the head's reading of a restart's model words to the instruction it journals. An instruction that names no model comes back byte-identical, so every restart that was silent stays silent.

func MarkTaskModel

func MarkTaskModel(instruction, model string) string

MarkTaskModel appends the head's reading of a task's model words. An empty argument comes back byte-identical, so every task that named no model stays exactly what the person said.

func ReceiptStreamSession

func ReceiptStreamSession(sessionID string) string

ReceiptStreamSession is the stream key the wake's deltas carry.

func RecognizesQualityIntent

func RecognizesQualityIntent(instruction string) bool

RecognizesQualityIntent reads the user asking for quality rather than for routine work. It is the signal media tools need to reach for "best".

func RecognizesServiceIntent

func RecognizesServiceIntent(instruction string) bool

RecognizesServiceIntent marks asks whose requested end-state is a running thing the user can continue to open or use. Ordinary "run tests" work is deliberately excluded.

func RecognizesStandingIntent

func RecognizesStandingIntent(instruction string) bool

RecognizesStandingIntent is the compiler's temporal reading: true means the ask survives one completion and therefore must cross ratification.

func SpliceCompilerAnswer

func SpliceCompilerAnswer(instruction, answer string) string

SpliceCompilerAnswer is the one way an answer rejoins its instruction.

func SpliceCorrection

func SpliceCorrection(words string, job store.Node, previous string, files []string, previousBytes int) string

SpliceCorrection is the one way a correction is written down. The user's words lead and are never touched — the same law every other instruction obeys — and the deterministic block follows, in the idiom attached documents and quality words already use: a compiler that ignores the prompt cannot lose what the sentence was about.

previousBytes is how much of the last version travels; a non-positive value is the literal this file argues for. It is the caller's number because the caller is the head, and how much of a deliverable fits in a brief is a question about the window the head is speaking through (budget.go).

Types

type Brief

type Brief struct {
	Goal        string   `json:"goal"`
	Assumptions []string `json:"assumptions"`

	// Constraints are the rules the request states about what the run may or
	// may not DO, in the person's own words.
	//
	// They are a field rather than prose because prose is not something a gate
	// can hold anything to. "Change no files" used to survive only inside the
	// goal and inside the working method, and the run that was told it ran the
	// command it was asked for, reported the line it was asked for, and was
	// then sent back by its own review to write a test file (#427). The field
	// travels to every spec of the job, is shown to every worker first, and is
	// held against the workspace's own before-and-after list at the gate. See
	// plan.Constraint and keepStatedConstraints, which is what keeps the field
	// to the person's words rather than the compiler's.
	Constraints []plan.Constraint `json:"constraints,omitempty"`

	// Title is the rail-sized display name for the job, produced by the one
	// call that has already read the whole ask. It used to be a second model
	// round-trip of its own — measured at 222-330 prompt tokens for a 5-token
	// answer, ~0.6s of the critical path before any leaf could start — for a
	// label nothing downstream waits on. Empty is a valid answer and the
	// caller falls back to whatever it named jobs with before.
	Title string `json:"title,omitempty"`

	// Scale is the compiler's honest judgement of shape: "lookup" (one fact,
	// one step), "task" (one worker end to end), or "project" (structured work
	// worth a planning pass). Downstream decides what to do with it; an
	// unrecognised value degrades to task.
	Scale string `json:"scale"`

	// Structure is the structural reading scale is supposed to follow from,
	// asked for in its own field so that it is ANSWERED before the label is
	// chosen rather than reconstructed to justify one.
	//
	// The prompt already said "scale is that judgement's answer, not a label
	// chosen first", and measured live that is exactly what went wrong: on an
	// ask that both enumerates and stratifies ("a short note on each one first,
	// and then one comparison table that uses all three notes") the compiler
	// returned "task" three times out of three — and the SAME model, on the
	// same prompt and the same sentence, returned "project" both times it was
	// additionally asked to quote the rule it was following. The judgement was
	// available; nothing made the model reach it before naming a label. So the
	// reading is a field, and reconcileScale below makes the label follow it.
	Structure string `json:"structure,omitempty"`

	// Contract is the working method for a task-scale job, written by the one
	// call that has already read the whole ask. It used to be a second
	// structuring round-trip serialized between compile and dispatch — a paid
	// call on the critical path of every single-worker job. Empty is a valid
	// answer and the caller falls back to that separate pass.
	Contract string `json:"contract,omitempty"`

	// Parts is the compiler's reading of how many separate answers the ask
	// wants: several requests in one breath that do not feed each other, each
	// written as a complete standalone assignment. The reading is made here,
	// where the whole ask was read; what shape the work then takes is decided
	// downstream by the planner, which is handed these words verbatim as
	// evidence and nothing more. There was once a route that took this field
	// as a layout instead, and its whole failure was that a layout cannot say
	// a request waits. Empty means one thing comes back.
	Parts PartList `json:"parts,omitempty"`

	// BuildsOn names earlier jobs this instruction continues or improves.
	// The reconciler turns each into a real dependency edge, so the prior
	// result flows to the new workers as an input digest instead of being
	// rediscovered or guessed at.
	BuildsOn []string `json:"builds_on"`

	// Question is the one gap too consequential to guess, when one exists.
	// Empty is the overwhelmingly common, correct value: asking is reserved
	// for irreversible or expensive mistakes, never for preferences.
	Question string `json:"question"`

	// TrialOf is the fact sequence of the retrieved unsettled pair that this
	// brief deliberately compares. Zero means no experiment was shaped.
	TrialOf int64 `json:"trial_of"`

	// QuestionOptions is the generic selectable askback surface. Charter is set
	// only by the temporal compiler; both omit cleanly for ordinary work.
	QuestionOptions []store.QuestionOption `json:"question_options,omitempty"`
	Charter         *store.CharterSpec     `json:"charter,omitempty"`
	// ServiceIntent is deterministic consent provenance; the provider never
	// gets to infer whether a process may outlive its leaf.
	ServiceIntent bool `json:"service_intent,omitempty"`

	// WorkModel is the model the user named for this job in their own words.
	// Empty is the ordinary case: the surface's current work model serves.
	WorkModel string `json:"work_model,omitempty"`

	// ModelNote is the one calm receipt line about that choice — which model
	// runs the job, or why the name they used did not land.
	ModelNote string `json:"model_note,omitempty"`

	// Note is the one calm receipt line the compiler itself adds, when it has
	// something to own up to: today, that it supplied no reading of its own and
	// the person's words stand as the goal. It is never read from the wire —
	// a model may not write the receipt about its own answer.
	Note string `json:"-"`
}

Brief is the complete, assumption-bearing intent handed to planning.

It carried a Deliverable and a Budget until this wave, and neither had a reader anywhere in the tree: the goal already names the deliverable, planning takes its money from measured history, and resident_build copies eleven fields across and dropped exactly these two. What they did have was a validator that hard-rejected an empty one with no retry, so an otherwise perfect request came back as "I couldn't apply that request: compile request: empty budget" for a field nothing would have consumed. A validated field with no consumer is a pure failure source, so both are gone from the schema and from validation.

type Client

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

Client is the one provider operation the conversational components need. Keeping the boundary this small makes both routing and compiling testable without a network.

type Compiler

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

Compiler converts verbatim user intent into a planning brief without asking the user to resolve unspecified details first.

func NewCompiler

func NewCompiler(client Client) *Compiler

NewCompiler returns an intent compiler backed by client.

func (*Compiler) Compile

func (c *Compiler) Compile(ctx context.Context, instruction string, graphContext string) (Brief, error)

Compile applies assume-and-declare once. The exact instruction is appended deterministically to Goal even if a provider ignores that prompt rule, so no downstream transformation can silently lose the user's words.

func (*Compiler) WithModelResolver

func (c *Compiler) WithModelResolver(resolve ModelResolver) *Compiler

WithModelResolver installs the surface's catalog-backed reading of model words. Without it the compiler still recognizes them and still says nothing wrong: every job simply runs on the default work model.

func (*Compiler) WithOneShotErrands

func (c *Compiler) WithOneShotErrands() *Compiler

WithOneShotErrands says every ask this compiler will ever see arrived on a surface that runs exactly one errand and then exits — `codeaf do`.

This is the surface stating a fact about itself, not an opinion about the work. "Once, not standing" is an option on the ratification card because a person may want it; a person who typed `codeaf do "<task>"` has already chosen it, in the verb, before the compiler read a word. Asking them again is asking a question into a process with nobody at the keyboard, and the live defect it caused was total: "flag every discrepancy" tripped the temporal recognizer's `every <word>` cue, a plain reconciliation of two CSVs was drafted as a standing rule with an invented two-minute cadence, and the run exited in three seconds having done none of the work it was sent to do.

So the temporal route is not taken here at all, and the ordinary compiler is told what surface it is compiling for. Nothing about the judgement of the WORK changes; the classification that changes is the one the surface already answered.

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

Head tails the durable thread and turns each new user message into one fast routing call, one reply, and at most one asynchronous command.

func New

func New(client Client, graphStore *store.Store) *Head

New returns a conversational head backed by graphStore. The window is unknown until a surface says otherwise, and unknown means every block renders at the literal it shipped with (budget.go).

func (*Head) ApplyInterrupt

func (h *Head) ApplyInterrupt(command store.Command) bool

ApplyInterrupt is the reconciler's arm, written here so the resident's edit is one line rather than a design. It reports whether there was a turn to stop, which is what the reconciler journals as the command's resolution.

The partial is deliberately not carried on the command: what the reader had seen is a property of the surface that was watching, not of the journal, and a stop arriving from a headless caller has no partial to carry. The turn's own machinery already keeps whatever the in-process path handed it.

func (*Head) Ask

func (h *Head) Ask(ctx context.Context, sessionID, question string) (string, error)

Ask runs one ephemeral turn and journals its collapsed stub.

It deliberately does NOT touch the turn machinery: turnCancel, turnFold and the partial belong to the conversation's own turn, and an aside asked while one is in flight must leave it exactly as it found it. That is 8.2.9's "the in-flight turn undisturbed", and it is why nothing here locks turnMu.

func (*Head) BackfillRoomNames

func (h *Head) BackfillRoomNames(ctx context.Context)

BackfillRoomNames names the rooms that existed before the scribe did.

The post-turn clerk only ever fires at the end of a turn, so every room whose last turn was already over when it landed stays "untitled room" forever, however much conversation is in it. That is the reported bug, and it is not a naming failure — it is a room that has never had a moment when naming was anyone's job. This is that moment, once per launch.

It is the same pass, not a second one: same claim, same read of what the room is about, same model rung, same normalizer, same one-per-room guard. A room somebody named by hand is never touched, because a title IS the record that this has run. And it happens off the launch entirely — the caller gets a goroutine and its own turn back — so a window opens at the speed it always did whether the naming works, fails, or never finishes.

The rooms are named one after another rather than all at once. Eight concurrent provider calls at the exact moment a window is opening is the kind of thundering start that makes a launch feel slow through no fault of the thing the person is waiting for.

func (*Head) ConfirmSurgery

func (h *Head) ConfirmSurgery(sessionID string, kind store.CommandKind, target string) (bool, error)

ConfirmSurgery is the confirm law offered to whoever else can journal a node command — today, the task page's single-key stop and restart.

The gates are the product's promise that nothing large is thrown away without somebody naming the loss out loud, and a keypress is not a smaller intention than a sentence. A key that journalled directly bought, for one accidental press, exactly what the conversational path has always had to ask for. So the key path asks this first: it reports true when it has asked, and the caller journals nothing — the durable question is the reply, and answering it replays the command through the same option machinery a spoken confirm does. False means the loss is small, and small work is simply done.

func (*Head) Interrupt

func (h *Head) Interrupt(partial string) bool

Interrupt stops the turn being answered right now and carries in whatever of it the reader had already seen. It reports whether there was a turn to stop, so a surface that asked at the wrong moment can tell that nothing happened.

func (*Head) RequestInterrupt

func (h *Head) RequestInterrupt(sessionID, reason, partial string) (InterruptRoute, error)

RequestInterrupt stops the head's turn in flight and reports how.

It journals first and on purpose: the funnel is the product's one authority path, and a stop that rode past it would be the second engine Part 3 forbids. Then it stops the turn in process, because until the reconciler's one case lands nothing drains that row, and a door that reports a stop while the turn carries on talking is worse than one that never claimed it.

The two roads are not a race. Both end at the same handle and the handle is idempotent: whichever arrives second finds no turn and says so.

The route reported is the durable one when the row landed, because that is the fact a surface needs — a journaled stop is replayable, reaches a headless caller and survives this process. NothingRunning is reserved for the case where neither road found anything at all to do.

partial is whatever of the turn the reader had already seen, so the transcript keeps the words that did arrive, marked where they stopped.

func (*Head) Serve

func (h *Head) Serve(ctx context.Context) error

Serve tails every session until ctx is cancelled.

On startup it resumes each session at that session's last non-user message. This intentionally replays only a trailing run of user messages: history ending in an agent or system message is treated as answered, while a crash after the user wrote but before the head replied remains recoverable. It is a deliberately simple journal rule; the cursor advances only after each message has been handled. The rule is asked per room because one number cannot state it for two — a reply in either would carry it past the other's unanswered rows, and those rows would never be read.

func (*Head) WithCompetenceMap

func (h *Head) WithCompetenceMap(competence func() string) *Head

WithCompetenceMap registers the derived capability view with the head's grounding path. It is read only for competence-shaped questions, and its structured data is voiced by the head's existing single routing call.

func (*Head) WithContextLength

func (h *Head) WithContextLength(tokens int) *Head

WithContextLength tells the head how much its talk model holds, in tokens.

It is a fact handed down rather than looked up, which is exec.Linear's own doctrine for the same question: the catalog belongs to the surface, and a package that reached for it would be a conversational head with an opinion about model metadata. Zero — a model the catalog cannot size, a visitor client that holds no catalog at all — is not read as small; it leaves the head exactly as New built it.

The budget is resolved here, once, for the life of the head.

func (*Head) WithDailyBudgetUSD

func (h *Head) WithDailyBudgetUSD(amount float64) *Head

WithDailyBudgetUSD lets the head render policy state and consume a pending rail question deterministically. Zero is unlimited.

func (*Head) WithImageInput

func (h *Head) WithImageInput(modalities interface {
	Supports(string, string, string) bool
}, defaultModel string) *Head

WithImageInput lets the routing head receive durable chat attachments as OpenAI-style image parts when its current talk model advertises vision.

func (*Head) WithMessageClient

func (h *Head) WithMessageClient(selectClient func(store.Message) (Client, error)) *Head

WithMessageClient selects a conversational client for one durable user message. Messages without an override continue through the Head's ordinary client; the callback is the single seam used by heavier chat lanes.

func (*Head) WithRoomNaming

func (h *Head) WithRoomNaming(enabled bool) *Head

WithRoomNaming turns on the post-turn clerk that names rooms (scribe.go).

It is a switch rather than always-on because it costs a provider call the TURN did not ask for, and only one kind of window is buying anything with it: one that draws a list of rooms. A headless errand has no rail, one room, and nobody to read a name — so the default is off and the chat window says so out loud, which also keeps every measurement of a turn's cost a measurement of the turn.

func (*Head) WithSelfKnowledge

func (h *Head) WithSelfKnowledge(knowledge func() string) *Head

WithSelfKnowledge supplies measured execution history to the routing call. Nil and empty values preserve the original prompt exactly.

func (*Head) WithStandingWatch

func (h *Head) WithStandingWatch(status func() string) *Head

WithStandingWatch registers the same calm status block used by doctor. It is read only for presence-shaped questions.

func (*Head) WithWorkspace

func (h *Head) WithWorkspace(root string) *Head

WithWorkspace tells the head where artifacts are born.

It is a builder rather than a constant because where a chat writes is a property of the surface that started it: `codeaf chat` in a project means that project's directory, a room under rooms will mean the room's own. Unset, the head writes into the process's working directory — the same choice `codeaf do` already makes for an errand pointed at somebody's own folder, and the one a person typing "write me the diagram" in a terminal expects.

type InterruptRoute

type InterruptRoute string

InterruptRoute says which way a stop actually travelled.

const (
	// InterruptJournaled is the destination: the stop is a durable row, replayed
	// like everything else, reachable from any surface and from a headless caller.
	InterruptJournaled InterruptRoute = "journaled"
	// InterruptInProcess is today: the handle the TUI's escape key already holds.
	InterruptInProcess InterruptRoute = "in-process"
	// InterruptNothingRunning is not a failure. A surface that asked at the wrong
	// moment must be able to tell that nothing happened.
	InterruptNothingRunning InterruptRoute = "nothing-running"
)

type ModelResolver

type ModelResolver func(ModelWords) WorkModelChoice

ModelResolver resolves recognized model words against the live catalog and the surface's slots. Nil leaves every job on the default work model.

type ModelWords

type ModelWords struct {
	// Boost means the ask named the boost slot rather than a model.
	Boost bool
	// Names are candidate model words in the order they appeared. The surface
	// resolves them against the catalog; the first that resolves wins.
	Names []string
	// Explicit means the ask said "model" beside the name, so an unresolvable
	// name is worth one calm receipt line rather than silence.
	Explicit bool
	// Answered means these words were read out of the user's answer to a
	// question this ask already asked. The choice is settled: whatever the
	// catalog says, the job proceeds and nothing asks again.
	Answered bool
}

ModelWords is the deterministic reading of the model words in a task ask.

func RecognizeModelWords

func RecognizeModelWords(instruction string) (ModelWords, bool)

RecognizeModelWords reads a task ask for the model the user asked for. An ask that already carries an answer is read answer-first: the answer is younger than the words that raised the question, and reading those words again is how the same question came back forever.

func RestartModel

func RestartModel(instruction string) (ModelWords, bool)

RestartModel reads back what MarkRestartModel wrote. The second return is whether the restart named a model at all; the first is the words to resolve, in the shape a ModelResolver already takes.

func TaskModel

func TaskModel(instruction string) (ModelWords, bool)

TaskModel reads back what MarkTaskModel wrote.

type PartList

type PartList []string

PartList tolerates the shapes models actually send when they list the ask's separate requests. The strict form is an array of strings; live models were measured wrapping each part in an object instead, and a declaration field must never be able to fail the compile — the worst legal outcome of a malformed parts array is no declaration, which is exactly what the field meant before it existed.

func (*PartList) UnmarshalJSON

func (p *PartList) UnmarshalJSON(data []byte) error

type WorkModelChoice

type WorkModelChoice struct {
	Model      string
	Candidates []string
	// Requested is the word the user actually used, for the receipt.
	Requested string
}

WorkModelChoice is the surface's answer about the model words in one ask. Exactly one of Model and Candidates is meaningful: a resolution, or the shortlist behind one choose question. Both empty means nothing matched.

Jump to

Keyboard shortcuts

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