sdk

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jul 25, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

README

chatwright.dev/sdk

The Go embodiment of the Chatwright run-bundle standard: the wire model for run-bundle format v1, Write/Read IO, and the generated JSON Schema.

A run bundle is the persisted, self-contained artifact a Chatwright run produces — everything a player (Chatwright Studio), a reviewer or a CI pipeline needs to see what happened during a run and why it concluded what it did, with no live emulator, database or network access. This module owns every type the published schema describes; the runtime that produces bundles lives in github.com/chatwright/chatwright.

Install

go get chatwright.dev/sdk

Usage

Read a bundle file (bundles are named <anything>.chatwright.json):

package main

import (
	"fmt"
	"os"

	sdk "chatwright.dev/sdk"
)

func main() {
	f, err := os.Open("greetbot-language.chatwright.json")
	if err != nil {
		panic(err)
	}
	defer f.Close()

	bundle, err := sdk.Read(f)
	if err != nil {
		panic(err)
	}
	for _, run := range bundle.Runs {
		fmt.Printf("run %s on %s (%s): %d part(s)\n",
			run.ID, run.Platform, run.EndpointProfile, len(run.Parts))
	}
}

sdk.Write is the inverse: deterministic, indented, human-readable JSON, suitable for checking into a repository and reviewing in a PR diff.

Schema

The wire shape is published as a JSON Schema (draft 2020-12), generated from this module's Go types and committed at formats/run-bundle/v1/schema.json:

The Go types are the schema's single source of truth; a drift-guard test keeps the committed file byte-identical to what the generator produces.

The standard

Specs, format documentation and design decisions live in the standard repository, github.com/chatwright/chatwright, and at chatwright.dev.

Licence

Apache-2.0 — see LICENSE and NOTICE.

Spec-first

Chatwright is developed spec-first with SpecScore — product specs live in the standard repository; this repository's own specs live under spec/.

Documentation

Overview

Package sdk is the Go embodiment of Chatwright's run-bundle format v1: the persisted, self-contained artifact a Web UI player (Chatwright Studio) replays. A Bundle needs nothing else — no live emulator, no database, no network access — to show what happened during a run and why it concluded what it did.

This module (chatwright.dev/sdk) owns the format's wire model: every type the published JSON Schema describes lives in this single package, alongside Write/Read IO and the schema generator (internal/schemagen). The runtime that produces bundles — platform emulators, the actor loop, campaign assembly — lives in github.com/chatwright/chatwright and builds on these types; nothing here depends on that runtime.

