config

package
v0.4.0 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: 40 Imported by: 0

Documentation

Overview

Package config holds the harness's process-wide defaults. It is the only place a model slug or an endpoint is written down, so changing the default model is a one-line edit rather than a search across the tree.

Index

Constants

View Source
const (
	KeyBashBackgroundAfter     = "bash.background_after_seconds"
	DefaultBashBackgroundAfter = 30
	BashBackgroundAfterHint    = "" /* 177-byte string literal not displayed */
)

The foreground-command handoff is a session setting rather than a tool timeout: it decides when the conversation moves on while the same process keeps running. Zero deliberately leaves the older timeout-only posture.

View Source
const (
	// DefaultModel is the harness's default. DeepSeek V4 Flash Latest is the
	// floating alias, so a new dated snapshot is picked up without a code change.
	// 1M context, ~$0.09/M in and ~$0.18/M out, and it advertises both
	// structured_outputs and reasoning on OpenRouter.
	//
	// A first-prompt stall on this default names `/model` rather than hanging
	// silent (F42). The slug itself is not swapped for a more expensive one:
	// the cheap default stays, and the stall is what must recover visibly.
	DefaultModel = "~deepseek/deepseek-v4-flash-latest"

	// DefaultVoiceModel is the independent speech-to-text slot. Voice never
	// enters the talk/work router: OpenRouter exposes it through the dedicated
	// audio transcription endpoint.
	DefaultVoiceModel = "qwen/qwen3-asr-flash-2026-02-10"

	// DefaultBaseURL is OpenRouter's OpenAI-compatible endpoint.
	DefaultBaseURL = catalog.DefaultBaseURL

	// DefaultSiteURL, DefaultSiteName and DefaultSiteCategories are the
	// OpenRouter app-attribution values this binary reports under
	// (HTTP-Referer, X-OpenRouter-Title, X-OpenRouter-Categories). They are
	// INTERPOLATED FROM internal/provider AND NOT RE-SPELLED, because the
	// package that writes the headers is the one place the values may live;
	// two copies of an app's identity is how one product's usage ends up on
	// two dashboard pages. Nothing resolves them from settings or the
	// environment — the app a request names is a fact about the product, not
	// an operator's preference.
	DefaultSiteURL        = provider.AppURL
	DefaultSiteName       = provider.AppName
	DefaultSiteCategories = provider.AppCategories
	// Preserve the names master exposed before the chat-v2 rollout, for any
	// caller still reaching them by the old spelling.
	OpenRouterAppURL  = DefaultSiteURL
	OpenRouterAppName = DefaultSiteName

	// DefaultDocumentEngine walks the deliberate local -> free -> rail-gated
	// OCR ladder. The other accepted values pin one rung and never fall through.
	DefaultDocumentEngine = "auto"

	// DefaultTimeout is generous because a reasoning pass can run for minutes on
	// a wide task even when the answer is short.
	DefaultTimeout = 300 * time.Second

	// DefaultReasoning IS ABSENCE, and so is [DefaultExecReasoning]. Nothing
	// about how hard a model thinks travels on a request this harness was not
	// told to shape: [provider.EffortNone] sends no `reasoning` object at all
	// and the model answers at its own published default.
	//
	// It used to be [provider.EffortOff], which is not silence but a REQUEST —
	// `{"reasoning":{"enabled":false}}` — chosen here on a latency argument
	// about planning and on an executor ablation about convergence. Both were
	// measurements of two models on two task shapes, and neither is a fact
	// about the model an operator points this binary at today: the same field
	// is a 400 on an endpoint that cannot switch thinking off, and a silent
	// downgrade on one that can. A harness that has not been asked for a level
	// asks for none.
	//
	// CODEAF_REASONING and CODEAF_EXEC_REASONING are unchanged and still take
	// off|low|medium|high — including `off`, which is how somebody who wants
	// the thinking pass actually suppressed says so and gets exactly the
	// request this default used to make on their behalf.
	DefaultReasoning = provider.EffortNone

	// DefaultExecReasoning is absence for the reason above: planning and
	// execution are different calls, and neither of them is a call this harness
	// has an opinion about the depth of.
	DefaultExecReasoning = provider.EffortNone

	// DefaultSpineSamples draws the spine more than once. It is the only call
	// whose framing every later pass inherits, so an unlucky draw does not
	// degrade the graph slightly — it replaces it. The samples run at the same
	// time, so this costs no wall clock and a fraction of a cent.
	DefaultSpineSamples = 3

	// DefaultMaxDepth bounds recursion. Depth is the only cost of decomposition
	// that is genuinely serial — a whole level expands in four call-rounds
	// however wide it is — so this is the guard that actually protects latency.
	DefaultMaxDepth = 2

	// DefaultNodeBudget is the 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 the recursion terminates.
	DefaultNodeBudget = 60

	// DefaultDailyBudgetUSD is the policy rail across every task using the
	// resident store. Token slices shape leaves internally; dollars decide when
	// new work needs the user's word. Zero disables the rail.
	//
	// IT IS DELIBERATELY LARGE — a backstop against a runaway, never a budget.
	// It sat at $20 and a single ordinary day of agent work reached it, so the
	// rail stopped being the thing that catches a loop and became the thing
	// that interrupts work: a person who had chosen no number at all was being
	// asked to raise a ceiling they never set. A rail nobody chose must only
	// fire where nobody would defend the spend, and $500 in one day is that
	// place. The number a person actually budgets with is the one they write
	// into the row themselves — and 0 there removes the rail entirely.
	DefaultDailyBudgetUSD = 500.0

	// DefaultPracticeBudgetUSD is the daily carve-out reserved for self-origin
	// curiosity work. The global rail remains an additional ceiling.
	//
	// IT IS A CARVE-OUT AND NOT A RAIL, which is why 0 reads the opposite way
	// here than it does on every other money row in this file: 0 is no practice
	// at all (internal/resident's WithPracticeLoop switches the loop off), never
	// unbounded practice. There is no way to spell "practice without a ceiling"
	// and that is deliberate — self-origin work runs while nobody is watching,
	// so it is the one pocket that always has a bottom. Raised with the rail
	// above so the carve-out is a real slice of a real day.
	DefaultPracticeBudgetUSD = 50.0

	DefaultPracticeIdle = 20 * time.Minute

	// DefaultBriefAfter keeps ordinary short breaks silent. A longer absence
	// earns one folded arrival summary when background life actually happened.
	DefaultBriefAfter = 4 * time.Hour

	// DefaultSwarm is whether cooperative decomposition is armed with nobody
	// having said anything about it. It is TRUE from the swarm-road wave on;
	// [Config.Swarm] carries the whole argument, and `CODEAF_SWARM=0` is the
	// escape hatch.
	DefaultSwarm = true
)
View Source
const (
	CrewFrugal   = "frugal"
	CrewBalanced = "balanced"
	CrewMax      = "max"
	// CrewCustom is a READING and never a write. It is what the row says when
	// the five tier values are somebody's own arrangement rather than one of the
	// three, which is what happens the moment a person answers one tier row
	// directly. It is deliberately absent from [CrewPresets]: "set the crew to
	// custom" is not a sentence with a meaning — custom is what you get, not
	// what you ask for.
	CrewCustom = "custom"
)

The preset words. They are the values KeyCrew takes, and they are strings on disk in the same sense every other choice row's words are — spelled here once, read by the row, the command and the manual.

View Source
const (
	// CrewSourceOpen is the open-weight family.
	CrewSourceOpen = "open"
	// CrewSourceAll is the whole catalog, closed and frontier models included,
	// and the family a profile that has answered nothing resolves.
	CrewSourceAll = "all"
)

The two families the preset words can draw from. They are the values KeyCrewSource takes, spelled here once and read by the row, the resolver and the manual.

View Source
const (
	// CrewPickTable is the measured rows this build ships, and the default.
	CrewPickTable = "table"
	// CrewPickCatalog is the catalog's own published figures, with nothing
	// measured on top.
	CrewPickCatalog = "catalog"
	// CrewPickLearn is the catalog computation plus the Model Pool's
	// measurements and the person's own judged runs.
	CrewPickLearn = "learn"
)

THE THIRD ROW THE CREW WORDS ARE ANSWERED THROUGH. The crew row says how much to spend and the family row says which shelf those budgets name; the pick row says where the models for that money come from when a tier row does not hold a model id of its own:

  • `table` — the rows this build measured and shipped ([crewModels] and [crewAllModels]), which is what an unwritten seat has always read;
  • `catalog` — the same three budgets recomputed off the catalog's own published prices and scores, on every read, with no measurement of anybody's own runs in it (AutoPickWith with no prior);
  • `learn` — the catalog computation plus the Model Pool's measurements and the person's own judged runs, carried as a quality prior ([autoPrior]).

THE DEFAULT IS THE TABLE because the table is what a profile has always read: an unwritten seat names the preset's own row, and nothing about a profile that has answered nothing moves until somebody answers a row. The other two words are an opt-in to a read that keeps moving — a seat that follows the catalog follows it whether or not the shipped rows do — and a person has to say so.

A PICK NEVER OVERRIDES A MODEL ID. The row answers for the seats a person did not name, and the seats they did — written by hand, or by a preset — keep their ids until the crew is picked again, except that a row holding the preset's own table value is the preset answering, not a person pinning one model by id. The rule is [pickedSeat]'s to apply and the manual's to state.

View Source
const (
	// ProjectConfigDir is the per-repository settings directory.
	//
	// THE LIVE DIRECTORY IS .codeaf. When its config.json is absent, reads still
	// accept the former .aforge-v3/config.json, but writes always name this live // legacy-name
	// path and nothing rewrites a person's repository on its own.
	ProjectConfigDir = ".codeaf"

	// ProjectConfigFile is the one file inside it this layer reads.
	ProjectConfigFile = "config.json"
)
View Source
const (
	ModelEnv      = "CODEAF_MODEL"
	PlanModelEnv  = "CODEAF_PLAN_MODEL"
	CheckModelEnv = "CODEAF_CHECK_MODEL"
)

ModelEnv, PlanModelEnv, and CheckModelEnv are the variables the seats read. They are spelled here once because the ladder, Load, and receipts name them.

View Source
const (
	// CategoryModels is what runs the work: one row per role the router has,
	// then the capability models beside them.
	CategoryModels = "models"
	// CategorySpending is every dollar the product will spend without asking,
	// and NOTHING that is not a dollar.
	CategorySpending = "spending"
	// CategorySafety is what codeaf may do without asking you first: the
	// approval gate, its exceptions, the model that stands in for you, and the
	// two clocks that answer when nobody does.
	CategorySafety = "safety"
	// CategoryTasks is how work you can walk away from is run — how it starts,
	// how it is checked, how much of it happens at once, and on whose hands.
	CategoryTasks = "tasks"
	// CategoryPractice is what codeaf does with its own time, and what it
	// remembers of yours.
	CategoryPractice = "memory & practice"
	// CategoryInterface is how the surface draws itself, and how it signs the
	// work that leaves the machine.
	CategoryInterface = "interface"
)

Category names are the faint lowercase words the sheet may announce a section with (15). There were seven, and five of them were labels doing structure's job: `rhythm`, `documents & vision` and `sharing` each announced two rows or one, which is a header naming a mechanism rather than a section a reader could otherwise not place. Deleting them is 15's own test — the rows still read, because position and spacing already said what the word said.

SPENDING IS MONEY AND NOTHING ELSE, and that is why there are six words here rather than four. `spending` had grown to hold twenty rows answering four different questions — what may it spend, what may it run without asking, how does it run tasks, and which workers exist — so a person looking for "how much may it spend" read about load averages and repair rounds first. The three questions are three sections now, and docs/design/spending/DESIGN.md is the argument: a category is what a row is ABOUT, and a category that answers four questions is a drawer rather than a section.

View Source
const (
	CategoryMoney      = CategorySpending
	CategoryLearning   = CategoryPractice
	CategoryAppearance = CategoryInterface
)

The old spellings, kept as aliases so a surface that still names one keeps compiling while it is being ported. They are the same four words; nothing resolves to a group that no longer exists.

