Documentation
¶
Overview ¶
Package view builds read models over a store for operator surfaces: what runs exist, what a run did, and which approval is waiting. Everything here reads; nothing decides. The summaries are the shape cmd/agentrt prints and both consumers' own commands report.
Index ¶
- Constants
- Variables
- func Approve(ctx context.Context, store *agentrt.Store, obs agentrt.Observer, ...) error
- func ApproveShown(ctx context.Context, store *agentrt.Store, obs agentrt.Observer, ...) error
- func BoundApproval(a agentrt.Approval) agentrt.Approval
- func BoundRaw(raw json.RawMessage) json.RawMessage
- func BoundValue(v any) any
- func Cancel(ctx context.Context, store *agentrt.Store, obs agentrt.Observer, ...) error
- func PendingApproval(ctx context.Context, store *agentrt.Store, runID, approvalID string) (agentrt.Approval, error)
- func Reject(ctx context.Context, store *agentrt.Store, obs agentrt.Observer, ...) error
- func RejectShown(ctx context.Context, store *agentrt.Store, obs agentrt.Observer, ...) error
- type RunDetail
- type RunDetailPage
- type RunSummary
- type StepSummary
Examples ¶
Constants ¶
const MaxFieldBytes = 2000
MaxFieldBytes bounds a single string value embedded in an approval's capability, presentation, or request arguments: all three come from a model or a policy, not from this package, so an operator surface that prints or encodes one uses this same limit whether it is writing text or JSON. cmd/agentrt's approval prompt applies it per line; BoundApproval applies it to JSON output the same way, so JSON does not carry more than the text an operator already sees.
Variables ¶
var ( ErrNotPending = errors.New("approval is not pending") ErrNoPendingApproval = errors.New("no pending approval") ErrAmbiguousApproval = errors.New("more than one pending approval") )
Errors PendingApproval wraps, so a front end can match them and word its own message; the text carries the ids.
var ( // ErrRunNotWaiting means the run is not (or no longer) waiting for // approval: another decision, a resume, or an expiry already moved it // on. ErrRunNotWaiting = errors.New("run is not waiting for approval") // ErrApprovalDecided means the named approval was already approved, // rejected, or expired; it is not the caller's to decide again. ErrApprovalDecided = errors.New("approval is already decided") // ErrDatabaseLocked means another process holds the database open for // writing. It wraps the driver's own error, whose text carries the raw // SQLite code, so a front end can say something a person can act on. ErrDatabaseLocked = errors.New("database is locked by another consumer") )
Errors decideError wraps around whatever agentrt.Approve, agentrt.Reject, and agentrt.Cancel report, so a caller such as cmd/agentrt can match the shape of a failure instead of parsing its text. The core functions return plain fmt.Errorf values with no sentinel of their own; these give one.
Functions ¶
func Approve ¶ added in v0.3.0
func Approve(ctx context.Context, store *agentrt.Store, obs agentrt.Observer, runID, approvalID, by, note string) error
Approve records an approval decision, wrapping the core's error so ErrRunNotWaiting, ErrApprovalDecided, and ErrDatabaseLocked can be matched with errors.Is.
func ApproveShown ¶ added in v0.3.0
func ApproveShown(ctx context.Context, store *agentrt.Store, obs agentrt.Observer, runID, approvalID, shownHash, by, note string) error
ApproveShown is Approve for a front end that printed the approval before asking for a decision: shownHash must be the hash of the approval as printed (agentrt.Approval.Hash). If the stored approval no longer matches that hash, the decision is refused with agentrt.ErrApprovalChanged rather than applied to whatever the approval has since become. Wrapped the same way as Approve.
func BoundApproval ¶ added in v0.3.0
BoundApproval returns a with Capability, Presentation, and Request.Args passed through BoundRaw: an approval built from a model's tool call and a policy's presentation can carry a value of any size, so a command encoding it as JSON bounds it the same way its text output does, rather than a large or crafted database exhausting a consumer's memory or disk just because it asked for JSON instead of text.
func BoundRaw ¶ added in v0.3.0
func BoundRaw(raw json.RawMessage) json.RawMessage
BoundRaw decodes raw as JSON, applies BoundValue, and re-encodes it. raw is returned unchanged if it is empty or does not round-trip through JSON, which only happens for a value that is already malformed for some other reason and is left for that error to surface elsewhere.
func BoundValue ¶ added in v0.3.0
BoundValue returns v with every string longer than MaxFieldBytes, anywhere inside it, replaced by its head and tail and the number of bytes omitted between them. Maps and slices are walked recursively so the bound applies to a value nested inside an argument object, not only a bare string; anything else (numbers, bools, null) passes through unchanged.
func Cancel ¶ added in v0.3.0
func Cancel(ctx context.Context, store *agentrt.Store, obs agentrt.Observer, runID, by, note string) error
Cancel ends a run, wrapping errors the same way Approve does.
func PendingApproval ¶
func PendingApproval(ctx context.Context, store *agentrt.Store, runID, approvalID string) (agentrt.Approval, error)
PendingApproval selects the approval an operator means. With an id it fetches that approval and requires it to be pending; without one the run must have exactly one pending approval, and the error names the candidates when it does not, because guessing which one to grant is not the runtime's decision to make.
Example ¶
PendingApproval is the rule for choosing the approval an operator means: without an id the run must have exactly one pending approval, and a named approval must still be pending. Guessing is not the runtime's decision.
ctx := context.Background()
store, err := agentrt.OpenStore(":memory:")
if err != nil {
panic(err)
}
defer store.Close()
push := tool{spec: agentrt.ToolSpec{
Name: "push", Description: "push", SideEffect: agentrt.RemoteMutation, Timeout: time.Second,
InputSchema: []byte(`{"type":"object","properties":{"n":{"type":"integer"}},"additionalProperties":false}`),
}}
agent := &scripted.Agent{Decisions: []agentrt.Decision{scripted.ToolCall("push", `{"n":7}`, "publish")}}
d, err := agentrt.NewDriver(agentrt.Config{
Store: store, Agent: agent, Policy: agentrt.DefaultPolicy(),
Tools: []agentrt.Tool{push},
NewID: func() func() string {
n := 0
return func() string { n++; return fmt.Sprintf("id-%d", n) }
}(),
})
if err != nil {
panic(err)
}
run, err := d.Start(ctx, "publish the counter", agentrt.DefaultLimits())
if err != nil {
panic(err)
}
// One pending approval, so the operator need not name it.
a, err := view.PendingApproval(ctx, store, run.ID, "")
fmt.Println(a.ID, a.Kind, a.Status, err)
if err := d.Approve(ctx, run.ID, a.ID, "joey", ""); err != nil {
panic(err)
}
// Once decided it is no longer a candidate, by id or without one.
_, err = view.PendingApproval(ctx, store, run.ID, a.ID)
fmt.Println(errors.Is(err, view.ErrNotPending))
_, err = view.PendingApproval(ctx, store, run.ID, "")
fmt.Println(errors.Is(err, view.ErrNoPendingApproval))
Output: id-3 remote_mutation pending <nil> true true
Types ¶
type RunDetail ¶ added in v0.3.0
type RunDetail struct {
Run RunSummary
Steps []StepSummary
Approvals []agentrt.Approval
}
RunDetail is one run with its steps and every approval: what a command that shows a run prints.
type RunDetailPage ¶ added in v0.3.0
type RunDetailPage struct {
Run RunSummary
Steps []StepSummary
StepsTotal int
Pending []agentrt.Approval
PendingTotal int
Approvals []agentrt.Approval
ApprovalsTotal int
Waiting *agentrt.Approval
}
RunDetailPage is one run read in pages: the run with its text capped, a page of its steps, a page of its pending approvals, and a page of all its approvals, each at most limit after skipping offset, with their totals. Waiting is the run's pending approval when it has exactly one, whatever the offset. Every read is bounded (the agentrt.Store page reads), so a front end can show a run of a database it does not trust.
func DetailPage ¶ added in v0.3.0
func DetailPage(ctx context.Context, store *agentrt.Store, runID string, limit, offset int) (RunDetailPage, error)
DetailPage reads a RunDetailPage.
type RunSummary ¶
type RunSummary struct {
ID string `json:"id"`
Goal string `json:"goal"`
Status agentrt.RunStatus `json:"status"`
Reason agentrt.TerminalReason `json:"reason,omitempty"`
ReasonDetail string `json:"reason_detail,omitempty"`
Steps int `json:"steps"`
CreatedAt time.Time `json:"created_at"`
// FinishedAt uses omitzero, not omitempty: time.Time is a struct, and
// omitempty never treats a struct as empty, so a zero time.Time would
// otherwise be marshaled as its zero-value RFC 3339 rendering.
FinishedAt time.Time `json:"finished_at,omitzero"`
// PendingApprovalID is set only for a run waiting for approval, which is
// the only state in which an approval can still be decided.
PendingApprovalID string `json:"pending_approval_id,omitempty"`
ModelCalls int `json:"model_calls,omitempty"`
Usage agentrt.Usage `json:"usage,omitzero"`
EstimatedCost agentrt.Micros `json:"estimated_cost_micros,omitempty"`
}
RunSummary is one run as an operator needs to see it. The model totals come from the run row itself, so a summary costs no extra query.
func Runs ¶
Runs summarizes every run, newest first, in two queries: the runs, and the pending approvals of every waiting run.
func RunsPage ¶ added in v0.3.0
func RunsPage(ctx context.Context, store *agentrt.Store, limit, offset int) ([]RunSummary, int, error)
RunsPage summarizes at most limit runs, newest first, after skipping offset, and returns how many runs there are. Every read is bounded by the page (agentrt.Store.ListRunsPage, PendingApprovalIDsOf), so a front end can list a database it does not trust.
type StepSummary ¶
type StepSummary struct {
Index int `json:"index"`
ID string `json:"id"`
Tool string `json:"tool,omitempty"`
Decision agentrt.DecisionKind `json:"decision,omitempty"`
Status agentrt.StepStatus `json:"status"`
Policy agentrt.PolicyOutcome `json:"policy,omitempty"`
Observation agentrt.ObservationKind `json:"observation,omitempty"`
Summary string `json:"summary,omitempty"`
}
StepSummary is one step: what the agent asked for, what the policy said, and what came back.