Shape: a Bundle carries one or more Runs (today's writers always emit exactly one). A Run carries an actors roster, a run-level, continuous, per-chat journal, and an ordered list of Parts — each Part names a kind (ai-goal today; deterministic reserved) and a JournalBoundary slicing the run-level journal into the entries that part covers, so parts never duplicate journal content. See Bundle, Run, Actor and Part for the full shape, and the standard repository's spec/ideas/hybrid-runs.md for why runs are structured this way: a plain campaign is exactly a single-part, single-run Bundle, and the same shape accommodates a run mixing deterministic and AI-goal passages without any schema change.

Filename convention: a Bundle file is named "<anything>.chatwright.json" (e.g. "greetbot-language.chatwright.json") so a player, a file browser or a directory listing can recognize one at a glance.

The full wire shape is also published as a JSON Schema, generated from these Go types (see internal/schemagen) and committed at formats/run-bundle/v1/schema.json; schema_test.go gates both that the schema stays in sync with these types and that a Bundle this package produces validates against it.

Index

Constants

View Source
const EndpointProfilePlatformEmulated = "platform-emulated"

EndpointProfilePlatformEmulated is a Run.EndpointProfile label for a run driven against an emulated platform API — the only endpoint profile the chatwright runtime currently produces (see the standard repository's decision 0008 and docs/glossary.md's "endpoint profile" entry: "platform-emulated (strongest), headless engine, or future profiles"). Run.EndpointProfile is a plain string, not restricted to this constant, so a future profile never requires a schema change — only a new label.

View Source
const FormatV1 = "https://chatwright.dev/formats/run-bundle/v1"

FormatV1 is the run-bundle format identifier this package reads and writes. It replaces an earlier draft's integer schema version with a namespaced URL, matching the style of other Chatwright format ids and leaving room for a v2 to be a different, equally explicit string. Read rejects any other value — see Read.

View Source
const ReportSchemaVersion = 1

ReportSchemaVersion is the current version of Report's JSON shape. Bump it whenever Report changes in a way a consumer must branch on, and never reinterpret an old SchemaVersion's fields under a new meaning.

Variables

View Source
var ErrMissingAIGoalSection = errors.New("bundle: ai-goal part missing its aiGoal section")

ErrMissingAIGoalSection means Read decoded a Part with Kind == PartKindAIGoal but no AIGoal section. Read returns it wrapped with the part id actually found (see Read), rather than handing back a Part whose AIGoal is silently nil.

View Source
var ErrUnknownBundleFormat = errors.New("bundle: unknown bundle format")

ErrUnknownBundleFormat means Read decoded a document whose top-level "format" is not FormatV1 — older, newer, or simply unrecognised. Read returns it wrapped with the value actually found (see Read), rather than unmarshalling the rest of a shape this package's current Bundle type might not agree with. A newer format is exactly as rejected as an older one, since this package has no way to know a future shape is backward-compatible.

View Source
var ErrUnknownPartKind = errors.New("bundle: unknown part kind")

ErrUnknownPartKind means Read decoded a Part whose Kind is neither PartKindAIGoal nor PartKindDeterministic. Read returns it wrapped with the kind and part id actually found (see Read).

Functions

func AggregateModelIDs

func AggregateModelIDs(events []LoopEvent) []string

AggregateModelIDs returns the sorted, deduplicated set of every non-empty Usage.Model value across events. It is the canonical way an ActorProvider.ModelIDs is computed, so two callers assembling a roster entry from the same events always produce the same aggregated identity list, regardless of how many times — or in what order — any one model was actually used.

func ModuleVersion

func ModuleVersion() string

ModuleVersion returns the sdk module's own resolved version, read from the currently running binary's runtime/debug build info, or "" when it cannot be determined.

A Bundle is normally produced by a program (e.g. a bot's test binary via the chatwright runtime) that imports this module as a dependency rather than being this module itself. In that (expected) case it is this module's entry in the binary's debug.BuildInfo.Deps that carries the meaningful resolved version (a git tag or pseudo-version), so Deps is searched for sdkModulePath. The less common case — the running binary IS this module, e.g. a test run inside this repository — is also covered, via debug.BuildInfo.Main, checked first.

Either way, "(devel)" (Go's placeholder for "no resolvable version") and "" are both treated as "not available": a plain `go build`/`go test` inside this repository, or inside a consumer module that has not pinned an sdk version, never has one — which is exactly why Metadata.ChatwrightVersion is optional.

func Write

func Write(w io.Writer, b Bundle) error

Write writes b to w as indented, human-readable JSON, terminated by a trailing newline — the same style the chatwright runtime's cassette files use for their own checked-in JSON, so a Bundle is reviewable in a PR diff and inspectable by hand, not just by a player. See the package doc comment for this format's file-naming convention ("*.chatwright.json").

Output is deterministic: encoding/json always renders a struct's fields in their declared order (see Bundle's own doc comment for that order) and always sorts a map's keys, so two calls encoding equal Bundle values produce byte-identical output — see TestBundleRoundTripIsDeterministic. Write performs no reordering or canonicalisation of its own beyond what json.MarshalIndent already guarantees; a Bundle's slice fields carry whatever order the caller assembled them in (each field's own doc comment states what that order is expected to be).

Types

type AIGoalSection

type AIGoalSection struct {
	// Goal is the goal definition this part's actor loop ran — verbatim,
	// not converted to plain strings the way Report's fields are, since a
	// player needs the full Goal (task dependencies, constraints, budgets)
	// to render it, not just the outcome Report already summarises.
	Goal Goal `json:"goal"`

	// ActorID references the Run.Actors entry that ran this part's loop.
	ActorID string `json:"actorId"`

	// Events is every LoopEvent the loop recorded for this part, in the
	// loop's own order (Index-ascending, across every task the loop ran).
	Events []LoopEvent `json:"events"`

	// Observations is every Observation the loop retained, ordered
	// ascending by Sequence — not the raw map[int64]Observation the
	// runtime's loop returns, so this section's JSON stays chronologically
	// readable regardless of encoding/json's own (string-lexicographic, not
	// numeric) map-key ordering for an integer-keyed map.
	Observations []RetainedObservation `json:"observations"`

	// Report is this part's assembled campaign Report — see Report.
	Report Report `json:"report"`

	// Evidence is the DataStateEvidence any data-state assertions produced
	// during this part, in the order they were run. Optional: a part with
	// no data-state assertions attached carries none.
	Evidence []DataStateEvidence `json:"evidence,omitempty"`
}

AIGoalSection is a PartKindAIGoal Part's kind-scoped detail: the Goal that part of the run pursued, which roster Actor ran the loop, and the evidence the loop produced — the same pieces the earlier, campaign-only Bundle draft carried at its top level, now scoped to one Part so a hybrid run can carry more than one.

type Action

type Action struct {
	Label string `json:"label"` // user-visible text (Telegram button text / WhatsApp reply title)
	ID    string `json:"id"`    // stable identifier (Telegram callback_data / WhatsApp reply id)
	URL   string `json:"url"`   // set for link actions
}

Action is a neutral interactive action (a button) captured from a bot message. Telegram inline buttons and WhatsApp interactive replies both normalize to it.

type ActionOutcome

type ActionOutcome struct {
	Kind ActionOutcomeKind `json:"kind"`
	// Detail is a human-readable explanation, set for
	// ActionSkippedInvalid/ActionResolutionFailed (why), empty otherwise.
	Detail string `json:"detail"`
}

ActionOutcome is what actually happened when the loop tried to act on a Proposal.

type ActionOutcomeKind

type ActionOutcomeKind string

ActionOutcomeKind classifies what happened when the loop acted on a proposal, or why it did not act at all. It is a string type, not an int enum, so it marshals to human-readable JSON (see the Chatwright standard's "JSON artefacts carry human-readable string constants" convention) rather than a bare, meaningless integer. Its Go zero value ("") is itself a real, meaningful wire value, not only an unset placeholder: a LoopEvent whose ProposeError is set carries a zero-value ActionOutcome (there was no action to have a Kind — the loop never got a Proposal to act on), and "" is what that Kind reads as.

const (
	// ActionSkippedInvalid: the proposal failed validation (a stale click)
	// or was malformed; the loop never submitted anything to the platform.
	ActionSkippedInvalid ActionOutcomeKind = "skipped-invalid"
	// ActionExecuted: the proposed action was submitted to the platform and
	// produced an observable change (a new message, an edit, or an
	// actions-changed update).
	ActionExecuted ActionOutcomeKind = "executed"
	// ActionExecutedNoEffect: the proposed action was submitted, but the
	// next observation showed no change at all.
	ActionExecutedNoEffect ActionOutcomeKind = "executed-no-effect"
	// ActionResolutionFailed: a freshly validated proposal that the loop
	// could not resolve to a concrete platform action — e.g. no button on
	// the current message carries the validated action's label. This counts
	// as a task failure.
	ActionResolutionFailed ActionOutcomeKind = "resolution-failed"
	// ActionTaskCompleted: a ProposeTaskDone proposal was accepted; the
	// task's status moved to completed.
	ActionTaskCompleted ActionOutcomeKind = "task-completed"
	// ActionTaskGivenUp: a ProposeGiveUp proposal was accepted; the task's
	// status moved to failed.
	ActionTaskGivenUp ActionOutcomeKind = "task-given-up"
	// ActionBlockedConstraintViolation: a ProposeSendText proposal's text
	// violated the active task's (or goal's) machine-checkable content
	// rules — a vocabulary allowlist, a deny-pattern, or a custom
	// predicate. The runtime never submitted it to the platform; it is
	// recorded here and re-prompted, counting toward non-progress exactly
	// like any other invalid proposal. See CampaignFinding's
	// "constraint-violation" kind.
	ActionBlockedConstraintViolation ActionOutcomeKind = "blocked-constraint-violation"
	// ActionOvershootProbe: a proposal requested and recorded strictly to
	// measure whether the actor would keep acting after its task's
	// machine-checkable completion criteria already held — the runtime
	// never submitted it to the platform. See CampaignFinding's
	// "actor-overshoot" kind.
	ActionOvershootProbe ActionOutcomeKind = "overshoot-probe"
)

Action outcome kinds. See ActionOutcome.

func (ActionOutcomeKind) String

func (k ActionOutcomeKind) String() string

String renders k for diagnostics, test failure messages and reports.

type Actor

type Actor struct {
	// ID is this Bundle's own stable identity for the actor — referenced by
	// Part's aiGoal.actorId and resolvable against a JournalEntry.FromID
	// through PlatformIdentities.
	ID string `json:"id"`

	// Type classifies this actor's origin — see ActorType.
	Type ActorType `json:"type"`

	// Name is an optional human-readable display name.
	Name string `json:"name,omitempty"`

	// PlatformIdentities maps a platform name (e.g. "telegram", matching
	// Run.Platform) to this actor's platform-native identity on that
	// platform. A map, not a single field, so a future actor active across
	// more than one platform needs no schema change — only a new key.
	PlatformIdentities map[string]PlatformIdentity `json:"platformIdentities,omitempty"`

	// Provider names the model/provider that proposed this actor's actions.
	// Only meaningful for ActorAIAgent and ActorReplay actors (a scripted or
	// human actor proposes nothing a "provider" describes); nil otherwise.
	Provider *ActorProvider `json:"provider,omitempty"`
}

Actor is one participant in a Run's conversation — the roster entry that lets a player attribute every JournalEntry (via its FromID) and every LoopEvent (via a Part's aiGoal.actorId) to whoever actually produced it, rather than leaving that to be inferred from Direction alone.

type ActorProvider

type ActorProvider struct {
	// Name is the provider's short identifier (e.g. "anthropic").
	Name string `json:"name,omitempty"`

	// ModelIDs is the aggregated set of Usage.Model ids that actually
	// proposed an action for this actor during the run — see
	// AggregateModelIDs, the canonical way to compute it.
	ModelIDs []string `json:"modelIds,omitempty"`
}

ActorProvider names the model/provider behind an ActorAIAgent or ActorReplay Actor.

type ActorType

type ActorType string

ActorType classifies one roster Actor's origin.

const (
	// ActorAIAgent: an AI model proposing actions via the runtime's
	// provider seam.
	ActorAIAgent ActorType = "ai-agent"
	// ActorHuman: a person driving the conversation directly.
	ActorHuman ActorType = "human"
	// ActorScripted: a fixed, deterministic proposal sequence or a
	// deterministic scenario fragment.
	ActorScripted ActorType = "scripted"
	// ActorReplay: a recorded run replayed from a cassette.
	ActorReplay ActorType = "replay"
	// ActorBot: the bot-under-test itself, the other side of the
	// conversation from every other actor type above.
	ActorBot ActorType = "bot"
)

Actor types. See Actor and ActorType.

type AggregateUsage

type AggregateUsage struct {
	InputTokens  int     `json:"inputTokens"`
	OutputTokens int     `json:"outputTokens"`
	Cost         float64 `json:"cost,omitempty"`
	// CallCount is the number of provider calls the campaign made — i.e.
	// the number of LoopEvents.
	CallCount int `json:"callCount"`
}

AggregateUsage sums the Usage of every LoopEvent a Report was assembled from.

type Anchor

type Anchor struct {
	// ChatID names the Run.Chats entry this anchor points into.
	ChatID int64 `json:"chatId"`
	// EntryIndex is the index into that ChatJournal.Entries this anchor
	// points at.
	EntryIndex int `json:"entryIndex"`
	// MessageID optionally pins the logical message (JournalEntry.MessageID)
	// this anchor is about, when it is more specific than "this journal
	// entry" — e.g. an Annotation about a message that was later edited,
	// anchored to the message rather than to one particular edit.
	MessageID int `json:"messageId,omitempty"`
	// Version optionally pins the exact edit (JournalEntry.Version)
	// MessageID was at, so an Annotation about "this specific wording"
	// survives a later edit instead of silently retargeting to the
	// message's newest version.
	Version int `json:"version,omitempty"`
}

Anchor locates one moment in a run's journal, shared by Bookmark and Annotation. ChatID and EntryIndex are required and always resolvable against Run.Chats for a Bundle the runtime wrote; MessageID and Version are optional and, together, pin an exact revision of an edited message — versioned message identity — rather than whatever its latest version happens to be by the time a player renders it.

type Annotation

type Annotation struct {
	// ID is caller-supplied and only needs to be unique within its Run.
	ID string `json:"id"`
	// Anchor locates the annotated moment in the run's journal — see Anchor.
	Anchor Anchor `json:"anchor"`
	// Author optionally attributes this Annotation — see Author's own doc
	// comment (never auto-populated).
	Author *Author `json:"author,omitempty"`
	// CreatedAt is when this Annotation was authored, supplied by the
	// caller — see Metadata.CreatedAt's own doc comment on why this package
	// never stamps a time itself.
	CreatedAt time.Time `json:"createdAt"`
	// Text is the annotation's own comment body.
	Text string `json:"text"`
	// ReplyTo optionally names another Annotation.ID in this Run that this
	// one replies to, threading both into one conversation about a message.
	// Empty for a root Annotation.
	ReplyTo string `json:"replyTo,omitempty"`
}

Annotation is a comment attached to one moment of this run's conversation — e.g. "See how instead of $4 bot returned 4$". ReplyTo threads Annotations into a conversation about a message: a root Annotation leaves ReplyTo empty, a reply names the Annotation.ID it responds to.

Read-side tolerance: a ReplyTo naming an Annotation ID this Run does not actually carry, and an Anchor whose EntryIndex (or MessageID/Version) does not resolve against the referenced chat, are both NOT errors from Read — bundles are hand-editable files, and a consumer must be prepared to surface a dangling reference rather than assume Read already validated it. See TestBundleReadToleratesDanglingAnnotationReferences.

type AttachmentPoint

type AttachmentPoint string

AttachmentPoint names where in a scenario a data assertion runs, matching the data-state-assertions feature's "Assertion attachment points" behaviour (see the standard repository's spec): after a user message or action's registered application work has settled, immediately before a named checkpoint is published, or at the end of a branch or scenario fragment.

const (
	// AttachmentAfterMessage is a data assertion attached after a message or
	// action and its registered application work has settled.
	AttachmentAfterMessage AttachmentPoint = "after-message"
	// AttachmentCheckpoint is a data assertion that gates a named
	// checkpoint's publication.
	AttachmentCheckpoint AttachmentPoint = "checkpoint"
	// AttachmentBranchCompletion is a data assertion run at the end of a
	// branch or scenario fragment.
	AttachmentBranchCompletion AttachmentPoint = "branch-completion"
)

type Author

type Author struct {
	Name  string `json:"name,omitempty"`
	Email string `json:"email,omitempty"`
}

Author optionally attributes provenance to a Bundle (Metadata.Author) or to an Annotation (Annotation.Author). Both fields are optional free-text strings — this package does not validate an email's shape or resolve a name against any identity system. See Metadata.Author's doc comment for why it is always caller-supplied, never auto-populated.

type AvailableAction

type AvailableAction struct {
	ID     string `json:"id"`     // opaque, stable Chatwright action identity
	Label  string `json:"label"`  // user-visible text
	SeenAt int64  `json:"seenAt"` // the Observation.Sequence this action was (re)issued at
}

AvailableAction is a generic, opaque interaction an actor can take: a stable Chatwright ID and its user-visible label. Platform-native callback data, request payloads and button coordinates are never exposed here — an authorised developer inspector reaches those through the platform's journal/transcript trace (JournalEntry), not through this type (see the standard repository's observation-model/actor-actions feature).

type Bookmark

type Bookmark struct {
	// ID is caller-supplied and only needs to be unique within its Run.
	ID string `json:"id"`
	// Title is the human-readable label a player shows for this marker.
	Title string `json:"title"`
	// Anchor locates the marker in the run's journal — see Anchor.
	Anchor Anchor `json:"anchor"`
}

Bookmark is a manual fast-forward marker for a player: a caller-chosen point in the run's journal worth jumping straight to. Bookmark exists only for markers a player cannot already derive on its own — part boundaries, task completions and Finding entries are all recoverable from Run.Parts/AIGoalSection directly, so nothing here should duplicate those; use a Bookmark for the rest (e.g. "the moment the bug reproduced").

type Budgets

type Budgets struct {
	// MaxSteps caps the number of steps the runtime's campaign state counts.
	// Zero means unlimited.
	MaxSteps int `json:"maxSteps"`

	// MaxDuration caps wall-clock time elapsed since the campaign started,
	// measured by the runtime's injected clock. Zero means unlimited.
	MaxDuration time.Duration `json:"maxDurationNanoseconds"`

	// MaxRepeatedFailures caps how many times a single task may fail before
	// the campaign stops. Zero means unlimited.
	MaxRepeatedFailures int `json:"maxRepeatedFailures"`

	// MaxCost optionally caps spend against the campaign (tokens, currency
	// or another caller-defined unit — whatever unit the runtime accrues).
	// Nil means cost is not budgeted.
	MaxCost *float64 `json:"maxCost"`
}

Budgets bounds one campaign run. Every numeric field's zero value means "no limit"; a negative value is invalid. MaxCost is the one genuinely optional field: nil means cost is not budgeted at all.

type Bundle

type Bundle struct {
	// Format is always FormatV1 for a Bundle this package produced. See Read.
	Format string `json:"format"`

	// Metadata carries this Bundle's caller-supplied provenance — see
	// Metadata.
	Metadata Metadata `json:"metadata"`

	// Runs is every run this Bundle carries, in the order the caller
	// assembled them. Today's writers always produce exactly one; the shape
	// accommodates a future multi-run file (e.g. several campaigns bundled
	// for one delivery) without a schema change.
	Runs []Run `json:"runs"`
}

Bundle is the top-level run-bundle document: a declared Format, caller Metadata, and one or more Runs. Field order below is Bundle's stable JSON shape (Go's encoding/json preserves struct field declaration order for objects, and sorts map keys deterministically for anything encoded as a JSON object) — see Write for the ordering guarantees this gives a round-tripped Bundle, and each slice field's own doc comment for the order its elements are stored in.