View Source
const (
	KeyDailyBudget    = "daily_budget_usd"
	KeyPlanConsent    = "plan_consent_usd"
	KeyPracticeBudget = "practice_budget_usd"
	KeyPracticeIdle   = "practice_idle"
	KeyBriefAfter     = "brief_after"
	KeyTenureAfter    = "tenure_after"
	KeyDocumentEngine = "document_engine"
	KeyVisionModel    = "vision_model"
	// KeyModelPool is the stored word the pool's resolver takes: the same three
	// answers the CODEAF_MODEL_POOL pin and a CI environment may give it
	// (internal/pool/poolcfg). The resolver beside the search rows is the one
	// place the process environment is read for the pool.
	KeyModelPool = "model_pool"
	// KeyModelPoolPublicKey is the trusted key a fetched pool index is checked
	// under: base64 text beside the pool row it narrows, and empty for the
	// key the binary carries. The environment pin CODEAF_MODEL_POOL_PUBLIC_KEY
	// outranks it, through the same resolver.
	KeyModelPoolPublicKey = "models.pool.public_key"
	KeyAttribution        = "attribution"
	KeySplitPct           = "split_pct"

	// The two rows the v3 chat surface keeps on disk BESIDE the conversation:
	// what was typed, and what was half-typed. They are one pair of questions —
	// "may codeaf remember my own words between sessions" — and they are two
	// rows rather than one because they answer it at different depths: history
	// is every prompt ever submitted from this machine, the draft is the single
	// unsent sentence in front of you right now, and a person who wants the
	// second without the first (or the reverse) is not confused.
	KeyHistoryEnabled = "history.enabled"
	KeyDraftPersist   = "draft.persist"

	// KeyTelemetry is the anonymous-usage switch: one row, default on, off
	// turns the whole pipe (see internal/telemetry). It sits beside the
	// history row because they answer the same question at different
	// depths — "may codeaf record what happened on this machine" — and
	// the session counters count either way: only the sending asks.
	KeyTelemetry = "telemetry"

	// The v3 session's own keys. They are DOTTED where the older ones are
	// snake_case because they name a path into a settings tree the file writer
	// will eventually hold — tools.approval is a map, models.roles is a map —
	// and the flat text rows below are the readable stand-in until it lands.
	KeyToolApprovalMode = "tools.approvalMode"
	KeyToolApprovals    = "tools.approval"
	// KeyBashApprovals is the ordered rule list for the one tool whose arguments
	// are a language. The row above answers per TOOL — "never ask me about read"
	// — and there is no useful per-tool answer for bash: a person who allowed the
	// tool would have allowed every command it will ever be handed. This row is
	// where the answer can name the command, which is what internal/approval's
	// bash patterns are for and where the consent card's "always, this command"
	// lands (approvalmemory.go).
	KeyBashApprovals = "tools.bashPatterns"

	// KeyGuardian turns on the small model that answers a tool prompt before you
	// are asked (internal/session's guardian.go). It is named under `approval.`
	// rather than beside the two `tools.` rows above because it is not a rule
	// about tools at all: it is a statement about WHO ANSWERS — the person, or a
	// model standing in for them — and grouping it with the rule rows would file
	// it as one more exception in a list of exceptions.
	KeyGuardian = "approval.guardian"
	// KeyConsentTimeout is how long an approval question counts down before it
	// PAUSES and keeps waiting. It never answers for the person — silence is
	// not a no (F41) — and it stops the moment a key is pressed, because a
	// person who has started reading is a person who is going to answer.
	//
	// Seconds, not a duration string, for the reason [KeyTaskAutoApprove] is
	// spelled that way: the number is small and read at a glance off a line that
	// is counting it down. 0 turns the clock off and the question waits from
	// the start, which is what a person who reads every prompt wants.
	KeyConsentTimeout = "approval.timeout_seconds"

	// KeyPromptProfile is how much codeaf puts in front of the model before a
	// person has typed: the whole page and the whole tool list, or the lean
	// pair a small window can afford (internal/session's promptprofile.go).
	//
	// IT IS A ROW BECAUSE THE DERIVATION CAN BE WRONG. The profile is settled
	// from the model's context window and that is right almost every time, but
	// an endpoint that reports a window its loaded model does not really have
	// leaves the person with no way to say so, and a derived state with no row
	// anywhere is a state nobody can read off a screen. `auto` is the default
	// and keeps the derivation; the other two words are the person overruling
	// it, in the same words [EnvPromptProfile] takes.
	KeyPromptProfile = "prompt.profile"

	KeyTierLowModel  = "models.tiers.low"
	KeyTierHighModel = "models.tiers.high"
	// KeyTierWorkerModel is the seat that does the work — the worker of every
	// task, the parts it hands out, the nodes of an adaptive run
	// ([roles.TierWorker]). It reads from the PROFILE ALONE, unlike the two rows
	// above it, for the reason the task model row does: a repository that could
	// answer this could send a visitor's work — and their credit — to a model
	// they never picked, by being cloned.
	KeyTierWorkerModel = "models.tiers.worker"
	// KeyTierReflexModel is the third tier, and the only one with a model in it
	// out of the box. It is read TWICE A TURN by the routing and extraction
	// calls the reflex tier exists for (internal/reflex), which is a rhythm no
	// other auxiliary call has: a model that costs a tenth of a cent a call is
	// free on the low tier and is real money here. So the row ships pointed at
	// a model that costs near nothing rather than at "follows the conversation"
	// — a person who never opens the sheet gets the cheap thing, and a person
	// who clears the row gets the conversation's own model, deliberately.
	KeyTierReflexModel = "models.tiers.reflex"
	// KeyTierMastermindModel is the fourth tier and the only one whose model is
	// chosen for THINKING rather than for a price. Two roles ride it — the
	// planner that amends an adaptive run's plan after every node, and the
	// designer that writes a harness page everybody afterwards runs — and both
	// were on the careful-work tier beside the check on finished work, which made
	// one figure answer two unrelated bills: the careful calls are many and short,
	// these are few and decide what all the other calls do.
	//
	// It is the one row whose value may carry a LEVEL as well as a model
	// (`moonshotai/kimi-k3:low`), because it is the one row where how hard the
	// model thinks is the point. Every tier row accepts the notation —
	// [ValidateTierValue] is the same gate on all five — but this is the one the
	// shipped crew writes it into.
	KeyTierMastermindModel = "models.tiers.mastermind"
	// KeyCrew is the five tiers answered as ONE DECISION. Nobody arrives wanting
	// to name five model ids; they arrive wanting to spend pennies, or to spend
	// what it takes. So the row takes one word — frugal, balanced, max — and
	// writes all five tier rows from it.
	//
	// IT IS NOT STORED. The row's reading is DERIVED from the five live tier
	// values: they match a preset and it says so, or they do not and it says
	// custom. A stored word would be a claim about five other rows that any one
	// of them could falsify by being edited, and a settings sheet that told you
	// "balanced" over a hand-pinned tier would be lying in the one place a person
	// went to check.
	//
	// THE BUILD WRITES NO WORD HERE, AND READS ONE THAT IS. The derivation above is
	// what every profile this product shapes reads — but a run that wrote the
	// word itself (a harness, a hand edit) named a budget, and a crew word that
	// reached nobody is worse than a row five others can falsify: [storedCrewWord]
	// reads it as the budget its seats run at, and the class rows under it are
	// that run's own pins ([pickedSeat]).
	KeyCrew = "models.crew"
	// KeyCrewSource is which family the crew words draw from: `open`, the
	// open-weight table this build ships, or `all`, the same three words
	// resolved over the whole catalog with closed and frontier models in it.
	// IT IS A ROW RATHER THAN A SECOND VOCABULARY because the three words are
	// the only thing anybody learns: the question a person arrives with is how
	// much to spend, and which shelf the answer comes off is one more answer to
	// the same question, not six new preset words. The row is read by the
	// crew's own machinery (crew.go's [CrewSourceAt]) and never by a caller
	// spelling the ids itself, so the family and the tables cannot disagree
	// about what a preset means. It is PROFILE-ONLY with the tier rows, for the
	// worker row's own reason: a repository that could answer it could send a
	// visitor's work, and their credit, to a vendor they never chose.
	KeyCrewSource = "models.crew.source"
	// KeyCrewPick is where the crew's seats are picked from when a tier row
	// does not hold a model id of its own. The three words are read and
	// answered by the crew's own machinery (crew.go's [CrewPickAt] and
	// [SetCrewPick]) beside the words [KeyCrew] and [KeyCrewSource] take: the
	// crew row says how much to spend, the family row says which shelf those
	// budgets name, and this row says where the models for that money come
	// from — the rows this build measured, or a computation off the catalog
	// made again on every read, with or without what the Model Pool measured.
	// It is PROFILE-ONLY with the crew and family rows, for the worker row's
	// own reason: a repository that could answer it could send a visitor's
	// work, and their credit, to a model nobody on that machine chose.
	KeyCrewPick = "models.crew.pick"
	// KeyMouse is whether the surface reports the mouse at all. ON is the
	// default ([DefaultMouse]), because hover, click and the wheel are v3's own
	// language and the thing they cost is bought back by a key: an alt-screen
	// app that reports the mouse OWNS every drag, so the terminal's native text
	// selection dies the moment reporting starts — and `ctrl+s` hands the
	// pointer back for as long as somebody is dragging with it.
	//
	// THIS COMMENT SAID "OFF IS THE DEFAULT" while [DefaultMouse] said on, and a
	// stale sentence here is the expensive kind: it is the first thing anybody
	// reads when the answer to "does codeaf ask for the mouse at all" decides
	// whether a pointer bug is in this program or in the terminal. One source of
	// truth — [MouseModes] states the choice and [DefaultMouse] states the
	// answer, and this row's doc may not contradict either.
	KeyMouse = "ui.mouse"
	// KeyTimestamps is how much of the clock the v3 conversation carries: a
	// footer under every finished turn, only the coarse marks where the
	// conversation was put down and picked up again, or nothing at all
	// (internal/tui3's render.go). It is named beside the mouse row because it
	// is the same kind of question — how much furniture the transcript draws —
	// and it is a CHOICE rather than a bool because "less" and "none" are two
	// different answers a reader gives for two different reasons.
	KeyTimestamps = "ui.timestamps"

	// KeyTaskColumn is whether the v3 chat opens with the task roster's column
	// standing beside the conversation. It is a BOOLEAN — the column has no
	// middle rung: its two narrower tiers are decided by the frame's own width,
	// and the one answer a person gives it by hand is whether the column is
	// there at all.
	//
	// A person who put v3's column away has said
	// nothing whatever about v2's three rungs.
	KeyTaskColumn = "ui.task_column"
	// KeyQuickSwitch is whether the conversation switcher (internal/tui3's
	// hop.go) SWITCHES ON EACH PRESS of `ctrl+tab` — that chord lands you in the
	// previous conversation at once and pressing again keeps going — or opens as
	// a card that waits for `enter`. It is a BOOLEAN because the two behaviours
	// are the whole of the choice: there is no third rung between "the key is
	// the switch" and "the key is the menu".
	//
	// IT IS `ctrl+tab` AND NOT THE BINDING, which this doc said for a long while
	// and which the code has never done ([app.hopKey] reads the setting only for
	// the alias and its reverse). The binding always browses: it is the chord
	// everybody has, so it is the one that has to behave the same on every
	// machine, and a card that waits for `enter` is the behaviour that needs
	// nothing from the terminal. Neither gesture watches for a modifier being
	// RELEASED — ordinary terminals do not report that at all (hop.go).
	KeyQuickSwitch = "ui.quick_switch"
	// KeyHints is whether the v3 chat shows its earned hints — the one-line tips
	// in the slot above the message box that each retire once the key or command
	// they name has been used (internal/tui3's notice.go) — and, with them, the
	// one-line what's-new notes a new build may say. It is one row and not two
	// because a person who has silenced the tips has said they know the surface,
	// and being told about features is the same conversation.
	KeyHints = "ui.hints"
	// KeyWork controls whether completed turn machinery starts folded or open.
	KeyWork = "ui.work"
	// KeyIcons selects the step icon repertoire independently of colour.
	KeyIcons = "ui.icons"
	// KeyTaskAudit is whether an independent auditor verifies each task node
	// before its work may merge (internal/session's task_audit.go). It sits
	// beside the guardian because both spend a model on the person's behalf:
	// the guardian to answer, the auditor to check.
	KeyTaskAudit = "task.audit"
	// KeyReplyGuard is whether a reply that has stopped being language is cut
	// and asked again (internal/provider's streamguard.go). It is a row because
	// the cut is a JUDGEMENT about somebody else's text, and a person who writes
	// in four alphabets, or who asks for pages of repeated output outside a code
	// fence, is entitled to say they would rather see whatever arrives.
	//
	// The silence watchdog beside it has no row: a request that produced nothing
	// at all has failed by any reading.
	KeyReplyGuard = "reply.guard"
	// KeyTaskStart is what a bare `/task <brief>` COSTS TO SHAPE, and it is no
	// longer a question about shape at all (internal/tui3's taskcommand.go).
	// Every answer starts ONE WORKER. What the answers differ in is whether a
	// small sizing call reads the brief first, because a yes from that call is
	// what arms the worker to hand the work out mid-run once it has opened the
	// material and found the width is real (internal/session's task_divide.go).
	//
	// It is a row because the question is not really about one task. Somebody who
	// does not want the sizing call on their bill is refusing it every time, and
	// that is a preference stated once rather than on every `/task`.
	//
	// THE PLANNED-GRAPH ANSWER IS GONE, and with it the last way a preference
	// could open an adaptive run behind somebody's back. A `/task` takes one road
	// now — one worker that divides itself from the material — and there is no
	// second road left to prefer: no chat door reaches the planner at all, not a
	// setting, not a hand on the belt, and not a form of words somebody types
	// (internal/session's loop.go). A profile still holding the retired word reads
	// as the default, silently, the way any word this build does not know reads
	// ([TaskStartAt]).
	KeyTaskStart = "task.start"
	// KeyMemoryEnabled is whether this build remembers anything across
	// conversations at all (internal/session's memory.go): the pre-turn router
	// that decides which remembered lines a turn needs, the post-turn pass that
	// decides whether the exchange held anything worth keeping, and the
	// `remember` tool the model reaches for. Off is a conversation that starts
	// knowing nothing about you, and that makes not one extra call.
	KeyMemoryEnabled = "memory.enabled"

	// KeyStandingBackground is whether this machine's own scheduler keeps
	// standing items current when no codeaf window is open (internal/standing's
	// watch.go). It is dotted with the other v3 keys, under `standing.` because
	// that is the thing it is about, and it is PROFILE-ONLY — deliberately
	// absent from [ProjectKeys]: installing a timer is a change to somebody's
	// machine, and a checked-in file that could make one is a repository
	// arranging to run a program on every laptop that clones it.
	//
	// ON IS THE DEFAULT and the row is the only place it is ever asked. What
	// the row READS is derived from the timer itself rather than from this
	// value, so the sheet cannot say `on` over a machine where nothing is
	// installed; this value is the person's INTENT, which is what the launch
	// repair reads before it puts a drifted timer back
	// ([BackgroundChecksWantedAt]).
	KeyStandingBackground = "standing.background"

	KeyModelRoles = "models.roles"
	// KeyModelFallbacks is the ordered list of models a conversation moves to
	// when no endpoint serving the one it is on will accept the request at all
	// (internal/provider's endpoints.go). Comma-separated slugs, first tried
	// first. Empty lets the catalog pick the nearest same-class model instead.
	KeyModelFallbacks = "models.fallbacks"
	KeySpendRail      = "session.spendRailUSD"

	// KeyRouting is how a session asks the router to choose among the endpoints
	// serving one model (internal/provider's velocity.go). A model is not one
	// machine: the same id is fanned over several endpoints that answer at very
	// different speeds for the same price, and this row is which of those
	// differences the session is willing to pay attention to.
	KeyRouting = "routing"

	// KeyLaneGuard is whether one slow answer may be rescued by asking a second
	// lane the same question while the first is still thinking (internal/lane's
	// watch.go). It is a separate row from [KeyRouting] because it answers a
	// different question: routing says WHICH lane a request prefers, and this
	// says whether a request that has already gone wrong is allowed to spend a
	// little more to come back on time.
	//
	// It is on by default. The extra call is capped at one per answer and at a
	// tenth of what the session spends, and it is off under price routing,
	// where nobody is buying seconds at all.
	KeyLaneGuard = "lane.guard"

	// KeyTaskAutoApprove is the countdown a proposed task waits before it
	// starts on its own (internal/session's task.go). It is named under `task.`
	// rather than beside the approval rows for the reason the guardian row is
	// named apart from them: this is not a rule about what may run, it is HOW
	// LONG YOU GET to say something about work that is going to run either way.
	//
	// Seconds, not a duration string, because the number is small and read at a
	// glance under a bar that is counting it down — "5" is the row, "5s" would
	// be the row pretending to be a unit it never varies.
	KeyTaskAutoApprove = "task.autoapprove_seconds"

	// KeyTaskRepairRounds is how many times a task whose work came back
	// INCOMPLETE is sent back to close the gaps before it lands as a failure
	// (internal/session's task_audit.go). It is named under `task.` beside the
	// countdown and the audit row because it answers their question in the third
	// currency: those say how long you get to redirect work and who checks it,
	// this says HOW MANY TIMES a piece of work that nearly landed is allowed to
	// finish itself.
	//
	// A count rather than a word, and 0 turns it off: the number is the whole
	// setting, and the person who wants the old behaviour — one pass, and a gap
	// is a dead node — writes 0 rather than learning a vocabulary. It is small on
	// purpose. Each round is another worker and another check on the same node's
	// bill, and a loop that could run five times is a loop that can spend five
	// times without anybody watching.
	KeyTaskRepairRounds = "task.repair_rounds"

	// KeyTaskParallel is how many tasks may RUN AT ONCE (internal/session's
	// task_run.go). It is named under `task.` with the countdown and the repair
	// count because it answers their question in the fourth currency: those say
	// how long you get to redirect work, how many times it may finish itself,
	// and whose hands it is in — this says HOW MUCH OF IT happens at the same
	// time.
	//
	// BLANK IS NO LIMIT, and that is the default. The number of tasks was never
	// the resource: what runs out is this machine's cores and memory — the two
	// rows below — and the model provider's rate limit, which the adapter reads
	// off 429s and adapts to on its own (internal/provider's limiter.go). This
	// row exists for the person who wants a number anyway, and it is a QUEUE and
	// not a refusal: work past the cap waits and starts when a slot frees.
	KeyTaskParallel = "task.parallel"

	// KeyTaskMaxLoad is the load average PER CORE at or above which no new task
	// is started (internal/session's task_pressure.go). It is one of the two
	// real ceilings the cap above stopped pretending to be.
	//
	// Per core, so that the number means the same thing on a laptop and on a
	// workstation: 1.5 is "half again as many runnable threads as there are
	// cores to run them", which is where the scheduler starts handing out slices
	// rather than running work and one more build makes every build slower. 0
	// turns the check off. It holds STARTS only — nothing already running is
	// ever touched, so the pressure drains on its own.
	KeyTaskMaxLoad = "task.max_load"

	// KeyTaskMinFreeMB is the floor of available memory below which no new task
	// is started (internal/session's task_pressure.go), in mebibytes, and the
	// other half of the ceiling above.
	//
	// It reads the kernel's MemAvailable — what a new process could actually get
	// — and not free memory, which on a working machine is near zero by design
	// because the page cache has the rest. 0 turns the check off.
	KeyTaskMinFreeMB = "task.min_free_mb"

	// KeyTaskModel is the model a task runs on when the conversation does not
	// name one for it (internal/session's taskmodel.go). It is named under
	// `task.` beside the countdown rather than among the `models.` rows because
	// it is not a rule about how a call is made: it is WHOSE HANDS the work that
	// leaves this conversation ends up in, which is the same subject the
	// countdown and the audit rows are about.
	//
	// Blank is the conversation's own model, which is what every task ran on
	// before the row existed: a node is the same worker doing the same job
	// somewhere quieter.
	KeyTaskModel = "task.model"

	// KeyTaskSettle is WHO DECIDES a task that finished but that nobody could
	// check (internal/session's task_contract.go). It sits under `task.` beside
	// the audit row because it is the other end of that row's question: the audit
	// says whether the work is checked at all, and this says what happens when
	// the check came back with nothing.
	//
	// `ask` is the default and puts the decision on the landed card, where the
	// person answers it with one press. `auto` hands it to the chat: the landing
	// note tells the model to read the work and settle it, and to come back only
	// when it genuinely cannot tell. Both leave the same three answers available
	// to both of them — this row changes who is asked first, and nothing else.
	KeyTaskSettle = "task.settle"

	// The web-search rows. They are four rather than one because they answer
	// four separable questions: WHERE a lookup goes, and the three credentials
	// that change what "where" can mean. A person with no key still searches —
	// internal/search's last rung takes none — so the keys are an upgrade and
	// never a prerequisite, and none of the three has to be answered for the
	// session to be able to look something up.
	KeySearchProvider = "search.provider"
	KeyExaKey         = "search.exaKey"
	KeyFirecrawlKey   = "search.firecrawlKey"
	KeyJinaKey        = "search.jinaKey"

	// The two rows that let a person connect their Google account
	// (internal/connect). They are a PAIR and neither is useful alone: an
	// application id names the application asking, and its secret proves the
	// ask came from it, so a build holding one of them can offer exactly
	// nothing. The session reads them together for that reason
	// ([GoogleOAuthClientAt]), and offers the connect tools only when both are
	// answered.
	//
	// Answering them REPLACES the registration this build ships with
	// (connect_defaults.go): a person or a deployment that wants its own
	// application asking — its own quota, its own consent screen, its own
	// revocation — writes it here or exports it, and nothing else changes.
	KeyGoogleOAuthClient = "google_oauth_client"
	KeyGoogleOAuthSecret = "google_oauth_secret"

	// The one row that lets a person replace the Slack application this build
	// ships with (connect_defaults.go). It is ONE ROW AND NOT A PAIR because
	// Slack's PKCE registration has no secret. An organisation that registers
	// its own internal application to escape the outside-Marketplace throttle
	// pastes that application's id here.
	KeySlackOAuthClient = "slack_oauth_client"

	// The four context-law knobs. Fill is how much of a model's window any
	// agent may use before compaction fires — and WHETHER A PERSON SET IT is
	// itself part of the law, because a conversation nobody has pinned folds
	// against a line derived from its model's window instead
	// (ctxbudget.PinnedFillPercent, internal/session's compactThresholdOf);
	// the reserve is the room every
	// call keeps for its answer and its reasoning; the working set caps what
	// an agent keeps quoted in front of itself however large the window is;
	// and reuse caps how many times over it may re-send that working set.
	KeyContextFill       = "context_fill_pct"
	KeyCompletionReserve = "completion_reserve"
	KeyWorkingSet        = "working_set_tokens"
	KeyContextReuse      = "context_reuse_pct"
)

Persisted keys are also the json field names in the profile's config.json. KeyDailyBudget keeps the name /budget default already writes.

View Source
const (
	GuardianOff = "off"
	GuardianOn  = "on"
)

The guardian row's two answers. It is a CHOICE and not a bool for the reason every other two-word row here is one: "off/on" is what the sheet renders and what the file holds, and a person reading their config.json back should find a word they chose rather than a `true` they have to remember the question for.

View Source
const (
	MouseOff = "off"
	MouseOn  = "on"
)
View Source
const (
	TaskAuditOff = "off"
	TaskAuditOn  = "on"
)
View Source
const (
	ReplyGuardOff = "off"
	ReplyGuardOn  = "on"
)
View Source
const (
	// TaskSettleAsk puts it in front of the person, on the landed card.
	TaskSettleAsk = "ask"
	// TaskSettleAuto hands it to the chat, which reads the work and decides.
	TaskSettleAuto = "auto"
)

The two answers to KeyTaskSettle: who settles a task that finished with nobody able to say whether it holds.

View Source
const (
	TaskStartSized  = "sized"
	TaskStartSingle = "single"
)

The two answers to KeyTaskStart, and they are not two settings but one question asked once instead of on every `/task`: what a bare `/task` pays to find out before its one worker starts.

sized      one worker, with the sizing call read over the brief first. A yes
           from it arms that worker to hand the work out as it goes, once it
           has opened the material and found the width is real — so wide work
           runs wide without a planner ever being asked to guess at it.
single     one worker, and the sizing call is not made at all. The work can
           still divide itself, but only off what its own brief already says
           (internal/splitgate), because nothing was read over it.

THERE WERE FOUR, AND BOTH THE RETIRED ONES NAMED A CARD OR A ROAD THAT IS GONE. `ask` went with the two-row chooser a yes from the sizing call used to raise; `adaptive` went with the planned-graph road itself, which a chat turn may no longer open at all. Neither retirement is allowed to be felt: a profile still holding either word reads as the default, silently, because TaskStartAt treats a word this build does not know as no answer at all — and being told that a preference set months ago is now an error is the one thing a retirement must never do. NEITHER RETIRED WORD IS SPELLED HERE, and `ask` set that precedent: a constant for a word nothing may write, nothing may offer and nothing may resolve to is a name the next reader has to be told is not a mode. The retirement needs no constant to work — it is TaskStartModes not containing the word, which is what the sheet, [writeChoice] and TaskStartAt all read.

View Source
const (
	TimestampsFooters    = "footers"
	TimestampsSeparators = "separators"
	TimestampsOff        = "off"
)

The timestamps row's three answers, and they are a LADDER rather than three unrelated pictures: each rung draws strictly less of the clock than the one above it.

footers      the turn footer (· 14:02 · 2m12s · 3 tools · $0.04 ·) AND the
             gap and day marks, which are what a footer is read against
separators   only the marks: where the conversation was put down for ten
             minutes, and where a day ended
off          no clock at all

The middle rung exists because the two features answer the same question at different costs: a reader who wants to know that yesterday's exchange was yesterday does not necessarily want a line of figures under every turn, and a footer with no day mark above it would be a time with no date.

View Source
const (
	WorkFold = "fold"
	WorkOpen = "open"
)
View Source
const (
	IconsAuto  = "auto"
	IconsRich  = "rich"
	IconsPlain = "plain"
)

IconModes keeps the normal rich presentation and its compatibility floor selectable without changing the terminal's colour or animation settings.

View Source
const (
	MemoryOff = "off"
	MemoryOn  = "on"
)
View Source
const (
	// PromptProfileAuto derives the profile from the model's context window,
	// which is what every session did before this row existed.
	PromptProfileAuto = "auto"
	// PromptProfileLean is the shorter page and the shorter tool list, whatever
	// window the model reports.
	PromptProfileLean = "lean"
	// PromptProfileFull is every law on the page and every ordinary verb in the
	// tool block, whatever window the model reports.
	PromptProfileFull = "full"
)

The prompt profile's three answers, and they are three rather than two because the honest default is not a size at all: it is "work it out". A row offering only `lean` and `full` would force everybody to hold an opinion about a figure the catalog already knows.

View Source
const (
	BackgroundOff = "off"
	BackgroundOn  = "on"
)

The background-checks row's two answers.

View Source
const (
	// RoutingLatency asks for the currently-fastest endpoint.
	RoutingLatency = "latency"
	// RoutingPrice asks for the cheapest one that can serve the request.
	RoutingPrice = "price"
	// RoutingSimple sends no preference of ours at all: when no provider is
	// pinned the router's own default answers, and a pinned provider is the
	// whole request.
	RoutingSimple = "simple"
	// RoutingOff sends no preference at all, and stops measuring with it.
	RoutingOff = "off"
)

The routing row's four answers. They are spelled here rather than imported from internal/provider for the reason DocumentEngines is: a settings key's vocabulary is a string on disk, and it must not change because a package renamed a constant.

View Source
const (
	// LaneAuto lets the belief pick a lane per answer. It is the default and it
	// is what an unset row reads as.
	LaneAuto = "auto"
	// LaneOpenRouter asks for no lane at all and lets the router balance on
	// price, which is what this build did before it held an opinion.
	LaneOpenRouter = "openrouter"
)
View Source
const (
	ModelTierLow  = "low"
	ModelTierHigh = "high"
	// ModelTierWorker is the seat that does the work ([roles.TierWorker]).
	ModelTierWorker = "worker"
	// ModelTierReflex is the per-turn tier ([roles.TierReflex]).
	ModelTierReflex = "reflex"
	// ModelTierMastermind is the thinking tier ([roles.TierMastermind]).
	ModelTierMastermind = "mastermind"
)

The two tier names internal/roles resolves auxiliary calls under. They are spelled here rather than imported for the reason DocumentEngines is: the registry is a settings surface, and a settings key is a string on disk that must not change spelling because a package renamed a constant.

View Source
const (
	DefaultReflexModel = "google/gemini-2.5-flash"
	DefaultLowModel    = "deepseek/deepseek-v4-flash-0731"
	// The worker is the seat that pays most of a task's bill, so it is the last
	// seat a preset spends on: a step here multiplies through every token a task
	// runs up, where a step on the two low-volume seats is paid a handful of
	// times. glm-5.3-flash is the point on the long-cached-loop front that
	// balanced runs at, and it can see images, which the parent can hand it
	// without a vision detour.
	DefaultWorkerModel = "z-ai/glm-5.3-flash"
	// The careful tier is ALWAYS A DIFFERENT VENDOR FROM THE WORKER, in every
	// preset, and always a model that sees images: a check from a second vendor
	// catches what the first vendor's blind spots let through, and the vision
	// role rides this row.
	DefaultHighModel = "anthropic/claude-fable-5.1"
	// The mastermind names a capable planning model. Its generation behavior is
	// left to the provider unless an operator adds a level to the model id.
	DefaultMastermindModel = "anthropic/claude-opus-5"
)

