Documentation
¶
Overview ¶
Package advisor reviews completed work and, optionally, work in progress at turn boundaries. Reviewer callbacks contribute bounded, deduplicated notes; material concerns reopen a finish or enter the next request. Each reviewer owns independent review caps, cooldown and checkpointed delivery history. Native bindings account AI usage to the owning run. No background scheduler or in-flight turn interruption is introduced.
Index ¶
- Constants
- func FormatAdvisories(notes []Note) string
- func FormatInjection(notes []Note) string
- func Native(provider *ai.FallbackProvider) agentcore.Plugin
- func NativeWithOptions(provider *ai.FallbackProvider, options Plugin) agentcore.Plugin
- func NormalizeNote(note string) string
- type EmissionGuard
- type Note
- type Plugin
- type Review
- type Reviewer
- type ReviewerConfig
- type Severity
Constants ¶
const DefaultMaxNotesPerReview = 3
DefaultMaxNotesPerReview bounds how many notes one review may deliver. It is breadth control, not rate limiting — a reviewer handed a whole run will happily list nine things, and an injection that long stops being advice and becomes a second task.
const DefaultMaxRounds = 2
DefaultMaxRounds caps reviewer consultations per run. Two is the same allowance finishguard gives, for the same reason: a reviewer that is never satisfied must not be able to loop the run against MaxTurns.
const MaxNoteRunes = 2000
MaxNoteRunes clamps one note's length. The reviewer reads a transcript that may contain attacker-controlled text and its note becomes a user message in the primary conversation, so an unbounded note is an unbounded injection.
Variables ¶
This section is empty.
Functions ¶
func FormatAdvisories ¶
FormatAdvisories renders notes as the agent-facing advisory elements: one element per note, severity as an attribute, text XML-escaped.
Escaping is not cosmetic. The reviewer read a transcript that may contain text an attacker controls (a fetched page, a row from the event store), and its note becomes a user message in the primary conversation. Escaping keeps a quoted `</advisory>` inside the note from closing the element and letting whatever follows read as the host's own instructions.
func FormatInjection ¶
FormatInjection is the complete message injected when a note re-opens the run: the advisories, then what the agent is expected to do about them.
func Native ¶
func Native(provider *ai.FallbackProvider) agentcore.Plugin
Native binds the reviewer to native AI fallback. It has no tools and accounts every attempt's usage to the actual owning agent, including on child forks.
func NativeWithOptions ¶
func NativeWithOptions(provider *ai.FallbackProvider, options Plugin) agentcore.Plugin
NativeWithOptions binds the native reviewer with periodic scheduling and caps.
func NormalizeNote ¶
NormalizeNote folds a note to its identity key: lowercase, NFKC-normalized, every run of non-letter/non-digit characters collapsed to a single space, trimmed. "Stop.", "*Stop*", and " stop " all key to "stop", while "No issue; continue." keys to "no issue continue".
Exported because the dedupe key is part of the plugin's observable contract: a caller recording notes on a run needs the same identity the guard used.
Types ¶
type EmissionGuard ¶
type EmissionGuard struct {
// contains filtered or unexported fields
}
EmissionGuard decides which reviewer notes reach the primary agent.
It enforces, in order: the length clamp, the content-free phrase filter, run-scoped dedupe by normalized text (FIFO-evicted at noteCapacity), escalation-rank dedupe (a repeat passes only when its severity strictly exceeds the rank already delivered for that text), and a per-review breadth budget. Suppressed noise never consumes the budget — a junk note must not burn the slot for a real concern behind it in the same review.
The zero value is not usable; construct with NewEmissionGuard.
func NewEmissionGuard ¶
func NewEmissionGuard(maxPerReview int) *EmissionGuard
NewEmissionGuard builds a guard accepting at most maxPerReview notes per review round. A non-positive maxPerReview means unbounded breadth, which is only ever right for a test.
func (*EmissionGuard) Accept ¶
func (g *EmissionGuard) Accept(n Note) (Note, bool)
Accept reports whether a note should reach the primary, and returns the note as it should be delivered (length-clamped). On true the guard has already recorded it: the budget is spent and the text is in the dedupe history.
func (*EmissionGuard) BeginReview ¶
func (g *EmissionGuard) BeginReview()
BeginReview clears the per-review budget. Called once before each reviewer consultation; the dedupe history deliberately survives, because "you already said that last round" is the whole point of a second round.
func (*EmissionGuard) Reset ¶
func (g *EmissionGuard) Reset()
Reset drops all state. Called when the conversation the notes were about is rewritten (compaction, a rebase), so a re-primed reviewer may re-raise an issue it already raised against a transcript that no longer exists.
type Note ¶
type Note struct {
// Text is the advice itself: concrete, terse, actionable.
Text string `json:"text"`
// Severity decides delivery. An unrecognized or empty value is treated as
// a nit, so a reviewer that omits the field cannot accidentally interrupt.
Severity Severity `json:"severity,omitempty"`
}
Note is one piece of advice from the reviewer.
type Plugin ¶
type Plugin struct {
// Reviewer is consulted at each normal finish.
Reviewer Reviewer
// Reviewers adds independently scheduled reviewers (at most four).
Reviewers []ReviewerConfig
// IntervalTurns enables periodic review after this many completed turns.
IntervalTurns int
MaxPeriodicReviews int
CooldownTurns int
Timeout time.Duration
// MaxRounds bounds consultations per run. 0 uses DefaultMaxRounds. The cap
// lives here rather than in the loop because it is this capability's
// property: the loop only knows that SOMETHING asked to continue.
MaxRounds int
// MaxNotesPerReview bounds notes delivered per review. 0 uses
// DefaultMaxNotesPerReview.
MaxNotesPerReview int
// OnNotes, when set, receives every note the guard accepted — including
// the nits the agent never sees — so the host can record what the reviewer
// said. delivered reports whether these notes were actually put in front of
// the agent: a review is injected whole or not at all, so a nit riding
// alongside a blocker IS delivered, and severity alone cannot tell you that.
OnNotes func(ctx context.Context, notes []Note, delivered bool)
}
Plugin installs the advisor. A nil Reviewer registers nothing, so a composition that wires the plugin for a disabled agent is inert rather than broken — which is what makes "advisor: off" a config value rather than a different composition.
func (Plugin) BeginRun ¶
BeginRun starts one run's advisor state: its round budget, its emission guard, and the message snapshot the reviewer will read.
type Review ¶
type Review struct {
// Final is the answer the run would return.
Final string
// Turns is the number of reasoning turns consumed, including this one.
Turns int
// Tools is the run's tool trace — what was called, what was blocked, what
// errored. Read-only; it aliases the run's live slice.
Tools []agentcore.ToolTrace
// Messages is the conversation the final answer came out of, captured at
// the last provider request. It does NOT include the final assistant
// message itself, which is Final.
Messages []agentcore.Message
// Round is 0 on the first review of a run and increments per re-opening,
// so a reviewer can tell "look at this work" from "look at whether the
// agent dealt with what you already said".
Round int
// Delivered is every note this run has already put in front of the agent,
// oldest first. On a second round it is what the reviewer checks the new
// answer against; repeating one of these verbatim is dropped by the
// emission guard, so a reviewer that still objects must escalate.
Delivered []Note
}
Review is the evidence handed to a Reviewer.
type Reviewer ¶
Reviewer reads a finished run and returns the notes worth making. Returning no notes accepts the finish, and silence is the expected outcome of a run that went fine.
An error accepts the finish too. A reviewer is a second opinion, not a dependency: a provider outage, a timeout, or an unparseable response must leave the agent's answer exactly as it would have been with no advisor configured, never wedge or fail the run.
type ReviewerConfig ¶
type ReviewerConfig struct {
Name string
Reviewer Reviewer
IntervalTurns int
MaxRounds int
MaxPeriodicReviews int
CooldownTurns int
}
ReviewerConfig names an independent review policy. No goroutines or second scheduler: periodic reviews run at the core's existing turn boundary.
type Severity ¶
type Severity string
Severity is how strongly one note should be weighed. It also decides delivery: a nit is recorded and the finish is accepted, while a concern or a blocker re-opens the run.
const ( // SeverityNit is cleanup, simplification, a low-risk edge case. It never // re-opens the run: at a finish there is no next step boundary for an aside // to ride, so spending a whole turn on a nit costs more than the nit is // worth. It is reported to the host instead. SeverityNit Severity = "nit" // SeverityConcern is material risk: a likely-wrong direction, a missing // constraint, a figure that does not follow from the evidence. Re-opens the // run so the agent can weigh it. SeverityConcern Severity = "concern" // SeverityBlocker is work that would be wrong to hand over: a claim of // completion over sampled scope, an answer never exercised against what was // asked. Re-opens the run. SeverityBlocker Severity = "blocker" )
func (Severity) Interrupting ¶
Interrupting reports whether a note at this severity re-opens the run.