Documentation
¶
Overview ¶
Package report holds the structured end-of-turn report: its shape, the checks it must pass before the agent accepts it, and the rendering the agent and the interactive front end both draw it with.
A report is what a turn ends with instead of free prose. The model calls the Report tool with JSON in the shape below; the tool refuses JSON that does not fit and says what is wrong, and the turn cannot end until an acceptable report exists. The front end then draws the accepted report as a card at the prompt, where one keypress answers a decision it raises.
This package depends on nothing else in kvit-coder, so the tool that produces a report, the session reader that finds one and the composer that draws it all work from one definition of it.
docs/structured-reports.md is the design.
Index ¶
- Constants
- Variables
- func Answer(blockID, label string) string
- func BuriedProposalHint(r *Report) bool
- func FormatProblems(ps []Problem) string
- func JSONSchema(maxBlocks int) map[string]any
- func Normalize(r *Report)
- func Render(r *Report, opts Options) string
- type Block
- type BlockType
- type Choice
- type Line
- type LineChoice
- type LineKind
- type Option
- type Options
- type Problem
- type Report
- func (r *Report) Block(id string) *Block
- func (r *Report) Choice(number int) *Choice
- func (r *Report) Choices() []Choice
- func (r *Report) DefaultTaskStatus()
- func (r *Report) FirstInteractive() int
- func (r *Report) HasInteractive() bool
- func (r *Report) HiddenNotRunCount(opts Options) int
- func (r *Report) InferStatus() Status
- func (r *Report) Order() []int
- func (r *Report) PlainText() string
- func (r *Report) PrimaryChoice() *Choice
- func (r *Report) PrimaryInteractive() int
- type Rules
- type Status
Constants ¶
const ( SectionHappened = "What happened" SectionAsks = "Your call" )
Section titles.
const ( CheckPassed = "passed" CheckFailed = "failed" // CheckPartial and CheckNotRun both require the block to say what was // not checked and why. CheckPartial = "partial" CheckNotRun = "not_run" )
Check statuses.
const ( // EffectDispatch sends the option's instruction as the next turn's prompt. EffectDispatch = "dispatch" // EffectCollect puts an answer in the composer to edit and send, rather // than starting a turn at once. EffectCollect = "collect" // EffectResolve records that the user is stopping here. No turn starts. EffectResolve = "resolve" )
Option effects: what picking an option does.
const ( // HeadlineMax is the longest headline, in characters. HeadlineMax = 160 // SummaryMax is the longest block summary, in characters. SummaryMax = 240 // DetailsMax is the largest details field, in bytes. DetailsMax = 32 * 1024 // LabelMax is the longest option label, in characters. LabelMax = 60 // TextMax is the longest option description or consequence. TextMax = 200 // PreviewMax is the largest option preview, in bytes. PreviewMax = 4 * 1024 // InstructionMax is the largest dispatch instruction, in bytes. InstructionMax = 1024 // IDMax is the longest block or option id, in characters. IDMax = 64 // MinOptions and MaxOptions bound an interactive block's option list. MinOptions = 2 MaxOptions = 6 // DefaultMaxBlocks is the block limit when configuration sets none. DefaultMaxBlocks = 20 )
Limits. They are the schema's documented bounds and the validator's checks, so a model reading the schema and the tool checking the result agree.
const MaxChoices = 9
MaxChoices is how many options one card can offer to a keypress, which is how many digits there are to press.
const ResponseSingle = "single"
ResponseSingle is the only response_type there is: one option, with free text always allowed alongside it.
Variables ¶
var BlockTypes = []BlockType{ BlockChange, BlockFinding, BlockCheck, BlockWarning, BlockQuestion, BlockNext, BlockUnclassified, }
BlockTypes lists every block type a model may send.
var CheckStatuses = []string{CheckPassed, CheckFailed, CheckPartial, CheckNotRun}
CheckStatuses lists the allowed values of a check block's status.
var Effects = []string{EffectDispatch, EffectCollect, EffectResolve}
Effects lists the allowed values of an option's effect.
var Levels = []string{"low", "medium", "high", "critical"}
Levels are the values importance and severity take, least to most urgent.
var Statuses = []Status{ StatusCompleted, StatusCompletedWithNotes, StatusNeedsAction, StatusBlocked, StatusFailed, }
Statuses lists every status an older report may carry, in the order they used to be documented. New reports should omit task_status entirely; the schema keeps the field only so old sessions still decode.
Functions ¶
func Answer ¶ added in v0.2.0
Answer is the prompt that replies to a block with one of its options: the block's id in brackets, so the next turn can tell which question is being answered, then the option's label.
func BuriedProposalHint ¶
BuriedProposalHint reports whether a rejected report proposes a follow-up in prose while offering no interactive block for it. The caller appends a one-line hint telling the model to expose each proposal as its own next block instead.
func FormatProblems ¶
FormatProblems is the rejection the model reads. It leads with the marker so the problem is unmistakable in a wall of tool output, then lists every problem as a path and a sentence.
func JSONSchema ¶
JSONSchema is the tool's parameter schema as the model sees it.
The block types share one object rather than being expressed as a discriminated union: several endpoints handle oneOf poorly, and a model that cannot decode the schema sends nothing usable at all. Each field's description says which types require it, and Validate is what actually enforces that, reporting every violation at once so a wrong guess costs one round trip rather than one per mistake.
func Normalize ¶
func Normalize(r *Report)
Normalize cleans up what a model sent before it is checked: surrounding whitespace everywhere, a code fence wrapped around a whole Markdown value, a block type under a name it used to have or left out where the block's fields name it, and a task_status coerced into agreement with the blocks. It never changes meaning, so a report that only needed normalizing is accepted rather than bounced for a formatting habit.
Types ¶
type Block ¶
type Block struct {
Type BlockType `json:"type"`
ID string `json:"id"`
Summary string `json:"summary"`
// Shared optional fields.
Impact string `json:"impact,omitempty"`
Recommendation string `json:"recommendation,omitempty"`
Details string `json:"details,omitempty"`
RelatedFiles []string `json:"related_files,omitempty"`
// finding
Importance string `json:"importance,omitempty"`
// check
Status string `json:"status,omitempty"`
Evidence string `json:"evidence,omitempty"`
Limitation string `json:"limitation,omitempty"`
RequiredToVerify string `json:"required_to_verify,omitempty"`
// warning
Severity string `json:"severity,omitempty"`
// question, next
Options []Option `json:"options,omitempty"`
ResponseType string `json:"response_type,omitempty"`
RecommendationReason string `json:"recommendation_reason,omitempty"`
// unclassified
ReasonUnclassified string `json:"reason_unclassified,omitempty"`
SuggestedType string `json:"suggested_type,omitempty"`
}
Block is one typed item of a report. Every block type shares id and summary; the rest of the fields apply to the types that document them, and the validator rejects a block that omits one its type requires.
One flat struct rather than one per type is deliberate: it keeps decoding to a plain json.Unmarshal, and it lets the validator report every problem in a report at once instead of stopping at the first type it cannot decode.
func (*Block) Glyph ¶
Glyph is the one-character marker at the start of a block's line. A check's glyph says how the check went, so the outcome is legible without reading the line.
func (*Block) HasMore ¶ added in v0.2.0
HasMore reports whether the block has anything a collapsed rendering does not show: typed fields, files or details on a block that reports something; on one that asks, option descriptions and consequences, or instructions beyond the one shown.
func (*Block) Interactive ¶
Interactive reports whether this block asks the user for something, which is what makes it answerable from the card and what holds back an inbox-only turn while it waits.
func (*Block) IsNotRunCheck ¶ added in v0.2.0
IsNotRunCheck reports whether this block is a check that ran nothing. Those are the rows the live card collapses: required by the validator, noise on the card.
func (*Block) Prompt ¶ added in v0.2.0
Prompt is what picking o sends as the next turn's prompt, or "" when picking it starts no turn. A dispatch option sends its instruction. On a question a dispatch option may leave the instruction out, and then picking it sends the option's label as the answer — which is what makes the answer reach the model at all.
type BlockType ¶
type BlockType string
BlockType selects which shape a block has.
const ( // BlockChange is file edits and other work output. BlockChange BlockType = "change" // BlockFinding is something learned that matters to the task. BlockFinding BlockType = "finding" // BlockCheck is how the work was checked. BlockCheck BlockType = "check" // BlockWarning is a risk or caveat. BlockWarning BlockType = "warning" // BlockQuestion is something only the user can settle: a choice between // ways forward, or information the user has. It differs from next // in having no obvious continuation to accept. BlockQuestion BlockType = "question" // BlockNext is a proposed continuation the user accepts or redirects. BlockNext BlockType = "next" // BlockUnclassified is the escape hatch for something the model has to // say that fits no other type. It is not meant to be used routinely. BlockUnclassified BlockType = "unclassified" )
type Choice ¶
type Choice struct {
// Block indexes Report.Blocks; Option indexes that block's Options.
Block, Option int
// Number is what the user presses, 1-based. Options past the ninth get 0:
// there is no key for them, so the card shows them without a number.
Number int
}
Choice is one option the report offers, located by the block and option it sits in and numbered as the card shows it.
type Line ¶
type Line struct {
Kind LineKind
// Text is the whole line as plain text, indentation included.
Text string
// Indent is how many columns of Text are indentation, and Tag and Body
// are the rest split where a painter styles them differently: the glyph
// or "? next" before a summary, "Sends:" before an instruction. Tag is
// empty on lines that have no such split, and Body is then the rest.
Indent int
Tag string
Body string
// Block is the index into Report.Blocks, or -1 for lines that belong to
// no block.
Block int
// BlockID is that block's id, for callers that track selection by id.
BlockID string
// Option is the 1-based number of the option this line is about, or 0.
Option int
// More says the block has fields or details this rendering folds away,
// which the card marks at the end of the row.
More bool
// Choices are the options a LineChoices line offers, in order.
Choices []LineChoice
}
Line is one rendered line with enough structure to style or act on it.
type LineChoice ¶ added in v0.2.0
type LineChoice struct {
Number int
Label string
// Recommended says this option is the block's pick. Primary says it is
// also the report's primary recommendation — the one the card highlights
// as the pick. A secondary recommended block keeps Recommended without
// Primary, so the card can paint it as an outline chip.
Recommended bool
Primary bool
}
LineChoice is one option on a LineChoices line.
func (LineChoice) Marker ¶ added in v0.2.0
func (c LineChoice) Marker() string
Marker is how the choice is written: "[1]", or "[-]" when no key picks it.
type LineKind ¶
type LineKind int
LineKind says what a rendered line is, so a caller that paints the report — the composer's card — can style each kind without parsing the text back apart. The plain renderer ignores it.
const ( // LineHeader is the headline, after a dot. The status is not drawn: what // the turn amounted to is in the headline, and the card shows what to // answer. LineHeader LineKind = iota // LineBlank separates the header, the sections and the blocks that ask // something. LineBlank // LineSection names one of the card's two parts: what the turn did, and // what it asks of the user. LineSection // LineBlock is a block's own line. For a block that reports something it // is the glyph and the summary; for one that asks something it is "?", the // type and the summary. Tag holds the part before the summary. LineBlock // LineField is one of a block's typed fields, shown when the block is // expanded. LineField // LineReason is why the block recommends what it recommends. LineReason // LineChoices is a block's options side by side, one entry in Choices each. LineChoices // LineOption is one option with its description and consequence, shown // when the block is expanded. LineOption // LineInstruction is what picking an option sends. Tag is "Sends:". LineInstruction // LineDetails is one line of a block's Markdown details. LineDetails )
type Option ¶
type Option struct {
ID string `json:"id"`
Label string `json:"label"`
Description string `json:"description,omitempty"`
Consequence string `json:"consequence,omitempty"`
Preview string `json:"preview,omitempty"`
Effect string `json:"effect"`
// Instruction is what a dispatch option sends as the next prompt. It is
// shown in full on the card, so the user sees what picking it will do.
Instruction string `json:"instruction,omitempty"`
}
Option is one way forward offered by an interactive block.
type Options ¶
type Options struct {
// Expanded holds the ids of blocks whose details are shown.
Expanded map[string]bool
// ExpandAll shows every block's details, which is what a headless run and
// the full viewer do.
ExpandAll bool
// HideNotRun collapses check blocks that ran nothing into a
// one-line footnote, which is what the live card does: the check stays
// mandatory, but a docs-only edit no longer spends two lines saying so.
// ExpandAll or an entry in Expanded still shows the block in full, and
// the scrollback transcript (which expands everything) is unaffected.
HideNotRun bool
}
Options controls how much of a report is drawn.
type Problem ¶
type Problem struct {
Path string `json:"path"`
Code string `json:"code"`
Message string `json:"message"`
}
Problem is one thing wrong with a report. Path is a JSON Pointer into the submitted arguments, so the model is told which field to fix rather than which rule it broke. Code is stable across wordings, for counting what models get wrong.
type Report ¶
type Report struct {
TaskStatus Status `json:"task_status"`
Headline string `json:"headline"`
Blocks []Block `json:"blocks"`
}
Report is the whole thing: what the turn amounted to, one sentence about it, and the typed items behind that sentence.
func (*Report) Choices ¶
Choices lists every option in the report, in display order, numbered continuously across blocks. Numbering across blocks rather than within one selected block is what lets a digit answer any question the report asks without a selection to move first.
func (*Report) DefaultTaskStatus ¶ added in v0.6.0
func (r *Report) DefaultTaskStatus()
DefaultTaskStatus coerces task_status into agreement with the blocks. It is normalization, not validation: an omitted or unknown status is inferred (completed when the report asks nothing, needs_action when it does), a completed/completed_with_notes holding a question or next becomes needs_action, and a needs_action holding nothing to answer becomes completed. An explicit blocked or failed is left alone. The validator never rejects on the status, so after this call there is nothing left to reject.
func (*Report) FirstInteractive ¶
FirstInteractive returns the index of the first block, in display order, that asks the user something, or -1. It is what a digit key answers.
func (*Report) HasInteractive ¶
HasInteractive reports whether any block asks the user for something.
func (*Report) HiddenNotRunCount ¶
HiddenNotRunCount is how many not_run checks opts would collapse. The card uses it for its footnote and for offering the expand key even when there are no details to show.
func (*Report) InferStatus ¶ added in v0.6.0
InferStatus derives the task status from the blocks: a report that asks the user something needs action, anything else counts as completed. It is what Normalize fills an omitted or unknown status with, and what an explicit completed/completed_with_notes becomes when the report holds a question or next block (and vice versa for a needs_action with nothing to answer), so a stale label never costs a repair round trip. An explicit blocked or failed is preserved as sent, since only the model knows it is stuck.
func (*Report) Order ¶
Order returns the block indices in display order: warning, change, finding, check, unclassified, question, next — regardless of the order the model sent them in. Blocks that tie keep the order they were sent in, so a report renders the same way every time.
func (*Report) PrimaryChoice ¶ added in v0.3.0
PrimaryChoice returns the choice the primary recommendation names, or nil when nothing recommends anything. It is what an empty Enter picks: the same option a digit would pick, without needing its number. Options past the ninth have no digit (Number 0) but are still pickable this way, so the number is recorded for the scrollback rather than used as a key.
func (*Report) PrimaryInteractive ¶ added in v0.3.0
PrimaryInteractive returns the index of the report's primary recommendation: the first block, in display order, whose recommendation names one of its options. The card highlights only this option as the pick and the plain rendering introduces it with "Recommended because:"; further recommended blocks are secondary. It returns -1 when nothing recommends anything, which is valid for a question with no safe default.
type Rules ¶
type Rules struct {
// MaxBlocks bounds the block list. Zero selects DefaultMaxBlocks.
MaxBlocks int
// Mutated says whether the turn changed anything. It decides what
// "completed" has to show: a turn that edited files says how it checked
// them, while a turn that only read and explained is finished without a
// check block. Demanding one of a question-answering turn is how the
// explanation ends up inside a block that checked nothing.
Mutated bool
}
Rules are the parts of validation that depend on the turn rather than on the report, so the same report can be right for one turn and wrong for another.
type Status ¶
type Status string
Status is the task_status field. It is deprecated and informational only: the card never shows it, the validator never rejects on it, and whether the turn waits on the user is derived from the blocks (HasInteractive), not from this field. It is kept so reports saved by older sessions still decode, and so an explicit blocked or failed still tints the headline dot red.
const ( // StatusCompleted means the work is done and checked. StatusCompleted Status = "completed" // StatusCompletedWithNotes means done, with a caveat stated in the report. StatusCompletedWithNotes Status = "completed_with_notes" // StatusNeedsAction means the turn stopped where the user must choose or // answer something. StatusNeedsAction Status = "needs_action" // StatusBlocked means the work cannot continue without the user or // something outside the session. A warning or finding says what is in // the way. StatusBlocked Status = "blocked" // StatusFailed means the work did not succeed, with the reason recorded. StatusFailed Status = "failed" )