THE SHIPPED CREW. All five tiers arrive pointed at a model, and the five together are exactly the `balanced` row of the DEFAULT FAMILY (crew.go's [crewAllModels], named by DefaultCrewSource) — which is what makes the crew row read "balanced" on a profile nobody has touched instead of reading "custom" about its own defaults.

Each is a bare OpenRouter id, spelled ONCE here and read by every caller through TierModelAt, so the model this build considers near-free is one string rather than a figure repeated in a row, a resolver and a page.

Blank is still an answer on every one of them: a row a person emptied on purpose reads empty and the roles on it follow the model the person is talking to, which is roles.Resolve's floor. UNSET and CLEARED are different answers here, and that distinction is the whole mechanism (TierModelAt says how).

The ids are read off the catalog's own published rows — its intelligence, coding and agentic indexes against its prompt, completion and cache-read prices, priced under each seat's own call shape (crew.go's [crewAllModels] comment owns the method and the date). The low row is pinned to a DATED build on purpose: the bare `deepseek/deepseek-v4-flash` id resolves to the April build, and the July build costs the same.

View Source
const (
	// DefaultPlanConsentUSD is where ambition stops being cheap. Below it a
	// plan simply runs, because asking about a small errand is the nagging
	// nobody wants; above it the user is quoted a count and a price and gets to
	// say no first. It is deliberately far under the daily rail: the rail is a
	// stop after the fact, and this is the moment before.
	//
	// IT WAS $3 AND $3 IS THE PRICE OF AN ORDINARY PIECE OF WORK, so the
	// question fired on nearly every plan and became a keystroke the person
	// owed rather than a decision they made — which is the failure mode a
	// consent gate cannot survive, because a question asked every time is a
	// question nobody reads. At $100 the gate fires on the plans a person
	// would genuinely want to see priced first. 0 never asks.
	DefaultPlanConsentUSD = 100.0

	// DefaultTenureAfter is the clean-firing count a standing charter needs
	// before it earns tenure.
	DefaultTenureAfter = 3

	// DefaultAttribution signs by default, because the signature is provenance:
	// work the user did not type should be readable as such by whoever reads
	// the history later. One row turns it off.
	DefaultAttribution = true

	// The divider clamps so neither pane can be set into uselessness. The TUI
	// reads these so the drag, the [ ] nudge, and the sheet agree.
	DefaultSplitPct = 80
	MinSplitPct     = 25
	MaxSplitPct     = 85

	// DefaultHistoryEnabled remembers what was typed, because a prompt is the
	// most expensive sentence in the product to re-type and the up arrow is the
	// cheapest way to get it back. The file is a recall list and nothing else —
	// internal/history caps it and never sends it anywhere.
	DefaultHistoryEnabled = true

	// DefaultTelemetry is on, because the events are coarse counts the
	// contract allows (docs/TELEMETRY.md) and the notice names them before
	// the first byte leaves the machine. One row turns the pipe off, and
	// DO_NOT_TRACK answers the same question for anyone who arrives with
	// the ecosystem's own word for it.
	DefaultTelemetry = true

	// DefaultDraftPersist keeps the unsent sentence across a restart, for the
	// reason a text field in any other application does: the draft is the
	// PERSON's, not the session's, and losing it to a crash or a closed window
	// is the surface throwing away the only thing on screen it did not write.
	DefaultDraftPersist = true

	// DefaultToolApprovalMode opens new conversations in YOLO unless a saved
	// profile, project or conversation choice supplies another posture.
	DefaultToolApprovalMode = "allow"

	// DefaultGuardian is off because choosing YOLO does not appoint a model
	// to answer approval questions. The guardian remains an explicit choice.
	DefaultGuardian = GuardianOff

	// DefaultSpendRailUSD is 0 — no per-session ceiling. The rail that is on by
	// default is the DAILY one, because that is the number a person actually
	// budgets; a session ceiling is for the sitting somebody wants to box in,
	// and a default would box in every sitting at a number nobody chose.
	DefaultSpendRailUSD = 0.0

	// DefaultTaskAutoApprove is fifteen seconds, and the direction it is wrong in
	// is the whole choice. A task proposal is not a permission question — the
	// model has already groomed the work and the brief, and the countdown is the
	// person's window to REDIRECT it or wave it off. Five seconds once looked
	// long enough to read a title and summary and reach for a key; a real card,
	// with its where line and three answers, was gone before the person's no.
	// Fifteen keeps silence as approval without making every task a keystroke the
	// person owes, and gives the actual card enough time to read.
	DefaultTaskAutoApprove = 15

	// DefaultTaskRepairRounds is ONE, and one is the whole argument. The failure
	// this exists for is a piece of work that came back nearly right — a report
	// covering ten of the eleven companies it was asked for — and died, leaving
	// the person to type the whole task again by hand. One round is what turns
	// that into a task that finishes: the same worktree, the same brief, and the
	// gaps in front of a fresh worker.
	//
	// It is not two, and it is not five. A second round buys much less than the
	// first — work that is still wrong after being told exactly what is missing
	// is work whose brief is wrong, and no number of rounds fixes a brief — while
	// every round costs another worker and another check on the same node. So the
	// default closes the near-misses and stops.
	DefaultTaskRepairRounds = 1

	// DefaultTaskParallel is NO LIMIT, and the change of mind it records is
	// worth the sentence. The frontier used to hold two tasks at once, and two
	// was a guess standing in for a resource nobody had measured — it left a
	// sixteen-core machine idle behind a queue of ready work, and it was still
	// one too many on a laptop already carrying somebody else's compile. The
	// number of tasks is not what runs out. So the count became a row a person
	// may set when they want one, the ceilings became the two below, and the
	// default is the honest one: as much as the machine and the provider will
	// carry.
	DefaultTaskParallel = 0

	// DefaultTaskMaxLoad is one and a half runnable threads per core, which is
	// the number an earlier incident settled on: codeaf pinning a laptop's fan
	// by running real compilers and real test suites beside each other
	// (internal/exec's governor.go carries the same figure for the same class of
	// work). Below it the machine is busy; at it, the scheduler is handing out
	// slices and one more task makes every task slower.
	DefaultTaskMaxLoad = 1.5

	// DefaultTaskMinFreeMB is a gibibyte and a half, which is roughly what one
	// more task needs to be worth starting: a child agent, a git worktree, and
	// whatever build it is about to run. Starting one under that floor is how a
	// machine reaches the OOM killer, and what the OOM killer takes is not the
	// task that was too many — it is whichever process was largest, which on a
	// developer's machine is usually theirs.
	DefaultTaskMinFreeMB = 1536

	// DefaultConsentTimeout is ten seconds of reminder, and it is a different
	// number from the one above because it is a different KIND of clock. The
	// task countdown runs toward the permissive answer, so it is kept short
	// enough to notice. This one used to run toward the refusal (F41) and
	// does not: at expiry the question pauses and keeps waiting. Ten seconds
	// is long enough to read a command and a rule; after that the card stays
	// up until somebody answers.
	DefaultConsentTimeout = 10

	// DefaultSearchProvider pins nothing. Auto is the only default that stays
	// right as a person's keys change: the day they paste an Exa key the
	// searches move to Exa without a second row being touched, and the day it
	// expires they keep searching instead of getting an error from a plug they
	// pinned six months ago and forgot.
	DefaultSearchProvider = SearchProviderAuto

	// DefaultTaskColumn stands the v3 task column up on a session that has never
	// been told otherwise: THE COLUMN TEACHES BY EXISTING — it is how a person
	// finds out that this chat runs work you can walk away from. Once they put it away we never stand it up again on
	// their behalf — the strip is what keeps running work reachable from a frame
	// with no column on it (internal/tui3's taskstrip.go).
	DefaultTaskColumn = true

	// DefaultQuickSwitch controls immediate switching with ctrl+tab where the
	// terminal can deliver it. alt+k always browses before opening a chat.
	DefaultQuickSwitch = true

	// DefaultHints shows the v3 chat's tips to a profile that has never said
	// otherwise. The tips retire themselves the moment each is acted on, so the
	// default costs a veteran one line per gesture they already know, once.
	DefaultHints = true
)

Defaults the registry owns beyond the ones config.go already declares.

View Source
const (
	// KeyResponseAttempts is N: how many times ONE request is tried on its own
	// tier before the wire is given up on. The first try is included.
	KeyResponseAttempts = "response.attempts"
	// KeyResponseLiftAfter is K: how many checks must read finished work and
	// find gaps in it, on the same tier and with the wire ruled out, before a
	// stronger model is bought.
	KeyResponseLiftAfter = "response.lift_after"
	// KeyResponseLiftCap is what that stronger model may cost ONE piece of
	// work, in dollars. 0 is no cap.
	KeyResponseLiftCap = "response.lift_cap_usd"
)

The keys the boundary's numbers persist under. They are named `response.` because that is what the boundary reads — one response, and what kind of failure it was — rather than `task.` or `model.`, either of which would put the row beside the wrong question (internal/taxonomy).

View Source
const (
	PlanPausedWait     = "wait"
	PlanPausedUseMeter = "use pay-as-you-go"
)
View Source
const (
	KeySSHControlPersist = "ssh.control_persist_seconds"
	KeySSHServerAlive    = "ssh.server_alive_seconds"
	KeySSHServerMisses   = "ssh.server_alive_misses"
	KeySSHIPQoS          = "ssh.ip_qos"

	DefaultSSHControlPersist = 5 * 60
	DefaultSSHServerAlive    = 3
	DefaultSSHServerMisses   = 3
	DefaultSSHIPQoS          = "lowdelay"
)
View Source
const APIKeyEnv = "OPENROUTER_API_KEY"

APIKeyEnv is the environment variable that outranks the profile's key. It is spelled once here because three surfaces name it to a person: the missing-key sentence at the door, the settings row's pin, and the first-run page.

View Source
const AutoValue = "auto"

AutoValue is the bare word a tier row holds to have its seat computed from the catalog.

View Source
const (
	// BestModelWord is the one spelling that means "the strongest advertised
	// model for this modality", resolved from the catalog at call time.
	BestModelWord = "best"
)
View Source
const CrewCommand = "/crew"

CrewCommand is the one door that ends the inheritance [Notice] describes. It is a constant so the sentence and the surface that answers to it cannot drift apart — the notice is printed by four commands that cannot reach it, and a remedy naming a door that has been renamed is worse than one naming none.

View Source
const DefaultBackground = BackgroundOn

DefaultBackground is on.

View Source
const DefaultCrew = CrewBalanced

DefaultCrew is what a profile nobody has touched reads. It is balanced because the five shipped tier defaults ARE the balanced row of the DEFAULT FAMILY — see [crewAllModels] — and that identity is asserted by a test rather than trusted.

View Source
const DefaultCrewPick = CrewPickTable

DefaultCrewPick is the table: the rows this build measured are where an unwritten seat's model comes from until somebody answers the row.

View Source
const DefaultCrewSource = CrewSourceAll

DefaultCrewSource is all: the three words are read off the whole catalog unless the row says otherwise, and the five shipped tier defaults are that family's balanced row.

View Source
const DefaultLaneGuard = true

DefaultLaneGuard is on: a slow answer is worth one extra call to rescue, and the budget around it is what keeps that true rather than a hope.

View Source
const DefaultMemory = MemoryOn

DefaultMemory is on.

View Source
const DefaultMouse = MouseOn

DefaultMouse is on.

View Source
const DefaultPromptProfile = PromptProfileAuto

DefaultPromptProfile is auto.

View Source
const DefaultReplyGuard = ReplyGuardOn

DefaultReplyGuard is on.

View Source
const DefaultRouting = RoutingSimple

DefaultRouting is simple: what a person asked for is what goes on the wire, and nothing else does.

IT WAS `latency` UNTIL THIS BUILD, and the reason it moved is that the choosing was not visible. Sorting by speed brings a whole apparatus with it — a ranking this process keeps, a price ceiling, refusals learned from earlier answers, a pin retired against a belief saved from an earlier run — and each of those is a decision nobody watched being made. What the picker showed, what was chosen, and what the record said were three answers to one question. Under this row they are one answer: with no lane pinned the request carries no preference at all and the router's own default routing answers it, and with a lane pinned that pin is the whole request. `latency` and `price` are both still here for somebody who wants the apparatus, one word away (internal/provider's velocity.go).

View Source
const DefaultTaskAudit = TaskAuditOn

DefaultTaskAudit is on.

View Source
const DefaultTaskSettle = TaskSettleAsk

DefaultTaskSettle is ask.

View Source
const DefaultTaskStart = TaskStartSized

DefaultTaskStart is sized: one worker that knows whether it is allowed to divide, which is the shape the measured road is built around.

View Source
const DefaultTimestamps = TimestampsFooters

DefaultTimestamps is the footers. When a turn took two minutes and cost four cents, those are facts about work the person paid for, and a transcript that never says when anything happened cannot be read back a day later.

View Source
const DefaultWork = WorkFold
View Source
const EnvPromptProfile = "CODEAF_PROMPT_PROFILE"

EnvPromptProfile pins the profile for one launch, in the same three words the row takes. It is spelled here and read from here by internal/session's promptprofile.go, because a pin the sheet renders read-only and a pin the engine obeys must be one string or they drift.

View Source
const KeyAPIKey = "api_key"

KeyAPIKey is the profile field the provider key lives in. It predates the settings registry — the background timer's copy of the environment key has always landed here — and the registry's row (settings.go) writes the same field, so a key pasted on the first run, one typed into /settings and one copied from the shell are one value in one place.

View Source
const KeyChatModel = "model.talk"

KeyChatModel is where that choice is written in the profile's config.json.

It is the SETTINGS ROW'S OWN KEY — ModelSettingKey for the talk slot — rather than a name of this file's invention, so if the registry ever persists its model rows itself the two halves land on one key instead of two. It is spelled as a constant because a key is a constant; [TestChatModelKeyIsTheRow] pins the two spellings together.

View Source
const KeyEffort = "effort"

KeyEffort is where the install's rung is written in the profile's config.json.

It is spelled bare rather than under a `model.` or `task.` prefix because it is not about one slot or one kind of work: it is the default for all of them, which is exactly what the resolver's last rung means.

View Source
const KeySetupSeen = "setup_seen_at"

KeySetupSeen is the profile field that says the setup has been shown. It is a timestamp rather than a bool for the one thing a bool cannot answer later — which features arrived after this person's first day — and it lives in config.json beside KeyAPIKey rather than in a stamp file because it is a fact about the profile, and the profile has one file.

It is NOT a settings row: nothing about it is a preference, and a row a person could edit would be a way to be greeted twice.

View Source
const LaneSlotTalk = "talk"

LaneSlotTalk is the ONE slot the settings registry carries a row for: the conversation. Every slot has a key — a task worker can be pinned by the same grammar — but a panel with nine lane rows on it would be a panel about endpoints rather than about models, and the other eight are set from the picker on the model they belong to.

View Source
const NoLimitWord = "no limit"

noLimitWord is what a money row says about itself when its number is zero.

A DOLLAR ROW READING "$0" IS THE ONE PLACE THE EMPTINESS LAW CANNOT REACH. Zero on these rows is not an absence — it is the person's own instruction, and the instruction it spells is the OPPOSITE of what "$0" reads as at a glance: "no money at all" where the code means "no ceiling at all". Every other kind of row has a word for its off state (Setting.EmptyLabel) and a dollar row cannot borrow one, because its reading is a formatted number and therefore never empty. So the word goes where a row already says the dim true thing beside its value: the receipt.

It is EXPORTED because the surfaces say it too — the v3 spend place's pointer line and the settings tab's `today` reading both have to spell "nothing bounds this" and there is one spelling of it.

View Source
const PoolQualityMetric = "role_quality"

PoolQualityMetric names the index metric the picker reads a measured seat quality from: a gaussian metric whose mean is on the same 0-100 scale crewpick scores a seat on, with one cell per role and model.

View Source
const ProfileDirEnv = "CODEAF_PROFILE_DIR"

ProfileDirEnv is the variable that moves the whole profile — the key, the settings file, the measured behaviour — somewhere else. It is what an isolated run sets, and it is spelled here once so every reader of the profile asks the same question.

View Source
const SearchProviderAuto = "auto"

SearchProviderAuto is the row's default: no pin, and internal/search walks its own ladder — the keyed plug when its key is present, the zero-key plug otherwise. It is spelled here rather than as the empty string because a choice row has to have a word for "I did not choose", and "" would render as a blank cell nobody can tell from a broken read.

View Source
const UnitInLabel = "-"

UnitInLabel is the Setting.Unit of a row whose own LABEL names what is counted: `tasks at once 3` needs no `tasks` on the end, and `task repair rounds 1 round` would be the row saying `rounds` twice. It reads as nothing and it is not nothing — it is the row saying it was looked at.

Variables

View Source
var AutoIndex func() *index.Index

AutoIndex is how the Model Pool's measurement index reaches seat resolution: the binary holding the index sets it ONCE AT START-UP, from whatever read it already made, and never a fetch — a tier row that says auto resolves from the index already in hand, the same posture AutoModels keeps. Nil is an ordinary state, not an error: a row that says auto then reads the catalog's own figures alone, which is what it read before this seam existed.

View Source
var AutoModels func() []catalog.Model

AutoModels is how the catalog reaches seat resolution: the binary holding the catalog sets it ONCE AT START-UP, from its non-blocking read, and never a fetch — a tier row that says auto resolves from whatever the catalog already holds, the same posture every other catalog reader here keeps. Nil, and a func answering with no rows, are ordinary states rather than errors: a row that says auto then reads the family's table row ([autoRow]), which is the answer it had before this word existed.

View Source
var AutoOwnCells func() []crewpick.Cell

AutoOwnCells is how an install's own judged scores reach seat resolution: the binary holding the pool sets it ONCE AT START-UP, from the own sheet it read from disk under the pool directory, the same posture AutoIndex keeps. Nil is an ordinary state, not an error: the prior then reads the index's cells alone, which is what it read before this seam existed. The cells are never held to the index's min_installs — an install's own scores are its own evidence, one observation of which is worth having.

View Source
var BackgroundModes = []string{BackgroundOn, BackgroundOff}

BackgroundModes lists them, on first — which is the default. Something you asked to happen every morning is something you asked to happen on the mornings you do not open a terminal, and a person who wanted it only while they were sitting there says so here.

CrewPicks lists the words a person may WRITE, narrowest first: the shipped table, then the catalog on its own, then the catalog with what runs measured. A pick word is not a preset and names no budget — the crew row above it still does that.

CrewPresets lists the words a person may WRITE, cheapest first. CrewCustom is not among them; see its own comment.

CrewSources lists them, open first, which is the order the row widens in: the narrower shelf, then the whole catalog. The default is the second of them, DefaultCrewSource, because this list is about width and not about which one a profile starts on.

View Source
var DocumentEngines = []string{"auto", "local", "free", "ocr"}

DocumentEngines are the four rungs CODEAF_DOC_ENGINE accepts.

View Source
var EffortChoices = func() []string {
	choices := make([]string, 0, len(effort.Rungs)+1)
	choices = append(choices, "auto")
	for _, rung := range effort.Rungs {
		choices = append(choices, rung.String())
	}
	return choices
}()

EffortChoices offers the provider default followed by the five explicit levels. Auto means no reasoning override, not disabled reasoning.

View Source
var ErrNoAPIKey = errors.New(APIKeyEnv + " (or OPENAI_API_KEY) is required")

ErrNoAPIKey is what Load returns when nothing — the two variables, the profile file — holds a key. It is a value rather than a sentence so that a door can tell "no key" from "config broken": the first is a person who has not set up yet, and the interactive chat opens anyway and asks them (LoadKeyless); the second stops the launch, whoever is watching.

View Source
var GuardianModes = []string{GuardianOff, GuardianOn}

GuardianModes lists them, off first — which is also the default, and the order the row widens in.

View Source
var MemoryModes = []string{MemoryOn, MemoryOff}

MemoryModes lists them, on first — which is the default. A colleague who forgot every preference you stated the moment you closed the window would be one you had to brief again every morning, and the calls that carry this are the cheapest the surface makes.

View Source
var ModelPoolChoices = []string{"on", "read", "off"}

ModelPoolChoices are the answers the pool row accepts, and the same words the pool's resolver reads from its environment pin.

ModelTiers lists the five tier words in the order a settings surface renders them, cheapest first. It is roles.Tiers spelled as the words on disk, and [tierKeyFor] is total over it.

View Source
var MouseModes = []string{MouseOn, MouseOff}

MouseModes lists them, on first — which is also the default: hover, click and the wheel are the surface's own language. Selecting text does not die for it — ctrl+s hands the pointer to the terminal for as long as somebody is dragging with it, and ctrl+b copies from the keyboard — but a person who wants the terminal's plain drag at all times turns the row off.

Shift+drag is the answer this hint used to give, and it was the wrong one: it is true, it is a fact about terminals rather than about this product, and the terminals that have it disagree about the modifier (Option in iTerm2). A key this surface owns is a key this surface can print.

View Source
var OperatorEnvPins = []string{
	"CODEAF_BASE_URL",

	"CODEAF_CHECK_MODEL",

	"CODEAF_HOME",

	"CODEAF_RELAY",

	"CODEAF_FURROW",

	"CODEAF_PROFILE_DIR",

	"CODEAF_NO_UPDATE_CHECK",
	"CODEAF_GITHUB_API",
	"CODEAF_GITHUB_DOWNLOAD",

	"CODEAF_INSTALL_NAME",

	"CODEAF_CALL_LOG",
	"CODEAF_CALL_LOG_BODIES",

	"CODEAF_TELEMETRY_ENDPOINT",

	"CODEAF_DEBUG",
	"CODEAF_TRACE_MAX_MB",
	"CODEAF_TRACE_KEEP",
	"CODEAF_MODELS",
	"CODEAF_REASONING",
	"CODEAF_EXEC_REASONING",

	"CODEAF_EXEC_TURNS",
	"CODEAF_EXEC_BUDGET",
	"CODEAF_EXEC_TIMEOUT",

	"CODEAF_MAX_HOURS",
	"CODEAF_MAX_COST",
	"CODEAF_SPINE_SAMPLES",
	"CODEAF_MAX_DEPTH",
	"CODEAF_NODE_BUDGET",
	"CODEAF_SKILL_DIR",
	"CODEAF_SKILLS_BIN",
	"CODEAF_RTK",
	"CODEAF_RTK_BIN",
	"CODEAF_PREAUTHORIZE_SPEND",

	"CODEAF_WIRE_LOG",

	"CODEAF_QUESTION_DEMO",

	"CODEAF_SUITE_DIRLOCK_PATH",

	"CODEAF_GROWTH_GATE",

	"CODEAF_SWARM",

	"CODEAF_SPLITGATE",

	"CODEAF_MECHANISM",

	"CODEAF_QUORUM",

	"CODEAF_EXIT_CODES",

	"CODEAF_RESPONSE_ATTEMPTS",
	"CODEAF_RESPONSE_LIFT_AFTER",
	"CODEAF_RESPONSE_LIFT_CAP",

	"CODEAF_QUESTION_DEMO",

	"CODEAF_TASK_BELT",

	"CODEAF_PLANDB_BIN",

	"CODEAF_MODEL_POOL_RELAY_URL",
	"CODEAF_MODEL_POOL_URL",
	"CODEAF_MODEL_POOL_SUBMIT_URL",
	"CODEAF_MODEL_POOL_MIRROR_URL",
	"CODEAF_MODEL_POOL_TTL",
}

OperatorEnvPins is the explicit allowlist of environment variables that are plumbing rather than settings: endpoints, credentials, profile roots, and planner internals. They are listed read-only in the sheet's environment footer and never become editable rows.

ProjectKeys is the allowlist: the rows that may be answered by a repository.

It is an allowlist rather than "whatever the registry has" for two reasons. A checked-in file must not be able to move somebody's daily budget or point their vision model at a model they pay for — those are the PERSON's rows. And an unrecognised key here is not an error (a later version's row, another tool's section), so without a list of what this build honors there would be no way to tell a row that does not apply yet from a row that never will.

PromptProfileModes lists them, auto first, which is the default.

View Source
var ReplyGuardModes = []string{ReplyGuardOn, ReplyGuardOff}

ReplyGuardModes lists them, on first — which is also the default. A reply that has come apart is worth almost nothing and costs the whole of the next request, because it goes back into the conversation and the model reads its own soup before writing more.

RoutingModes lists them, simple first — which is also the default, and the order the row cycles in.

View Source
var SSHIPQoSChoices = []string{"lowdelay", "af21", "none"}
View Source
var SearchProviders = []string{SearchProviderAuto, "firecrawl", "duckduckgo", "exa", "jina-search"}

SearchProviders are the answers the search row accepts.

The names are STRINGS HERE and not search.RegisteredSearch, for the reason DocumentEngines is a literal: a settings value is a word on disk, and a list derived from a registry would silently change what a person's saved answer means the day a plug is renamed or one is added. The cost is that a new plug needs a line here to be pinnable — which is the right cost, because a plug nobody can name in the sheet is still reachable through auto. jina-search is named here too so every registered search plug is pinnable.

SettingCategories is the render order of the sheet. Spending leads the three new sections because "what may it spend" is the question people arrive with; safety and tasks follow it in the order the design's own hierarchy names.

View Source
var TaskAuditModes = []string{TaskAuditOn, TaskAuditOff}

TaskAuditModes lists them, on first — which is also the default: a node that verifies its own work is the whole point of the verified frontier, and the audit's cost is the price of trusting what merges.

View Source
var TaskSettleModes = []string{TaskSettleAsk, TaskSettleAuto}

TaskSettleModes lists them, ask first — which is also the default. Deciding on somebody's behalf is a thing they say yes to, never a thing they get by saying nothing.

TaskStartModes lists what may be chosen, the default first. The retired word is not in it, and that absence is the whole mechanism — the sheet's choices, the writer's validation and the resolver all read this one list.

TimestampModes lists them richest first, which is also the default order the row widens in.

View Source
var ToolApprovalModes = []string{"prompt", "allow", "deny"}

ToolApprovalModes are the three answers the tool gate can be set to, in the order they widen: ask about everything, run everything, refuse everything. They are internal/approval's own words — the registry must not invent a fourth spelling for a decision that package already names.

View Source
var WorkModes = []string{WorkFold, WorkOpen}

Functions

func APIKeyAt

func APIKeyAt(profileDir string) string

APIKeyAt is the key a session opened on this profile would talk with, in Load's own order: the OpenRouter variable, the OpenAI one, then the profile file. It is the reading the settings row and the first-run setup share, so neither can say "no key" while Load would have found one.

func APIKeyConfigured

func APIKeyConfigured(profileDir string) bool

APIKeyConfigured is whether a session opened on this profile has a key to talk with, from anywhere Load would look.

func ActiveConnectionFor added in v0.3.0

func ActiveConnectionFor(model string, sources modelsource.Set) (modelsource.Connected, bool)

ActiveConnectionFor answers the service a conversation's live model answers on. THE ACTIVE CONNECTION IS DERIVED FROM THE CONVERSATION'S MODEL, NOT THE SHARED PROFILE: a caller holding the model this conversation actually runs ([app.model] in the talk surface, the deferred target while a move waits out a working turn) must not be routed through ChatModelAt, which is the LAST model any conversation settled on and is written asynchronously by the engine host — two tabs on different connections would read each other's. The blank-model and empty-set guards are the only logic here; everything else is [Set.For], which answers with the default service when no connected Written prefix matches — so the switcher that hands this set around reaches the default service the same way the conversation itself does. False only when there are no services at all, or the conversation has settled on no model yet.

func AnyTierAutoAt added in v0.3.0

func AnyTierAutoAt(profileDir string) bool

AnyTierAutoAt answers whether any of the five tier rows reads `auto` on this profile — written directly, or reaching the word through an older row ([crewRow]) — which is the second way a seat resolution is computed from the catalog's rows, beside the pick row (CrewPickAt). A door that resolves seats asks both before it resolves, because both answers are computed from the rows the process already holds, and a door that asks before they land reads the family's table row over a profile that never chose it.

func ApplyCrew

func ApplyCrew(profileDir, preset string) error

ApplyCrew writes all five tier rows from one preset, IN ONE FILE WRITE.

The five keys land together or not at all. Five separate writes would leave a window — one process crash, one full disk — in which two classes belong to the old crew and two to the new, and the crew row would read "custom" about a state nobody chose. It is also the only shape in which a reader that happens to be resolving a role while somebody presses enter cannot see half a crew.

The preset is resolved under the family CrewSourceAt names, so the row and the write cannot disagree about which table the word means: flip to `all`, press the crew again, and the five ids that land are the all-family ones.

func ApplyCrewUnder added in v0.3.0

func ApplyCrewUnder(profileDir, source, preset string) error

ApplyCrewUnder writes a FAMILY AND A PRESET AS ONE DECISION, IN ONE FILE WRITE. The family row and the five tier rows are one state, so writing them apart leaves a window in which a reader sees the family set to `all` while the rows still hold open ids, which is the half-written crew ApplyCrew forbids, read as `custom` about a state nobody chose.

The family row rides along ONLY WHEN IT CHANGES, so an enter that keeps the family does not pin a setting the person never answered: the five tier rows are written and the family row stays unanswered, free to follow a later default family.

func AttributionAt

func AttributionAt(profileDir string) bool

AttributionAt resolves whether codeaf signs the git work it does for the user. A malformed pin reads as the default rather than refusing a launch over a signature.

func AutoPick added in v0.3.0

func AutoPick(tier, family, preset string, models []catalog.Model) (modelID string, ok bool)

AutoPick is the pick with the measured quality the Model Pool holds ALWAYS carried as a prior: AutoPickWith with [autoPrior], whatever a profile's pick word says. No seat resolves through it any more — the bare `auto` row ([autoRow]) and the pick row's words ([pickedModel]) decide the prior together in [priorFor] — and it stays for a caller that wants the measured computation regardless of the pick word.

func AutoPickWith added in v0.3.0

func AutoPickWith(tier, family, preset string, models []catalog.Model, prior crewpick.Prior) (modelID string, ok bool)

AutoPickWith is the pick itself, with the measured quality NAMED: prior is carried into the front as a rating seats read on top of the catalog's own published scores, and a nil prior leaves every seat on those scores alone — the answer the `catalog` pick word carries, where `learn` passes the pool's measurements and the person's own judged runs ([autoPrior]).

It is PURE: no disk, no network, the rows are only read, and the same rows give the same answer however often it is asked and in whatever order they arrive (crewpick breaks its ties by id, not by order). Each row is read as a crewpick candidate — the three indexes, the three prices AS PUBLISHED (a uniform scale changes no pick on a front sorted by bill), whether the provider published a cache-read price at all, the window, the modalities and the parameters — and a row whose price the provider did not publish is left out, because a model that may cost anything has no place in a pick that is about cost.

tier names the seat the pick is read from: worker, high and mastermind have answers, reflex and low and any other word do not. family narrows the shelf: `open` picks off the open-weight rows, every other word off the whole catalog. preset is one of the three crew words, and any other word has no answer. No rows, an empty front or an empty id are no answer too; no answer is ok false and an empty id, which is the caller's cue to read the family's table row instead ([autoRow], [pickedSeat]).

func BackgroundChecksAt

func BackgroundChecksAt(profileDir string) string

BackgroundChecksAt resolves the background-checks row to its word, default on. It is the person's INTENT and not a reading of the machine: what the settings row shows is derived from the timer ([Settings.build]), and this is what the row was last told.

func BackgroundChecksWantedAt

func BackgroundChecksWantedAt(profileDir string) bool

BackgroundChecksWantedAt is BackgroundChecksAt as the bool the launch reads before it repairs a timer that has drifted off a program that moved (cmd/codeaf's chatv3_process.go). A person who turned the row off is a person whose machine must stay as they left it.

func BashApprovalsAt

func BashApprovalsAt(profileDir string) string

BashApprovalsAt resolves the bash rules row as the person wrote it. The text is the record; ParseBashApprovals is how a caller reads it.

func BashBackgroundAfterAt

func BashBackgroundAfterAt(profileDir string) int

BashBackgroundAfterAt resolves the foreground handoff clock in seconds. A persisted zero is the person's answer, not an absent value.

func BashShapes

func BashShapes(line string) []string

BashShapes derives the shapes one command line could be remembered as, in the order the card offers them: the command's own words first, then the program alone, then the line itself.

The order is not width. `git status*` is narrower than `git *` and leads anyway, because it is the shape a person pressing always on git status usually means, and the widest reading of what they did is the one that has to be read carefully rather than the one that is offered first. The LINE is last for the reason the head of this file gives.

A COMPOUND LINE DERIVES NOTHING. An allow rule vouches only for a command it matches whole (internal/approval's bash.go), so every shape drawn off `cd /tmp && rm -rf build` would be a rule that cannot fire, and offering one would be offering somebody a choice between three things that do nothing.

The result is never longer than [bashShapeCount], and where it is not empty its last entry is the trimmed line — which is what lets the card say "just this line" about that entry and mean it.

func BestMediaModel

func BestMediaModel(models *catalog.Catalog, modality string) string

BestMediaModel resolves the documented preference order for one modality against what the catalog advertises right now.

func BriefAfterAt

func BriefAfterAt(profileDir string) (time.Duration, error)

BriefAfterAt resolves the absence that earns an arrival brief.

func BudgetConfigPath

func BudgetConfigPath(profileDir string) string

BudgetConfigPath is config.json in codeaf's state root unless CODEAF_PROFILE_DIR supplies the same alternate root used by measured profiles.

func CandidateMediaModel

func CandidateMediaModel(models *catalog.Catalog, modality string) string

CandidateMediaModel is the CATALOG rung of the use-time resolver: the best model the catalog advertises for one modality, by the documented preference order and then by the catalog's own order.

It is deliberately not BestMediaModel. That one answers a person who typed "best" and falls back to the most expensive advertised row, because price is the only quality signal a catalog row carries and somebody who asked for the best has asked to be spent on. A fallback nobody asked for must not reach for the most expensive thing on the shelf.

func ChatModelAt

func ChatModelAt(profileDir string) string

ChatModelAt is the conversation model this profile last settled on, or empty when nobody has chosen one. Empty is not a failure and never a model name: the caller falls through to its own default, which is what an unconfigured install has always opened on.

func ClampSplitPct

func ClampSplitPct(pct int) int

ClampSplitPct keeps the divider inside the usable band. Zero stays "unset" and resolves to the default at layout time.

func ClientConfigFor

func ClientConfigFor(sources modelsource.Set, model string) provider.Config

ClientConfigFor assembles the provider settings for one model out of the services a caller already holds.

IT IS THE ONE PLACE A KEY AND A BASE URL BECOME A provider.Config, and that is the law rather than a convenience: six sites used to compose that literal themselves, so a level that belongs beside the slug travelled inside it and a second service would have reached none of them. A caller that holds a whole profile wants Config.ClientConfig; this door is for internal/session, whose own Config carries the account and nothing else. THE MODEL ID NOW DECIDES THE ACCOUNT, and nothing above this line chooses a key or a base URL.

func CompletionReserveAt

func CompletionReserveAt(profileDir string) int

CompletionReserveAt resolves the answer-and-reasoning reserve the same way.

func ConnectService

func ConnectService(ctx context.Context, profileDir string, row PersistedSource, src modelsource.Source, authors []string) (modelsource.Outcome, error)

ConnectService proves a key and says what it reaches, and writes nothing on any answer but yes. Ten seconds; a person is watching this one.

func ConsentTimeoutAt

func ConsentTimeoutAt(profileDir string) int

ConsentTimeoutAt resolves the approval countdown, in seconds. 0 is a clock that is off: the question waits from the start. A positive number is how long the reminder runs before the card pauses; it never answers no.

It tests ok before it tests the number for the reason TaskAutoApproveAt does: a persisted 0 is a person who turned the clock off, not an absence.

func ContextFillAt

func ContextFillAt(profileDir string) int

ContextFillAt resolves the fill law: environment pin, then the persisted row, then the package default. A malformed pin reads as the default.

func ContextReuseAt

func ContextReuseAt(profileDir string) int

ContextReuseAt resolves the cumulative re-send allowance the same way.

func Credentials

func Credentials(profileDir string) []string

Credentials is every credential this profile is configured with, in the exact values a call would carry — the key the models are talked to with, the search keys, an application secret somebody pasted for their own Google registration.

IT EXISTS SO THAT A RECORD OF A RUN CAN PROMISE A PERSON THEIR KEY IS NOT IN IT. The debug record (internal/trace) redacts credentials by shape — `Bearer …`, an `sk-…` token, a field spelled `authorization` — and a shape is only ever the keys somebody thought of: a Google `AIza…`, a Groq `gsk_…` or a self-hosted endpoint's plain token would land verbatim in a folder a person is about to attach to a bug report. The exact values are the only thing that catches a key whose shape nobody has seen, and this is where a process asks for them.

THE SETTINGS REGISTRY IS THE SOURCE, and this walks it rather than keeping a second list: a row that marks itself `Secret` is a credential, and a credential row added later is covered here the day it is added, with no second place to remember. Values are read the way every one of those rows reads them — the row's own environment variable first, then the profile file — and the empty ones are left out, so an unconfigured machine registers nothing and every record stays exactly as it was written.

func CrewAt

func CrewAt(profileDir string) string

CrewAt is the crew as the five live tier values make it: the preset they are, or CrewCustom.

It reads through TierModelAt, so a profile that has never been touched reads the shipped defaults and therefore reads DefaultCrew — the five defaults are the balanced row and nothing here needs to know that separately. A tier a person cleared on purpose reads empty, matches no preset, and turns the answer to custom, which is the truth: "one of these follows the conversation" is not any of the three.

The comparison runs against the family CrewSourceAt names, so the reading moves with the row and never behind it: flip the family and a crew the old family wrote matches nothing, which reads as custom and is true, because one family's five ids are not any preset of the other.

func CrewClassModels

func CrewClassModels(profileDir string) []string

CrewClassModels is the three ids CrewClasses names, in that order and without the role words in front of them:

claude-opus-5, glm-5.3-flash, claude-fable-5.1

It exists because a surface drawing the crew line has to be able to say which runs of it are the ANSWER — the ids a person typed /crew to change — and which are the labels around them (internal/tui3's payload.go). Reading them back out of the sentence would be a second parser for a string this file just built, so the sentence is built from this list instead and the two cannot disagree about how many models there are or which order they come in.

func CrewClasses

func CrewClasses(profileDir string) string

CrewClasses is the three class names alone:

brain claude-opus-5 · hands glm-5.3-flash · checks claude-fable-5.1

It is the tail of CrewSummary lifted out because a second surface prints the crew now — /status, where the word already has a label of its own and "crew →" in front of it would say the word twice. ONE SOURCE OF TRUTH: the three names, their order and their separator are spelled here once, so the confirmation a person reads after /crew and the line they read in /status cannot drift into naming the same four models differently.

func CrewConfigured

func CrewConfigured(profileDir string) bool

CrewConfigured is whether a person has ever answered the crew: any of the tier rows, or the family row above them, is in the profile file. It is any rather than all because ApplyCrew writes the tier rows together, and a person who pinned one tier by hand, or chose a family, has an opinion the setup must not paper over with a preset.

func CrewLine

func CrewLine(preset string) string

CrewLine is one preset's own line in the DEFAULT family, empty for a word that is not a preset. The family-aware spelling is CrewLineFor.

func CrewLineFor added in v0.3.0

func CrewLineFor(source, preset string) string

CrewLineFor is one preset's own line in one family, empty for a word that is not a preset. A family this build does not know reads as the default one, the way the row does.

func CrewModels

func CrewModels(preset string) (map[string]string, bool)

CrewModels is the five models one preset would set in the DEFAULT family, by tier word: the family a profile nobody has touched reads, and the spelling the callers hold. The family-aware spelling is CrewModelsForSource. It returns a copy, because a caller printing the table must not be able to edit it.

func CrewModelsForSource added in v0.3.0

func CrewModelsForSource(source, preset string) (map[string]string, bool)

CrewModelsForSource is the five models one preset would set under one family, by tier word, false for a word that is not a preset. It returns a copy for CrewModels's reason.

func CrewPickAt added in v0.3.0

func CrewPickAt(profileDir string) string

CrewPickAt is where the seats are picked from on this profile, DefaultCrewPick when the row is absent. A word this build does not know reads as the default pick, silently, the way a retired choice reads everywhere else on this sheet.

func CrewSourceAt added in v0.3.0

func CrewSourceAt(profileDir string) string

CrewSourceAt is which family the preset words draw from on this profile, DefaultCrewSource when the row is absent. A word this build does not know reads as the default family, silently, the way a retired choice reads everywhere else on this sheet.

func CrewSummary

func CrewSummary(profileDir string) string

CrewSummary is the one line a crew change confirms itself with:

crew → balanced · brain claude-opus-5 · hands glm-5.3-flash · checks claude-fable-5.1

The three names are the classes a person actually asked about — what thinks, what works, what checks — and HANDS IS THE WORKER: the seat that does the task, which is what everybody reading the word took it to mean back when it named the small-work tier. The reflex and small-work models are deliberately absent: they are the same near-free models in all three presets, so naming them would be facts that never vary. The ids are shortened to their base names because the vendor prefix is the half nobody reads twice.

func CrewSummaryPick added in v0.3.0

func CrewSummaryPick(profileDir string) string

CrewSummaryPick is the confirmation with the pick named when it is not the default one:

crew → balanced · learn · brain claude-opus-5 · hands glm-5.3-flash · checks claude-fable-5.1

The pick rides the preset word because the two are one decision read at two heights — how much to spend, and where the models for that money come from — and a confirmation that said only `balanced` would drop the half the person just changed. At the default pick this is CrewSummary itself, so a profile nobody has taught the pick to confirms exactly as it always has.

func DailyBudgetConfigured

func DailyBudgetConfigured(profileDir string) bool

DailyBudgetConfigured is whether a person has answered the daily ceiling: the environment pins it, or the profile file holds it.

func DailyBudgetUSD

func DailyBudgetUSD() (float64, error)

DailyBudgetUSD resolves the dollar rail without requiring a provider key. Status-only commands use it even when they never construct a model client.

func DailyBudgetUSDAt

func DailyBudgetUSDAt(profileDir string) (float64, error)

DailyBudgetUSDAt resolves env → persisted config → built-in default. The env remains the explicit headless override; /budget default writes the middle layer used by both chat and one-shot runs.

func DefaultEffortAt

func DefaultEffortAt(profileDir string) effort.Rung

DefaultEffortAt is the rung this profile last settled on, or effort.Ship when nobody has chosen one.

Missing settings use the shipped default; saved choices remain authoritative. Legacy "off" files still mean absence and are displayed as auto.

func DisconnectService

func DisconnectService(profileDir, id string) error

DisconnectService removes one service row and its stored key atomically.

func DocumentEngineAt

func DocumentEngineAt(profileDir string) (string, error)

DocumentEngineAt resolves the document-reading rung.

func DraftPersistAt

func DraftPersistAt(profileDir string) bool

DraftPersistAt resolves whether the v3 chat surface keeps the unsent draft on disk between sessions. Shaped exactly like HistoryEnabledAt.

func EffortWord

func EffortWord(rung effort.Rung) string

EffortWord names absence as auto in settings and persisted configuration.

func EnsurePersistedAPIKey

func EnsurePersistedAPIKey(profileDir string) (bool, string, error)

EnsurePersistedAPIKey copies the session's environment key into the profile config exactly once, so the standing watch can authenticate after the shell that ratified it is gone. It never overwrites a key already on disk, and the file ends owner-readable only. Returns whether a key was newly persisted and the path that holds it.

func ExaKeyAt

func ExaKeyAt(profileDir string) string

ExaKeyAt resolves the Exa credential: the environment first, then the sheet, then empty — and empty is a working configuration, not a fault.

func FallbackMediaModel

func FallbackMediaModel(modality string) string

FallbackMediaModel is the curated rung, and empty for a modality this build has no remembered name for.

func FirecrawlKeyAt

func FirecrawlKeyAt(profileDir string) string

FirecrawlKeyAt resolves the optional Firecrawl ceiling credential the same way.

func FirstPrompt

func FirstPrompt(profileDir string) bool

FirstPrompt reports whether this profile has never chosen a conversation model. A fresh install's first typed line is this: ChatModelAt is empty and the talk slot is still the build default. The hedge uses it so a stall there names `/model` instead of sitting silent until the ninety-second first-token cut (F42).

func FormatBashApprovals

func FormatBashApprovals(rules []BashRule) string

FormatBashApprovals writes the rules back as the row's own text, quoting only the globs that need it. A glob that reads plainly stays plain: the row is a line a person edits by hand, and quotes around every entry would be a file format wearing a settings row's clothes.

func GoogleOAuthClientAt

func GoogleOAuthClientAt(profileDir string) (id, secret string)

GoogleOAuthClientAt resolves the Google registration: the environment first, then the sheet, then the registration this build ships with (connect_defaults.go, which states why a desktop client's secret may be compiled in at all). So the answer is never empty, and every build can offer to connect an account without a person filling in a registration form first.

IT ANSWERS BOTH HALVES OR NEITHER IS WORTH HAVING, which is why it is one call and not two. An id without its secret cannot ask Google for anything, so a caller handed half a pair would have to write the same "and the other one" check every reader of these rows already needs; here it is written once, and the caller's test is the one it should be — is the id there.

EACH HALF WALKS THE RUNGS ALONE, which is the shape the rows already had: the two are separate values a person copies from two separate boxes, and each is resolved by the same credentialAt every other credential uses. The one edge that leaves is a person who answers ONE half of their own registration — they get their id against this build's secret, which Google refuses — and the cure is the obvious one, answer the other half too, which is what the sheet's hint on both rows already says.

func GuardianAt

func GuardianAt(profileDir string) string

GuardianAt resolves whether a small model answers a tool prompt before the person is asked. An unrecognised persisted value reads as the default, which is off — a garbled setting must never be the one that appoints a stand-in.

func GuardianEnabledAt

func GuardianEnabledAt(profileDir string) bool

GuardianEnabledAt is GuardianAt as the bool internal/session's Config takes. The two exist separately because the row's value is a WORD — that is what the sheet renders and what the file holds — and the seam on the other side is a switch; one function answering both would have to pick which of those it lies about.

func HeadlessToolApprovalModeAt added in v0.4.0

func HeadlessToolApprovalModeAt(workspace, profileDir string) (string, error)

HeadlessToolApprovalModeAt requires an explicit setting to open an unwatched gate. Invalid saved values retain the ordinary reader's conservative fallback.

func HintsAt

func HintsAt(profileDir string) bool

HintsAt resolves whether the v3 chat shows its tips, default on. A row that will not parse reads as the default rather than as off, for TaskColumnAt's reason: a garbled row must not quietly take a newcomer's only pointers away.

func HistoryEnabledAt

func HistoryEnabledAt(profileDir string) bool

HistoryEnabledAt resolves whether the v3 chat surface records what was typed into ~/.codeaf/v3/history.jsonl. A malformed pin reads as the default rather than refusing a launch over a recall list.

func IconsAt

func IconsAt(profileDir string) string

IconsAt resolves the step icon preference. Unknown values use detection rather than turning off the normal rich presentation.

func InstallLaneRows

func InstallLaneRows(profileDir string)

InstallLaneRows hands this profile's routing posture to the process-wide knobs the transport reads it from. It uses the RESOLVER'S entrance so loading a row already in force never forgets a retirement the wire earned; only a person's own act belongs at provider.RepinLane.

THE ROUTING ROW IS ONE OF THEM, and it is here rather than only on the session's own config because of the clients nobody hands one to. The harness, the subharness, `read_document`, `view_image` and a panel's members are all assembled through Config.ClientConfig, which carries no routing answer — so before this line they ran on the default whatever a person had written, and one of them could retire a person's own pin, process-wide, before any wire was asked (internal/provider's velocity.go says what that cost). It is the CHOICE and not the resolved default, so an unwritten row installs nothing and every client falls to the shipped row together (provider.DefaultRouting).

func InstallPersistedEnv

func InstallPersistedEnv(profileDir string)

InstallPersistedEnv exports the persisted value of every knob whose only reader is the process environment, so a choice made in the sheet survives a relaunch without a second lookup path. A variable the user actually set is never overwritten — the environment still wins.

func InstallRoutingRow

func InstallRoutingRow(profileDir string)

InstallRoutingRow hands the routing row ALONE to the transport, and it is the half of InstallLaneRows the settings panel calls by itself: somebody cycles `routing`, the row is written, and the very next request has to go out under it. The lane rows beside it are untouched because nothing about them changed — re-stating a pin here would be a resolver's write nobody asked for.

func IsAuto added in v0.3.0

func IsAuto(value string) bool

IsAuto reports whether a tier value is the bare word, case folded with the surrounding space ignored. `auto:high` is not it: the rows own the `:<level>` notation, and a suffixed word is a model id with a level, handed on whole the way every other one is.

func JinaKeyAt

func JinaKeyAt(profileDir string) string

JinaKeyAt resolves the Jina credential the same way.

func LaneAt

func LaneAt(profileDir, slot string) string

LaneAt is the lane one slot is held to, default LaneAuto. A blank or unreadable row reads as auto rather than as a pin nobody can see.

func LaneBorrowAt

func LaneBorrowAt(profileDir, slot string) bool

LaneBorrowAt is whether a pinned slot may be borrowed away from when its lane is slow. It is false unless somebody said so: a pin means the machine they named, and widening it on their behalf is not this row's to do.

func LaneBorrowKey

func LaneBorrowKey(slot string) string

LaneBorrowKey is the row beside it: whether a PINNED lane may still be borrowed away from when it is slow. It is a second key rather than a fourth word in the first because the two are independent — borrowing means nothing under `auto` — and because a pin that quietly stopped being a pin the day somebody turned rescuing on would be a promise this build did not keep.

func LaneGuardAt

func LaneGuardAt(profileDir string) bool

LaneGuardAt resolves the speed-guard row. A malformed row reads as the default rather than quietly turning the rescue off — a person who never touched this row has not asked to wait.

func LanePinAt

func LanePinAt(profileDir, slot string) provider.LanePin

LanePinAt is one slot's lane row resolved into the answer the transport takes. The three states of the row are the three states of the pin, and a row nobody has written is `auto` — the belief chooses per answer.

func LanePinned

func LanePinned(profileDir, slot string) (string, bool)

LanePinned is the lane named by a slot's row, and false when the row names no machine — which is every reading of `auto` and of `openrouter`.

func LaneRowWord

func LaneRowWord(profileDir, slot string) string

LaneRowWord is one slot's lane row as a person reads it: `auto`, `openrouter`, `pinned: cloudflare`, or `pinned: cloudflare, borrow when slow`. It is here rather than in a surface because the same words are what WriteLaneRow reads back, and two spellings of one row is how a row stops round-tripping.

func LaneSettingKey

func LaneSettingKey(slot string) string

LaneSettingKey is the row holding one slot's lane: `auto`, `openrouter`, or a lane's own name as the wire spells it.

func LivePinsAt

func LivePinsAt(profileDir string) string

LivePinsAt is the pinned-roles row AS IT IS ACTED ON: the person's own text with the pins this build will never consult taken out.

THE ROW DRAWS WHAT IS TRUE. ModelRolesAt is the raw stored string and stays that, because the writer needs the text to rewrite; but a row that DISPLAYED it would draw `compaction: some/model` as a live pin while the role list under it had no such line, and the person would be reading a setting that does nothing. What they are told instead is RetiredPinNote, next to the row.

func LooksLikeAPIKey

func LooksLikeAPIKey(key string) bool

LooksLikeAPIKey is the shape check the first-run setup applies to a pasted key, and it is deliberately only a shape check: it spends no network call, because the setup runs before a person has agreed to spend anything. An OpenRouter key reads `sk-or-v1-…`; an OpenAI-shaped key, which Load also accepts from the environment, reads `sk-…`. Whitespace inside is a paste that picked up a line break, which is the one thing worth refusing here rather than discovering as a 401 on the first turn.

func MarkSetupSeen

func MarkSetupSeen(profileDir string, at time.Time) error

MarkSetupSeen records that the setup was shown now, through the same atomic writer every setting uses.

func MediaSlotModelAt

func MediaSlotModelAt(profileDir, slot string) string

MediaSlotModelAt is ONE CAPABILITY SLOT'S MODEL, in the order every other persisted knob resolves in: the operator's environment variable, then the row the settings sheet wrote into the profile, then nothing.

Nothing is the honest last answer rather than a curated name. This function answers "what did a person choose", and the ladder that turns no choice into a working model is the resolver's business (cmd/codeaf's chatv3_media.go, and CandidateMediaModel behind it) — a default invented here would be a rung the resolver could not tell from a deliberate pin.

A slot that is not a capability slot answers nothing at all: "talk" is the conversation, and reading CODEAF_MODEL through this door would let a media resolver quietly take the chat model for a modality it cannot serve.

func MemoryAt

func MemoryAt(profileDir string) string

MemoryAt resolves the memory row to its word, default on.

func MemoryEnabledAt

func MemoryEnabledAt(profileDir string) bool

MemoryEnabledAt is MemoryAt as the bool the v3 door reads before it opens a brain at all: memory off is a session handed no store, which is what makes "no block and no calls" a property of the wiring rather than a branch every caller has to remember (internal/session's memory.go states the law).

func ModelCandidates

func ModelCandidates(models *catalog.Catalog, slot string) []catalog.Model

ModelCandidates is the shared capability gate for every slot in the model palette. Keeping music's TTS exclusion here makes discovery and runtime resolution agree about what can occupy that slot.

func ModelFallbacksAt

func ModelFallbacksAt(profileDir string) string

ModelFallbacksAt resolves the fallback chain as the person wrote it.

func ModelMatches

func ModelMatches(models *catalog.Catalog, slot, word string, limit int) []string

ModelMatches returns the models in slot that word could mean, best first and at most limit of them. Exactly one result is an unambiguous resolution; more than one means the word was genuinely ambiguous and the caller should ask. Scoring is contains-and-prefix on purpose: subsequence fuzziness is fine for a palette a human is watching, and far too loose for a word lifted out of a sentence.

func ModelPoolAt added in v0.3.0

func ModelPoolAt(profileDir string) poolcfg.Config

ModelPoolAt resolves how the Model Pool behaves: the stored mode word, the stored public key and the pool's environment names, through poolcfg.Resolve. IT IS THE ONE PLACE THE PROCESS ENVIRONMENT IS READ FOR THE POOL — poolcfg itself takes the environment as a function, and a caller with its own (the `codeaf pool` verb, whose tests inject one) resolves ModelPoolSettingAt and ModelPoolPublicKeySettingAt against its own lookup rather than calling this.

func ModelPoolPublicKeySettingAt added in v0.3.0

func ModelPoolPublicKeySettingAt(profileDir string) string

ModelPoolPublicKeySettingAt is the stored word the pool key row holds: base64 text, or empty for the key the binary carries. It is the second setting ModelPoolAt resolves.

func ModelPoolResolved added in v0.4.0

func ModelPoolResolved(profileDir string, lookup func(string) (string, bool)) poolcfg.Config

ModelPoolResolved is ModelPoolAt with the environment injected, for the verbs whose tests hand one in. It is where the telemetry off switch reaches the pool: the environment rungs (CODEAF_TELEMETRY, DO_NOT_TRACK) are read by the resolver through lookup, and the two rungs that live on disk — the project file and the profile row that `codeaf telemetry off` writes — are read here and applied with poolcfg.Config.Quieted. The rows, not the pin: a caller that injected an environment must get the answer for THAT environment's CODEAF_TELEMETRY, not the one the harness happens to export (the fall-through [telemetryRowsOff] describes is the one exception).

func ModelPoolSettingAt added in v0.3.0

func ModelPoolSettingAt(profileDir string) string

ModelPoolSettingAt is the stored word the pool row holds: one of the three choices, or empty for a person who has never answered. It is the argument the pool's resolver takes, and the raw half of ModelPoolAt.

func ModelRolesAt

func ModelRolesAt(profileDir string) string

ModelRolesAt resolves the per-role pins as the person wrote them.

func ModelSettingKey

func ModelSettingKey(slot string) string

ModelSettingKey is the registry key fronting one model slot.

func MouseAt

func MouseAt(profileDir string) string

MouseAt resolves the mouse row to its word, default off.

func MouseEnabledAt

func MouseEnabledAt(profileDir string) bool

MouseEnabledAt is MouseAt as the bool the surface's View takes — the same word/switch split GuardianEnabledAt documents.

func ParseModelFallbacks

func ParseModelFallbacks(raw string) []string

ParseModelFallbacks reads the row into an ordered list of model slugs.

It is comma-separated and NOT a pair list, unlike the two model rows above it: there is no key here, only an order, and the order is the whole content. A slug may carry a colon of its own (`…/model:free`), which is exactly why this splits on commas and nothing else.

Blank entries are dropped and duplicates collapse to their first appearance, so a trailing comma or a name written twice is a tidy-up rather than an error. It cannot fail: this row names models, and whether a model exists is a question only the provider can answer.

func ParseModelRoles

func ParseModelRoles(raw string) (map[string]string, error)

ParseModelRoles reads `title:openai/gpt-5-mini` into role → model. The value is split at the FIRST colon only, because a model slug can carry one of its own (`…/model:free`).

A PIN FOR A WORD THAT IS NOT A ROLE IS DROPPED, NOT REFUSED — see [parseModelRoles], which is this and the names it dropped.

func ParseToolApprovals

func ParseToolApprovals(raw string) (map[string]string, error)

ParseToolApprovals reads `read:allow, bash:prompt` into the map internal/approval's Load takes as its "tools" section.

The flat text is a STAND-IN. The structured map — per-tool rules, and the ordered bash pattern list beside them — lands with the settings file that can hold a nested shape; until then a person needs some way to say "never ask me about read", and one line they can read back beats a nested editor nobody has written yet. The parse is strict about the action for the reason approval.ParseAction is: a typo that was silently dropped would be a rule somebody thinks is protecting them.

func PersistedAPIKey

func PersistedAPIKey(profileDir string) string

PersistedAPIKey reads the api_key stored in the profile config file. It is the last rung of Load's key resolution: a timer-driven `codeaf wake` runs with no shell environment, so the profile file is the only place a key can survive to reach it.

func PlanConsentUSDAt

func PlanConsentUSDAt(profileDir string) (float64, error)

PlanConsentUSDAt resolves the estimate above which a plan asks first.

func PracticeBudgetUSDAt

func PracticeBudgetUSDAt(profileDir string) (float64, error)

PracticeBudgetUSDAt resolves the daily self-practice carve-out.

func PracticeIdleAt

func PracticeIdleAt(profileDir string) (time.Duration, error)

PracticeIdleAt resolves the quiet period before self-practice.

func ProfileDir

func ProfileDir() string

ProfileDir is the profile this process reads and writes. Empty is the ordinary answer and means the state root's own profile; the readers below take it as such, so a caller never has to know what the default expands to.

func ProfilePath

func ProfilePath(profileDir, name string) string

ProfilePath names a file this profile keeps, and is THE ONE PLACE THAT KNOWS WHAT AN EMPTY PROFILE DIRECTORY MEANS.

AN EMPTY PROFILE DIRECTORY IS THE NORMAL CASE, NOT THE ABSENT CASE, AND ABSENCE IS A HOSTED WINDOW. ProfileDir carries CODEAF_PROFILE_DIR, which almost nobody exports, so the empty string is what very nearly every launch passes down here — and it has always meant "the profile where it always is", codeaf's own state root, which internal/home owns and CODEAF_HOME moves. A caller that reads emptiness as "there is no profile" and goes quiet is therefore silent on the ordinary launch and loud only on the rare one, which is the exact inversion this function exists to stop being retyped: it has cost the model picker's lane pin, the settings pair on the belt, the status line's crew segment and the notices' own memory, each found separately.

func ProjectBoolAt

func ProjectBoolAt(cwd, profileDir, key string) (bool, error)

ProjectBoolAt is ProjectStringAt for the two on/off rows.

func ProjectConfigPath

func ProjectConfigPath(cwd string) string

ProjectConfigPath is the file this layer reads for one workspace. An empty workspace has no path and therefore no layer.

func ProjectFloatAt

func ProjectFloatAt(cwd, profileDir, key string) (float64, error)

ProjectFloatAt is ProjectStringAt for the dollar row.

func ProjectKeyAllowed

func ProjectKeyAllowed(key string) bool

ProjectKeyAllowed reports whether a row may live in a project file.

func ProjectStringAt

func ProjectStringAt(cwd, profileDir, key string) (string, error)

ProjectStringAt resolves one text row for a workspace: the project file's answer when it has one, otherwise the profile reader that already owns the row, otherwise that reader's default. A missing project file is silent; a broken one stops the caller with the path in the message.

func PromptProfileAt

func PromptProfileAt(profileDir string) string

PromptProfileAt resolves the prompt-profile row to its word: the environment pin, then the persisted row, then `auto`.

AN UNRECOGNISED PIN IS NOT A PIN, and that is where this differs from the bool rows above, which read a malformed pin as their default. The engine already rules it that way (internal/session's promptprofile.go says so at length, after internal/splitgate's Mode): a stale or mistyped word in somebody's shell must not quietly move a conversation onto the other arm, and it must not quietly cancel the row they did choose either. So a word this list does not have falls through to the row, exactly as an unset variable does.

func QuickSwitchAt

func QuickSwitchAt(profileDir string) bool

QuickSwitchAt resolves whether the conversation switcher's chord switches on each press, default on. A row that will not parse reads as the default rather than as off, for TaskColumnAt's reason: a garbled row must not quietly slow a gesture down.

func ReasoningProfileSeam

func ReasoningProfileSeam(models *catalog.Catalog) func(string) (provider.ReasoningProfile, bool)

ReasoningProfileSeam hands the catalog's published reasoning profile to the adapter in the adapter's own vocabulary. A nil catalog answers "unknown", exactly as its SupportsParameter does.

func RememberBashApproval

func RememberBashApproval(profileDir, match string) error

RememberBashApproval appends one allow rule for the SHAPE a person has just picked on a consent card.

It takes the shape and not the line. The card banks what was CHOSEN (bashshapes.go derives the offer, tui3's consent.go puts it), so the argument here is `git status*` as readily as `git status --short`, and this function asks nothing about which of the two it was handed: a rule is a glob, and the person read the glob before they pressed the key. What it does ask is whether the glob is one worth writing down — [bashShapeHolds] — because a shape that pins nothing down is a row entry that answers for every command there is.

Four refusals, and each of them is internal/approval's law rather than this file's caution:

  • A COMPOUND SHAPE CANNOT BE REMEMBERED. An allow rule vouches only for a single command it matches whole (bash.go's matching law), so a rule written for `cd /tmp && rm -rf build` could never fire. Writing it anyway would put a line in somebody's settings that says they approved something and does nothing at all.
  • A SHAPE THAT NAMES NO COMMAND IS NOT A RULE, and neither is one whose only word is sudo (bashshapes.go states both).
  • A SHAPE THE ROW ALREADY ALLOWS IS NOT WRITTEN AGAIN, whether the rule that allows it is this exact glob or a broader one somebody wrote by hand.
  • A SHAPE THE ROW ALREADY DENIES OR ASKS ABOUT IS LEFT ALONE, and the caller is told. First match wins, so an allow appended after a standing deny is a rule that never runs; the standing rule is a decision the person made in the settings sheet, and a keystroke on a card does not overturn it.

A LINE IS STORED AS THE GLOB IT IS. The dialect has no escape for '*', so a command line containing one is remembered as a pattern with a wildcard in it — `ls *.go` approved is `ls *.go` allowed. That is the honest reading of the line the person saw and approved, it stays bounded to a single non-compound command, and the critical-command table still asks about the shapes that destroy a disk whatever this row says.

func RememberToolApproval

func RememberToolApproval(profileDir, tool, action string) error

RememberToolApproval merges one tool's answer into the person's tool exceptions row — their file, their row, one entry changed and everything else left exactly as they typed it.

An entry that already says this is not written at all: the caller gets nil and the file is not touched, which is what makes pressing always twice a no-op rather than a row that grows.

See this file's head for the one surprising consequence: while a REPOSITORY answers tools.approval, its row replaces the person's whole at launch (projectconfig.go, law 2), so a preference written here takes effect everywhere except inside that repository.

func RenameConnectionModels added in v0.3.0

func RenameConnectionModels(profileDir, oldWritten, newWritten string) (changed []string, err error)

RenameConnectionModels carries a connection rename across every STORED model id the profile holds under the old Written name: the crew's five tier rows, the fallback chain, the role pins and the capability slots. The conversation slot's id is not stored vocabulary here — routing keys on the Written prefix of a model id, so a connection renamed from homelab to lab leaves every homelab/... id answering on a service that no longer exists, resolving to no service and handing itself to the default one: a silent misroute that only fails at send.

THE NAME IS THE ONLY THING THAT MOVES, IN ONE WRITE. The id is re-spelled under the new prefix — the SAME model, spelled the new way — and nothing else about a row is touched; every re-prefixed value passes the tier gate (ValidateTierValue) and the whole decision lands through one [writeProfileValues], so a failure leaves the profile byte-identical. A tier row that was never held stays never held: writing one would convert an inherited tier into a pinned one. Only a row whose value actually changes is written.

Node pins (the plan and work model recorded on already-created tasks) and journaled role bindings are historical records of what ran, and are not rewritten; store.SetRoleBinding has no caller outside the store.

func ReplyGuardAt

func ReplyGuardAt(profileDir string) string

ReplyGuardAt resolves the reply-guard row to its word, default on.

func ReplyGuardEnabledAt

func ReplyGuardEnabledAt(profileDir string) bool

ReplyGuardEnabledAt is ReplyGuardAt as the bool the session's Config takes. The two exist separately for the reason the guardian's pair does: the row's value is a WORD, and the seam on the other side is a switch.

func ReprefixModelID added in v0.3.0

func ReprefixModelID(id, oldWritten, newWritten string) string

ReprefixModelID rewrites one model id whose connection segment is the old Written name. Ids on other services, and bare ids, come back unchanged.

func ResolveMediaModel

func ResolveMediaModel(models *catalog.Catalog, modality, word string) (string, error)

ResolveMediaModel reads one media tool's model argument. An empty word means the caller keeps its slot default. "best" resolves the preference order. Any other word is resolved inside the modality, and a name that belongs to a different modality is refused by naming what it actually makes.

func ResolveSources

func ResolveSources(profileDir, defaultKey, defaultBase string) modelsource.Set

ResolveSources builds the whole registry: the synthesised default service first, then every persisted row this build still knows.

func ResponseAttemptsAt

func ResponseAttemptsAt(profileDir string) float64

ResponseAttemptsAt resolves N. A pin below one is nonsense — a request that is never sent — and reads as the default rather than as an instruction.

── WHAT N MEANS CHANGED, AND THE ROW DID NOT ───────────────────────────────

It was a count of sends and it is a MULTIPLIER ON THE DEADLINE (docs/design/recovery/DESIGN.md §4, taxonomy.Limits.Patience): `3` is three times the role's own give-up — four and a half minutes on a conversation's turn rather than ninety seconds — and never three identical requests. The key keeps its name because the QUESTION a person is answering when they turn it is unchanged: how hard should this try before it tells me it could not. What it no longer buys is the one thing that never helped, which is the same bytes sent again to the machine that has just refused them.

The default is one, where it was three: three was the count this build shipped with, and one is the measured give-up with nothing multiplied on top. A person who had written `3` into their profile when three was the default is asking for three times the patience now — which is a reading of their row this change cannot avoid and says so in its change entry.

func ResponseLiftAfterAt

func ResponseLiftAfterAt(profileDir string) int

ResponseLiftAfterAt resolves K, the same way.

func ResponseLiftCapAt

func ResponseLiftCapAt(profileDir string) float64

ResponseLiftCapAt resolves the cap, in dollars.

A PERSISTED 0 IS A VALUE AND NOT AN ABSENCE, for TaskAutoApproveAt's reason: 0 means no cap, and somebody who wrote it must not find one back in the morning.

func ResponseLimitsAt

func ResponseLimitsAt(profileDir string) taxonomy.Limits

ResponseLimitsAt resolves the whole of taxonomy.Limits for a profile: environment pin, then the persisted row, then the package default, which is the order every other number in this file resolves in.

IT IS ONE READER AND NOT THREE, because the three numbers are one policy and a caller that resolved two of them would be running a boundary nobody configured. The backoff is not among them: it is derived from the attempt count's own schedule and there has never been a reason to turn it apart from the count.

func RetiredPinNote

func RetiredPinNote(raw string) string

RetiredPinNote is what a surface with a person in front of it says about pins this build will not act on, and "" when there are none. It is a sentence and not a log line, because the only reader who wants it is the one looking at the row it is about.

func RoutingAt

func RoutingAt(profileDir string) string

RoutingAt resolves the routing row to the word in force, DefaultRouting when nobody wrote one. An unreadable or unknown word falls back to the default rather than to off: a garbled row must not quietly stop a session sending what it would otherwise send, and off is a real answer somebody chooses rather than one they arrive at by accident.

func RoutingChoiceAt

func RoutingChoiceAt(profileDir string) string

RoutingChoiceAt is the routing row A PERSON ACTUALLY WROTE, empty when they have written nothing readable.

It is the same read as RoutingAt without the fallback, and the two are both needed because they answer different questions. A settings sheet asks "what is in force?" and must be told the shipped row, which is what an unset row does. The adapter asks "did somebody CHOOSE?", and it must be able to hear no — which is what lets a client that was handed nothing fall to the row this process installed, while an explicit word still wins over both (internal/provider's velocity.go).

func RoutingWord

func RoutingWord(raw string) string

RoutingWord is what an already-read routing row is IN FORCE as: the word when somebody wrote one this build knows, and DefaultRouting otherwise.

A surface that holds the row in hand asks this rather than deciding for itself what an unknown word means, so the shipped row is stated once (internal/tui3's palette.go reads it for the sentence on the `auto` row).

func SaveTaskColumn

func SaveTaskColumn(profileDir string, open bool) error

SaveTaskColumn records what the person did to the column with their hands.

It is EXPORTED because the surface writes it from a chord, and it is the v3 half of the same bargain: this is the one interface row whose value is normally chosen by a keystroke rather than by visiting the sheet, so the key needs a door to disk that goes through the same writer the row's own does. Two doors, one validation.

func SearchOptionsAt

func SearchOptionsAt(profileDir string) search.Options

SearchOptionsAt is the one mapping from profile rows to the search layer's input. Auto becomes an absent pin because the resolver treats absence as the instruction to walk its ladder; credentials retain their environment-first resolution from the rows above.

func SearchProviderAt

func SearchProviderAt(profileDir string) string

SearchProviderAt resolves where a web search goes: the persisted row when it names a provider this build accepts, otherwise auto. An unrecognised value reads as auto rather than as an error, which is the direction a garbled setting may be wrong in — auto still searches.

func SearchProviderHintAt

func SearchProviderHintAt(profileDir string) string

SearchProviderHintAt explains what the searching row means right now. It is recomputed when the sheet rebuilds after a write, so the row describes the next search without turning every rendered frame into a config-file read.

func SetCrewPick added in v0.3.0

func SetCrewPick(profileDir, pick string) error

SetCrewPick writes the pick row ALONE, in one file write. The word is refused the way every choice row refuses one, so a typo cannot land a pick nothing reads. It writes no tier row: the pick says where seats are read from, and the seats keep the ids on disk until the crew is picked again.

func SetCrewSource added in v0.3.0

func SetCrewSource(profileDir, source string) error

SetCrewSource writes the family row ALONE, in one file write. The settings row is its caller; the chooser commits the family and the preset together through ApplyCrewUnder. The word is refused the way every choice row refuses one, so a typo cannot land a family nothing reads.

func SetLane

func SetLane(profileDir, slot, value string) error

SetLane writes one slot's lane. An empty word clears the row back to auto, which is the same thing said two ways and both of them arrive here.

func SetLaneBorrow

func SetLaneBorrow(profileDir, slot string, borrow bool) error

SetLaneBorrow writes the borrow flag beside a pin.

func SettingsGeneration

func SettingsGeneration() uint64

SettingsGeneration is the number of persisted settings writes this process has made. A reader that holds a snapshot keeps the value it read at, and rebuilds when the two differ.

func SetupSeenAt

func SetupSeenAt(profileDir string) time.Time

SetupSeenAt is when the setup was last shown, or the zero time. Skipping with esc counts as shown — the marker records that the person met it, not that they answered it.

func SlackOAuthClientAt

func SlackOAuthClientAt(profileDir string) string

SlackOAuthClientAt resolves the Slack registration: the environment first, then the sheet, then the public application this build ships with. The answer is never empty, so every build can offer Slack without asking a person to register an application first.

func SourceKeyAt

func SourceKeyAt(profileDir string, row PersistedSource, src modelsource.Source) string

SourceKeyAt is APIKeyAt's generalisation. The default service keeps its existing three-rung implementation; another service reads its conventional variable, its stored key, then the variable a person named.

func SpendRailUSDAt

func SpendRailUSDAt(profileDir string) float64

SpendRailUSDAt resolves one conversation's own ceiling. 0 is off.

func SpentFigure

func SpentFigure(usd float64) string

SpentFigure is how a SPEND is written, which is not how a LIMIT is written.

A limit is a figure somebody typed and [formatDollars] writes it back the shortest way that is still the same number — right for a config file and right for `$500`. A spend is a measurement nobody chose, and the shortest honest form of one is a disaster: four fifths of a tenth of a cent came out of the provider as 0.0005688764200000001 and went onto the row exactly like that, twenty-two digits of float noise where a person wanted to read a price. So a spend is cents, and four decimals under a cent — the same ladder the surface's own money word uses, so the receipt and the figure beside it agree.

AND A SPEND OF NOTHING SAYS NOTHING. The emptiness law lives here rather than at each caller so there is one answer to "how is a spend written" and not two: `$0.00` and `$0.0000` are claims nobody earned, and a headless footer that ended `0s · 0 nodes · $0.0000` made three of them on the one line a person reads to find out what happened. It is exported for the doors outside this package — the headless footer, doctor, the notebook — for the same reason.

func TaskAuditAt

func TaskAuditAt(profileDir string) string

TaskAuditAt resolves the audit row to its word, default on.

func TaskAuditEnabledAt

func TaskAuditEnabledAt(profileDir string) bool

TaskAuditEnabledAt is TaskAuditAt as the bool the session's Config takes.

func TaskAutoApproveAt

func TaskAutoApproveAt(profileDir string) int

TaskAutoApproveAt resolves the task countdown, in seconds. 0 is a clock that is off: the proposal waits for an answer instead of starting itself.

A persisted 0 is a VALUE and not an absence, which is why the reader tests ok before it tests the number: a person who turned the clock off must not find it back at fifteen the next morning.

func TaskColumnAt

func TaskColumnAt(profileDir string) bool

TaskColumnAt resolves whether the v3 chat stands its task column up, default on. A row that will not parse reads as the default rather than as off, for TimestampsAt's reason: a garbled row must not quietly take the session's record of its own work off the screen.

func TaskMaxLoadAt

func TaskMaxLoadAt(profileDir string) float64

TaskMaxLoadAt resolves the per-core load average above which no new task is started. 0 turns the check off.

func TaskMinFreeMBAt

func TaskMinFreeMBAt(profileDir string) int

TaskMinFreeMBAt resolves the available-memory floor under starting a task, in mebibytes. 0 turns the check off.

func TaskModelAt

func TaskModelAt(profileDir string) string

TaskModelAt resolves the model tasks run on, as the person wrote it. Empty is the ordinary answer and means "the conversation's own": the row is an override, and whether the name in it exists is a question only the catalog can answer (internal/session resolves it against one).

func TaskParallelAt

func TaskParallelAt(profileDir string) int

TaskParallelAt resolves how many tasks may run at once. 0 is no limit, and no limit is the default.

A persisted 0 is a VALUE and not an absence, for TaskAutoApproveAt's reason — though here the value and the default agree, so the test that matters is the other direction: a person who wrote 2 must not find it gone.

func TaskRepairRoundsAt

func TaskRepairRoundsAt(profileDir string) int

TaskRepairRoundsAt resolves how many repair rounds a task gets, default one.

A persisted 0 is a VALUE and not an absence, for TaskAutoApproveAt's reason: a person who turned the loop off must not find it back on in the morning.

func TaskSettleAt

func TaskSettleAt(profileDir string) string

TaskSettleAt resolves the row to its word, default ask. A value this build does not recognise reads as the default rather than as an error, on the rule TaskStartAt states: this row decides who is asked about somebody's work, and a typo in a config file must not start answering on their behalf.

func TaskStartAt

func TaskStartAt(profileDir string) string

TaskStartAt resolves KeyTaskStart: the persisted row, else the default. A value this build does not recognise reads as the default rather than as an error, because the row decides what a command does and a typo in a config file must not be a command that refuses.

THAT RULE IS ALSO HOW A RETIREMENT IS PAID FOR. `adaptive` and `ask` were both answers here once; taking a word out of TaskStartModes is the whole of retiring it, because a profile that still holds one falls through this loop and reads as DefaultTaskStart — no migration, no error, and nothing said to somebody about a preference they set months ago.

func TelemetryAt

func TelemetryAt(profileDir string) bool

TelemetryAt resolves whether the anonymous-usage pipe is on, default on. The environment pin wins over the project file over the profile config over the built-in default — the same ladder every other on/off row walks, with the working directory's own file between the pin and the profile because a repository may answer for itself what a machine answers for everybody (see ProjectKeys). A pin or file that will not parse reads as the default rather than refusing a launch over a count nobody can see.

func TelemetryAtIn

func TelemetryAtIn(cwd, profileDir string) bool

TelemetryAtIn is TelemetryAt with a working directory whose project file may hold the answer. An empty cwd has no project layer, and a project file that cannot be loaded is skipped for the same reason a malformed one is — the row reads as if nobody had written it.

func TelemetryOffReason

func TelemetryOffReason(cwd, profileDir string) (off bool, reason string)

TelemetryOffReason answers what turned the pipe off, in the words the telemetry command prints beside the off reading: the environment pin, the project file, the profile config, or nothing at all when the answer is on. It is the accessor the binary calls to learn “did config turn telemetry off”, and the reason is returned with it because a switch that went quiet without saying why is a row nobody can audit.

func TenureAfterAt

func TenureAfterAt(profileDir string) int

TenureAfterAt resolves the clean-firing count that earns a charter tenure.

func TierModelAt

func TierModelAt(profileDir, tier string) string

TierModelAt resolves the model one auxiliary tier runs on. Empty means the tier follows the session's own model, which is internal/roles' floor.

UNSET AND CLEARED ARE DIFFERENT ANSWERS, on all five tiers. A profile that has never held the key gets this build's own choice for that class of work (DefaultReflexModel and its four neighbours), because a person who never opened the sheet should not have the whole crew answering on the most expensive model in the build — which is what following the conversation means once there is a mastermind tier in it. A row somebody emptied ON PURPOSE reads empty and follows the conversation, because refusing to let them turn it off would make a default into a rule.

AND UNSET HAS TWO READINGS OF ITS OWN, which is the rung this function learned in #312. A key that was never held on a profile OLDER THAN ITS SEAT is not a person declining to answer — it is a crew chosen before the row existed — so the read climbs TierSeatAt, where an unheld key asks the row it was split out of first ([tierLineage]) and only a profile with nothing above it reaches the build's choice. Every caller of this function therefore reads the model a conversation ACTUALLY runs that class of work on: the role map cmd/codeaf builds, the settings sheet's five rows, and CrewAt, which is why the crew word and the work cannot disagree. A caller that also needs to say WHERE the answer came from asks TierSeatAt for the seat instead of this for the model.

The reflex tier was the first row written this way, for the reason its key still states: a call made twice a turn is a bill nobody agreed to. The other three joined it when the crew landed, and the four defaults together are one preset rather than four opinions (crew.go).

The value may carry a level (`moonshotai/kimi-k3:low`) and IS RETURNED WHOLE. Splitting is roles.SplitEffort's job at the point of resolution, because a settings surface wants the string the person wrote and a request wants the two halves apart.

func TimestampsAt

func TimestampsAt(profileDir string) string

TimestampsAt resolves the timestamps row to its word, default footers. An unknown word reads as the default rather than as off, for RoutingAt's reason: a garbled row must not quietly take a fact off the screen.

func ToolApprovalModeAt

func ToolApprovalModeAt(profileDir string) string

ToolApprovalModeAt resolves the blanket answer the tool gate starts from. An absent setting uses YOLO; an unrecognised persisted value still asks, so a garbled setting does not widen a previously selected posture.

func ToolApprovalsAt

func ToolApprovalsAt(profileDir string) string

ToolApprovalsAt resolves the per-tool exceptions as the person wrote them. The text is the record; ParseToolApprovals is how a caller reads it.

func ValidateTierValue

func ValidateTierValue(value string) error

ValidateTierValue refuses every suffix except the three thinking levels. Tier rows own the `:<level>` notation, so accepting an unknown suffix here would silently turn a misspelling into a model id and defer a clear settings error until a provider call much later.

func VisionModelAt

func VisionModelAt(profileDir string) string

VisionModelAt resolves the image-inspection proxy slot. Empty means resolve from the live catalog at use.

func WithoutSeatPin

func WithoutSeatPin(configured provider.Config) provider.Config

WithoutSeatPin is the settings a raw request path takes: the same account, the same model, and the seat's thinking level dropped.

The document path builds its own raw body with no reasoning object, so a seat pin left on the client would make the raw request and the model-call row describe different calls. This door is exported because internal/session reads documents through the same shape and may not spell the adapter's effort vocabulary itself: its effort law (effortguard_test.go) treats a file that names provider.Effort as claiming a depth, while the document path is declining to carry one.

func WorkAt

func WorkAt(profileDir string) string

WorkAt resolves the completed-work presentation, default fold.

func WorkingSetAt

func WorkingSetAt(profileDir string) int

WorkingSetAt resolves the cap on the live working set the same way.

func WriteAPIKey

func WriteAPIKey(profileDir, key string) error

WriteAPIKey persists a key a person handed over, through the same atomic writer every other setting uses. The file is created owner-readable only (writeProfileValues), which is the property EnsurePersistedAPIKey tightens after the fact on a file that predated any secret in it.

func WriteChatModel

func WriteChatModel(profileDir, slug string) error

WriteChatModel records the choice. It goes through the one writer every persisted row goes through, so the rest of the file — the gate, the rail, somebody else's rows — survives untouched (budget.go's writeProfileValue).

func WriteDailyBudgetUSD

func WriteDailyBudgetUSD(profileDir string, amount float64) error

WriteDailyBudgetUSD atomically persists the default rail while preserving any unrelated future keys in the JSON object.

func WriteDefaultEffort

func WriteDefaultEffort(profileDir string, rung effort.Rung) error

WriteDefaultEffort records the choice.

It goes through [writeChoice] — the same validating writer every other choice row uses — so an unknown rung is refused in the same words as every other bad choice, rather than being written and then read back as the shipped default forever, which looks exactly like the write having been ignored.

func WriteLaneRow

func WriteLaneRow(profileDir, slot, raw string) error

WriteLaneRow takes what LaneRowWord says and puts it back: `auto`, `openrouter`, a bare lane name, or either `pinned:` form.

func WriteSources

func WriteSources(profileDir string, rows []PersistedSource) error

WriteSources atomically replaces the non-default service rows while keeping every unrelated profile setting.

func WriteTelemetry

func WriteTelemetry(profileDir string, on bool) error

WriteTelemetry persists the person's own answer to the telemetry row — the writer `codeaf telemetry on|off` goes through, so the command and the settings sheet write the same file the same way and cannot drift.

Types

type BashRule

type BashRule struct {
	Match  string
	Action string
}

BashRule is one entry of the bash rules row: a glob in internal/approval's restricted dialect ('*' and literal text, nothing else) and the answer it carries.

It is a config-side twin of approval.Rule rather than that type re-exported, for the reason [targetField] in the surface is a twin of session's glossField: this one is a line of text a person edits in a settings sheet, that one is a decision surface, and one type serving both would be one reason to change both.

func ParseBashApprovals

func ParseBashApprovals(raw string) ([]BashRule, error)

ParseBashApprovals reads the row into the ordered rule list internal/approval matches in order.

The shape is one rule per entry, entries separated by commas or newlines, each entry an ACTION and then the command glob it answers for:

allow git status*, allow "npm test -- --grep=a,b", deny rm -rf *

The action leads because it is the short, fixed half: a person scanning the row is looking for the word deny, and a command line is long enough to push it off the end of a line if it went first. The glob may be QUOTED in Go's own syntax, and has to be when it carries a comma, a newline or a quote of its own — a shell line contains anything, and a separator that a command can also contain is a row that reads back as two rules nobody wrote. Unquoted is accepted and is what the row looks like nine times in ten.

ORDER IS THE AUTHOR'S PRIORITY STATEMENT and is preserved exactly: the policy takes the first rule that matches, so a deny somebody put at the top of the row stays at the top of the row.

type Config

type Config struct {
	APIKey  string
	BaseURL string
	// UnreadProfileKeys is the sorted result of the profile reader registry check.
	UnreadProfileKeys []string
	// Sources is the resolved service set. Empty preserves every scalar
	// construction that predates services through [modelsource.Set.OrDefault].
	Sources modelsource.Set
	Model   string
	// PlanModel is the model that structures work — the task graph, replans,
	// contracts, the delivery gate. Empty means the work model plans too, which
	// is the default and the kill switch: with no plan slot configured exactly
	// one client exists and nothing about the single-model path changes.
	PlanModel  string
	VoiceModel string
	// Media model fields are capability slots. Empty means resolve at use
	// from the live catalog, rather than trusting a floating default slug.
	ImageModel        string
	SpeechModel       string
	MusicModel        string
	VideoModel        string
	VisionModel       string
	DocumentEngine    string
	Timeout           time.Duration
	Reasoning         provider.Effort
	ExecReasoning     provider.Effort
	SpineSamples      int
	MaxDepth          int
	NodeBudget        int
	DailyBudgetUSD    float64
	PracticeBudgetUSD float64
	// PlanConsentUSD is the estimate above which a planned job asks before it
	// starts. Zero never asks.
	PlanConsentUSD float64
	PracticeIdle   time.Duration
	BriefAfter     time.Duration

	// Attribution admits the standing attribution law into a worker's contract:
	// the trailer on commits it authors, the footer on pull requests and issues
	// it opens. Off is the law's absence, not an instruction to hide.
	Attribution bool

	// Swarm is the cooperative-decomposition mode, and it is ON by default
	// ([DefaultSwarm]). On, a worker gains a verb for handing work back when
	// its brief turns out to hold more than one worker's share: the resident's
	// leaves get `request_split` and may end their run early by naming two or
	// more ownable parts plus the evidence that revealed them, and a v3 task's
	// worker gets `divide_work`, which splits the task into parts under it and
	// keeps the coordination (internal/session's task_divide.go). On also feeds
	// measured capacity statistics (overrun base rates from the journal) into
	// the sizing and split judgments.
	//
	// WHY ON IS THE DEFAULT NOW. It shipped off because nobody had measured
	// what a division costs when it does not pay. The bench corpus then
	// measured it (bench/swarm/AB-REPORT.md), and the answer was that the
	// expense is not division — it is division of work that was never wide. So
	// the wave that landed the mode also landed the gate that refuses narrow
	// work (internal/splitgate), and with the gate in front of it the mode
	// costs nothing below the width floor and is worth 1.15×–1.95× wall clock
	// above it. A capability that is free when it does not apply belongs on.
	//
	// OFF IS STILL EXACTLY TODAY'S BEHAVIOUR, and `CODEAF_SWARM=0` is how
	// somebody asks for it: workers run their brief to settlement and growth is
	// failure-driven only (overrun, revision, JIT). Everything gated by Swarm
	// is inert when it is off.
	Swarm bool

	// Quorum is the two-verifier gate: when a deliverable passes the judge,
	// two cheap validators independently verify it against the original ask.
	// Both must ACCEPT; any REJECT buys one revision round, then the result
	// commits unconditionally. Off (the default) is the judge's pass as the
	// final word — byte-identical to before this existed.
	Quorum bool

	// Panel is the set of models a run may route across, from CODEAF_MODELS. An
	// empty panel is the default and is the kill switch: with no panel the
	// harness builds the same single adapter it always did and no routing code
	// runs at all.
	Panel router.Panel

	// ProfileDir holds measured executor behaviour. Empty means ~/.codeaf.
	ProfileDir string

	// Models is the model catalog every adapter built from this config consults
	// before it shapes a request — today, to decide whether a reasoning knob may
	// travel at all. Nil is honest and safe: the adapter then knows nothing
	// about any model and sends only what the operator asked for explicitly,
	// which is what every caller did before the catalog was wired in.
	//
	// It is set by the surfaces that load a catalog anyway (chat, run, doctor)
	// rather than loaded here, because a config that fetched would make building
	// a client a network operation.
	Models *catalog.Catalog
}

Config is the resolved runtime configuration.

IT CARRIES NO GENERATION KNOB IT WAS NOT GIVEN. There is no output cap here and no sampling field: a request this config builds names the model, the messages and what the call needs to work, and every generation parameter the operator did not ask for is ABSENT from the body, so the provider's own default answers. The one field that looks like an exception is not one — Config.Reasoning and Config.ExecReasoning default to provider.EffortNone, which sends nothing, and carry a level only when CODEAF_REASONING or CODEAF_EXEC_REASONING said so.

AN OPERATOR OR EMBEDDER MAY STILL SIZE A CALL, with ai.WithMaxTokens on that one request — the seam is open and the adapter honours it (internal/provider). What is gone is this file deciding a ceiling for every call in the process, and, with it, every errand in this tree that used to reach for that option on the harness's behalf.

func Load

func Load() (Config, error)

Load resolves configuration from the environment, falling back to the defaults above. Only the API key has no default; everything else runs unconfigured.

func LoadKeyless

func LoadKeyless() (Config, error)

LoadKeyless is Load for a launch that can collect the key itself: the interactive chat, whose provider screen connects OpenRouter or takes a pasted key (internal/tui3's firstrun.go). Everything else resolves exactly as Load resolves it, and APIKey is simply empty until the person hands one over.

AND FOR A PASS THAT WILL PROBABLY DO NOTHING, which is the second legitimate caller and the one nobody expects: the standing tick (cmd/codeaf's v3StandingTicker), run every five minutes by whichever of a window or the operating system's timer gets there first. Most passes decline the lock or find every item asleep, and a pass that will do nothing costs nothing and needs nothing — so it builds keyless and carries whatever key was there through to the one moment a judgment actually calls a model, where a machine with none says so on that item's own row.

A door that has nobody to ask AND something to spend — --once, an engine, a pipe — still has no business calling this: it would fail on its first request instead of at the door, where the sentence can be read.

func (Config) Adapter

func (c Config) Adapter(model string) (*provider.Client, error)

Adapter is the ONE place a model client is built. The model id decides the source; the source decides the key and the address. Nothing above this line knows either.

func (Config) Client

func (c Config) Client() (router.Client, error)

Client builds what the planner and the executor call.

With no panel configured this is the single adapter it has always been, built exactly as before — that is the kill switch, and it is the default. With a panel it is a router over one adapter per model, which satisfies the same interface, so nothing above this line changes.

func (Config) ClientConfig

func (c Config) ClientConfig(model string) provider.Config

ClientConfig is this profile's answer to "how do I talk to that model". It is what every client outside this package is built from; a caller sets only what legitimately differs — a timeout, a routing strategy, or seams its own surface owns — never APIKey and never BaseURL.

func (Config) ClientFor

func (c Config) ClientFor(model string) (router.Client, error)

ClientFor builds a client for an explicitly chosen model. With no panel it is exactly the single-model adapter it always was. With a panel the bare model id is pinned as the router's opener while the whole value builds every adapter, so a thinking level stays with the seat without becoming part of the provider's slug.

func (Config) Context

func (c Config) Context(ctx context.Context, task string) context.Context

Context stamps the run's provider knobs onto ctx: the effort the operator chose, and a cache key derived from the task so every call in one run asks for the same warm instance.

func (Config) DocumentClient

func (c Config) DocumentClient() (*provider.Client, error)

DocumentClient is direct for the same reason as VisionClient: read_document selects an explicit parser engine and model at the leaf boundary, and a second router substitution would make both capability and cost opaque.

func (Config) ExecContext

func (c Config) ExecContext(ctx context.Context) context.Context

ExecContext layers the executor's reasoning level over a planning context. Planning and execution are different kinds of call — one structures, the other works — so the economy that makes planning fast must not travel into the loop, where a model with reasoning suppressed stops writing anything down and never converges.

func (Config) MediaClient

func (c Config) MediaClient() (*provider.MediaClient, error)

MediaClient builds the non-chat OpenRouter endpoint client with the same bearer key, base URL, attribution, timeout, and transport configuration.

func (Config) PlanModelResolved

func (c Config) PlanModelResolved() string

PlanModelResolved is the model planning-class calls run on: the plan slot when the operator set one, otherwise the work model.

func (Config) PlanSplit

func (c Config) PlanSplit() bool

PlanSplit reports whether planning runs on a different model than the work.

func (Config) ResolveImageModel

func (c Config) ResolveImageModel(models *catalog.Catalog) string

ResolveImageModel applies the runtime preference order: an explicit slot, Seedream 5.0 Pro then Krea 2 Medium Turbo as each is advertised, then the catalog's first image output model. Catalog's offline fallbacks make the last resort usable while keeping the ordinary path free of unverified defaults.

func (Config) ResolveMusicModel

func (c Config) ResolveMusicModel(models *catalog.Catalog) string

ResolveMusicModel prefers the Lyria 3 clip row, then Lyria 3 Pro, then the first music/audio model that is not recognizably a TTS model. AN UNAVAILABLE CAPABILITY STAYS OFF THE BELT: with no published row it returns nothing.

func (Config) ResolveSpeechModel

func (c Config) ResolveSpeechModel(models *catalog.Catalog) string

ResolveSpeechModel prefers Fish Audio S2.1 Pro, then Fish Audio S1, then Kokoro, then OpenAI mini TTS as each is advertised, then the first speech-output model. The person's own saved row wins over all of it: a default is only a default.

func (Config) ResolveVideoModel

func (c Config) ResolveVideoModel(models *catalog.Catalog) string

ResolveVideoModel prefers Seedance 2.5, then Seedance 2.0 Mini as each is advertised, then the catalog's first exact video-output model.

func (Config) ResolveVisionModel

func (c Config) ResolveVisionModel(models *catalog.Catalog, talkModel, workModel string) string

ResolveVisionModel applies the inspection-proxy order at the moment a leaf starts: an explicit environment slot, the live talk and work choices when each advertises image input, Qwen VL when advertised, then the first model with image input. Unlike generation slots, an unadvertised default is never invented: no result means view_image can preserve its calm refusal.

func (Config) VisionClient

func (c Config) VisionClient() (*provider.Client, error)

VisionClient is deliberately direct rather than panel-routed. view_image resolves one capability-qualified model and overrides this client's default per call; routing it again could substitute a text-only model and would make the proxy attribution dishonest.

type ModelSlot

type ModelSlot struct {
	// Slot is the word the models door and [SettingsOptions.SetModel] take. For
	// the three roles the engine still holds a client for it is the legacy
	// engine word; for the other two it is the role itself.
	Slot string

	// Label is the plain word. Never the machinery name: nobody has an
	// "orchestrate model", they have a model that does the conversation.
	Label string

	// Role is the router role this row binds, empty for a capability slot.
	Role store.ModelRole

	// Follows names the row whose model this one uses while it is unset. It is
	// read as the row's empty reading, so an unset row says what will actually
	// run instead of showing a blank a reader would have to guess at.
	Follows string

	// Held says the running engine holds a client for this slot and can be
	// asked what it is on right now. The two roles nothing resolves yet are
	// not held, and asking the engine for them would get the conversation
	// model back — a wrong answer rendered as a confident one.
	Held bool
}

ModelSlot is one row of the models group: what it binds, what it is called in the product's language (14), and what it falls back to when it is unset.

func ModelSlotFor

func ModelSlotFor(slot string) (ModelSlot, bool)

ModelSlotFor finds one row of the models group by its slot word.

func ModelSlots

func ModelSlots() []ModelSlot

ModelSlots is the models group, in the order a person reaches for it: the five roles in the router's own ladder order first, because that is the order the ladder is described in and the order the first three are touched in, then the capability models.

type PersistedSource

type PersistedSource struct {
	ID      string `json:"id"`
	Written string `json:"written"`
	Region  string `json:"region"`
	Address string `json:"address,omitempty"`
	Key     string `json:"key,omitempty"`
	KeyEnv  string `json:"key_env,omitempty"`
	// Door is the billing road explicitly proved at connect time. Empty belongs
	// to a pre-door row and resolves to its old metered address, never to a new
	// subscription road that has not been proved for that key.
	Door string `json:"door,omitempty"`
	// PlanPaused is the person's per-service answer. Empty is wait, preserving
	// the fixed-price account unless they deliberately opt into metered spend.
	PlanPaused string `json:"when_plan_paused,omitempty"`
	// Listed records what the service itself answered at connect time. Nil is an
	// older row that still follows the vendored hint; false and true override it.
	Listed *bool `json:"listed,omitempty"`
	Order  int   `json:"order"`
}

PersistedSource is one non-default service in the profile. Address is kept only for custom services; a vendored row derives it from its region.

func PersistedSources

func PersistedSources(profileDir string) []PersistedSource

PersistedSources returns no services for every unreadable profile shape. A newer or damaged row must not prevent an older build from starting.

func PrepareCustomSource added in v0.3.0

func PrepareCustomSource(profileDir, address, written string) PersistedSource

PrepareCustomSource is the one door that mints a custom connection's persisted row, and both doors onto a connection (/connect and the Providers tab) call it before ConnectService, which stays the one validate, probe and persist path. written is the name the person gave or empty for the default the host slug answers; the id is minted from the name that will actually be used, first instance keeping the vendored id and later ones taking a numeric tiebreak when the slug is already taken.

type ProjectConfig

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

ProjectConfig is one loaded project layer. The zero value is the empty layer, which is what a directory with no settings has and what every read falls through.

func LoadProjectConfig

func LoadProjectConfig(cwd string) (ProjectConfig, error)

LoadProjectConfig reads <cwd>/.codeaf/config.json, falling back to the legacy project file only when the current file is absent.

Absent is empty; unreadable, unparseable, or written in the nested shape is an error naming the path (law 3). Keys this build does not know are kept and ignored, the way the profile writer preserves unrelated keys: a file is allowed to be from a later version, or to carry another tool's section.

func (ProjectConfig) Bool

func (p ProjectConfig) Bool(key string) (bool, bool, error)

Bool reads one on/off row. The words the sheet shows — on, off, yes, no — are accepted beside true and false, because a person writing this file by hand has only ever seen the words.

func (ProjectConfig) Float

func (p ProjectConfig) Float(key string) (float64, bool, error)

Float reads one dollar row. A quoted amount is accepted for the reason the words are above; a negative or non-finite one is refused with the row named.

func (ProjectConfig) Has

func (p ProjectConfig) Has(key string) bool

Has reports whether the project file answers this row at all.

func (ProjectConfig) Path

func (p ProjectConfig) Path() string

Path is the file this layer came from, empty when there is no workspace.

func (ProjectConfig) ResolveBool

func (p ProjectConfig) ResolveBool(profileDir, key string) (bool, error)

ResolveBool answers one on/off row through the ladder.

The environment is checked FIRST and outranks the project file, which is the only place in this file the order is not simply project-over-profile: these two rows are pinned (CODEAF_HISTORY, CODEAF_DRAFT_PERSIST), and a pin is the operator speaking about this process. An unreadable pin is not a choice at all — it falls through to the project file, and then to the profile reader, which lands on the default exactly as it always did.

func (ProjectConfig) ResolveFloat

func (p ProjectConfig) ResolveFloat(profileDir, key string) (float64, error)

ResolveFloat answers one dollar row through the ladder. Today that is the session ceiling, which has no environment pin by design.

func (ProjectConfig) ResolveString

func (p ProjectConfig) ResolveString(profileDir, key string) (string, error)

ResolveString answers one text row through the whole ladder: the project file, then the profile reader that already owns this row, then that reader's built-in default.

AN EMPTY PROJECT VALUE IS AN ANSWER AND NOT AN ABSENCE. `"models.tiers.low": ""` is how a repository says "ignore whatever this machine has pinned and let the small calls follow the conversation" — the alternative, treating empty as unset, would leave a repository unable to turn a personal setting OFF.

func (ProjectConfig) String

func (p ProjectConfig) String(key string) (string, bool, error)

String reads one text row.

The two MAP rows may be written either as the flat `read:allow, bash:prompt` text the settings sheet stores or as a JSON object — the same map, and the object is what a person hand-writing a settings file reaches for first. Whichever shape it arrives in it REPLACES the profile's answer entirely (law 2); the object is flattened to the flat text so exactly one parser (ParseToolApprovals, ParseModelRoles) reads it downstream.

type SSHTransport

type SSHTransport struct {
	ControlPersistSeconds int
	ServerAliveSeconds    int
	ServerAliveMisses     int
	IPQoS                 string
}

SSHTransport is the local surface's policy for the ssh process carrying a hosted conversation. These are settings rather than environment-only pins because a person on a slow or unusually filtered network may need to change them, and the settings registry is the one supported path for such choices.

func SSHTransportAt

func SSHTransportAt(profileDir string) SSHTransport

SSHTransportAt resolves all four knobs from the profile's one config file. A malformed hand edit falls back one field at a time rather than disabling every transport improvement because one value could not be read.

type Seat

type Seat struct {
	Role   SeatRole
	Model  string
	Source SeatSource
	// From is the tier word this seat's model was INHERITED from, and is empty
	// unless Source is [SeatInherited]. It is carried rather than re-derived
	// because the line that tells a person what happened has to name the row it
	// read, and a second walk of the lineage at print time could name a
	// different one.
	From string
	// Crew is the preset word the profile's five tier rows make — `frugal`,
	// `balanced`, `max` or `custom` ([CrewAt]) — and is empty unless Source is
	// [SeatCrew], [SeatInherited], [SeatComputed] or [SeatTable]. It is read
	// rather than stored, exactly as the settings sheet reads it, so a receipt
	// and the sheet cannot disagree about which crew ran. On the computed and
	// table rungs it is the preset the auto row is computed at, read from the
	// stored rows ([crewPresetUnder]) rather than derived through the resolver,
	// which would be the seam asking itself.
	Crew string
	// contains filtered or unexported fields
}

Seat is one seat's answer: the model, and where it came from.

func CheckSeat added in v0.4.0

func CheckSeat(flagCheck string, plan Seat) Seat

CheckSeat is the check seat's own ladder, resolved at the door. The flag wins, then the check environment. A plan seat pinned by either its flag or its environment answers next. Any other plan source leaves the seat empty for the run's crew factory to fill from the careful row.

func TierSeatAt

func TierSeatAt(profileDir, tier string) Seat

TierSeatAt is ONE TIER ROW READ AS A SEAT, for the surfaces that seat roles rather than run a door: the conversation's role map (cmd/codeaf's v3Crew), the five rows of the settings sheet, and the crew word derived from them.

IT IS [resolveSeat] WITHOUT THE TWO RUNGS THAT BELONG TO AN INVOCATION. A flag is something a command line said and a conversation has no command line for its crew; CODEAF_MODEL names the model a person TALKS TO (Load folds it into Config.Model), and a variable that also filled the work seat of every task handed off in that conversation would be one word quietly moving two dials. What is left is the profile — which is where a conversation's seats have always come from.

Where it differs from a run is the BOTTOM, and only there:

  • a row the person WROTE is the crew answering;
  • a row they CLEARED reads empty, and stays empty, because on this surface that is an answer — "follow the conversation" — and refusing it would make a default into a rule (TierModelAt argues it at length);
  • a key NEVER HELD asks the lineage before the bottom rung, so a profile older than the worker seat hands a task the same model `codeaf do` hands it (#302 headless, #312 in the conversation), and the seat carries the fact so a surface can say it once (Seat.Notice);
  • anything else is this build's own choice for that class of work.

THE PRESET WORD IS DELIBERATELY NOT READ HERE. Seat.Crew stays empty: CrewAt derives the preset from all five rows THROUGH this function, so a seat that filled it in would be the ladder asking the summary that is computed from the ladder — five extra file reads per row, and a cycle. A surface that wants both facts asks for both.

func (Seat) Describe

func (s Seat) Describe() string

Describe is one seat in a receipt's voice:

work z-ai/glm-5.3-flash (crew frugal)
plan follows the work model (default)

THE EMPTINESS LAW, as the crew row already keeps it: an unfilled plan seat is said in words rather than left as a gap somebody has to interpret.

func (Seat) Env

func (s Seat) Env() string

Env is the variable that fills this seat.

func (Seat) Flag

func (s Seat) Flag() string

Flag is the flag that fills this seat.

func (Seat) FromWords

func (s Seat) FromWords() string

FromWords is the row this seat's model was inherited from, in the words the settings sheet calls that row by — empty unless the source is SeatInherited. It is exported for the surface that has its own sentence to build about the same fact ([Notice] is the sentence; this is the noun), so two surfaces cannot invent two names for one row.

func (Seat) Line

func (s Seat) Line() string

Line is one seat labelled, for the door that seats only one — `exec`, which executes and never plans.

func (Seat) Notice

func (s Seat) Notice() string

Notice is the ONE line a person reads when this seat was not filled by a row they wrote, and it is empty for every other seat.

IT IS SAID ONCE, WHERE THE SEATS ARE REPORTED, and never at the point of a call: this seat answers every request a run makes, and a line that arrived with all of them would be noise a person learns to read past — which leaves them exactly where the silence did. [Report] is how a door prints it, so the door does not have to remember.

It is the register the surface already speaks in — an observation, a middle dot, a promise, lowercase, no full stop, nothing about machinery (internal/session's taskEscalationNote and checkpointCeilingNote). The observation is the true thing: the profile is older than the seat. The promise is what is running instead and what ends it.

AND THE PROMISE NAMES THE DOOR, because it used to end in one nobody could find. `until you pick a crew again` is a remedy with no address on it, and this line is printed on FOUR HEADLESS DOORS — `codeaf do`, `codeaf plan run`, `codeaf exec` and `codeaf run` — where there is no way at all to pick a crew: [CrewPreset] is written by `/crew` and by the settings sheet's Providers row, both of which are the conversation, and no flag and no terminal verb sets one. So a person reading this in a terminal was told to do something, given no way to do it, and left to discover on their own that the answer was a different surface. Cause plus what to do is the law on both surfaces; a cause plus a dead end is the defect.

ONE FORM, TRUE FROM BOTH PLACES IT IS PRINTED. Naming `/crew` reads as the next keystroke in the conversation and as a destination from the terminal, and it is the same sentence in both — which is what keeps somebody who has seen one surface recognising the other. `seat` STAYS: it is this product's own noun for a row of the crew, taught under that name on the manual's own page and spoken by the settings sheet and the model picker, and the vocabulary law is about MACHINERY — the program's words for its own process — not about a domain noun the product teaches.

AND THE PROMISE ATTRIBUTES THE MODEL RATHER THAN DESCRIBING IT. This read `it is running on your small work model`, printed directly under a models line naming `deepseek/deepseek-v4-pro` — so two consecutive lines called one model by its name and then called it small, and the developer who met them could not tell which model the lane was actually on. The two lines were never in disagreement about the FACT: whenever the source is SeatInherited the model on the line above IS the inherited one, always, because that is what inheriting means. What differed was the grammar. `small work` is the NAME OF A SEAT on the settings sheet, one of the five this product seats, and putting it in front of `model` turns a seat's name into an adjective about the model it holds.

So the notice never characterises the model a second time — it says which SEAT lent it. `your small work seat's model` cannot be read as a claim about deepseek-v4-pro, and it answers the question the person actually has: this seat has no row of its own, so it is borrowing that one's. A description was always going to contradict the line above it, since the two are one model.

func (Seat) Report

func (s Seat) Report() string

Report is what a door prints: the seat, and the line that says a row was inherited when one was.

func (Seat) Rung

func (s Seat) Rung() string

Rung is the answering rung as a receipt says it: the flag or the variable by name, the crew by preset, and otherwise the one word `default`. Naming the flag and the variable rather than saying "flag" and "env" costs nothing and tells a reader which of the two they would have to change.

type SeatRole

type SeatRole string

SeatRole is which of the two seats one answer fills. It is on the Seat so a seat can name its own flag and its own variable rather than being handed them.

const (
	// SeatWork is the model that does the work.
	SeatWork SeatRole = "work"
	// SeatPlan is the model that structures it — plans, replans, contracts, the
	// delivery gate. Empty is legal here and means "the work model plans too";
	// see [Config.PlanModelResolved].
	SeatPlan SeatRole = "plan"
	// SeatCheck is the model that checks finished work. It has no flagless
	// rung of its own here: an empty check seat is the crew's careful row,
	// read by the run's crew factory rather than resolved here, because the
	// check is the one seat a two flag run must not let a third model fill.
	SeatCheck SeatRole = "check"
)

type SeatSource

type SeatSource string

SeatSource is the rung that answered, and the five constants are the ladder in order. It is an enum rather than a sentence because a caller putting it in JSON is making a promise a script parses.

const (
	// SeatFlag is `--model` or `--plan-model`: the most recent thing the person
	// said, and it always wins.
	SeatFlag SeatSource = "flag"
	// SeatEnv is the environment: automation's override, set once for a campaign
	// instead of threaded onto every invocation.
	SeatEnv SeatSource = "env"
	// SeatCrew is the profile's own tier row — what /crew wrote.
	SeatCrew SeatSource = "crew"
	// SeatInherited is the profile's crew answering through an OLDER row than
	// this seat's own, because the profile was written before this seat existed
	// ([tierLineage]). It is still the crew answering — the model came from what
	// the person chose — and it is a separate rung because a person is owed the
	// difference between "the row you wrote" and "the row your row was split out
	// of".
	SeatInherited SeatSource = "inherited"
	// SeatComputed is a tier row that says `auto` answering from the catalog:
	// the seat's model computed off the published figures at read time
	// ([AutoPick]). It is a rung of the crew's own, not the build's default —
	// the person wrote the row — and it says so because a receipt that called it
	// `crew` would hide the one fact a reader of the run is checking: the id was
	// derived, not named.
	SeatComputed SeatSource = "computed"
	// SeatTable is a tier row that says `auto` answering from the family's
	// table row ([autoRow], [pickedModel]) because nothing could be computed —
	// no catalog, or no pick off it. The id is the preset's own, and the rung
	// says which kind of answer it was.
	SeatTable SeatSource = "table"
	// SeatLearned is a seat computed at the crew's budget under the `learn`
	// pick ([CrewPickAt]): the catalog's own figures, plus the Model Pool's
	// measurements and the person's own judged runs carried as a quality
	// prior. It is a rung of its own, beside [SeatComputed], because the one
	// fact a reader of a run is checking — was this id derived, and from
	// what — is a different answer under the two words: the catalog alone,
	// or the catalog plus what runs measured.
	SeatLearned SeatSource = "learned"
	// SeatDefault is this build's choice, for a profile that has never said
	// anything about models at all.
	SeatDefault SeatSource = "default"
)

type Seats

type Seats struct {
	Work  Seat
	Plan  Seat
	Check Seat
}

Seats is both seats of one run, and the check seat beside them when a door resolved one. Work and Plan are the ladder's two answers (ResolveSeats); Check is the door's own answer for the review round (CheckSeat), and empty on a door that named nothing, which the run's crew factory reads as the profile's careful row.

func ResolveSeats

func ResolveSeats(profileDir, flagModel, flagPlanModel string) Seats

ResolveSeats climbs the ladder once per seat.

  1. the flag, which is what this invocation said;
  2. the environment, which is what this campaign said;
  3. the profile's crew — the planning seat takes the MASTERMIND tier and the work seat takes the WORKER tier, because that is where the two roles ride in chat: roles.DefaultAssignment puts RolePlanner on TierMastermind and RoleWorker on TierWorker, and a chat task's own worker resolves through the same row (internal/session's defaultTaskModel), so a headless run, an adaptive run and a task handed off in conversation all call the same model on the same profile;
  4. the crew again, through an OLDER ROW, for a profile written before this seat existed — the worker row's ancestor is the small-work row it was split out of ([tierLineage]), and the seat says it was inherited rather than reporting the model as though the person had pinned it;
  5. this build's default, which a profile that has said nothing about models at all is the only thing left for.

profileDir is the profile to ask, ProfileDir for an ordinary process. The environment is read HERE, at call time, and not carried in from Load: Load resolves CODEAF_MODEL and DefaultModel into one field and the difference between them is exactly what the receipt has to report.

A tier value may carry a thinking level (`moonshotai/kimi-k3:low`) and is handed on WHOLE, exactly as a flag or a variable carrying one is. The level is applied where every other level is applied — the role ladder splits it into a model and an effort at the point of the call (roles.SplitEffort, internal/session's roleRequest) — and the client seam takes the level off the slug it sends ([Config.providerConfig]). Neither of those is this function's business, and a rung that quietly shortened the value would be the second model policy this whole file exists to remove.

func (Seats) Line

func (s Seats) Line() string

Line is the one line a headless run opens with:

models: work z-ai/glm-5.3-flash (crew frugal) · plan z-ai/glm-5.3-flash (crew frugal)

func (Seats) Notice

func (s Seats) Notice() string

Notice is the inheritance line for whichever seat was filled by an older row, and empty when neither was. Both seats are asked and their lines joined, because the table decides which tiers have ancestors and this must not have to be edited when a second one does.

func (Seats) Report

func (s Seats) Report() string

Report is what a door prints instead of [Line]: the models line, and beneath it the one line that says a seat was inherited. ONE PLACE, so a door added tomorrow cannot print the models and swallow the reason.

func (Seats) Sentence

func (s Seats) Sentence() string

Sentence is both seats, unlabelled, for a door whose opening lines have a label column of their own:

work z-ai/glm-5.3-flash (crew frugal) · plan z-ai/glm-5.3-flash (crew frugal)

ONE SHAPE AND NOT TWO. It would read a little better to collapse a run whose seats came from the same crew into one clause, and it would mean a script parsing this has two grammars to know and a person comparing two runs has two shapes to compare. Every run says both seats and names the rung beside each.

type Setting

type Setting struct {
	Key      string
	Category string
	Label    string
	Hint     string
	Kind     SettingKind

	// Unit is WHAT THIS ROW'S NUMBER IS COUNTED IN, and it lives here beside
	// the default rather than in the surface that draws the row. `ssh reuse
	// 300` is a row nobody can decide — three hundred seconds, connections,
	// kilobytes? — and a panel that spelled the `s` for itself would be a
	// second place for that answer to live, drifting the day somebody widened
	// the row. One source: the registry says what the number is, every surface
	// renders it ([Setting.Reading]).
	//
	// THE SYMBOLS ATTACH AND THE WORDS DO NOT. `300s`, `60%`, `20m` are one
	// token in every terminal font, the way the two SettingDuration rows beside
	// them already read; `1536 MB` and `65536 tok` are two words and read as
	// two ([unitAttaches] is the whole list).
	//
	// A ROW WHOSE READING ALREADY CARRIES ITS UNIT LEAVES THIS EMPTY — a
	// duration writes `20m`, a dollar row writes `$5`, [formatPercent] writes
	// its own `%`. A row whose LABEL already names what is counted declares
	// [UnitInLabel] rather than nothing, so "somebody decided this row needs no
	// suffix" and "nobody has looked at this row yet" are different states and
	// the completeness test can tell them apart.
	Unit string

	// UnitOne is [Setting.Unit] at exactly one — `1 clean firing` rather than
	// `1 clean firings`. Empty means the unit reads the same at every number,
	// which is true of every symbol and of `tok`, `MB` and `per core`.
	UnitOne string

	// Slot is the model role a SettingModel row fronts.
	Slot string

	// Choices lists the accepted values of a SettingChoice row.
	Choices []string

	// Env pins the row from the environment: while it is set the value is
	// read-only and the surface says which variable owns it.
	Env string

	// EnvDefault only seeds a value the user has never chosen — the model
	// slots work this way, so a pinned default never freezes the row.
	EnvDefault string

	// PrefsField names the chat-prefs json field this row fronts when the
	// value lives beside the graph rather than in the profile config.
	PrefsField string

	// EmptyLabel reads for a text row whose value is unset.
	EmptyLabel string

	// Secret marks a row whose value is a credential. Such a row READS MASKED
	// — its own reader returns the mask, so every surface that renders a value
	// renders the mask without having to know the row is special — and its
	// writer treats the mask as "unchanged" (see writeCredential), so an editor
	// that opens on the displayed value and is saved unedited cannot overwrite
	// the key with a row of bullets.
	//
	// The flag is here rather than a [SettingKind] because a credential edits
	// exactly like text: the difference is what it shows, not what it accepts,
	// and a fourth kind would make every switch in every surface grow an arm
	// that did the same thing SettingText already does. A surface that wants to
	// suppress its own echo while typing reads this.
	Secret bool
	// contains filtered or unexported fields
}

Setting is one row: what it is called, what it reads now, and what happens when the user changes it.

func (Setting) Accepts

func (s Setting) Accepts() string

Accepts is what this row will take, in the words its own writer refuses in.

It lives here rather than in the surfaces because it is the WRITER'S sentence read forwards: "that's not a dollar amount" and "an amount in dollars" are one fact, and a caller that spelled the second for itself would drift from the first the day a parser widened. A panel with a picker never asks; a tool putting the row in front of a model that has to type a value does, and it is the difference between one call and three.

func (Setting) Apply

func (s Setting) Apply(raw string) error

Apply validates, persists, and lands the live effect. A pinned row refuses calmly rather than writing a value the environment would keep overriding.

func (Setting) PinnedBy

func (s Setting) PinnedBy() (string, bool)

PinnedBy names the environment variable holding this row read-only.

func (Setting) Reading

func (s Setting) Reading() string

Reading is Setting.Value with the row's unit on the end — the string a surface DRAWS, where Value is the string an editor opens on.

The two are separate because a unit is a fact about the number and never part of it: an edit box that opened on `300s` would be asking a person to type the `s` back, and the writers refuse anything that is not a bare figure.

func (Setting) Receipt

func (s Setting) Receipt() string

Receipt is the dim fact that belongs beside this row's value — today's spend beside the day's ceiling, the price tier beside a model. It is a receipt and never a second value: it is derived from a live read, it is never editable, and a row with nothing true to add returns the empty string rather than a placeholder (13, and 10.2.8's rule against inventing a reading).

func (Setting) SelfService

func (s Setting) SelfService() bool

SelfService reports whether a model changing settings on the person's behalf may write this row. A surface rendering the sheet can read it too — a row the chat will refuse is a fact worth showing beside the row.

func (Setting) SelfServiceRefusal

func (s Setting) SelfServiceRefusal() string

SelfServiceRefusal is the sentence a guarded row answers a model with, and the empty string for a row the model may write.

It names the row twice — the person's label and the key — because the two readers of this sentence want different halves: the person recognises the label they see in the panel, and the model needs the key to be sure it is talking about the same row when it explains itself. And it says where to go, because a refusal that does not name the door is a refusal that ends the conversation.

func (Setting) Value

func (s Setting) Value() string

Value is the row's current reading, already formatted for display.

type SettingGroup

type SettingGroup struct {
	Title string
	Rows  []Setting
}

SettingGroup is one rendered category.

type SettingKind

type SettingKind int

SettingKind decides how a row reads, edits, and validates.

const (
	// SettingModel opens the existing capability-filtered model picker.
	SettingModel SettingKind = iota
	SettingDollars
	SettingDuration
	SettingPercent
	SettingCount
	SettingBool
	SettingChoice
	SettingText
)

type Settings

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

Settings is the built registry.

func NewSettings

func NewSettings(options SettingsOptions) *Settings

NewSettings builds the registry against one profile directory and whatever live seams the caller can supply. Missing seams make a row read-only rather than absent, so the sheet always shows the complete surface.

ONE ROW IS THE EXCEPTION and [Settings.build] states it where it happens: the divider has nowhere to be written without SettingsOptions.SaveSplitPct, and a read-only divider is not a row somebody can read — it is a control whose only behaviour is to refuse.

func (*Settings) EnvironmentPins

func (s *Settings) EnvironmentPins() []string

EnvironmentPins lists the operator plumbing currently set, for the sheet's read-only footer.

func (*Settings) Groups

func (s *Settings) Groups() []SettingGroup

Groups returns the rows grouped for rendering, skipping empty categories.

func (*Settings) ModelSlotBindings

func (s *Settings) ModelSlotBindings() map[string]string

ModelSlotBindings is WHICH MODEL EACH ROLE SLOT IS BOUND TO RIGHT NOW, keyed by the slot word (ModelSlot.Slot).

It is the join SCREEN 2c's model table asks for: a page holding a model id out of the spending ledger wants the role a person could go and change, which is the binding, and never the auxiliary word one call gave itself.

A SLOT NOTHING HAS BOUND IS ABSENT, not present and empty. That is what lets a caller tell "this model is the one execution runs on" from "nothing answers for planning yet", which are the two halves of the design's own table — and it is why this reads [modelSlotReading] rather than Setting.Value, whose empty reading is the row's `follows execution` label rather than a blank.

THE CAPABILITY SLOTS ARE NOT IN IT. Drawing, speaking, composing, filming and voice are knobs with readers rather than router roles ([Settings.modelRow] makes the split), and none of them is a role a text model's bill could be attributed to.

func (*Settings) PersistedKeys

func (s *Settings) PersistedKeys() []string

PersistedKeys lists the rows whose current value is written down in the profile's config.json rather than resolved from a built-in default. The order is the registry's own row order, so a caller rendering in that order can walk both lists together.

A row fronting a store this registry does not own — anything with a Setting.PrefsField, meaning the model slots and the chat divider, which live beside the graph — is never listed, even if a hand-edited config.json happens to carry a key by that name. Absence from THIS file is not evidence about another one, and a provenance chip that guessed would be exactly the estimate 10.2.8 bans.

An unreadable or absent config.json returns no keys: a file that is not there yet is a profile with nothing written down, which is the truth.

func (*Settings) Row

func (s *Settings) Row(key string) (Setting, bool)

Row finds one row by key.

func (*Settings) Rows

func (s *Settings) Rows() []Setting

Rows returns every row in category order.

type SettingsOptions

type SettingsOptions struct {
	ProfileDir string

	// ModelValue and SetModel front the model slots. They stay in the chat
	// prefs file; the registry does not migrate them.
	ModelValue func(slot string) string
	SetModel   func(slot, slug string) error

	// SplitPct and SaveSplitPct front the chat/task divider.
	SplitPct     func() int
	SaveSplitPct func(pct int)

	// RoleModel answers what a router role is bound to right now, for the roles
	// the engine holds no client for. Nil is the honest state of this build —
	// nothing resolves verify or scribe yet — and those rows then read as the
	// role they follow instead of as a guess.
	RoleModel func(role string) (string, bool)

	// SpentTodayUSD is the day's spend, for the receipt beside the day's
	// ceiling. The bool separates "spent nothing" from "nobody counted"
	// (10.2.8); nil leaves the receipt off rather than printing $0.00.
	SpentTodayUSD func() (float64, bool)

	// SpentThisSessionUSD is what the conversation in front of the reader has
	// spent, for the receipt beside its own ceiling. Nil on every door that is
	// not a live conversation, and the bool carries the same distinction
	// [SettingsOptions.SpentTodayUSD] carries: not counted is not zero.
	SpentThisSessionUSD func() (float64, bool)

	// ModelCost is what the provider table knows about one model's price. It is
	// a hint beside a model row and never a filter: an unpriced model is the
	// offline case, not a bad model. [ModelCostHint] is the derivation the
	// wiring lane hands in.
	ModelCost func(slug string) string

	// BackgroundChecks is this machine's own scheduler as internal/standing
	// drives it: the launchd agent or systemd user timer that runs `codeaf tick`
	// every [standing.Interval] so standing items are checked with no window
	// open. It is what the `standing.background` row reads and writes.
	//
	// NIL LEAVES THE ROW OUT ALTOGETHER, which is this codebase's law about a
	// capability that cannot work rather than a shortcut: a Windows host, a
	// build with no store, a registry built to read defaults out of a profile
	// nobody has — none of them has a timer to turn on, and a row present and
	// refusing every write would be a switch wired to nothing. The divider row
	// is absent for the same reason ([Settings.build]).
	BackgroundChecks standing.Watch

	// Applied fires after a row is successfully written, so a process holding
	// its own copy of a value can honor the change without waiting for a
	// relaunch.
	Applied func(key string)
}

SettingsOptions supplies the live seams the registry cannot reach on its own: the running model slots and the surface's own divider preference.

Jump to

Keyboard shortcuts

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