Documentation
¶
Overview ¶
Package agentsession implements the Agent Session Format: an append-only, tree-structured JSONL record of one agent session whose conversation payloads are Open Responses items. The format is specified in docs/rfcs/0001-agent-session-format.md; this package is its reference implementation.
Shape ¶
A session file is a header line followed by entries, one JSON object per line. An entry's id is the hash of its envelope over the hash of its body, and parent names a parent by hash, so a file verifies itself and a leaf commits to its whole path; Session.Append computes both. Entries form a tree through those members, so a branch is a child of an earlier entry, in place, and an abandoned branch stays in the file. A session made with Fork continues from an entry of another, its base, and opens with the path to it. An entry may also name further predecessors in EntryBase.Parents — a subagent's result, a branch merged back — which record where converged work came from and are never walked when building a context. Header and Entry are the two line shapes; the concrete entry types are ItemEntry (one Open Responses item, verbatim), ResponseEntry (the envelope of one model call), ConfigEntry (a delta to request settings), CompactionEntry and BranchSummaryEntry (summaries that enter context), and the out-of-context LabelEntry, InfoEntry, EnvEntry, OutcomeEntry, LinkEntry and CustomEntry. Namespaced types decode to UnknownEntry and are written back byte for byte, as are namespaced items inside an item entry.
Sessions ¶
Session is the tree in memory: Read loads one, Write emits one, and Session.Append adds an entry as a child of the current leaf. Session.Branch moves the leaf so the next append forks; a fresh root follows Session.ResetLeaf. A file cut short by a crash still loads, and Session.Truncated reports the broken line.
Context ¶
Session.ContextAt runs the format's context algorithm: it walks the path to an entry, replays config entries into Settings, applies the last compaction and returns the item list a model call there would receive. Settings.Request turns that into the canonical openresponses.Request, and RequestHash hashes it as the format defines (RFC 8785 canonical JSON, SHA-256). Session.Verify checks a stored response's hash against the rebuilt request, and reports ErrNoHash rather than nil when the response recorded none.
Stores ¶
Store is the persistence interface; MemoryStore is the in-memory one and the jsonl subpackage is the file store. The storetest subpackage is the conformance suite every store runs.
Export ¶
The export subpackage turns a session's root-to-leaf paths into ATIF documents, using the types in the atif subpackage, with the raw items carried in the extras so nothing is lost.
Index ¶
- Constants
- Variables
- func CanonicalTime(t time.Time) string
- func ComputeReason(path, segment []Entry) string
- func EntryHashes(data []byte) (id, content string, err error)
- func GitRevision(dir string) (string, error)
- func HashBytes(data []byte) string
- func HashFile(path string) (string, error)
- func HashFiles(dir string, paths ...string) (map[string]string, error)
- func HashRequestJSON(data []byte) (string, error)
- func HashText(s string) string
- func InContext(e Entry) bool
- func IsExtension(typ string) bool
- func JoinInstructions(parts []InstructionPart) string
- func MarshalEntry(e Entry) ([]byte, error)
- func NewEntryID() string
- func NewSessionID() string
- func ParseCanonicalTime(s string) (time.Time, bool)
- func ParseFormat(format string) (major, minor int, err error)
- func RecordResponse(ctx context.Context, store Store, sessionID string, req openresponses.Request, ...) (string, error)
- func RequestHash(req openresponses.Request) (string, error)
- func SameWorkspace(a, b *Workspace) bool
- func SubsessionID(parentSessionID, callID string) string
- func ToolName(t openresponses.Tool) string
- func ValidHash(s string) bool
- func Write(w io.Writer, s *Session) error
- type BranchSummaryEntry
- type Call
- func (c *Call) Answered() bool
- func (c *Call) Args() string
- func (c *Call) DispatchedArgs() string
- func (c *Call) From() []EntryRef
- func (c *Call) Held() bool
- func (c *Call) ID() string
- func (c *Call) IdempotencyKey() string
- func (c *Call) InFlight() bool
- func (c *Call) Pending() bool
- func (c *Call) Rejected() bool
- func (c *Call) State(h Header) CallState
- type CallState
- type CompactionEntry
- type ConfigEntry
- type Context
- type CustomEntry
- type DecisionEntry
- type DispatchEntry
- type Entry
- type EntryBase
- type EntryRef
- type EnvEntry
- func (e *EnvEntry) AddRead(path, hash string)
- func (e *EnvEntry) AddTool(name, version string)
- func (e *EnvEntry) AddWritten(path, hash string)
- func (*EnvEntry) EntryType() string
- func (e *EnvEntry) MarshalJSON() ([]byte, error)
- func (e *EnvEntry) SetGit(dir string, dirty bool) error
- func (e *EnvEntry) SetWorkspace(kind, ref string) *Workspace
- func (e *EnvEntry) UnmarshalJSON(data []byte) error
- type FileHashes
- type Harness
- type Header
- type InfoEntry
- type InstructionPart
- type ItemEntry
- type LabelEntry
- type LinkEntry
- type ListFilter
- type MemoryStore
- func (m *MemoryStore) Append(ctx context.Context, sessionID string, e Entry) (string, error)
- func (m *MemoryStore) Create(_ context.Context, h Header) (*Session, error)
- func (m *MemoryStore) Delete(_ context.Context, id string) error
- func (m *MemoryStore) List(_ context.Context, f ListFilter) iter.Seq2[Summary, error]
- func (m *MemoryStore) Open(_ context.Context, id string) (*Session, error)
- type Normalisation
- type OmittedPart
- type Outcome
- type OutcomeEntry
- func (*OutcomeEntry) EntryType() string
- func (e *OutcomeEntry) MarshalJSON() ([]byte, error)
- func (e *OutcomeEntry) UnmarshalJSON(data []byte) error
- func (e *OutcomeEntry) WithDetails(v any) *OutcomeEntry
- func (e *OutcomeEntry) WithLabel(label string) *OutcomeEntry
- func (e *OutcomeEntry) WithPass(pass bool) *OutcomeEntry
- func (e *OutcomeEntry) WithScore(score float64) *OutcomeEntry
- type QueuedEntry
- type ResponseEntry
- type Result
- type Run
- type RunEntry
- type Session
- func (s *Session) Append(e Entry) (string, error)
- func (s *Session) Branch(id string) error
- func (s *Session) Calls(leaf string) ([]*Call, error)
- func (s *Session) Children(id string) []string
- func (s *Session) Commit(e Entry) (Result, error)
- func (s *Session) Compact(firstKept string, summary openresponses.Item) (*CompactionEntry, error)
- func (s *Session) CompactFrom(first int, summary openresponses.Item) (*CompactionEntry, error)
- func (s *Session) CompactKeeping(kept int, summary openresponses.Item) (*CompactionEntry, error)
- func (s *Session) Context() (Context, error)
- func (s *Session) ContextAt(id string) (Context, error)
- func (s *Session) ContextHash(id string) (string, error)
- func (s *Session) EndRun(reason, ref string) (*RunEntry, error)
- func (s *Session) Entries() []Entry
- func (s *Session) Entry(id string) (Entry, bool)
- func (s *Session) Header() Header
- func (s *Session) ID() string
- func (s *Session) Labels() map[string]string
- func (s *Session) Leaf() string
- func (s *Session) Leaves() []string
- func (s *Session) Len() int
- func (s *Session) MarkLeaf() (*LabelEntry, error)
- func (s *Session) Migrated() (bool, []string)
- func (s *Session) Name() string
- func (s *Session) OpenRun(leaf string) (*Run, error)
- func (s *Session) Path(id string) []Entry
- func (s *Session) PendingCalls(leaf string) ([]*Call, error)
- func (s *Session) PendingQueued(leaf string) ([]*QueuedEntry, error)
- func (s *Session) Prefix(id string) bool
- func (s *Session) Prepare(e Entry) (Result, error)
- func (s *Session) Repeated() []string
- func (s *Session) RequestContext(id string) (Context, error)
- func (s *Session) ResetLeaf()
- func (s *Session) Resolve(id string) (string, bool)
- func (s *Session) Roots() []string
- func (s *Session) Runs(leaf string) ([]*Run, error)
- func (s *Session) SummarizeBranch(from string, summary openresponses.Item) (*BranchSummaryEntry, error)
- func (s *Session) SupersededBy() string
- func (s *Session) Truncated() *TruncatedLine
- func (s *Session) Verify(id string) error
- func (s *Session) VerifyRecords(leaf string) error
- type Settings
- func (s Settings) Apply(c *ConfigEntry) Settings
- func (s Settings) ExtraValue(key string, v any) (bool, error)
- func (s Settings) InstructionsDelta(parts []InstructionPart) *ConfigEntry
- func (s Settings) OmittedDelta(omitted []OmittedPart) []OmittedPart
- func (s Settings) Request(items openresponses.Items) (openresponses.Request, error)
- func (s *Settings) UnmarshalJSON(data []byte) error
- type Store
- type Summary
- type Trigger
- type TruncatedLine
- type UnknownEntry
- type VCS
- type Workspace
Constants ¶
const ( TypeItem = "item" TypeResponse = "response" TypeConfig = "config" TypeCompaction = "compaction" TypeBranchSummary = "branch_summary" TypeLabel = "label" TypeInfo = "info" TypeEnv = "env" TypeOutcome = "outcome" TypeLink = "link" TypeCustom = "custom" )
Entry types defined by the format. Any other type of the form "ns:name" is an extension and decodes to UnknownEntry.
const ( RelSubsession = "subsession" RelForkOf = "fork_of" RelContinuedIn = "continued_in" )
Relations a LinkEntry may carry.
const ( OutcomeFeedback = "feedback" OutcomeTest = "test" OutcomeTask = "task" OutcomeToolError = "tool_error" OutcomeEval = "eval" OutcomeCustom = "custom" )
Kinds an OutcomeEntry may carry.
const ( MediaInline = "inline" MediaSidecar = "sidecar" )
Media storage modes named by the header.
const ( TypeRun = "run" TypeDispatch = "dispatch" TypeDecision = "decision" TypeQueued = "queued" )
Record entry types added in format 0.2, and queued in 0.3. They are never in context.
const ( ModeSteer = "steer" ModeFollowUp = "followup" )
Modes a QueuedEntry may carry: an input that joins the run in flight, and one that waits for it to end.
const ( RunStart = "start" RunEnd = "end" )
Phases of a RunEntry.
const ( SourceInput = "input" SourceResume = "resume" )
Sources of a run, on its start entry. Both are shapes of the path: a resume answers a call that was pending when the run began; an input is everything else, a retry after an error included.
const ( ReasonDone = "done" ReasonStopped = "stopped" ReasonInterrupted = "interrupted" ReasonInputRequired = "input_required" ReasonAborted = "aborted" ReasonError = "error" )
Reasons a run ended, on its end entry. The first five are computable from the run's segment and the path it ends by ComputeReason; ReasonError and ReasonInterrupted are the two a writer adds where the segment cannot show them, and a written one stands over any segment.
const ( VerdictProceed = "proceed" VerdictReject = "reject" VerdictHold = "hold" // VerdictAnswer ends a call that may already have run, one in // flight when the record stopped or one the file cannot say about, // with an output the harness wrote rather than one its tool // returned: the call is not handed to its tool again. By says who // answered and Reason why. VerdictAnswer = "answer" )
Verdicts a DecisionEntry may carry, each defined by what follows the decision on the path: a dispatch, an output carrying the reason, nothing, or an output the harness wrote for a call that may already have run.
const ( ByHuman = "human" ByPolicy = "policy" ByAgent = "agent" )
Deciders a DecisionEntry may name.
const ( WorkspaceLocal = "local" WorkspaceContainer = "container" WorkspaceRemote = "remote" )
Workspace kinds an EnvEntry may name.
const Format = "agentsession/0.9"
Format is the session format version this package writes.
const FormatMajor = 0
FormatMajor is the major version of the format this package reads. Any minor version of it is accepted; files are migrated in memory.
const FormatMinor = 9
FormatMinor is the minor version this package writes. A file of an earlier minor is migrated in memory on read: from 0.5 an entry's id is its envelope hash, so every earlier entry is rehashed and keeps its old id in legacy_id. A 0.5, 0.6, 0.7 or 0.8 file reads as it stands, since 0.6 to 0.9 only add optional members and elements, but for two rules 0.8 reads differently: instructions_omitted stays in force until a later config changes it, and a run whose pending call is held after its dispatch ends input_required, not aborted. 0.9 lets instructions_omitted name a run of the list in force by keep, which no 0.8 omitted part could be.
const HashPrefix = "sha256:"
HashPrefix precedes the hexadecimal digest in a request hash.
const LeafLabel = "leaf"
LeafLabel is the reserved label that makes the leaf durable. A label entry carrying it names the branch the next append should continue, not a fixed entry to pin the leaf at: Session.Append keeps the leaf at the label's target rather than moving it to the label entry, and Read resolves the leaf to the newest entry appended under that target after the label, so a branch marked and then written on resumes where it was written to rather than rewinding to the mark. A mark nothing followed resolves to itself. A null label on the same target clears it. The format holds this as a library convention; see the RFC's open questions.
const PartSeparator = "\n\n"
PartSeparator joins the instruction parts into the instructions string. The rule is the format's, so a writer that records parts and a reader that rebuilds the string produce the same bytes and the request hash verifies.
const Payload = "openresponses/" + openresponses.SpecVersion
Payload is the payload profile this package writes: Open Responses items at the specification version openresponses targets.
Variables ¶
var AllRecords = []string{TypeRun, TypeDispatch, TypeDecision}
AllRecords are the three record entry types a harness that runs the loop itself can promise: run, dispatch and decision. A harness that also accepts inputs while a run is in flight adds TypeQueued, which promises that every such input is recorded as a queued entry before it is acted on, so a reader may take the absence of one as nothing having been queued.
var ErrAnswerNotDispatched = errors.New("agentsession: answer for a call the record shows never started")
ErrAnswerNotDispatched is returned when an answer decision is appended for a call with no dispatch in a session whose header promises dispatch records: the record says that call never started, so it did not run, and a writer that ends it without running it writes a reject.
var ErrBadConvergence = errors.New("agentsession: bad convergence reference")
ErrBadConvergence is returned for an entry whose Parents break the format's rules: a reference naming no entry, the same entry named twice, a reference to the entry's own parent, or a reference into this session naming an entry that does not already exist.
var ErrBadID = errors.New("agentsession: entry id does not match its hash")
ErrBadID is returned by Append for an entry whose ID was set by the caller and does not match the hash the format defines, and by Read for a line whose id does not verify.
var ErrBadNormalisation = errors.New("agentsession: normalised entry is malformed")
ErrBadNormalisation is returned for a Normalisation a caller set that the format cannot carry: no pointer, or not exactly one of Was and Raw.
var ErrCallAnswered = errors.New("agentsession: call was answered")
ErrCallAnswered is returned when a dispatch or a decision is appended for a call that an answer decision on the path already ended: what follows an answer is the call's output and nothing else.
var ErrCallCompleted = errors.New("agentsession: call has its output")
ErrCallCompleted is returned when an answer is appended for a call whose output is already on the path: an answer is for a call that may have run and has none.
var ErrCallRejected = errors.New("agentsession: call was rejected")
ErrCallRejected is returned when a dispatch is appended for a call that a decision on the path already rejected.
var ErrHashMismatch = errors.New("agentsession: request hash mismatch")
ErrHashMismatch is returned by Session.Verify when the rebuilt request does not hash to the recorded value.
var ErrNoEntry = errors.New("agentsession: no such entry")
ErrNoEntry is returned when an entry ID is not in the session.
var ErrNoHash = errors.New("agentsession: no request hash recorded")
ErrNoHash is returned by Session.Verify for a response entry that recorded no request hash. The response is not verified and not mismatched: there was nothing to check. A writer records no hash when it cannot stand behind one, which is what a layer that edits the request outside the transcript leaves behind, so a caller that accepts unverified requests opts in with errors.Is rather than by reading err == nil.
var ErrNoSession = errors.New("agentsession: no such session")
ErrNoSession is returned when a session ID is not in the store.
var ErrNotGit = errors.New("agentsession: not a git checkout")
ErrNotGit is returned by GitRevision when dir is not inside a git checkout.
var ErrReadOnly = errors.New("agentsession: store is read-only")
ErrReadOnly is returned by a store opened read-only when a caller tries to write: such a store takes no hold on the sessions it opens, so it must not append to them either. Reading a session while a harness writes it is what it is for.
var ErrReasonMismatch = errors.New("agentsession: run end disagrees with its segment")
ErrReasonMismatch is returned by Run.Verify when a run's written end reason or pending list disagrees with its segment.
var ErrRecordMissing = errors.New("agentsession: promised record entry missing")
ErrRecordMissing is returned by Session.VerifyRecords when the header promises a record entry type and the path lacks one where the event plainly happened.
var ErrReservedMember = errors.New("agentsession: body carries a reserved envelope name")
ErrReservedMember is returned for a body carrying a top-level member by one of the envelope's names.
var ErrSessionExists = errors.New("agentsession: session already exists")
ErrSessionExists is returned when Create is given an ID already in the store.
var ErrSessionLocked = errors.New("agentsession: session is open in another process")
ErrSessionLocked is returned by a store whose session is held by another process. It is the one sentinel for that condition: each store wraps it, so a host written against the Store interface can tell "another process has this session", which means leave the conversation alone, from a store that is broken, which means the daemon is unhealthy, without knowing which store it was given.
var ErrSuperseded = errors.New("agentsession: session was continued in a successor")
ErrSuperseded is returned by a store that refuses to append to a session a continued_in link has retired. No store in this module refuses; the error is here for one that wants to.
var ErrUnresolvedMigration = errors.New("agentsession: migrated session holds entries that could not be rewritten")
ErrUnresolvedMigration is returned by Write for a session migrated from an earlier minor version that holds an entry the migration could not rewrite.
var ErrUnsupportedFormat = errors.New("agentsession: unsupported format")
ErrUnsupportedFormat is returned when a header names a format this package cannot read.
Functions ¶
func CanonicalTime ¶ added in v0.0.8
CanonicalTime renders t in the one form the format admits for ts: UTC, uppercase T and Z, a fractional part only when non-zero, with no trailing zeros and at most nine digits. It is what Go's RFC3339Nano layout produces for a UTC time, so a time.Time round trips unchanged, which is the property the rule was chosen for.
func ComputeReason ¶ added in v0.0.5
ComputeReason recomputes a run's end reason from its segment and the root-first path the segment ends, as the format defines it: the first of error, input_required, aborted, done and stopped whose shape they match, with aborted again as the value for anything left. A nil path means the segment stands for the path. It never returns ReasonInterrupted, and returns ReasonError only for a response that carries an error; both are values a writer adds where the segment cannot show them.
The stopped step reads the path because a run that answers a call and ends without calling the model again, which is what a resume whose tool asks to terminate and a refusal both are, holds no response of its own: the response that made the calls is on the path before the segment.
func EntryHashes ¶ added in v0.0.8
EntryHashes computes an entry's two hashes from its encoded form, a JSON object with the envelope members in it: the content hash over the body, the members outside the envelope, and the id over the envelope object — type, parent, parents when present and non-empty, ts, and content holding the content hash. The id member in data, if any, is ignored; ts is taken as the string it is. Both are "sha256:" plus lowercase hex over the canonical bytes.
func GitRevision ¶
GitRevision returns the commit HEAD points at for the checkout containing dir, reading .git directly so no git binary is needed. It follows symbolic refs through loose refs and packed-refs and handles worktrees and submodules whose .git is a file.
func HashFile ¶
HashFile returns the content hash of a file in the form the env entry uses: "sha256:" followed by lowercase hex.
func HashFiles ¶
HashFiles hashes each path, keyed as given. Paths are hashed as they are; callers that want keys relative to a directory pass them that way and resolve against dir.
func HashRequestJSON ¶
HashRequestJSON computes the request hash of an already-encoded request. Member order and whitespace in data do not matter.
func HashText ¶ added in v0.0.6
HashText returns the hash of a string in the format's notation, which is HashBytes over its UTF-8 bytes: HashPrefix and the lowercase hexadecimal SHA-256. It is how a config delta names the text of an instructions part it does not repeat, so a reader can tell the part it already has from one that changed.
func InContext ¶
InContext reports whether an entry contributes an item to the model context: item, branch_summary and compaction entries do. Whether a compaction is actually selected depends on the path; see Session.ContextAt.
func IsExtension ¶
IsExtension reports whether typ is a namespaced extension type.
func JoinInstructions ¶ added in v0.0.6
func JoinInstructions(parts []InstructionPart) string
JoinInstructions returns the instructions the parts compose: their texts joined with PartSeparator, in order.
func MarshalEntry ¶
MarshalEntry encodes one entry as a single JSON line without the trailing newline. The envelope members come first in a fixed order, then the type's members, then any unknown members in key order.
func NewEntryID ¶
func NewEntryID() string
NewEntryID returns a short random entry ID: eight hexadecimal characters. Session.Append regenerates on the rare collision within a file, so callers need not check.
func NewSessionID ¶
func NewSessionID() string
NewSessionID returns a UUIDv7 in its canonical text form. The leading 48 bits are the current Unix time in milliseconds, so IDs sort by creation time; the rest is random.
func ParseCanonicalTime ¶ added in v0.0.8
ParseCanonicalTime parses s and reports whether it is spelled in the canonical form: a spelling the format admits renders back to itself.
func ParseFormat ¶
ParseFormat splits a format string such as "agentsession/0.1" into its major and minor versions.
func RecordResponse ¶
func RecordResponse(ctx context.Context, store Store, sessionID string, req openresponses.Request, resp *openresponses.Response, latency time.Duration) (string, error)
RecordResponse writes one model call to a session: every output item of resp as an item entry carrying resp.ID, then the response entry with the response's model, status, usage, incomplete details, error, the hash of req and the latency. req must be the request as it was sent, so the recorded hash is the one a reader rebuilds from the path; pass a zero latency to leave latency_ms out. It returns the ID of the response entry.
The caller records the user items and function call outputs that precede the request before calling this, as the format requires.
func RequestHash ¶
func RequestHash(req openresponses.Request) (string, error)
RequestHash computes the request hash the format defines: the request serialised with the JSON Canonicalization Scheme (RFC 8785) and digested with SHA-256, rendered as "sha256:" plus lowercase hex. The input must be the request as it was sent, passthrough members included, because those reached the model.
func SameWorkspace ¶ added in v0.0.10
SameWorkspace reports whether two workspace members are the same under the format's substitution rule: compared member by member in their canonical form, the ones the format does not define included, and an absent one equal only to another absent one. An empty kind or ref is the same as an absent one, as the format says. An env entry whose workspace is not the same as the one in force before it on the path is a substitution.
func SubsessionID ¶ added in v0.0.5
SubsessionID derives the session ID the format recommends for a subsession: a UUIDv5 under the nil namespace over "<parent session id>/<call_id>", so a reader can compute the child's ID from the parent's link or function call alone, before or after the child exists. A second child for the same call appends a new root to the existing child session rather than minting a second ID.
func ToolName ¶
func ToolName(t openresponses.Tool) string
ToolName returns the name of a tool: the function name for a function tool, the "name" member of an extension tool when it has one, else "".
func ValidHash ¶ added in v0.0.8
ValidHash reports whether s is a hash in the format's notation: "sha256:" followed by exactly 64 lowercase hexadecimal characters. Anything a store puts on a filesystem as a hash is checked with it.
func Write ¶
Write encodes the session as JSONL: the header, then every entry in file order, one per line, each in its canonical form. Preservation is of members and not bytes, and the hashes are over canonical forms, so a canonical line is what a reader verifies most directly. A session migrated from an earlier minor version that holds an entry the migration could not rewrite is refused, since the format forbids re-emitting such a file as 0.5.
Types ¶
type BranchSummaryEntry ¶
type BranchSummaryEntry struct {
EntryBase `json:"-"`
From string `json:"from"`
Summary openresponses.Item `json:"summary"`
Usage *openresponses.Usage `json:"usage,omitempty"`
}
BranchSummaryEntry carries context across a branch switch. Its parent is where the new branch continues; From is the leaf that was left. It is in context through its summary.
func (*BranchSummaryEntry) EntryType ¶
func (*BranchSummaryEntry) EntryType() string
EntryType returns "branch_summary".
func (*BranchSummaryEntry) MarshalJSON ¶
func (e *BranchSummaryEntry) MarshalJSON() ([]byte, error)
MarshalJSON emits the entry as one JSON object.
func (*BranchSummaryEntry) UnmarshalJSON ¶
func (e *BranchSummaryEntry) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes the entry.
type Call ¶ added in v0.0.5
type Call struct {
// Entry is the item entry holding the function call.
Entry *ItemEntry
// Call is the function call item.
Call *openresponses.FunctionCall
// Decisions are the decisions on the call, in path order.
Decisions []*DecisionEntry
// Dispatch is the first dispatch entry, or nil when none is on the
// path.
Dispatch *DispatchEntry
// Dispatches are the dispatch entries, in path order: one for each
// time the call was handed to its tool, so a call run again after a
// restart has two. Dispatch is the first of them.
Dispatches []*DispatchEntry
// Output is the item entry holding the function call output, or nil
// when the call is pending.
Output *ItemEntry
// contains filtered or unexported fields
}
Call is one function call on a path and everything that happened to it there: the decisions made about it, its dispatches and its output.
func Calls ¶ added in v0.0.5
Calls collects the function calls on a root-first path, in the order their items appear, with the decisions, dispatch and output the path holds for each. A decision, dispatch or output whose call is not on the path is ignored.
func (*Call) Answered ¶ added in v0.0.11
Answered reports whether a decision ended the call with an output the harness wrote rather than one its tool returned: a call that may have run, answered without being handed to its tool again.
func (*Call) Args ¶ added in v0.0.5
Args returns the arguments the tool runs with: those of the last decision that rewrote them, else the call's own.
func (*Call) DispatchedArgs ¶ added in v0.0.11
DispatchedArgs returns the arguments the call's last dispatch handed its tool: those of the last decision before it that rewrote them, else the call's own. It is "" when the call has no dispatch. A replay rule deciding whether that hand-off may run again reads these beside Call.IdempotencyKey; Call.Args are the arguments a new hand-off would run with, which a later decision may have changed.
func (*Call) From ¶ added in v0.0.8
From returns the predecessors the call's output converges: for a call a subagent answered, the leaf of the child session the answer was taken from, which the format says the output entry SHOULD name and which is the first moment the parent knows it. It is nil for a pending call, and for an output whose writer recorded no provenance.
A LinkEntry names the child session and is written when the call is dispatched, before the child has a point to name; this is the other half of that round trip. Without it, a child that branched leaves no record of which of its leaves answered, and a projection has to choose.
func (*Call) Held ¶ added in v0.0.5
Held reports whether the call is waiting on an answer: its latest decision is a hold and no dispatch follows it. A hold after a dispatch is a call that may have run and waits on someone to say whether it runs again; it is held, and its Dispatches say it may have run.
func (*Call) IdempotencyKey ¶ added in v0.0.11
IdempotencyKey returns the key the call's last dispatch handed its tool, or "" when it has no dispatch or the dispatch no key. It is the key a further run of that hand-off repeats, and it pairs with Call.DispatchedArgs: a harness that ran the call again under a new key, which it does for new arguments, made a new operation, and the key of an earlier dispatch describes the earlier arguments.
func (*Call) InFlight ¶ added in v0.0.11
InFlight reports whether the call was handed to its tool when the record stopped: it has a dispatch and no output, and no decision after its last dispatch holds it or answers it. Its side effect may have happened.
func (*Call) Rejected ¶ added in v0.0.5
Rejected reports whether a decision ended the call without running it.
type CallState ¶ added in v0.0.5
type CallState int
CallState is what the path says happened to a call.
const ( // CallCompleted: the call has an output. CallCompleted CallState = iota // CallHeld: the call waits on an answer to a hold. CallHeld // CallInFlight: the call was dispatched and no output arrived, so // its side effect may have happened. CallInFlight // CallNeverStarted: no dispatch and no output, in a file whose // header promises dispatches are recorded. CallNeverStarted // CallUnknown: no dispatch and no output, in a file that makes no // such promise, so the file does not say whether the tool ran. CallUnknown // CallAnswered: an answer decision ended the call and its output is // not on the path yet, since the record stopped between the two. // The harness that continues the path writes the output and // nothing else for the call; no dispatch may follow. CallAnswered )
type CompactionEntry ¶
type CompactionEntry struct {
EntryBase `json:"-"`
// FirstKept names the earliest entry on the path that stays in
// context.
FirstKept string `json:"first_kept"`
// Summary is an item: the server's compaction item verbatim, or a
// message for a local summary.
Summary openresponses.Item `json:"summary"`
// Pinned are items kept verbatim from before FirstKept. They are in
// context immediately after Summary and before the entries from
// FirstKept.
Pinned openresponses.Items `json:"pinned,omitempty"`
// Config is a full settings checkpoint so a reader need not replay
// config entries from before the compaction.
Config Settings `json:"config"`
TokensBefore int `json:"tokens_before,omitempty"`
Usage *openresponses.Usage `json:"usage,omitempty"`
}
CompactionEntry replaces the context before FirstKept with a summary. It is in context through its summary and its pinned items.
func (*CompactionEntry) EntryType ¶
func (*CompactionEntry) EntryType() string
EntryType returns "compaction".
func (*CompactionEntry) MarshalJSON ¶
func (e *CompactionEntry) MarshalJSON() ([]byte, error)
MarshalJSON emits the entry as one JSON object.
func (*CompactionEntry) UnmarshalJSON ¶
func (e *CompactionEntry) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes the entry.
type ConfigEntry ¶
type ConfigEntry struct {
EntryBase `json:"-"`
Model string `json:"model,omitempty"`
Instructions *string `json:"instructions,omitempty"`
Reasoning *openresponses.ReasoningConfig `json:"reasoning,omitempty"`
Text *openresponses.TextConfig `json:"text,omitempty"`
// InstructionsParts names the parts the instructions are composed
// of, in the order they are joined. A delta carries the whole
// ordered list: a part whose text changed carries its text, a part
// whose text is unchanged carries its Hash alone, and a part left
// out of the list is removed. Set it through
// [Settings.InstructionsDelta] rather than by hand, which computes
// exactly that from the parts in force. When Instructions is set
// beside it the two must agree, since Instructions is the parts
// joined with "\n\n".
InstructionsParts []InstructionPart `json:"instructions_parts,omitempty"`
// InstructionsOmitted records the parts the writer considered and
// left out, so a session says what the model was not given as well
// as what it was. Nothing in it reaches the request, but it stays
// in force until a later config entry carries it: nil leaves the
// list in force as it was, and an empty, non-nil list, written as
// [], clears it. A writer sets it only when the list changed, and
// through [Settings.OmittedDelta], which names each run of parts
// that stay omitted by a keep.
InstructionsOmitted []OmittedPart `json:"instructions_omitted,omitzero"`
ToolsAdded openresponses.Tools `json:"tools_added,omitempty"`
ToolsRemoved []string `json:"tools_removed,omitempty"`
// Extra carries request members beyond the named ones, such as
// temperature or provider passthrough keys. A null value removes
// the key from the settings.
Extra map[string]json.RawMessage `json:"extra,omitempty"`
Replace bool `json:"replace,omitempty"`
}
ConfigEntry is a delta to request settings. Fields left at their zero value are unchanged; Replace discards all earlier config on the path before this one applies. The first entry on a root should be a config carrying full settings.
func ConfigFromRequest ¶
func ConfigFromRequest(req openresponses.Request) (*ConfigEntry, error)
ConfigFromRequest builds the full config entry for the settings a request was sent with: its model, instructions, reasoning, text and tools, with every other request member that is not about the one call (temperature, tool_choice, metadata, provider passthrough keys and so on) in Extra under its wire name. Replay of the result over empty settings followed by Settings.Request over the same input hashes to the same value as the request, which is what the first entry on a root should establish. Replace is set so the entry stands alone wherever it is placed.
func ConfigFromRequestParts ¶ added in v0.0.6
func ConfigFromRequestParts(req openresponses.Request, parts ...InstructionPart) (*ConfigEntry, error)
ConfigFromRequestParts is ConfigFromRequest for a caller that composed the instructions from named parts: the entry carries the parts instead of the joined string, so every delta after it names the one part that moved rather than repeating the whole prompt. The parts' texts joined with PartSeparator must equal the request's instructions, since that is what the model received and what the request hash covers.
func (*ConfigEntry) ClearExtra ¶ added in v0.0.3
func (c *ConfigEntry) ClearExtra(key string)
ClearExtra records the removal of a passthrough member: the delta carries a null for key, which deletes it from the settings on replay.
func (*ConfigEntry) MarshalJSON ¶
func (e *ConfigEntry) MarshalJSON() ([]byte, error)
MarshalJSON emits the entry as one JSON object.
func (*ConfigEntry) SetExtra ¶ added in v0.0.3
func (c *ConfigEntry) SetExtra(key string, v any) error
SetExtra records a passthrough request member on the delta: v is marshalled and stored under key, replacing any earlier value.
func (*ConfigEntry) UnmarshalJSON ¶
func (e *ConfigEntry) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes the entry.
type Context ¶
type Context struct {
Settings Settings
Items openresponses.Items
// Entries are the entries selected by the context algorithm, in
// order: after a compaction, the compaction itself, then the kept
// entries from FirstKept up to it, then everything after. Entries
// that contribute no item are included so a renderer can show them.
Entries []Entry
// ItemEntries is aligned with Items: ItemEntries[i] is the entry
// that contributed Items[i], so an index into the request input
// maps back to the entry that produced it. After a compaction the
// compaction entry fills one slot for its summary and one for each
// of its pinned items, so it repeats as many times as it
// contributed: Items[0] is its summary and the next len(Pinned)
// items are its pins.
ItemEntries []Entry
}
Context is what a model call at a point on a path receives: the replayed settings, the item list ready to be the request input and the entries the list was built from, for renderers.
func BuildContext ¶
BuildContext runs the context algorithm over a root-first path. Only the last compaction on the path is applied. FirstKept must name an entry on the path before the compaction.
func (Context) InstructionsOmitted ¶ added in v0.0.6
func (c Context) InstructionsOmitted() []OmittedPart
InstructionsOmitted returns the parts left out of the instructions in force: Settings.InstructionsOmitted. The list stays in force until a later config entry carries the member, so a writer writes it only when it changed, and a compaction checkpoint carries it.
type CustomEntry ¶
type CustomEntry struct {
EntryBase `json:"-"`
NS string `json:"ns"`
Data json.RawMessage `json:"data,omitempty"`
// CallID names the function call the record belongs to, when the
// writer knows it: a record a tool writes while it runs. A record's
// position cannot say which call of a batch in flight it belongs to;
// this can. A reader may use it and must not require it. A call_id
// member that is not a non-empty string, which a file from before
// the member was defined may hold, is kept as written in Unknown and
// CallID is empty.
CallID string `json:"-" member:"call_id"`
}
CustomEntry is application state that is not in context. State that the model should see is an ItemEntry holding a namespaced item.
func (*CustomEntry) MarshalJSON ¶
func (e *CustomEntry) MarshalJSON() ([]byte, error)
MarshalJSON emits the entry as one JSON object.
func (*CustomEntry) UnmarshalJSON ¶
func (e *CustomEntry) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes the entry.
type DecisionEntry ¶ added in v0.0.5
type DecisionEntry struct {
EntryBase `json:"-"`
CallID string `json:"call_id"`
Target string `json:"target"`
Verdict string `json:"verdict"`
By string `json:"by,omitempty"`
Reason string `json:"reason,omitempty"`
Args json.RawMessage `json:"args,omitempty"`
}
DecisionEntry records that a call's fate was decided outside the tool. Target is the item entry holding the function call. Reason is required when Verdict is VerdictReject, since it is what the model saw as the output. Args, when present, are the arguments the tool ran with after the decision rewrote them; the function call item stays as the model produced it. An answer, VerdictAnswer, ran nothing, so Args on one names nothing.
func NewDecision ¶ added in v0.0.5
func NewDecision(callID, target, verdict, by string) *DecisionEntry
NewDecision builds a decision on the call callID held by the item entry target. by may be "".
func (*DecisionEntry) EntryType ¶ added in v0.0.5
func (*DecisionEntry) EntryType() string
EntryType returns "decision".
func (*DecisionEntry) MarshalJSON ¶ added in v0.0.5
func (e *DecisionEntry) MarshalJSON() ([]byte, error)
MarshalJSON emits the entry as one JSON object.
func (*DecisionEntry) UnmarshalJSON ¶ added in v0.0.5
func (e *DecisionEntry) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes the entry.
func (*DecisionEntry) WithArgs ¶ added in v0.0.5
func (e *DecisionEntry) WithArgs(args json.RawMessage) *DecisionEntry
WithArgs records the arguments the tool ran with and returns the entry, for chaining. args must be a JSON value.
func (*DecisionEntry) WithReason ¶ added in v0.0.5
func (e *DecisionEntry) WithReason(reason string) *DecisionEntry
WithReason sets the decider's text and returns the entry, for chaining.
type DispatchEntry ¶ added in v0.0.5
type DispatchEntry struct {
EntryBase `json:"-"`
CallID string `json:"call_id"`
Target string `json:"target"`
// IdempotencyKey is the key the harness gave the tool for this
// hand-off. A dispatch that repeats an earlier hand-off carries that
// hand-off's key, and a new key is a new operation; the last
// dispatch's key is what [Call.IdempotencyKey] returns. An
// idempotency_key member
// that is not a non-empty string, which a file from before the
// member was defined may hold, is kept as written in Unknown and
// IdempotencyKey is empty.
IdempotencyKey string `json:"-" member:"idempotency_key"`
}
DispatchEntry records that a call was handed to its tool. Target is the item entry holding the function call. A writer that names "dispatch" in the header's records writes it, durably, before the tool runs. Each dispatch for a call is one hand-off, so a call run again after a restart has a second.
func NewDispatch ¶ added in v0.0.5
func NewDispatch(callID, target string) *DispatchEntry
NewDispatch builds a dispatch for the call callID held by the item entry target.
func (*DispatchEntry) EntryType ¶ added in v0.0.5
func (*DispatchEntry) EntryType() string
EntryType returns "dispatch".
func (*DispatchEntry) MarshalJSON ¶ added in v0.0.5
func (e *DispatchEntry) MarshalJSON() ([]byte, error)
MarshalJSON emits the entry as one JSON object.
func (*DispatchEntry) UnmarshalJSON ¶ added in v0.0.5
func (e *DispatchEntry) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes the entry.
func (*DispatchEntry) WithIdempotencyKey ¶ added in v0.0.11
func (e *DispatchEntry) WithIdempotencyKey(key string) *DispatchEntry
WithIdempotencyKey records the key the tool is handed and returns the entry, for chaining.
type Entry ¶
Entry is one line of a session after the header. Concrete types are ItemEntry, ResponseEntry, ConfigEntry, CompactionEntry, BranchSummaryEntry, RunEntry, DispatchEntry, DecisionEntry, QueuedEntry, LabelEntry, InfoEntry, EnvEntry, OutcomeEntry, LinkEntry, CustomEntry and UnknownEntry. Decoded values are always pointers, so switch on *ItemEntry and so on. An entry must not be modified once it has been appended to a session; corrections are new entries.
func UnmarshalEntry ¶
UnmarshalEntry decodes one line, dispatching on its type. Types this package does not define decode to *UnknownEntry.
type EntryBase ¶
type EntryBase struct {
// ID is unique within the session. Session.Append assigns one when
// it is empty.
ID string
// Parent is the ID of the parent entry, or "" for a root. It is the
// entry's line of descent: exactly one, in this session, and the
// only edge a context is built from.
Parent string
// Parents records further predecessors this entry converges: the
// result of a subagent session, a branch merged back, several
// workers joined at once. It is provenance. Nothing it names
// contributes to any context by virtue of being named — whatever
// crossed the boundary is in this entry's own payload, materialised
// — so [Session.Path] and every context the library builds follow
// Parent alone. Session.Append sorts it.
Parents []EntryRef
// Timestamp is when the entry was written. The format admits one
// spelling, UTC with at most nine fractional digits, so Append
// converts a caller's time to UTC.
Timestamp time.Time
// LegacyID is the id an entry had before its file was migrated to
// 0.5, when its id became the envelope hash. It is a body member,
// so it is hashed; a projection may emit it beside the new id so
// output made from the earlier file still resolves.
LegacyID string
// Normalised records what a writer changed in the body before
// writing so that it passed the I-JSON test: a lone surrogate to
// U+FFFD, an integer outside binary64 to a string or to its rounded
// value. It is sorted by At as UTF-16 code units.
Normalised []Normalisation
// Unknown holds body members this package does not define,
// preserved so a later minor version's optional fields survive a
// rewrite. It is nil when there are none.
Unknown map[string]json.RawMessage
// contains filtered or unexported fields
}
EntryBase is the envelope every entry carries.
func (*EntryBase) ContentHash ¶ added in v0.0.8
ContentHash returns the hash of the entry's body, the members outside the envelope, as computed when the entry was appended or read. It is empty for an entry that has been neither.
type EntryRef ¶ added in v0.0.8
type EntryRef struct {
// Session names the session Entry is in. It is empty when the entry
// is in this session, which is the only case a reader can resolve
// without a store.
Session string `json:"session,omitempty"`
// Entry is the ID of the entry referred to.
Entry string `json:"entry"`
}
EntryRef names one entry, here or in another session. It is what EntryBase.Parents holds.
type EnvEntry ¶
type EnvEntry struct {
EntryBase `json:"-"`
CWD string `json:"cwd,omitempty"`
VCS *VCS `json:"vcs,omitempty"`
Files *FileHashes `json:"files,omitempty"`
Tools map[string]string `json:"tools,omitempty"`
// Workspace says which file system CWD is a path in. A local run
// may leave it nil.
Workspace *Workspace `json:"workspace,omitempty"`
}
EnvEntry is a snapshot of the environment for replay: where the tools ran and what they saw. It applies from its position on the path until the next one, and its CWD takes precedence over the header's.
func NewEnvEntry ¶
NewEnvEntry starts an env entry for a working directory. Use EnvEntry.SetGit, EnvEntry.AddRead and EnvEntry.AddWritten to fill it in.
func (*EnvEntry) AddWritten ¶
AddWritten records the hash of a file the session wrote.
func (*EnvEntry) MarshalJSON ¶
MarshalJSON emits the entry as one JSON object.
func (*EnvEntry) SetGit ¶
SetGit records the git revision of dir, read from its .git directory without running git, and whether the tree is known to be dirty. A directory that is not a git checkout leaves VCS unset and returns nil; other errors are returned.
func (*EnvEntry) SetWorkspace ¶ added in v0.0.5
SetWorkspace records which file system CWD is a path in: kind is WorkspaceLocal, WorkspaceContainer or WorkspaceRemote, and ref is what the harness resolves to it (an image digest, a host, an instance ID). A container on a remote host is a container, with the host a member of the workspace: set it, and anything else that tells one file system from another, with Workspace.SetMember on the result, and not as an unknown member of the entry, which the substitution rule does not compare.
func (*EnvEntry) UnmarshalJSON ¶
UnmarshalJSON decodes the entry.
type FileHashes ¶
type FileHashes struct {
Read map[string]string `json:"read,omitempty"`
Written map[string]string `json:"written,omitempty"`
}
FileHashes maps paths to content hashes ("sha256:...") for files read and written.
type Header ¶
type Header struct {
Format string `json:"format"`
ID string `json:"id"`
CreatedAt time.Time `json:"created_at"`
Payload string `json:"payload"`
Harness *Harness `json:"harness,omitempty"`
// Records lists the record entry types this writer writes whenever
// their event occurs, so a reader may take their absence on a path
// as the event not having happened. Empty means no such promise,
// which is what a converter over a native log without them sets.
Records []string `json:"records,omitempty"`
CWD string `json:"cwd,omitempty"`
ParentSession string `json:"parent_session,omitempty"`
// SpawnedBy is, for a subsession, the call_id of the parent's
// function call that spawned it.
SpawnedBy string `json:"spawned_by,omitempty"`
// Base is the hash of the entry this session continues from, in
// the session ParentSession names, or empty for a session that
// starts fresh. A file with a base opens with the path to it, the
// prefix, before any entry the session appended itself, and every
// own entry hangs from the base. A base is never a leaf label.
Base string `json:"base,omitempty"`
Media string `json:"media,omitempty"`
// Redacted is true when bodies were changed after they were written,
// as export redaction does. The redactor recomputes every hash and
// reference over the redacted bodies, so the file walks and
// verifies against itself and not against the original.
Redacted bool `json:"redacted,omitempty"`
// Extra holds header fields this package does not define.
Extra map[string]json.RawMessage `json:"-"`
}
Header is the first line of a session file. It is not part of the tree. Unknown fields decode into Extra and are written back, as the format requires of any tool that rewrites a file.
func (Header) HasRecord ¶ added in v0.0.5
HasRecord reports whether the header promises that entries of type typ are written whenever their event occurs, so their absence means the event did not happen.
func (Header) MarshalJSON ¶
MarshalJSON emits the header with its type discriminator first and Extra flattened into the object.
func (*Header) UnmarshalJSON ¶
UnmarshalJSON decodes the header, keeping unknown fields in Extra.
type InfoEntry ¶
InfoEntry carries display metadata such as a session name. Further members are kept in Unknown.
func (*InfoEntry) MarshalJSON ¶
MarshalJSON emits the entry as one JSON object.
func (*InfoEntry) UnmarshalJSON ¶
UnmarshalJSON decodes the entry.
type InstructionPart ¶ added in v0.0.6
type InstructionPart struct {
// ID is the part's stable name, chosen by the harness: the same
// string across the session, so a delta can name a part it does
// not repeat. It is empty on a keep, and on a part in force that
// an element naming nothing left unresolved.
ID string `json:"id,omitempty"`
// Text is the part's text. On a delta it is absent for a part
// whose text is unchanged, which carries Hash instead.
Text string `json:"text,omitempty"`
// Source names the layer that produced the part, in the harness's
// own terms.
Source string `json:"source,omitempty"`
// Hash is the hash of the text a delta does not repeat, in the
// format's notation: [HashPrefix] and the SHA-256 of the text.
// [HashText] computes it. It is set on a delta's unchanged part
// and empty on a part that carries its text.
Hash string `json:"hash,omitempty"`
// Keep, on a delta, stands for the next Keep parts in force,
// unchanged, and is the element's only member: see
// [Settings.InstructionsDelta]. A keep member that is not a
// positive integer, which a file from before the member was defined
// may hold, is kept as written and Keep is zero; on an element that
// names an ID it means nothing.
Keep int `json:"keep,omitempty"`
// contains filtered or unexported fields
}
InstructionPart is one named part of the instructions. A harness that composes the instructions from several layers, a product prompt, an AGENTS.md chain, a skill catalogue, a memory block, gives each layer a part, so a change to one is recorded as a change to one and a reader can say which layer an instruction came from.
func (*InstructionPart) UnmarshalJSON ¶ added in v0.0.10
func (p *InstructionPart) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes the part, taking keep only when it is a positive integer written as digits, so an earlier file's keep in any other form stays a member this package does not define.
func (InstructionPart) Unresolved ¶ added in v0.0.10
func (p InstructionPart) Unresolved() bool
Unresolved reports whether the path could not rebuild the part's text: it was named by a hash or a keep the path could not resolve, or by an element that names nothing.
type ItemEntry ¶
type ItemEntry struct {
EntryBase `json:"-"`
// Item is the Open Responses item, verbatim. Extension items decode
// to *openresponses.UnknownItem and re-encode byte for byte.
Item openresponses.Item `json:"item"`
// ResponseID names the response this item belongs to when the item
// was model output; it matches ResponseEntry.ResponseID.
ResponseID string `json:"response,omitempty"`
// Visible is false for an item that is in context but that a
// renderer should hide. Nil means visible.
Visible *bool `json:"visible,omitempty"`
// Source says how the input arrived, for an item a person or
// another system sent rather than the model or the loop. It is the
// trigger a [QueuedEntry] carried, so two people steering one run
// are told apart.
Source *Trigger `json:"source,omitempty"`
// QueuedFrom names the [QueuedEntry] this item was accepted as,
// for an input that waited before it could be appended.
QueuedFrom string `json:"queued_from,omitempty"`
}
ItemEntry is one conversation item in the payload profile. It is in model context.
func NewItemEntry ¶
func NewItemEntry(item openresponses.Item) *ItemEntry
NewItemEntry builds an item entry.
func OutputEntries ¶ added in v0.0.6
func OutputEntries(path []Entry, resp *ResponseEntry) []*ItemEntry
OutputEntries returns the item entries on path that hold the output of resp, in path order. It is the format's rule for finding a response's own output items, stated in RFC 0001 under "Request context of a response", and it is the one implementation: a reader that needs those items, and one that needs to exclude them, must agree or the same file rebuilds two different requests.
Walking back from the end of path: an entry that is not an item entry is skipped, an item entry whose Response names resp is one of its output items, and the walk stops at the first item entry that names another response or none. A response with no ResponseID has no output items.
path is resp's path with resp itself at the end, or that path without it; either gives the same answer, since an entry that is not an item entry is skipped. It MUST NOT run past resp: a path that continues beyond the response ends in the next call's items, the walk stops on the first of them, and the result is an empty slice rather than an error. Only the matching item entries are returned, never the entries skipped between them: those are on the path for their own reasons and stay there.
The returned entries are the session's own, not copies. A caller that serves their items to something that records must clone them; a caller that only reads the path need not.
func (*ItemEntry) MarshalJSON ¶
MarshalJSON emits the entry as one JSON object.
func (*ItemEntry) UnmarshalJSON ¶
UnmarshalJSON decodes the entry, dispatching the item through the openresponses item registry.
type LabelEntry ¶
type LabelEntry struct {
EntryBase `json:"-"`
Target string `json:"target"`
Label *string `json:"label"`
}
LabelEntry bookmarks another entry. A nil Label clears an earlier label on the same target.
func NewLabelEntry ¶
func NewLabelEntry(target, label string) *LabelEntry
NewLabelEntry builds a label on target; an empty label clears.
func (*LabelEntry) MarshalJSON ¶
func (e *LabelEntry) MarshalJSON() ([]byte, error)
MarshalJSON emits the entry as one JSON object.
func (*LabelEntry) UnmarshalJSON ¶
func (e *LabelEntry) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes the entry.
type LinkEntry ¶
type LinkEntry struct {
EntryBase `json:"-"`
Rel string `json:"rel"`
Session string `json:"session"`
// CallID ties a subsession to the function call that spawned it.
CallID string `json:"call_id,omitempty"`
}
LinkEntry references another session, for subagents and forks.
func NewLinkEntry ¶
NewLinkEntry builds a link to another session.
func NewSubsessionLink ¶
NewSubsessionLink builds the link entry the format prescribes for a subagent session spawned by the function call callID.
func (*LinkEntry) MarshalJSON ¶
MarshalJSON emits the entry as one JSON object.
func (*LinkEntry) UnmarshalJSON ¶
UnmarshalJSON decodes the entry.
type ListFilter ¶
type ListFilter struct {
// CWD matches the header's working directory exactly.
CWD string
// ParentSession matches sessions forked or spawned from the given
// one.
ParentSession string
// After and Before bound created_at, exclusive.
After, Before time.Time
// Limit caps the number of results; zero means no cap.
Limit int
// WithNames asks for Summary.Name. A store that keeps names
// indexed fills it regardless; a file store must scan each
// session's entries to find it, so it does so only when asked.
WithNames bool
// Current excludes sessions a continued_in link has retired, so a
// listing shows the successor and not the session it replaced.
// A file store scans each session's entries to know, as for
// WithNames.
Current bool
}
ListFilter narrows a List. Zero fields do not filter.
func (ListFilter) Keep ¶ added in v0.0.5
func (f ListFilter) Keep(sum Summary) bool
Keep reports whether a summary passes the filter: the header checks of Matches, and Current against Summary.SupersededBy.
func (ListFilter) Matches ¶
func (f ListFilter) Matches(h Header) bool
Matches reports whether a header passes the filter.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore keeps sessions in memory. It is the reference store for tests and for sessions that are never persisted.
func (*MemoryStore) Create ¶
Create implements Store. A header whose Base is set makes a fork of the session holding the base, as Fork does.
func (*MemoryStore) Delete ¶
func (m *MemoryStore) Delete(_ context.Context, id string) error
Delete implements Store.
func (*MemoryStore) List ¶
func (m *MemoryStore) List(_ context.Context, f ListFilter) iter.Seq2[Summary, error]
List implements Store.
type Normalisation ¶ added in v0.0.8
type Normalisation struct {
// At is an RFC 6901 JSON Pointer relative to the body.
At string `json:"at"`
// Was is the member's original JSON source text, escapes included,
// so it is I-JSON whatever it describes. It is empty when the
// output was not valid UTF-8 and had no JSON text to record.
Was string `json:"was,omitempty"`
// Raw carries the original bytes, base64 under RFC 4648 §4 with
// padding, when Was cannot: output that was not valid UTF-8.
Raw string `json:"raw,omitempty"`
}
Normalisation is one change a writer made to a body before writing it, recorded in EntryBase.Normalised.
func NormaliseJSON ¶ added in v0.0.8
func NormaliseJSON(data []byte) ([]byte, []Normalisation, error)
NormaliseJSON rewrites a JSON document so that every value passes the I-JSON test the format hashes under, and reports each change the way the format has a writer record it. A whole number outside binary64 is rounded to binary64 and written canonically, so 9007199254740993 becomes 9007199254740992 however it was spelled and a 64-bit id such as 1234567890123456789 becomes 1234567890123456800; a lone surrogate escape becomes U+FFFD; in a string that is not valid UTF-8 each maximal subpart of an ill-formed sequence (Unicode §3.9) becomes one U+FFFD, as the WHATWG decoder does. Each change carries the RFC 6901 pointer of the value relative to data and the value's original JSON source text, a string's quotes included, or its original bytes in base64 when there was no valid text to record. The changes are sorted by pointer as UTF-16 code units, the order the format fixes so two writers normalising one output agree.
Session.Append runs this over an entry's body, and repairs a Go string that is not valid UTF-8 the same way, so a harness that hands the library a body straight from a provider need do nothing. It is exported for the case Go's decoder hides: a lone surrogate in provider bytes is replaced on the way into a struct, and the record that it happened is lost unless the harness normalises the bytes first and keeps the changes, prefixing each pointer with that of the member the bytes become (for an item, "/item") before setting them on EntryBase.Normalised.
What cannot be normalised is an error: a number that is not finite in binary64, a repeated member name, a member name that itself fails the test.
type OmittedPart ¶ added in v0.0.6
type OmittedPart struct {
// ID names the part. It is empty on a keep, and on an element in
// force that names no part: see [OmittedPart.Unresolved].
ID string `json:"id,omitempty"`
Reason string `json:"reason,omitempty"`
Size int `json:"size,omitempty"`
Source string `json:"source,omitempty"`
// Keep, on a config delta, stands for the next Keep parts of the
// list in force, unchanged, and is the element's only member: see
// [Settings.OmittedDelta]. A keep member that is not a positive
// integer, which a file from before the member was defined may
// hold, is kept as written and Keep is zero; on an element that
// names an ID it means nothing.
Keep int `json:"keep,omitempty"`
}
OmittedPart is a part the writer considered for the instructions and left out: a file the budget did not reach, a memory entry that did not fit, a skill out of scope. Reason is the writer's own word for why, Size the bytes the part would have added.
func (*OmittedPart) UnmarshalJSON ¶ added in v0.0.12
func (p *OmittedPart) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes the part, taking keep only when it is a positive integer written as digits, so an earlier file's keep in any other form stays a member this package does not define.
func (OmittedPart) Unresolved ¶ added in v0.0.12
func (p OmittedPart) Unresolved() bool
Unresolved reports whether the element names no part: a keep the path could not satisfy, left in the list in force as written, or an element with neither an ID nor a keep.
type Outcome ¶ added in v0.0.8
type Outcome int
Outcome is what an append did, which the format has a store report.
const ( // Continued: the entry was added under the leaf and is the leaf. Continued Outcome = iota // Branched: the entry was added elsewhere and the leaf did not move. Branched // Held: the session already held the entry; nothing changed. Held // LeafMoved: a leaf label moved the leaf to its target. LeafMoved // LeafNotMoved: a leaf label named a target the leaf may not rest // on, and was added without moving it. LeafNotMoved )
type OutcomeEntry ¶
type OutcomeEntry struct {
EntryBase `json:"-"`
Kind string `json:"kind"`
Target string `json:"target,omitempty"`
Score *float64 `json:"score,omitempty"`
Pass *bool `json:"pass,omitempty"`
Label string `json:"label,omitempty"`
Details json.RawMessage `json:"details,omitempty"`
}
OutcomeEntry is a judgement of how the session, or a range of it, went. Target names an entry in this session, usually the last entry of the range judged; a task or test name belongs in Details. Score is any finite number on the judge's own scale, named by Label; Pass is the judge's verdict when it has one.
func NewOutcomeEntry ¶
func NewOutcomeEntry(kind, target string) *OutcomeEntry
NewOutcomeEntry builds an outcome entry of the given kind about target, which may be "" for the session as a whole.
func (*OutcomeEntry) EntryType ¶
func (*OutcomeEntry) EntryType() string
EntryType returns "outcome".
func (*OutcomeEntry) MarshalJSON ¶
func (e *OutcomeEntry) MarshalJSON() ([]byte, error)
MarshalJSON emits the entry as one JSON object.
func (*OutcomeEntry) UnmarshalJSON ¶
func (e *OutcomeEntry) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes the entry.
func (*OutcomeEntry) WithDetails ¶
func (e *OutcomeEntry) WithDetails(v any) *OutcomeEntry
WithDetails encodes v as the details and returns the entry, for chaining. An encoding failure leaves Details unset and is returned by the next Session.Append through MarshalEntry only if v cannot be marshalled at all, so callers should pass plain data.
func (*OutcomeEntry) WithLabel ¶
func (e *OutcomeEntry) WithLabel(label string) *OutcomeEntry
WithLabel sets the label and returns the entry, for chaining.
func (*OutcomeEntry) WithPass ¶ added in v0.0.5
func (e *OutcomeEntry) WithPass(pass bool) *OutcomeEntry
WithPass sets the judge's verdict and returns the entry, for chaining.
func (*OutcomeEntry) WithScore ¶
func (e *OutcomeEntry) WithScore(score float64) *OutcomeEntry
WithScore sets the score and returns the entry, for chaining.
type QueuedEntry ¶ added in v0.0.6
type QueuedEntry struct {
EntryBase `json:"-"`
// Item is the input as it will be appended.
Item openresponses.Item `json:"item"`
// Mode is ModeSteer or ModeFollowUp.
Mode string `json:"mode"`
// Trigger is what brought the input in.
Trigger *Trigger `json:"trigger,omitempty"`
// Ref names the queued input in the harness's own terms, for a
// caller that holds a handle to it.
Ref string `json:"ref,omitempty"`
}
QueuedEntry records an input a harness accepted before it could be appended to the conversation: a steer typed while the model is running, or a follow-up that waits for the run to end. It is a record entry, so the context algorithm ignores it and the item it holds reaches no request until it is appended as an ItemEntry.
A queued entry with no item entry naming it and no run end after it on the path is an input the harness still owes the conversation; Session.PendingQueued lists them, which is what a gateway that answered 202 drains on resume. The entry is where the trigger of that input lives, since the run entry names the trigger of the run and an input that joins a run in flight has a different one.
func NewQueued ¶ added in v0.0.6
func NewQueued(item openresponses.Item, mode string) *QueuedEntry
NewQueued builds a queued entry for an input accepted in mode ModeSteer or ModeFollowUp.
func Queued ¶ added in v0.0.6
func Queued(path []Entry) []*QueuedEntry
Queued returns the queued entries on a root-first path that are still waiting: no item entry on the path names them in QueuedFrom, and no run end follows them. They are the inputs a harness accepted and has not yet appended to the conversation, the durable inbox a resume drains. A run end after a queued entry closes it, since the run it was queued into has ended: a harness that still wants the input queues it again.
func (*QueuedEntry) Drain ¶ added in v0.0.6
func (e *QueuedEntry) Drain() *ItemEntry
Drain returns the item entry that appends this queued input to the conversation: the item, the trigger as its source and QueuedFrom naming this entry, so the record says why the item is there. The entry must already have an ID, which it has once it is appended. The trigger is copied whole, the members the format does not define included, so the two entries do not share one once both are appended and neither may be modified.
func (*QueuedEntry) EntryType ¶ added in v0.0.6
func (*QueuedEntry) EntryType() string
EntryType returns "queued".
func (*QueuedEntry) MarshalJSON ¶ added in v0.0.6
func (e *QueuedEntry) MarshalJSON() ([]byte, error)
MarshalJSON emits the entry as one JSON object.
func (*QueuedEntry) UnmarshalJSON ¶ added in v0.0.6
func (e *QueuedEntry) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes the entry, dispatching the item through the openresponses item registry.
func (*QueuedEntry) WithTrigger ¶ added in v0.0.6
func (e *QueuedEntry) WithTrigger(kind, ref, source string) *QueuedEntry
WithTrigger records what brought the input in and returns the entry, for chaining.
type ResponseEntry ¶
type ResponseEntry struct {
EntryBase `json:"-"`
ResponseID string `json:"response_id"`
Model string `json:"model,omitempty"`
Status openresponses.ResponseStatus `json:"status"`
Usage *openresponses.Usage `json:"usage,omitempty"`
Incomplete *openresponses.IncompleteDetails `json:"incomplete,omitempty"`
Error *openresponses.ErrorPayload `json:"error,omitempty"`
RequestHash string `json:"request_hash,omitempty"`
LatencyMS int64 `json:"latency_ms,omitempty"`
// Attempts is the number of calls to the model this response took,
// itself included, when the harness retried calls that failed and
// recorded none of them as a response entry of its own. Zero means
// one. An attempts member that is not a positive integer, which a
// file from before the member was defined may hold, is kept as
// written in Unknown and Attempts is zero.
Attempts int `json:"-" member:"attempts"`
}
ResponseEntry is the envelope of one model call, written after its output items. It is not in context.
func (*ResponseEntry) Calls ¶ added in v0.0.10
func (e *ResponseEntry) Calls() int
Calls returns the number of calls to the model the response took: Attempts, or one when it is not set.
func (*ResponseEntry) EntryType ¶
func (*ResponseEntry) EntryType() string
EntryType returns "response".
func (*ResponseEntry) MarshalJSON ¶
func (e *ResponseEntry) MarshalJSON() ([]byte, error)
MarshalJSON emits the entry as one JSON object.
func (*ResponseEntry) UnmarshalJSON ¶
func (e *ResponseEntry) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes the entry.
type Result ¶ added in v0.0.8
type Result struct {
ID string
Outcome Outcome
// Unresolved lists references in the entry a store could not
// resolve — a sidecar blob it does not hold, a convergence into a
// session it does not have — which the format lets a store report
// rather than refuse. A session in memory sets nothing here.
Unresolved []string
// Reopen is set by a store when the append is durable but the
// caller's session could not be brought in step with it, so the
// caller opens the session again before using it further.
Reopen bool
// Durable is set by a store that says whether it acknowledged the
// append once it was durable, as RFC 0002 asks of a store that
// may acknowledge one before. A session in memory, and a store
// that does not report it, leaves it false.
Durable bool
}
Result is what Commit reports: the entry's ID and what appending it did. A store adds what it alone can know.
type Run ¶ added in v0.0.5
type Run struct {
Start *RunEntry
// End is nil when no end entry for the run is on the segment.
End *RunEntry
Segment []Entry
// Path is the root-first path up to the last entry of the segment,
// of which Segment is the tail. The end reason of a run that
// answers a call an earlier run's model call made is a shape of
// the path, not of the segment alone; see [ComputeReason]. It is
// nil in a Run built by hand, and then the segment stands for the
// path.
Path []Entry
}
Run is one run's segment of a path: the entries from its start entry to its end entry, or to the end of the path when the run was cut off or closed by a branch.
func Runs ¶ added in v0.0.5
Runs partitions a root-first path into its runs. Entries before the first start entry belong to no run and are dropped; a start entry closes any run still open, as a branch does. Only an end entry whose run_id matches the open run closes it.
func (*Run) Pending ¶ added in v0.0.5
Pending returns the IDs of the calls on the segment with no output on it.
func (*Run) Verify ¶ added in v0.0.5
Verify checks the run's end entry against its segment. A written error or interrupted stands over any segment; any other reason must match ComputeReason, and the pending list must match the calls on the segment without an output. A run without an end verifies trivially.
type RunEntry ¶ added in v0.0.5
type RunEntry struct {
EntryBase `json:"-"`
RunID string `json:"run_id"`
Phase string `json:"phase"`
// Source is set on a start entry.
Source string `json:"source,omitempty"`
// Reason is set on an end entry.
Reason string `json:"reason,omitempty"`
// Ref names the trigger of a start or the cause of an end in the
// harness's own terms. Readers treat it as opaque.
Ref string `json:"ref,omitempty"`
// Trigger, on a start entry, says how the input that started the run
// arrived, in parts, beside Ref and changing nothing about it. A
// harness's richer facts about the firing, such as when it was due
// or which attempt it is, go in [Trigger.Unknown]; members of the
// run entry this package does not define, where format 0.6 put
// them, are kept in [EntryBase.Unknown]. A trigger member this type
// cannot hold exactly, one written before the member was defined,
// is kept there as written and Trigger is nil.
Trigger *Trigger `json:"-" member:"trigger"`
// Pending lists, on an end entry, the IDs of the calls left without
// an output. It is written even when empty.
Pending []string `json:"pending"`
}
RunEntry marks the start or the end of one pass of the harness's loop. Two entries per run share a RunID. A start carries Source and an optional Ref naming what triggered the input in the harness's own terms; an end carries Reason, the IDs of the calls left pending and an optional Ref naming the cause.
func NewRunEnd ¶ added in v0.0.5
NewRunEnd builds the entry that closes a run. pending lists the IDs of the calls left without an output; Session.EndRun computes it from the path. ref may be "".
func NewRunStart ¶ added in v0.0.5
NewRunStart builds the entry that opens a run. ref may be "".
func (*RunEntry) MarshalJSON ¶ added in v0.0.5
MarshalJSON emits the entry as one JSON object. Which members are written depends on the phase: a start carries source, an end carries reason and always carries pending, even when empty, since a run that ended owing nothing is a fact a reader relies on.
The other phase's members are written when they are set rather than dropped. Nothing this library builds sets them, and validateLifecycle asks only for the phase's own, so a well-formed entry is written exactly as it was before. A file from elsewhere that carries one is the case that matters: the envelope rule is that a reader preserves what it does not itself need, and rewriting such a file to drop a member it declared breaks that promise silently. Rejecting the shape on the way in would be the alternative, and a larger decision than an encoder.
func (*RunEntry) UnmarshalJSON ¶ added in v0.0.5
UnmarshalJSON decodes the entry.
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session is one session in memory: the header, the entries in file order, the tree they form and the current leaf. It is safe for concurrent use. Entries are shared, not copied, and must not be modified after they are appended.
func Continue ¶ added in v0.0.5
func Continue(ctx context.Context, store Store, id string, summary openresponses.Item) (*Session, error)
Continue rolls a session over into a successor, for a conversation that has outgrown its file: a months-long unattended session, or one whose compaction has folded the context many times over while the file kept growing. It does the four steps in the order the format wants them: the successor is created with parent_session naming id and the old header's harness, working directory, records and media mode; its first entry is a full config carrying the settings in force at the old leaf, so the first request on the new root rebuilds and a recorder finds a config on the path; summary, when not nil, is appended as the item that carries the conversation across; the old session's display name is carried over; and the old session gets a continued_in link naming the successor, which is what a store reads to mark it superseded. It returns the successor.
func Fork ¶ added in v0.0.8
Fork creates a session that continues from entry at of origin: a session with a base, in the format's terms. The new session's header is h with Base set to at and, when h names none, ParentSession set to origin's ID; its entries open with origin's path to at, the prefix, which the new session shares with origin rather than copies, and its leaf is the base. Every entry it appends hangs from the base or from an entry it appended itself, and its file opens with the prefix so it stands alone. The base may not be a leaf label, since it is the fork's first leaf and the leaf never rests on one, and h's media, when set, must be origin's, since the prefix was written in that form. Fork refuses a prefix that could not stand alone: one holding an entry that converges an entry of origin off the path (ErrBadConvergence), or an entry of a migrated origin that the migration could not rewrite (ErrUnresolvedMigration).
A store's Create does the same for a header whose Base is set, so Fork is the in-memory form and a store's Create the persistent one.
func New ¶
New creates an empty session. Header fields left empty are filled: a UUIDv7 ID, the current time, this package's format and payload. Times the session assigns are in UTC so a file carries one offset however the writer's clock is configured; times the caller supplies are kept as given.
func Read ¶
Read decodes a session from its JSONL form. A final line that does not parse is tolerated and reported through Session.Truncated; any other malformed line, a missing parent, a convergence reference that names no entry yet in this file, or a repeated ID is an error. The leaf is the last entry in the file unless a LeafLabel is in force, in which case it is the entry that label names.
func (*Session) Append ¶
Append adds e to the tree. Its ID is the hash of its envelope over the hash of its body, as the format defines, and is computed here; an ID the caller set must match or the append is refused. An empty Parent is set to the current leaf, so an explicit Parent branches in place; a zero Timestamp is set to now, and any timestamp is taken to UTC, the one spelling the format admits. In a session with a base, the parent must be the base or an own entry.
An append under the leaf makes the entry the leaf; an append elsewhere is a branch and the leaf does not move. A leaf label makes its target the leaf, wherever the label's own parent sits, when the target is the base or an own entry and not itself a label; otherwise nothing moves. The leaf never rests on a leaf label. An entry the session already holds — same type, body, parent, parents and timestamp — is a no-op that returns the existing ID.
Parents, when the caller set any, is sorted into the order the format requires and checked against the convergence rules; it does not move the leaf and does not reach any context. On success e is owned by the session and must not be modified.
func (*Session) Branch ¶
Branch moves the leaf to id, so the next append becomes a child of that entry. When id is inside a run, the run is open on the new path; see Session.EndRun for who closes it.
func (*Session) Children ¶
Children returns the IDs of the entries whose parent is id, in file order. Pass "" for the roots.
func (*Session) Compact ¶
func (s *Session) Compact(firstKept string, summary openresponses.Item) (*CompactionEntry, error)
Compact builds the compaction entry for the current leaf: FirstKept names the earliest entry on the path that stays in context, summary is the item that replaces everything before it, and the settings checkpoint is taken from the context at the leaf. The entry is not appended; set TokensBefore, Usage or Pinned if they apply, then append it through the store. A caller that knows an item index rather than an entry ID uses Session.CompactFrom or Session.CompactKeeping.
func (*Session) CompactFrom ¶ added in v0.0.4
func (s *Session) CompactFrom(first int, summary openresponses.Item) (*CompactionEntry, error)
CompactFrom is Session.Compact for a caller that split the request input at an index: FirstKept is the entry that contributed item first of the context at the leaf, and the items before it are what summary replaces. The index counts the items the context algorithm produces, Context.Items, so entries that contribute no item do not shift it. The item at first cannot be an earlier compaction's summary or one of its pinned items: the format keeps entries, and what a compaction contributes is kept only while it is the last compaction on the path.
func (*Session) CompactKeeping ¶ added in v0.0.4
func (s *Session) CompactKeeping(kept int, summary openresponses.Item) (*CompactionEntry, error)
CompactKeeping is Session.Compact for a caller that knows how many items of the request input it kept: the last kept items of the context at the leaf stay, and summary replaces the rest. kept must be at least 1, because FirstKept names an entry, and at most the number of items in the context.
func (*Session) ContextAt ¶
ContextAt builds the context at entry id, the request a model call appended after that entry would receive. An empty id yields an empty context.
func (*Session) ContextHash ¶ added in v0.0.8
ContextHash computes the context hash at entry id, as RFC 0002 defines it: the key a store can compute as it appends and a router can key a provider's cached prefix on. It is defined over the path ending at the entry by the entry's type and not by any later leaf. An item, config, compaction or branch_summary contributes; a response, a record entry and an extension entry do not. The value before any contributing entry is the hash of the canonical null; a non-contributing entry takes its parent's; a contributing entry hashes the three-element array of its parent's context hash, its type and its content hash as the section defines, with the per-run provenance members removed from the body first and a compaction's first_kept replaced by the context hash and type of the entry it names.
It excludes ts and parents, it is incremental, and two sessions whose contributing entries are byte-identical share it. It is not request_hash: on a path with no compaction the two identify the same request, and after a fold they part.
func (*Session) EndRun ¶ added in v0.0.5
EndRun builds the end entry for the run open at the current leaf: its pending list is the calls on the segment with no output. reason is one of the Reason constants and ref may be "". The entry is not appended.
A writer that continues a path on which a run is open that it is not running owns that run and closes it before appending anything else, as the format has it: after Session.Branch into a run, with ReasonInterrupted and a ref naming the rewind; on resuming a run a crash cut off, with ReasonError and a ref naming the cut. Session.OpenRun at the leaf says whether there is one.
func (*Session) Entries ¶
Entries returns the entries in file order. The slice is a copy; the entries are shared.
func (*Session) Labels ¶
Labels returns the current label of every labelled entry: the last label entry per target wins, and a null label clears.
func (*Session) Leaf ¶
Leaf returns the ID of the entry the next append will name as its parent, or "" when the next append starts a new root.
func (*Session) Leaves ¶
Leaves returns the IDs of the entries that have no children, in file order.
func (*Session) MarkLeaf ¶ added in v0.0.5
func (s *Session) MarkLeaf() (*LabelEntry, error)
MarkLeaf builds the label entry that makes the current leaf durable, so a reopened session resumes on this branch rather than on whichever one the last line happens to be under. Append it through the store after Branch; the leaf stays where it is, and appending past the mark moves the resumed leaf with it. It returns an error when there is no leaf.
A mark records which branch is live, so a host that moves to another branch marks again; nothing under an abandoned mark follows it, and the abandoned mark is what a reopen honours.
func (*Session) Migrated ¶ added in v0.0.8
Migrated reports whether the session was read from a file of an earlier minor version and rewritten in memory, so its entries carry LegacyID, and returns the IDs of entries the migration could not rewrite: extension entries, whose members may name entries the reader cannot recognise. A migrated session with unresolved entries cannot be written as 0.5.
func (*Session) OpenRun ¶ added in v0.0.5
OpenRun returns the run on the path to leaf that has no end entry, or nil when every run is closed or there is none.
func (*Session) Path ¶
Path returns the entries from the root to id, root first, or nil when id is not in the session.
func (*Session) PendingCalls ¶ added in v0.0.5
PendingCalls returns the calls on the path to leaf that have no output, which is what a resume answers.
func (*Session) PendingQueued ¶ added in v0.0.6
func (s *Session) PendingQueued(leaf string) ([]*QueuedEntry, error)
PendingQueued returns the inputs queued on the path to leaf that have not been appended; see Queued.
func (*Session) Prefix ¶ added in v0.0.8
Prefix reports whether id is on the path to the session's base: an entry another session wrote, carried so the file stands alone.
func (*Session) Prepare ¶ added in v0.0.8
Prepare does everything Append does short of adding the entry: it fills the parent and the timestamp, sorts and checks the references, applies the parent rule, and computes the hashes, setting the ID on the entry. It reports the outcome Commit would have. A store uses it to know the entry's hashes before anything is written, so that nothing is visible in memory before the store's commit point; a Commit of the same entry afterwards recomputes the same values.
func (*Session) Repeated ¶ added in v0.0.8
Repeated returns the IDs Read met a second time in the file, each treated as the same entry as the first, in file order.
func (*Session) RequestContext ¶
RequestContext rebuilds the context of the request that produced the response entry id: the path to the entry with the response's own output items removed, as OutputEntries finds them. Only those item entries are removed; everything else on the path stays, so an entry another layer wrote between two output items of one response, which contributes nothing to context, leaves the rebuilt request and its hash alone.
func (*Session) ResetLeaf ¶
func (s *Session) ResetLeaf()
ResetLeaf clears the leaf, so the next append starts a new root.
func (*Session) Resolve ¶ added in v0.0.9
Resolve returns the ID of the entry id names: id itself when it is an entry's ID, else the ID of the entry a migration rewrote from id, whose LegacyID it is, in the session as read or in a 0.5 file that kept the member. Everything written about a session before its file was migrated names entries by their old IDs; Resolve is how a reader holding one finds the entry, since Entry, Path and the rest take the current ID alone.
func (*Session) SummarizeBranch ¶
func (s *Session) SummarizeBranch(from string, summary openresponses.Item) (*BranchSummaryEntry, error)
SummarizeBranch builds the branch summary that carries context from the abandoned leaf from to the current leaf, where the new branch continues. Move the leaf with Branch first, then append the result through the store; its parent is set on append. When the new leaf is inside a run, that run is open on the new path and the writer closes it first: append Session.EndRun with ReasonInterrupted before the summary, so the path does not read as a run a crash cut off.
func (*Session) SupersededBy ¶ added in v0.0.5
SupersededBy returns the successor named by the last continued_in link in the session, or "".
func (*Session) Truncated ¶
func (s *Session) Truncated() *TruncatedLine
Truncated reports the final line of the file this session was read from when that line did not parse, or nil. A truncated line is what a crash mid-append leaves behind; the entries before it are intact.
func (*Session) Verify ¶
Verify rebuilds the request for the response entry id and checks that it hashes to the entry's RequestHash. A response that recorded no hash returns ErrNoHash: nil means the request was checked.
func (*Session) VerifyRecords ¶ added in v0.0.5
VerifyRecords checks the record entries on the path to leaf against the format's rules: every run end agrees with its segment, no dispatch follows a reject or an answer on the same call, no decision follows an answer and no answer follows an output, and, when the header names dispatch in records, no answer ends a call with no dispatch and every call that ran has a dispatch. It returns the first problem found.
type Settings ¶
type Settings struct {
Model string `json:"model,omitempty"`
Instructions string `json:"instructions,omitempty"`
Reasoning openresponses.ReasoningConfig `json:"reasoning,omitzero"`
Text openresponses.TextConfig `json:"text,omitzero"`
// InstructionsParts are the parts the instructions are composed
// of, in order, when the path named them, each carrying its text.
// Instructions is always their texts joined with [PartSeparator],
// so a reader that does not care about the composition reads the
// string as before. It is empty for a path that set the
// instructions as one string.
InstructionsParts []InstructionPart `json:"instructions_parts,omitempty"`
// InstructionsOmitted are the parts left out of the instructions
// that are in force: the list the last config entry carrying one
// wrote, each keep in it resolved against the list before it,
// cleared by an empty list or a replace without one. A keep the
// path could not satisfy stays in the list as written: see
// [OmittedPart.Unresolved]. It
// reaches no request, so [Settings.Request] leaves it out and the
// request hash does not cover it; a compaction checkpoint carries
// it so the list survives the fold. A checkpoint member that does
// not decode as a list of parts, which a file from before 0.8 may
// hold, is kept as written and InstructionsOmitted is nil.
InstructionsOmitted []OmittedPart `json:"instructions_omitted,omitempty"`
Tools openresponses.Tools `json:"tools,omitempty"`
// Extra carries request members beyond the named ones, keyed by
// their wire name.
Extra map[string]json.RawMessage `json:"extra,omitempty"`
}
Settings are the request settings in force at a point on a path: the result of replaying config entries. The JSON form is the full-config checkpoint a compaction entry carries.
func (Settings) Apply ¶
func (s Settings) Apply(c *ConfigEntry) Settings
Apply returns the settings after the delta c. The receiver is not modified.
func (Settings) ExtraValue ¶ added in v0.0.3
ExtraValue decodes the passthrough member key into v. It reports false, and leaves v alone, when the settings carry no such member.
func (Settings) InstructionsDelta ¶ added in v0.0.6
func (s Settings) InstructionsDelta(parts []InstructionPart) *ConfigEntry
InstructionsDelta returns the config delta that takes the instructions from these settings to parts: the whole ordered list, with the text of every part that is new or whose text or source changed, a keep for every run of unchanged parts in the order they are in force, and the hash alone of an unchanged part out of that order, so a change to one layer costs that layer and not the whole prompt, however many parts it has. A part in force that parts leaves out is removed by its absence. parts names each ID once.
It returns nil when parts are exactly the ones in force, so a harness that re-renders its layers every turn writes nothing when nothing moved.
func (Settings) OmittedDelta ¶ added in v0.0.12
func (s Settings) OmittedDelta(omitted []OmittedPart) []OmittedPart
OmittedDelta returns the instructions_omitted member that takes the omitted parts in force in these settings to omitted, for ConfigEntry.InstructionsOmitted: nil when omitted is the list in force, so a writer that renders its omissions every turn writes nothing when nothing moved; an empty, non-nil list, written as [], when omitted is empty and a list is in force; and otherwise omitted with every run of parts in force, unchanged and in the order they are in force, named by a keep, so a part moving across a budget costs that part and not the whole list. A part that changed, is new or is out of that order is written whole. omitted names each ID once; when it does not, or the list in force names an ID twice, omitted is returned whole.
A delta with Replace set discards the list in force, so its keeps would resolve against nothing: such a delta carries omitted itself.
func (Settings) Request ¶
func (s Settings) Request(items openresponses.Items) (openresponses.Request, error)
Request builds the canonical request for these settings over items: store false and no previous_response_id, as the context algorithm requires. Extra members ride in Request.Extra and are flattened on encode.
func (*Settings) UnmarshalJSON ¶ added in v0.0.11
UnmarshalJSON decodes a checkpoint, taking instructions_omitted only when the member is spelled exactly and holds a list of parts that encodes back to what the line holds, extra members aside, as a member promoted into a typed field is taken. Anything else, such as a part whose id is spelled "ID", is left for the rewrite to keep as written.
type Store ¶
type Store interface {
// Create starts a new session from h, filling empty header fields.
// A header whose Base is set makes a fork, as [Fork] does: the
// session opens with the path to the base, taken from the session
// ParentSession names when that session holds it and otherwise from
// any session the store holds it in. Create refuses a base the store
// does not hold with ErrNoEntry, and a base that is a leaf label or a
// media form other than the origin's, rather than letting the first
// append fail.
Create(ctx context.Context, h Header) (*Session, error)
// Open loads the session with the given ID.
Open(ctx context.Context, id string) (*Session, error)
// Append adds e to the session and returns its ID.
Append(ctx context.Context, sessionID string, e Entry) (string, error)
// List enumerates sessions matching f, newest first.
List(ctx context.Context, f ListFilter) iter.Seq2[Summary, error]
// Delete removes the session.
Delete(ctx context.Context, id string) error
}
Store persists sessions. Append is the only write to a session's content; the entry is added to the in-memory tree of the open session and made durable according to the store's policy. Sessions returned by Create and Open are shared with the store, so leaf moves through Session.Branch and Session.ResetLeaf are seen by later appends.
A store that guards a session against a second writing process reports ErrSessionLocked from the calls that need the hold, and a store opened read-only reports ErrReadOnly from the calls that write.
type Summary ¶
type Summary struct {
Header Header
// Name is the session's display name, from the last info entry
// that set one, when the store provides it: see
// ListFilter.WithNames.
Name string
// SupersededBy names the successor the session was continued in,
// from its last continued_in link, when the store provides it: a
// store that indexes links fills it always, a file store when
// ListFilter.WithNames or Current asks it to scan.
SupersededBy string
// Path is where the session lives, for file-backed stores.
Path string
// Size is the stored size in bytes, when known.
Size int64
// Modified is when the session last changed, when known.
Modified time.Time
}
Summary describes a stored session without loading it.
type Trigger ¶ added in v0.0.6
type Trigger struct {
Kind string `json:"kind,omitempty"`
Ref string `json:"ref,omitempty"`
Source string `json:"source,omitempty"`
// Unknown holds the members of the trigger the format does not
// define, the harness's own facts about the arrival such as when a
// scheduled firing was due and which attempt it is, encoded inline
// beside kind, ref and source. It is nil when there are none.
Unknown map[string]json.RawMessage `json:"-"`
}
Trigger says how an input arrived, in the harness's own terms: Kind is the sort of thing it came from (a person, a channel, a schedule, another agent), Ref names the thing itself (a message ID, a cron name) and Source names the layer that took it. A reader treats all three as opaque; what the format fixes is where they are written, so two people steering one run are two triggers and not one.
func (*Trigger) Clone ¶ added in v0.0.11
Clone returns a copy of the trigger that shares no map with it, or nil for nil.
func (*Trigger) Equal ¶ added in v0.0.11
Equal reports whether two triggers are the same: kind, ref, source and every member the format does not define, compared in canonical form, so the order and spacing a line gave them do not count. Two nil triggers are equal. A Trigger holds a map, so == does not compile on it.
func (Trigger) MarshalJSON ¶ added in v0.0.11
MarshalJSON emits kind, ref, source and the unknown members as one object.
func (*Trigger) SetMember ¶ added in v0.0.11
SetMember sets a member of the trigger the format does not define, such as when a scheduled firing was due or which attempt it is.
func (*Trigger) UnmarshalJSON ¶ added in v0.0.11
UnmarshalJSON takes kind, ref and source, spelled exactly, and keeps every other member in Unknown.
type TruncatedLine ¶
type TruncatedLine struct {
// Line is the 1-based line number in the file.
Line int
// Data is the line as read, without its terminator.
Data []byte
// Err is the decode error.
Err error
}
TruncatedLine describes a final line that did not parse.
func (*TruncatedLine) Unwrap ¶
func (t *TruncatedLine) Unwrap() error
Unwrap returns the decode error.
type UnknownEntry ¶
type UnknownEntry struct {
EntryBase
Type string
Raw json.RawMessage
}
UnknownEntry is an entry whose type this package does not define, typically a namespaced extension. Raw holds the original line and is re-emitted verbatim. It is not in context.
func (*UnknownEntry) EntryType ¶
func (e *UnknownEntry) EntryType() string
EntryType returns the wire type.
func (*UnknownEntry) MarshalJSON ¶
func (e *UnknownEntry) MarshalJSON() ([]byte, error)
MarshalJSON emits the original bytes.
func (*UnknownEntry) UnmarshalJSON ¶
func (e *UnknownEntry) UnmarshalJSON(data []byte) error
UnmarshalJSON records the envelope and the raw bytes.
type VCS ¶
type VCS struct {
System string `json:"system"`
Revision string `json:"revision,omitempty"`
Dirty bool `json:"dirty,omitempty"`
}
VCS is the version-control state of the working directory.
type Workspace ¶ added in v0.0.5
type Workspace struct {
Kind string `json:"kind,omitempty"`
Ref string `json:"ref,omitempty"`
// Unknown holds the members of workspace the format does not
// define, such as host or instance, encoded inline beside kind and
// ref. It is nil when there are none.
Unknown map[string]json.RawMessage `json:"-"`
}
Workspace says which file system an env entry's cwd is a path in. Ref is one string the harness can resolve to it: an image digest, a host, an instance ID. A container's Ref should be a digest rather than a tag, because a tag moves. Anything else that tells one file system from another, the host a container runs on or its instance, goes in Unknown, inside workspace and not beside it, since the format's substitution rule compares workspace alone: see SameWorkspace.
func (Workspace) MarshalJSON ¶ added in v0.0.10
MarshalJSON emits kind, ref and the unknown members as one object.
func (*Workspace) SetMember ¶ added in v0.0.10
SetMember sets a member of the workspace the format does not define, such as the host a container runs on or its instance.
func (*Workspace) UnmarshalJSON ¶ added in v0.0.10
UnmarshalJSON takes kind and ref, spelled exactly, and keeps every other member in Unknown.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package atif holds Go types for the Agent Trajectory Interchange Format (ATIF) v1.8, Harbor's interchange format for agent trajectories, as defined by its RFC and the Pydantic models in harbor.models.trajectories.
|
Package atif holds Go types for the Agent Trajectory Interchange Format (ATIF) v1.8, Harbor's interchange format for agent trajectories, as defined by its RFC and the Pydantic models in harbor.models.trajectories. |
|
Package cas is the content-addressed session store RFC 0002 describes, laid out on a filesystem the way git lays out a repository:
|
Package cas is the content-addressed session store RFC 0002 describes, laid out on a filesystem the way git lays out a repository: |
|
cmd
|
|
|
agentsession
command
Command agentsession inspects, verifies, exports and lists Agent Session Format files, and the sessions of a cas store, from a shell.
|
Command agentsession inspects, verifies, exports and lists Agent Session Format files, and the sessions of a cas store, from a shell. |
|
Package export turns session trees into linear trajectories and ATIF documents.
|
Package export turns session trees into linear trajectories and ATIF documents. |
|
internal
|
|
|
ijson
Package ijson checks that a JSON document is I-JSON (RFC 7493) by the test the session format states: every number is finite once rounded to binary64, a number whose exact value is a whole number is exactly representable in binary64 or is the exact value of the canonical rendering of the nearest double, no object repeats a member name, and no string holds a lone surrogate.
|
Package ijson checks that a JSON document is I-JSON (RFC 7493) by the test the session format states: every number is finite once rounded to binary64, a number whose exact value is a whole number is exactly representable in binary64 or is the exact value of the canonical rendering of the nearest double, no object repeats a member name, and no string holds a lone surrogate. |
|
jcs
Package jcs implements the JSON Canonicalization Scheme (RFC 8785): members sorted by UTF-16 code units, no insignificant whitespace, strings with the minimal escapes the scheme prescribes and numbers in the ECMAScript Number.prototype.toString form.
|
Package jcs implements the JSON Canonicalization Scheme (RFC 8785): members sorted by UTF-16 code units, no insignificant whitespace, strings with the minimal escapes the scheme prescribes and numbers in the ECMAScript Number.prototype.toString form. |
|
jsonx
Package jsonx holds the encoding/json helpers the agentsession packages share: escape-free marshalling, member joining for objects with unknown members, and struct key discovery.
|
Package jsonx holds the encoding/json helpers the agentsession packages share: escape-free marshalling, member joining for objects with unknown members, and struct key discovery. |
|
procs
Package procs answers one question for the stores' session locks: does a process with this ID still exist on this host.
|
Package procs answers one question for the stores' session locks: does a process with this ID still exist on this host. |
|
Package jsonl is the default session store: one append-only JSONL file per session under a root directory, laid out as
|
Package jsonl is the default session store: one append-only JSONL file per session under a root directory, laid out as |
|
otel
module
|
|
|
sqlite
module
|
|
|
Package storetest is the conformance suite for agentsession.Store implementations.
|
Package storetest is the conformance suite for agentsession.Store implementations. |