func Read

func Read(r io.Reader) (Bundle, error)

Read reads a Bundle from r. It checks the top-level "format" before trusting the rest of the shape: a format other than FormatV1 returns an error wrapping ErrUnknownBundleFormat (naming the value actually found), rather than silently unmarshalling an old or newer schema's fields under today's meanings.

Once format is confirmed, Read applies two further structural checks no json.Unmarshal alone can express — see ErrUnknownPartKind and ErrMissingAIGoalSection — over every Part of every Run. Unknown extra JSON fields elsewhere in the document are ignored (encoding/json's default: no DisallowUnknownFields), so a Bundle written by a future minor version that only adds fields still reads cleanly here. Read does not, and deliberately never will, validate Bookmark/Annotation references (Annotation.ReplyTo, Anchor) — see Annotation's own doc comment for why a dangling reference is a consumer's concern, not a Read error.

type Change

type Change struct {
	Kind      ChangeKind   `json:"kind"`
	MessageID string       `json:"messageId"`
	Actor     MessageActor `json:"actor"`
	// PreviousVersion is set for ChangeMessageEdited: the message's Version
	// before this change.
	PreviousVersion int `json:"previousVersion"`
	// Version is the message's Version after this change (ChangeNewMessage,
	// ChangeMessageEdited) or its current, unchanged Version
	// (ChangeActionsChanged).
	Version int `json:"version"`
}

Change is one explicit, structured difference between an Observation and the previous Observation, computed by the runtime's observation engine so actors reason about what changed without diffing two Observations themselves (see the standard repository's observation-model/ observation-lineage feature).

