report

package
v0.6.2 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2026 License: MIT Imports: 5 Imported by: 0

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

View Source
const (
	SectionHappened = "What happened"
	SectionAsks     = "Your call"
)

Section titles.

View Source
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.

View Source
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.

View Source
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.

View Source
const MaxChoices = 9

MaxChoices is how many options one card can offer to a keypress, which is how many digits there are to press.

View Source
const ResponseSingle = "single"

ResponseSingle is the only response_type there is: one option, with free text always allowed alongside it.

Variables

BlockTypes lists every block type a model may send.

CheckStatuses lists the allowed values of a check block's status.

Effects lists the allowed values of an option's effect.

View Source
var Levels = []string{"low", "medium", "high", "critical"}

Levels are the values importance and severity take, least to most urgent.

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

func Answer(blockID, label string) string

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

func BuriedProposalHint(r *Report) bool

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

func FormatProblems(ps []Problem) string

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

func JSONSchema(maxBlocks int) map[string]any

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.

func Render

func Render(r *Report, opts Options) string

Render is the report as plain text, which is what a headless run prints and what the clipboard gets.

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

func (b *Block) Glyph() string

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

func (b *Block) HasMore() bool

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

func (b *Block) Interactive() bool

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

func (b *Block) IsNotRunCheck() bool

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) Option

func (b *Block) Option(id string) *Option

Option returns the option with the given id from the given block, or nil.

func (*Block) Prompt added in v0.2.0

func (b *Block) Prompt(o *Option) string

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"
)

func (BlockType) Label

func (t BlockType) Label() string

Label is the block type as it appears at the head of its line.

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.

func Lines

func Lines(r *Report, opts Options) []Line

Lines renders a report as structured lines in display order: the headline, then what the turn did, then what it asks of the user, which is nearest the prompt where the answer is typed.

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.

func Validate

func Validate(r *Report, rules Rules) []Problem

Validate checks a normalized report and returns everything wrong with it, so the model can fix a whole report in one pass rather than one problem per round trip. An empty result means the report is acceptable.

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) Block

func (r *Report) Block(id string) *Block

Block returns the block with the given id, or nil.

func (*Report) Choice

func (r *Report) Choice(number int) *Choice

Choice returns what the given key number picks, or nil.

func (*Report) Choices

func (r *Report) Choices() []Choice

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

func (r *Report) FirstInteractive() int

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

func (r *Report) HasInteractive() bool

HasInteractive reports whether any block asks the user for something.

func (*Report) HiddenNotRunCount

func (r *Report) HiddenNotRunCount(opts Options) int

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

func (r *Report) InferStatus() Status

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

func (r *Report) Order() []int

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) PlainText

func (r *Report) PlainText() string

PlainText is the whole report with every detail shown.

func (*Report) PrimaryChoice added in v0.3.0

func (r *Report) PrimaryChoice() *Choice

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

func (r *Report) PrimaryInteractive() int

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"
)

Jump to

Keyboard shortcuts

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