Documentation
¶
Overview ¶
Package behavior builds the system prompt.
Behaviour is not "a prompt" — it is a stack of layers with different costs and precision, and the engineering is deciding which layer a rule belongs to:
doctrine always in the prefix, permanent cost, low precision tool schema always in the prefix, high precision at the point of use user rules always in the prefix skill index one line each in the prefix tool errors zero cost until the failure, maximum precision
The operative rule: a rule that can be enforced in code does not belong in a prompt at all. Prompt is for what cannot be structurally enforced, which is why the read-before-edit invariant lives in the tools package and only its one-line summary appears here.
Build is pure, so the prefix is byte-identical between turns.
Spec: docs/specs/architecture/behavior-definition/202608080016-*.
Index ¶
- Constants
- func Build(p Prompt, f Formulation) (string, error)
- func LoadDoctrineOverlay(dir string, maxBytes int) (DoctrineOverlay, []Notice, error)
- func Render(r Reminder) string
- func RenderSkill(s Skill) string
- type BudgetBand
- type Doctrine
- type DoctrineOverlay
- type Formulation
- type Instruction
- type InstructionSource
- type Notice
- type Origin
- type OutOfChainInstruction
- type Prompt
- type Reminder
- type ReminderKind
- type Repo
- type SectionOrigins
- type SessionState
- type Skill
- type SkillIndexEntry
Constants ¶
const DefaultMaxIndex = 64
DefaultMaxIndex caps how many skills reach the prefix.
The index line is paid for on every turn of every session, so an unbounded index is an unbounded tax: a directory grown to two hundred skills quietly adds two hundred lines to every prompt, and the cost arrives as a slow bill rather than as an error.
const DoctrineDirName = "doctrine"
DoctrineDirName is the directory searched under the user's config root.
const MaxWhenToUse = 120
MaxWhenToUse caps the index line. One line is the contract: the index is paid for on every turn, so a skill that describes itself in a paragraph is charging every session for a context most of them never enter.
const SkillsDirName = "skills"
SkillsDirName is the directory searched under each root.
Variables ¶
This section is empty.
Functions ¶
func Build ¶
func Build(p Prompt, f Formulation) (string, error)
Build renders the system prompt. Pure: same input, byte-identical output.
func LoadDoctrineOverlay ¶
func LoadDoctrineOverlay(dir string, maxBytes int) (DoctrineOverlay, []Notice, error)
LoadDoctrineOverlay reads the overlay from one directory.
The parameter is ONE directory, not a list. The contrast with LoadSkills(dirs []string, ...) is deliberate: a skill comes from two roots, a doctrine overlay comes from one (RN-11). The singular type says that better than a comment could, and the workspace root never becomes an argument in the first place.
func Render ¶
Render wraps a reminder in the marker the base doctrine teaches the model to recognise.
Without the marker the model reads a reminder as the user speaking, and answers it — which is both wrong and unnerving to watch.
func RenderSkill ¶
RenderSkill wraps a loaded body so the model can tell it from the user speaking, exactly as a reminder is wrapped.
Types ¶
type BudgetBand ¶
type BudgetBand int
BudgetBand is how full the context is, as announced to the model.
Mirrors contextengine.Band as a plain integer rather than importing it: the reminder channel is about text, and the arithmetic belongs where the window is measured. Importing would also point this package at one that assembles prompts, which is backwards.
const ( BudgetNone BudgetBand = iota Budget60 Budget80 Budget92 )
type Doctrine ¶
Doctrine is the base layer. Safety is isolated from the rest so no configuration path can reach it.
func DefaultDoctrine ¶
DefaultDoctrine is the shipped base layer.
It is deliberately short. Every line here costs tokens on every turn of every session forever, so a rule reaches this layer only when it cannot live anywhere cheaper: in a tool description, in an error message, or as an invariant in code.
func (Doctrine) Apply ¶
func (d Doctrine) Apply(o DoctrineOverlay) Doctrine
Apply returns the doctrine with the overlay applied. Pure.
Safety is not read here and could not be written if it were: the overlay has no such field.
type DoctrineOverlay ¶
type DoctrineOverlay struct {
Identity string // replaces
Style string // replaces
ToolsMore string // APPENDS to ToolPolicy; never replaces
}
DoctrineOverlay is what the user's configuration may change in the base layer (RN-11). An empty field leaves the shipped text intact.
Safety is NOT here, and that absence is the guarantee (RN-12): there is no path to close because there is no path. A lock by convention breaks at the first refactor; a lock by type does not compile.
ToolsMore is spelled differently from ToolPolicy for the same reason. No accidental assignment can swap one for the other.
func (DoctrineOverlay) Origins ¶
func (o DoctrineOverlay) Origins() SectionOrigins
Origins reports where each section will have come from once applied.
type Formulation ¶
type Formulation interface {
// Family names the family, so an assembled prompt can be traced back to
// the formulation that produced it.
Family() string
// Section renders one titled block. An empty title is the leading block
// that carries no heading.
Section(title, body string) string
}
Formulation is how a model family prefers a rule to be worded.
RN-8 draws the line and it is worth stating plainly: the RULE is single and lives in this spec; the FORMULATION belongs to the family. Two families do not answer the same sentence the same way — one prefers marked-up structure, another plain prose — and a family that needs to change the *rule* rather than the wording is a sign that the rule is wrong, or that the model is not supportable.
So this interface can only change how a section is delimited. There is no method here that could add, remove or reword a rule, and that is deliberate: the way this abstraction stays honest is by not being able to express the thing it must not do.
func FormulationFor ¶
func FormulationFor(family string) Formulation
FormulationFor picks the wording for a family.
The choice lives here rather than in the provider package on purpose. If each family carried its own wording, the wording would drift into the rule — a family would grow a sentence, then a caveat, then an exception, and two models would end up behaving differently while the spec said one thing. Here the whole surface is a delimiter.
An unknown family gets markdown. It is the safe default: a prompt that reads slightly less well is recoverable, and refusing to assemble one is not.
type Instruction ¶
type Instruction struct {
Source InstructionSource
Scope string
// Locked marks an instruction the administrator set, which is what
// SourceLocked ranking above everything else is FOR. Without the field the
// precedence table had a top row nothing could ever occupy.
Locked bool
Text string
}
Instruction is one block of user or administrator guidance.
type InstructionSource ¶
type InstructionSource string
InstructionSource ranks where an instruction came from.
const ( SourceLocked InstructionSource = "locked" SourceDirectory InstructionSource = "directory" SourceProject InstructionSource = "project" SourceUser InstructionSource = "user" // SourceLearned is what the agent noted for itself, read back from the // workspace. It exists to be OUTRANKED: see the authority table. SourceLearned InstructionSource = "learned" )
type Notice ¶
Notice is something the loader refused to do silently.
func SafetyClaims ¶
func SafetyClaims(in []Instruction) []Notice
SafetyClaims reports the places an instruction tries to loosen safety.
The instruction is NOT modified and NOT dropped: the rest of it is legitimate and discarding a whole file over one sentence is the silent-filter failure this project refuses everywhere else. What is returned is what to say out loud.
type Origin ¶
type Origin string
Origin says where a section of the assembled prompt came from.
It exists for the audit: an invisible replacement would be worse than the immutability it replaces, because the only way a user has today of knowing what reached the model is to read it.
type OutOfChainInstruction ¶
OutOfChainInstruction is an instruction file found in a directory the session touched, outside the chain frozen at session creation.
type Prompt ¶
type Prompt struct {
Doctrine Doctrine
Tools []string
Instructions []Instruction
SkillIndex []SkillIndexEntry
// Repo is where the work is happening, frozen at session creation. Nil for
// a directory that is not a repository, which is ordinary and silent.
Repo *Repo
}
Prompt is everything the prefix is built from.
type Reminder ¶
type Reminder struct {
Kind ReminderKind
Text string
}
Reminder is one appended note.
Appended, never prefixed. That is the whole point of the channel: it steers the model mid-turn without touching the prefix, so the cache survives. A reminder in the prefix would cost the entire cached prompt every time anything changed on disk.
func Emit ¶
func Emit(s SessionState) []Reminder
Emit is PURE: the same state always produces the same reminders, in the same order. That is what makes a replayed history byte-identical to a live one.
type ReminderKind ¶
type ReminderKind string
ReminderKind identifies a reminder. The text is constant per kind, which is what keeps the history reproducible.
const ( ReminderFileChanged ReminderKind = "file_changed" ReminderApprovalDenied ReminderKind = "approval_denied" ReminderCompacted ReminderKind = "compacted" ReminderToolsParallel ReminderKind = "tools_parallel" ReminderInstructionOutOfChain ReminderKind = "instruction_out_of_chain" ReminderContextBudget ReminderKind = "context_budget" ReminderUnmetCriteria ReminderKind = "unmet_criteria" ReminderUnplannedChange ReminderKind = "unplanned_change" ReminderWorthRemembering ReminderKind = "worth_remembering" ReminderProtectedTouched ReminderKind = "protected_touched" ReminderInterrupted ReminderKind = "interrupted" )
type Repo ¶
type Repo struct {
// Branch is empty when there is none to name — a detached head, or a
// repository with no commit yet.
Branch string
// MainBranch is what work is normally cut from and merged back into. It is
// what "am I on the right branch" is answered against.
MainBranch string
// Detached marks a head that is not on a branch. Separate from an empty
// Branch because "no branch" and "we did not find out" are different, and
// only one of them is worth telling the model.
Detached bool
// Clean is stated rather than derived from an empty Status: "nothing
// changed" and "we did not look" read the same when both are blank.
Clean bool
// Status is porcelain output, already bounded by whoever read it.
Status string
// Commits are the most recent, newest first, one line each.
Commits []string
// Truncated marks a status that did not fit. Nothing in this codebase cuts
// output without saying so.
Truncated bool
}
Repo is where the agent is working, as it stood when the session opened.
Data, never a reader: Build is pure and must stay pure, so nothing here runs a command. Whoever creates the session takes the snapshot and passes it in, the same way the instruction chain is frozen and handed over.
It is deliberately not a tool. `bash` already runs git, and a tool the model has to remember to call is a fact it uses when it happens to think of it. The branch it is on is not that kind of fact: every rule about where work belongs depends on it, and a rule that needs a lookup first is a rule followed by accident.
type SectionOrigins ¶
SectionOrigins is the provenance of all four sections.
type SessionState ¶
type SessionState struct {
// ChangedFiles are paths read this session whose content on disk no longer
// matches what the model was shown.
ChangedFiles []string
// DeniedTools are tool names whose boundary crossing the user refused.
DeniedTools []string
// Compacted reports that history was summarised since the last reminder.
Compacted bool
// ParallelBatch is the size of the batch that just ran. Only the fact that
// it exceeds one reaches the text — the number itself would vary between
// otherwise identical runs.
ParallelBatch int
// OutOfChain are instruction files discovered after the chain was frozen.
OutOfChain []OutOfChainInstruction
// UnmetCriteria are the names of the done criteria still not met. Set only
// when the turn is being asked to continue because of them.
UnmetCriteria []string
// ProtectedTouched are paths that ARE the measurement and were written this
// turn. Surfaced, never counted as progress in silence.
ProtectedTouched []string
// UnplannedChange reports that work has spread across several files with
// no plan recorded.
//
// A boolean and not a count: the count would vary between otherwise
// identical runs and reach the model through the text, which is what RN-7
// of the context engine forbids and why the budget texts are constants too.
UnplannedChange bool
// WorthRemembering reports that the turn hit the same wall twice: the same
// tool, the same failure, the same path. Once is a mistake; twice is the
// repository teaching something nobody wrote down.
//
// It exists because measurement said so. Four scenarios, four designs, and
// the model never once called `remember` on its own — so the prompt asking
// for it was not enough, and a fifth sentence in the same place would be the
// third time that failed. A reminder is the other layer: nothing until the
// situation exists, and delivered at the moment it is being missed.
WorthRemembering bool
// able to run at all.
//
// Different from UnmetCriteria, which means a check ran and said no. Here
// nothing ran, so there is no failure to fix and nothing to try again —
// only something to admit.
VerificationUnavailable bool
// BudgetCrossed is the occupancy band to announce, set only on the turn the
// band is crossed upward. BudgetNone announces nothing.
//
// The caller decides whether a crossing happened, because that needs the
// previous band, and Emit is a function of this state alone.
BudgetCrossed BudgetBand
}
SessionState is everything Emit is a function of. Nothing else is read: no clock, no filesystem, no globals.
type Skill ¶
Skill is a body of guidance that only matters in a specific context.
The index line goes in the prefix; the body is appended when a trigger fires. Loading every body into the prefix is the fastest route to a system prompt of tens of thousands of tokens, paid on every turn of every session, with the model's attention spread across all of it (RN-7).
func LoadSkills ¶
LoadSkills reads skills from a set of directories, later directories winning on a name collision.
A skill is either `<dir>/<name>.md` or `<dir>/<name>/SKILL.md`. The second form exists so a skill can carry files beside it.
func Match ¶
Match selects the skills whose triggers fire for a task. Deterministic: the same text always loads the same bodies, which is what keeps a replayed session identical to the live one.
An explicit `triggers` list is matched as a phrase. Without one, the when-to-use line is matched on its own significant words, and two distinct hits are required — a single common word would load a skill into a task that merely mentioned it in passing.
func ParseSkill ¶
ParseSkill reads frontmatter and body. Pure over its input.
type SkillIndexEntry ¶
SkillIndexEntry is the one line a skill contributes to the prefix. The body is loaded on demand: putting every skill body in the prefix is the fastest route to a prompt of tens of thousands of tokens paid on every turn.
func Index ¶
func Index(skills []Skill) []SkillIndexEntry
Index renders the prefix entries, one line each.
func IndexCapped ¶
func IndexCapped(skills []Skill, max int) []SkillIndexEntry
IndexCapped is Index with an explicit ceiling.
Over the cap it keeps the first n by name and says how many it left out. It does not truncate in silence: a skill missing from the index is one the model never learns exists, and discovering that from behaviour costs far more than reading one line.