type ChangeKind

type ChangeKind string

ChangeKind classifies one entry in an Observation's Changes feed. It is a string type, not an int enum, so it marshals to human-readable JSON (see the Chatwright standard's "JSON artefacts carry human-readable string constants" convention) rather than a bare, meaningless integer.

const (
	// ChangeNewMessage: a logical message not present in the previous
	// Observation now exists.
	ChangeNewMessage ChangeKind = "new-message"
	// ChangeMessageEdited: an existing logical message's Version advanced.
	ChangeMessageEdited ChangeKind = "edited-message"
	// ChangeActionsChanged: an existing logical message's available actions
	// changed without its Version advancing.
	ChangeActionsChanged ChangeKind = "actions-changed"
)

func (ChangeKind) String

func (k ChangeKind) String() string

String renders k for diagnostics and test failure messages.

type ChatBoundary

type ChatBoundary struct {
	ChatID int64 `json:"chatId"`
	// FirstEntry is the index, into the matching ChatJournal.Entries, of
	// this Part's first entry for this chat.
	FirstEntry int `json:"firstEntry"`
	// EntryCount is how many consecutive entries, starting at FirstEntry,
	// belong to this Part.
	EntryCount int `json:"entryCount"`
}

ChatBoundary is a half-open range ([FirstEntry, FirstEntry+EntryCount) into one chat's ChatJournal.Entries — never a duplicated copy of the entries themselves.

type ChatJournal

type ChatJournal struct {
	ChatID  int64          `json:"chatId"`
	Entries []JournalEntry `json:"entries"`
}

ChatJournal is one chat's complete structured journal — the same JournalEntry records the runtime's platform emulator journal returns, carried verbatim (including platform-native identifiers) because a Bundle is the developer/trace-level artifact JournalEntry's own doc comment describes, not the actor-facing observation surface. It is run-level and continuous: a Part never carries its own ChatJournal, only a JournalBoundary referencing a slice of this one.

type ChatRef

type ChatRef struct {
	ChatID int64 `json:"chatId"`
}

ChatRef identifies the chat an Observation projects. It carries Chatwright's own chat identity — never a raw platform chat ID scraped from the wire.

type DataStateEvidence

type DataStateEvidence struct {
	// Name is the triggering assertion's stable identity, correlating this
	// evidence to the message, checkpoint or branch that attached it.
	Name string `json:"name"`
	// AttachmentPoint is where in the scenario this assertion ran.
	AttachmentPoint AttachmentPoint `json:"attachmentPoint"`
	// Holder is the resolved holder name the query ran against (even an
	// unresolved request records the name that was asked for).
	Holder string `json:"holder"`
	// Query is the concrete DTQL text executed.
	Query string `json:"query"`
	// Params is a detached copy of the query's named parameters.
	Params map[string]any `json:"params"`
	// Outcome is OutcomePassed or OutcomeFailed.
	Outcome Outcome `json:"outcome"`
	// FailureMessage is set when Outcome is OutcomeFailed: holder
	// resolution, query execution or the expectation's failure message.
	FailureMessage string `json:"failureMessage"`
	// TotalRows is how many rows the query returned before any preview
	// bound was applied.
	TotalRows int `json:"totalRows"`
	// ReturnedRows is how many rows are present in Preview.
	ReturnedRows int `json:"returnedRows"`
	// Truncated is true when Preview omits rows (TotalRows > ReturnedRows)
	// or drops fields from at least one previewed row.
	Truncated bool `json:"truncated"`
	// Preview is the bounded, redacted, normalised recordset. Redacted
	// fields are present with their value replaced by the runtime's
	// redaction placeholder rather than omitted, so evidence still declares
	// which fields exist.
	Preview []Row `json:"preview"`
	// RedactedFields lists the field names configured for redaction (the
	// declared policy), regardless of whether any previewed row contained
	// them.
	RedactedFields []string `json:"redactedFields"`
	// ExcludedFields lists the field names normalisation removed from the
	// comparison basis. They remain visible in Preview: exclusion only
	// means "not part of the assertion", never "hidden from evidence".
	ExcludedFields []string `json:"excludedFields"`
}

DataStateEvidence is the canonical, JSON-serialisable record of one executed data-state assertion: the exact DTQL query and parameters, the holder it ran against, its pass/fail outcome, and a bounded, redacted, normalised preview of the rows it returned. Every exported field carries an explicit lower-camel-case `json` tag — this type reaches a run bundle (via AIGoalSection.Evidence) and the whole run-bundle wire is uniformly camelCase.

In the chatwright runtime this type is datastate.Evidence; it is renamed here — Go name only, the wire shape is identical — to keep it distinct from FindingEvidence in this single package.

type Direction

type Direction string

Direction identifies which side of a conversation produced a JournalEntry. It is a string type, not an int enum, so a JournalEntry marshals to human-readable JSON (see the Chatwright standard's "JSON artefacts carry human-readable string constants" convention) rather than a bare, meaningless integer.

const (
	DirectionUser Direction = "user"
	DirectionBot  Direction = "bot"
)

type Finding

type Finding struct {
	Kind    FindingKind `json:"kind"`
	TaskID  string      `json:"taskId"`
	Summary string      `json:"summary"`

	Evidence FindingEvidence `json:"evidence"`

	// Confidence distinguishes how the Finding was derived: "mechanical"
	// for the deterministic rules the runtime's report assembly applies
	// itself, or a caller's own label (e.g. "dtql-verified") for
	// caller-supplied findings.
	Confidence string `json:"confidence,omitempty"`
}

Finding is one reportable outcome of the campaign: a claim, classified, scoped to a task, and linked to the evidence that grounds it.

type FindingEvidence

type FindingEvidence struct {
	// ObservationSequences are Observation.Sequence values.
	ObservationSequences []int64 `json:"observationSequences,omitempty"`
	// LoopEventIndexes are LoopEvent.Index values.
	LoopEventIndexes []int `json:"loopEventIndexes,omitempty"`
}

FindingEvidence links a Finding back to the observations and loop events that ground it, so a developer can navigate from a claim to its proof. Both slices may be empty — e.g. a coverage-gap finding for a task that was never attempted has nothing to link to; that is precisely what makes it a gap.

In the chatwright runtime this type is campaign.Evidence; it is renamed here — Go name only, the wire shape is identical — to keep it distinct from DataStateEvidence in this single package.

type FindingKind

type FindingKind string

FindingKind classifies one Finding. This slice supports exactly the three kinds mechanical evidence (plus an explicit caller hook) can ground.

const (
	// FindingVerifiedDefect: the actor acted and the observed outcome was
	// wrong — backed by deterministic or DTQL evidence, or a
	// caller-supplied classification; mechanics alone cannot derive this
	// kind.
	FindingVerifiedDefect FindingKind = "verified-defect"
	// FindingAINavigationFailure: the task did not complete, and its
	// history shows the actor's own proposals going stale or invalid — the
	// bot was never shown to be at fault.
	FindingAINavigationFailure FindingKind = "ai-navigation-failure"
	// FindingCoverageGap: a task the campaign never attempted, or never
	// concluded, before it stopped — a gap in evidence, not a claim about
	// the bot.
	FindingCoverageGap FindingKind = "coverage-gap"
	// FindingActorOvershoot: the actor kept acting (or was shown, via the
	// overshoot probe, to want to keep acting) after its task's
	// machine-checkable completion criteria already held — attributed to
	// the actor, never misfiled as a bot defect.
	FindingActorOvershoot FindingKind = "actor-overshoot"
	// FindingConstraintViolation: the actor proposed text that violated
	// its task's (or goal's) machine-checkable content rules — a
	// vocabulary allowlist, a deny-pattern or a custom predicate. The
	// runtime blocked it before it ever reached the bot; this finding
	// records that it was attempted.
	FindingConstraintViolation FindingKind = "constraint-violation"
)

Finding kinds. See FindingKind.

type Freshness added in v0.3.0

type Freshness string

Freshness is the deterministic outcome of validating a click proposal against the runtime's current journal state: is the proposed action still present, unchanged, in the runtime's current projection? It is a validity check against the engine's own state, not a judgement against a criterion — see sdk's "verdict" for that (the AI-judged assertion outcome). It is a string type, not an int enum, so it marshals to human-readable JSON (see the Chatwright standard's "JSON artefacts carry human-readable string constants" convention) rather than a bare, meaningless integer.

const (
	// FreshnessFresh: the proposed action is present, unchanged, in the
	// engine's current projection.
	FreshnessFresh Freshness = "fresh"
	// FreshnessStale: the proposed action is not present in the engine's
	// current projection — its source observation is out of date, or was
	// never issued by that engine at all.
	FreshnessStale Freshness = "stale"
)

func (Freshness) String added in v0.3.0

func (f Freshness) String() string

String renders f for diagnostics and test failure messages.

type Goal

type Goal struct {
	ID          string   `json:"id"`
	Title       string   `json:"title"`
	Description string   `json:"description"`
	Tasks       []Task   `json:"tasks"`
	Constraints []string `json:"constraints"`
	Budgets     Budgets  `json:"budgets"`
}

Goal is one campaign's product-level intent: a natural-language outcome broken into Tasks, plus the Constraints and Budgets that bound how an actor may pursue it. A Goal describes intent, never platform mechanics — see the standard repository's goal-and-task-contract feature and its goal-does-not-leak-platform-mechanics acceptance criterion.

type JournalBoundary

type JournalBoundary struct {
	Chats []ChatBoundary `json:"chats"`
}

JournalBoundary slices a run-level journal (Run.Chats) into the entries one Part covers, per chat.

type JournalEntry

type JournalEntry struct {
	Direction    Direction        `json:"direction"`
	Kind         JournalEntryKind `json:"kind"`
	MessageID    int              `json:"messageId"`    // logical message identity, shared by inbound/outbound entries in this chat; 0 when Kind has no message identity of its own
	RefMessageID int              `json:"refMessageId"` // JournalEntryAction only: the message the action targeted
	Version      int              `json:"version"`      // JournalEntryMessage only: 0 = original send/inbound, N = the Nth edit
	Text         string           `json:"text"`
	Actions      [][]Action       `json:"actions"` // JournalEntryMessage only: actions attached to this entry, in platform row/col layout
	Method       string           `json:"method"`  // JournalEntryUncaptured only: the Bot API method name that was called
	At           time.Time        `json:"at"`

	// FromID is the platform-native identity of this entry's originator:
	// the Telegram user id of the client actor for a client-originated
	// entry, or the bot's own id for a bot-originated entry. It is 0 when
	// no identity is available (e.g. a pure method-call record with no
	// resolvable sender) — a platform never invents an identity it does not
	// actually know. This is what lets a run-bundle roster (see
	// Actor.PlatformIdentities) attribute every journal entry to whoever
	// produced it.
	FromID int64 `json:"fromId"`
}

JournalEntry is one chronological, structured record from a chat's append-only journal — the same events the chatwright runtime's platform emulator renders as human-readable prose, given directly to callers that need to reason about them structurally instead of parsing rendered text. It carries the emulator's full internal record, including platform-native identifiers and action data (e.g. Telegram callback_data via Actions[*][*].ID) — this is the developer/trace-level seam, not the actor-facing observation surface; Observation is where raw platform payloads are dropped before an actor ever sees them.

type JournalEntryKind

type JournalEntryKind string

JournalEntryKind distinguishes what a JournalEntry records. It is a string type for the same reason as Direction — see Direction's doc comment.

const (
	// JournalEntryMessage is an inbound user message or an outbound bot
	// send/edit; MessageID, Version, Text and Actions apply.
	JournalEntryMessage JournalEntryKind = "message"
	// JournalEntryAction is an inbound action activation (a button click or
	// equivalent interactive reply); RefMessageID names the message it
	// targeted, Text carries the platform action identifier that was
	// activated.
	JournalEntryAction JournalEntryKind = "action"
	// JournalEntryUncaptured records a bot API call the emulator does not
	// simulate — it produced no observable chat content; Method names the
	// call.
	JournalEntryUncaptured JournalEntryKind = "uncaptured"
)

type LoopEvent

type LoopEvent struct {
	// Index is 0-based and monotonic across one loop's lifetime (not just
	// one task), so it is stable to reference from a Finding.
	Index int `json:"index"`
	// At is stamped from the loop's injected clock, never time.Now, so a
	// run's timeline is reproducible.
	At time.Time `json:"at"`
	// TaskID is the task this iteration was attempting.
	TaskID string `json:"taskId"`

	// ObservationSequence is the Observation.Sequence this iteration
	// observed before proposing — the same value a Finding's evidence links
	// back to.
	ObservationSequence int64 `json:"observationSequence"`

	Proposal Proposal `json:"proposal"`
	Usage    Usage    `json:"usage"`

	// Validation is the loop's validate-step outcome for Proposal. It is
	// only Checked for ProposeClick — the loop has nothing to validate
	// against an observation for a send-text, task-done or give-up proposal.
	Validation ValidationOutcome `json:"validation"`

	// Action is what actually happened when the loop tried to act on
	// Proposal (or why it did not).
	Action ActionOutcome `json:"action"`

	// ProposeError is set exactly when this iteration's call to the AI
	// provider's Propose failed: it carries the returned error's own
	// message (error.Error()), and Proposal, Usage, Validation and Action
	// are all their zero value — there was nothing to validate or act on.
	// Empty for every iteration that got as far as a Proposal, which is
	// most of them; this field exists so a failed Propose call still leaves
	// a LoopEvent behind (index, timestamp, task, the observation it was
	// attempting to act from) instead of vanishing from the record with
	// only a returned Go error nobody downstream of the loop ever sees
	// (github.com/chatwright/runtime-go issue #4).
	ProposeError string `json:"proposeError,omitempty"`
}

LoopEvent is one loop iteration's complete structured record: what was observed, what was proposed, how the proposal validated, what happened when the loop acted on it (or chose not to), and what it cost. LoopEvents are the loop's entire raw material for Report — nothing the report needs is reconstructed after the fact from logs or a transcript.

type MessageActor

type MessageActor string

MessageActor identifies which side of a conversation produced a VisibleMessage. It is a string type, not an int enum, so it marshals to human-readable JSON (see the Chatwright standard's "JSON artefacts carry human-readable string constants" convention) rather than a bare, meaningless integer.

In the chatwright runtime this enum is observe.Actor; it is renamed here — Go name only, the wire values are identical — because this package's roster entry type already owns the name Actor.

const (
	MessageActorUser MessageActor = "user"
	MessageActorBot  MessageActor = "bot"
)

func (MessageActor) String

func (a MessageActor) String() string

String renders a for diagnostics and test failure messages.

type Metadata

type Metadata struct {
	// CreatedAt is when this Bundle was assembled, supplied by the caller
	// (never time.Now internally — see Chatwright's broader injected-clock
	// convention) so assembling a Bundle is itself deterministic and
	// testable.
	CreatedAt time.Time `json:"createdAt"`

	// ChatwrightVersion is the sdk module's own resolved version — see
	// ModuleVersion — left empty when it cannot be determined (e.g. a
	// `go test` run inside this repository itself, which always reports
	// "(devel)"; see ModuleVersion's doc comment). "If available" is
	// load-bearing: a Bundle is still valid and complete without it.
	ChatwrightVersion string `json:"chatwrightVersion,omitempty"`

	// Author optionally attributes this Bundle to whoever assembled it.
	// Never populated automatically from git config, the OS user or any
	// other ambient environment — a caller must supply it explicitly, or
	// leave it nil. Bundles get emailed and committed to public
	// repositories, so silently harvesting an identity into one is not this
	// package's call to make.
	Author *Author `json:"author,omitempty"`
}

Metadata declares a Bundle's provenance — independent of any one Run's own fidelity labels (Run.Platform, Run.EndpointProfile), which is why those moved out of Metadata and onto Run: a Bundle can in principle carry runs against different platforms or endpoint profiles, so a single Metadata-level label would have been misleading.

type Observation

type Observation struct {
	// Sequence is monotonic per engine, starting at 1.
	Sequence int64 `json:"sequence"`
	// PreviousSequence is the Sequence of the Observation this one
	// supersedes; 0 for an engine's first Observation.
	PreviousSequence int64   `json:"previousSequence"`
	Chat             ChatRef `json:"chat"`
	// Messages is chronological, oldest to newest: one entry per currently
	// visible logical message, at its current (possibly-edited) version.
	Messages []VisibleMessage `json:"messages"`
	// Changes is empty for an engine's first Observation; otherwise the
	// explicit differences since PreviousSequence.
	Changes []Change `json:"changes"`
}

Observation is one platform-neutral snapshot of a chat's visible conversation and available actions, with explicit lineage back to the previous Observation. Observations are produced by the runtime's observation engine — actors never build or diff one by hand.

type Outcome

type Outcome string

Outcome is the pass/fail result of one executed assertion.

const (
	OutcomePassed Outcome = "passed"
	OutcomeFailed Outcome = "failed"
)

type Part

type Part struct {
	// ID is caller-supplied and only needs to be unique within its Run.
	ID string `json:"id"`

	// Title is an optional human-readable label (e.g. "Shopping-list
	// exploration") for a player to show as a chapter heading.
	Title string `json:"title,omitempty"`

	// Kind discriminates which kind-scoped section below is populated — see
	// PartKind.
	Kind PartKind `json:"kind"`

	// JournalBoundary slices the run-level journal (Run.Chats) into the
	// entries this Part covers — see JournalBoundary.
	JournalBoundary JournalBoundary `json:"journalBoundary"`

	// AIGoal is populated when Kind is PartKindAIGoal, nil otherwise — see
	// AIGoalSection. Read returns ErrMissingAIGoalSection for an
	// PartKindAIGoal part with this unset, rather than silently returning a
	// half-decoded Part.
	AIGoal *AIGoalSection `json:"aiGoal,omitempty"`
}

Part is one ordered passage of a Run: a kind, a slice of the run-level journal this passage covers, and a kind-scoped section carrying that passage's own detail (aiGoal today; a future "deterministic" section is reserved — see PartKindDeterministic). Today's writers always produce exactly one Part per Run, covering the whole journal; the ordered-list shape on Run.Parts is what lets a future hybrid run add more Parts without any schema change.

type PartKind

type PartKind string

PartKind discriminates what a Part's kind-scoped section (aiGoal today) holds.

const (
	// PartKindAIGoal: the actor loop ran a goal/task contract for this
	// part — see AIGoalSection.
	PartKindAIGoal PartKind = "ai-goal"
	// PartKindDeterministic: a deterministic scenario fragment executed for
	// this part. Reserved for the hybrid-runs runtime (see the standard
	// repository's spec/ideas/hybrid-runs.md): no Go struct or
	// "deterministic" JSON section is defined yet, and no writer produces
	// one — Read still accepts the kind (so a future writer's output
	// round-trips once a section is defined) but the section itself is not
	// modelled.
	PartKindDeterministic PartKind = "deterministic"
)

Part kinds. See PartKind.

type PlatformIdentity

type PlatformIdentity struct {
	UserID    int64  `json:"userId"`
	Username  string `json:"username,omitempty"`
	FirstName string `json:"firstName,omitempty"`
}

PlatformIdentity is one actor's platform-native identity on one platform — sized for what Telegram needs today (a numeric user id, plus an optional username and first name); a future platform reuses the same shape or extends it, either way without changing how Actor.PlatformIdentities is keyed.

type Proposal

type Proposal struct {
	Kind ProposalKind `json:"kind"`

	// Text is set for ProposeSendText: the text to send as the user.
	Text string `json:"text"`

	// ActionID is set for ProposeClick: an AvailableAction.ID drawn from the
	// observation the proposal was made against.
	ActionID string `json:"actionId"`
	// ObservationSequence is the Observation.Sequence the proposal was
	// chosen from. Required for ProposeClick (the runtime validates the
	// click against it); ignored otherwise.
	ObservationSequence int64 `json:"observationSequence"`

	// Rationale is free text explaining the choice — never private
	// chain-of-thought, just enough for a developer or the campaign report
	// to understand why the actor did this.
	Rationale string `json:"rationale"`
}

Proposal is an AI provider's typed intent for the next action, plus its free-text rationale. The chatwright runtime's actor loop validates and executes it — nothing a provider proposes is trusted blindly.

type ProposalKind

type ProposalKind string

ProposalKind is the typed shape of an AI provider's proposed action. It is a string type, not an int enum, so it marshals to human-readable JSON — in bundles, cassette files and everywhere else — rather than a bare, meaningless integer (see the Chatwright standard's "JSON artefacts carry human-readable string constants" convention). Its Go zero value ("") is itself a real, meaningful wire value, not only an unset placeholder: a LoopEvent whose ProposeError is set carries a zero-value Proposal (there was no proposal to have a Kind), and "" is what that Kind reads as.

const (
	// ProposeSendText: send free text as the user.
	ProposeSendText ProposalKind = "send-text"
	// ProposeClick: activate a previously observed AvailableAction by its
	// opaque ID (Proposal.ActionID), as seen at Proposal.ObservationSequence.
	ProposeClick ProposalKind = "click"
	// ProposeTaskDone: the active task's success criteria are met.
	ProposeTaskDone ProposalKind = "task-done"
	// ProposeGiveUp: the active task cannot be completed; stop attempting it.
	ProposeGiveUp ProposalKind = "give-up"
)

Proposal kinds. See Proposal.

func (ProposalKind) String

func (k ProposalKind) String() string

String renders k for diagnostics, test failure messages and reports.

type Report

type Report struct {
	// SchemaVersion is always ReportSchemaVersion for a Report the runtime
	// produced; a consumer reading an older or newer value should not
	// assume today's field meanings.
	SchemaVersion int `json:"schemaVersion"`

	GoalID    string `json:"goalId"`
	GoalTitle string `json:"goalTitle"`

	// StopReason is why the campaign stopped (e.g. "goal-complete",
	// "budget-steps"), carried as a plain string so Report never imports
	// the runtime's Go type into its own JSON contract.
	StopReason string        `json:"stopReason"`
	Steps      int           `json:"steps"`
	Cost       float64       `json:"cost,omitempty"`
	Elapsed    time.Duration `json:"elapsedNanoseconds"`

	Tasks    []TaskOutcome `json:"tasks"`
	Findings []Finding     `json:"findings"`

	Usage AggregateUsage `json:"usage"`
}

Report is one campaign run's complete, evidence-linked outcome: versioned, JSON-serialisable, and portable — a consumer needs nothing but this value (plus, optionally, the trace/transcript a Finding's evidence points at) to understand what an actor attempted, what it found, and how sure the report is of each conclusion. Reports are assembled by the chatwright runtime from a completed (or budget-stopped) actor loop run.

type RetainedObservation

type RetainedObservation struct {
	Sequence    int64       `json:"sequence"`
	Observation Observation `json:"observation"`
}

RetainedObservation pairs one retained Observation with its own Sequence, so AIGoalSection.Observations reads as an ordered list rather than a JSON object keyed by a stringified int64 (see AIGoalSection.Observations).

type Row

type Row map[string]any

Row is one returned record. Field values may themselves be nested map[string]any or []any, matching an embedded-document shape such as a parent-scoped list record and its nested `items` field.

type Run

type Run struct {
	// ID is caller-supplied and only needs to be unique within this Bundle.
	ID string `json:"id"`

	// Platform is the platform name this run drove (e.g. "telegram").
	Platform string `json:"platform"`

	// EndpointProfile is this run's declared endpoint profile (the standard
	// repository's decision 0008; docs/glossary.md's "endpoint profile"
	// entry) — e.g. EndpointProfilePlatformEmulated. Evidence is never
	// interchangeable across profiles, so a player must always have this
	// label, never infer it.
	EndpointProfile string `json:"endpointProfile"`

	// Actors is the roster of everyone who took part in this run — every
	// AI agent, human, scripted or replay actor that acted, plus the
	// bot-under-test itself — so a player can attribute every journal entry
	// to whoever produced it (see JournalEntry.FromID and Actor's own doc
	// comment).
	Actors []Actor `json:"actors"`

	// Chats is the run's continuous, per-chat journal: one entry per
	// distinct chat ID, in the order the caller assembled them, each
	// carrying that chat's entire JournalEntry history for the whole run —
	// never re-split or duplicated per Part. A Part slices this journal by
	// reference (see Part.JournalBoundary) rather than embedding its own
	// copy.
	Chats []ChatJournal `json:"chats"`

	// Parts is this run's ordered sequence of passages — see Part. Today's
	// writers always produce exactly one ai-goal Part; the ordered-list
	// shape is what a future hybrid run (deterministic passages interleaved
	// with ai-goal exploration) composes without any schema change.
	Parts []Part `json:"parts"`

	// Bookmarks is an optional list of manual fast-forward markers a player
	// can offer alongside this run's own derived markers — part boundaries,
	// task completions, findings and the like need no schema entry here,
	// since a player derives them directly from Parts/AIGoalSection; a
	// Bookmark is only for a marker no derivation already produces. Today's
	// writers emit none unless the caller supplies them.
	Bookmarks []Bookmark `json:"bookmarks,omitempty"`

	// Annotations is an optional list of comments attached to moments in
	// this run's conversation — see Annotation. Today's writers emit none
	// unless the caller supplies them.
	Annotations []Annotation `json:"annotations,omitempty"`
}

Run is one run's complete, self-contained record: who was in the conversation (Actors), the continuous per-chat journal the whole run produced (Chats), and the ordered passages the run was composed of (Parts). Today's writers always emit a Run with exactly one ai-goal Part spanning the whole journal — see the standard repository's spec/ideas/hybrid-runs.md for the hybrid (deterministic + ai-goal) runs this shape exists to accommodate without a future schema change.

type Task

type Task struct {
	ID              string   `json:"id"`
	Title           string   `json:"title"`
	DependsOn       []string `json:"dependsOn"`
	SuccessCriteria string   `json:"successCriteria"`
	Milestones      []string `json:"milestones"`
}

Task is one trackable unit of work inside a Goal. Success is judged by prose SuccessCriteria — the contract never prescribes the bot commands or callback data used to satisfy it. DependsOn names other Task IDs in the same Goal that must be completed before this task becomes eligible for activation. Milestones names checkpoints this task's completion may reach; the reporting layer, not this type, interprets them.

type TaskOutcome

type TaskOutcome struct {
	TaskID          string `json:"taskId"`
	Title           string `json:"title,omitempty"`
	SuccessCriteria string `json:"successCriteria,omitempty"`
	// Status is the task's lifecycle status (e.g. "pending", "completed"),
	// carried as a plain string for the same reason as Report.StopReason.
	Status string `json:"status"`
	// Attempted is true once at least one LoopEvent was recorded for this
	// task.
	Attempted bool `json:"attempted"`
	// FailureCount is how many times the campaign recorded a failure for
	// this task.
	FailureCount int `json:"failureCount"`
}

TaskOutcome is one task's result within the campaign, evidence-grounded: Attempted reflects whether the loop actually recorded any LoopEvent for this task, not merely its terminal status.

type Usage

type Usage struct {
	Model        string        `json:"model"`
	InputTokens  int           `json:"inputTokens"`
	OutputTokens int           `json:"outputTokens"`
	Latency      time.Duration `json:"latencyNanoseconds"`
	Cost         *float64      `json:"cost,omitempty"`
}

Usage reports what one provider call cost: model identity, token counts, latency and, optionally, a caller-priced Cost. When Cost is set, the runtime feeds it to its campaign state so a configured Budgets.MaxCost is enforced.

type ValidationOutcome

type ValidationOutcome struct {
	// Checked is false for proposal kinds validation does not apply to
	// (ProposeSendText, ProposeTaskDone, ProposeGiveUp); Freshness and
	// Reason are meaningless when Checked is false.
	Checked   bool      `json:"checked"`
	Freshness Freshness `json:"freshness"`
	Reason    string    `json:"reason"`
}

ValidationOutcome is the loop's validate-step outcome for one proposal, carrying the runtime's own validation result verbatim when it applies.

type VisibleMessage

type VisibleMessage struct {
	ID      string            `json:"id"`      // stable synthetic Chatwright identity for this logical message, e.g. "msg7"
	Version int               `json:"version"` // monotonic version of this logical message; 0 for the original send
	Edited  bool              `json:"edited"`  // true once Version has advanced past 0
	Actor   MessageActor      `json:"actor"`   // who produced the message
	Text    string            `json:"text"`
	Actions []AvailableAction `json:"actions"` // interactions currently attached to this message
}

VisibleMessage is one user-visible logical message: stable identity across edits, a monotonic version and an edited flag, plus the actions currently attached to it. Only normalized text and action labels are carried — no platform-native message IDs, callback data or wire payloads (see the standard repository's observation-model/visible-conversation feature).

Directories

Path Synopsis
internal
schemagen
Package schemagen generates the run-bundle format v1's JSON Schema (formats/run-bundle/v1/schema.json) from the sdk package's own Go types, via reflection (github.com/invopop/jsonschema), so the Go types stay the format's single source of truth — nobody hand-maintains a second description of the wire shape that can drift from it.
Package schemagen generates the run-bundle format v1's JSON Schema (formats/run-bundle/v1/schema.json) from the sdk package's own Go types, via reflection (github.com/invopop/jsonschema), so the Go types stay the format's single source of truth — nobody hand-maintains a second description of the wire shape that can drift from it.
schemagen/gen command
Command gen regenerates formats/run-bundle/v1/schema.json from this module's Go types via internal/schemagen.
Command gen regenerates formats/run-bundle/v1/schema.json from this module's Go types via internal/schemagen.

Jump to

Keyboard shortcuts

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