agentsession

package module
v0.0.5 Latest Latest
Warning

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

Go to latest
Published: Sep 20, 2026 License: MIT Imports: 24 Imported by: 6

README

agentsession

Go Reference CI

The reference Go implementation of the Agent Session Format: an append-only, tree-structured JSONL record of one agent session whose conversation payloads are Open Responses items. One file holds what the model was sent, what it returned, what tools did, how the configuration changed, where the conversation branched, and what happened afterwards.

  • Lossless. Items are stored as the wire carried them, reasoning and extension types included. Anything a reader does not understand is written back byte for byte.
  • Replayable. Every model call can be rebuilt from the path to its entry: settings, items, compaction. A recorded request_hash (RFC 8785 canonical JSON, SHA-256) lets a reader check its work.
  • Resumable. Run, dispatch and decision entries record what happened around the conversation. For every call without an output, the path says whether it was never started, was in flight when the record stopped, or is waiting on an answer, in any file whose header promises those entries are written.
  • Append-only and crash-tolerant. A session file is always a valid prefix of the run; a line cut short by a crash is reported and skipped.
  • Tree-shaped. Branching creates children of an earlier entry in the same file. Abandoned branches stay, because they are preference data.
  • Exportable. One ATIF v1.8 document per root-to-leaf path, with the raw items in the extras, redaction at export and media spilled beside the documents.

The root module depends on openresponses and the standard library alone. Requires Go 1.25. The library is pre-1.0: minor versions may change the API, and CHANGELOG.md records every break.

Install

go get github.com/ChristopherDavenport/agentsession

Recording a session

store, err := jsonl.Open(filepath.Join(home, ".agent", "sessions"))
sess, err := store.Create(ctx, agentsession.Header{
    CWD:     cwd,
    Harness: &agentsession.Harness{Name: "my-agent", Version: "1.0"},
})
id := sess.ID()

// The first entry on a root is a config with the full settings.
cfg, err := agentsession.ConfigFromRequest(openresponses.Request{
    Model: "gpt-5", Instructions: "Be brief.", Tools: tools,
})
store.Append(ctx, id, cfg)
store.Append(ctx, id, agentsession.NewItemEntry(openresponses.UserText("What is 2+2?")))

// Build the request from the tree, send it, record what came back.
c, err := sess.Context()
req, err := c.Request()                 // store: false, no previous_response_id
start := time.Now()
resp, err := client.Create(ctx, req)
_, err = agentsession.RecordResponse(ctx, store, id, req, resp, time.Since(start))

RecordResponse appends the output items and the response entry with the hash of the request it was given. Every append becomes the leaf. sess.Branch(entryID) moves the leaf so the next append forks in place, and appending sess.MarkLeaf() makes that choice durable, so a reopened session resumes from it rather than from the last line; sess.ResetLeaf() starts a new root. sess.Compact(firstKept, summary) and sess.SummarizeBranch(from, summary) build the entries that fold context down or carry it across a branch switch; a caller that split the request input at an index, or kept its last n items, uses sess.CompactFrom(i, summary) or sess.CompactKeeping(n, summary) instead of mapping the index to an entry ID itself. Context.ItemEntries is the mapping, aligned with Context.Items. sess.Verify(responseEntryID) rebuilds the request and checks the hash. agentsession.Continue(ctx, store, id, summary) rolls a session that has outgrown its file into a successor that starts from the old leaf's settings, and marks the old one superseded so a Current listing shows only the successor.

A harness that runs the loop records the lifecycle around it. The header's Records lists the record entry types the writer promises to write whenever their event occurs, so a reader may take their absence as the event not having happened; a converter over a native log that has no such record leaves it empty.

sess, err := store.Create(ctx, agentsession.Header{Records: agentsession.AllRecords})
store.Append(ctx, id, agentsession.NewRunStart("run-1", agentsession.SourceInput, "cron:nightly"))
// ... the request, the response with its function calls ...
store.Append(ctx, id, agentsession.NewDecision("call_1", callEntryID, agentsession.VerdictHold, agentsession.ByPolicy).
    WithReason("destructive; needs approval"))
store.Append(ctx, id, agentsession.NewDispatch("call_2", otherCallEntryID)) // synced before the tool runs
end, err := sess.EndRun(agentsession.ReasonInputRequired, "")           // pending computed from the path
store.Append(ctx, id, end)

On resume, sess.PendingCalls(leaf) lists the calls without an output and Call.State(header) says what the path knows about each. A run's end reason is a shape of its segment: ComputeReason recomputes it and Run.Verify checks a written one against it.

Reading one

f, _ := os.Open("session.jsonl")
sess, err := agentsession.Read(f)
if t := sess.Truncated(); t != nil {
    log.Printf("last line was cut short: %v", t) // the prefix loaded
}
for _, e := range sess.Entries() {
    switch v := e.(type) {
    case *agentsession.ItemEntry:
        fmt.Println(v.Item.ItemType())
    case *agentsession.UnknownEntry:
        fmt.Println("extension", v.Type) // re-emitted verbatim by Write
    }
}

Exporting to ATIF

docs := func(yield func(*atif.Trajectory) bool) {
    for tr, err := range export.Trajectories(sess) {
        if err != nil {
            continue
        }
        doc, err := export.ToATIF(tr, export.Options{
            Redactors: []export.Redactor{
                export.Secrets(os.Getenv("OPENAI_API_KEY")),
                export.HomePaths(home),
            },
        })
        if err != nil || !yield(doc) {
            return
        }
    }
}
err := export.WriteATIF("out/", docs)

Each document is one root-to-leaf path with compaction applied. The session's current path is written as <session-id>.json, the others as <session-id>_<leaf>.json. A branch that was continued lists the leaves it was preferred over in extra.preferred_over; an abandoned one names the fork in extra.abandoned_at. By default the continued branch is the one appended to last; pass export.PreferCurrentLeaf, export.PreferLabel("kept") or export.PreferScore to Trajectories to decide otherwise. export.Items(doc) reads the raw items back out, and export.ItemsFrom(doc) rebuilds them, lossily, from the declared fields of a document any producer wrote. export.NoPassthrough() strips the raw items for a document a judge will read.

Inspecting from a shell

cmd/agentsession reads session files without taking their lock, so it is safe to run beside a harness that is writing.

go install github.com/ChristopherDavenport/agentsession/cmd/agentsession@latest

agentsession show session.jsonl            # entries in file order, then the context at the leaf
agentsession show session.jsonl -leaf ID   # the context at another entry
agentsession verify session.jsonl          # rebuild every request and check its hash
agentsession export session.jsonl -out dir -secret "$OPENAI_API_KEY" -redact-home
agentsession list ~/.agent/sessions        # a jsonl store's sessions, newest first
agentsession list ~/.agent/sessions -current   # leave out sessions continued in a successor

verify exits 1 on a mismatch, a truncated final line, a run end that disagrees with its segment, a dispatch after a reject, or a call that ran without the dispatch the header promised. export writes one ATIF document per leaf and embeds a linked subsession when its file is beside the exported one or in the same store.

Tracing

The otel module is the RFC's OpenTelemetry projection. otel.Export replays a session's path into any tracer with the entries' own timestamps: a session span, a span per run carrying its source and end reason, a chat <model> span per model call that links the previous turn's, and an execute_tool <name> span per call that links the inference that produced it and carries each decision as an event and the call's state when the record stopped. otel.Wrap decorates a store so a live harness emits the same spans as it appends, and a session reopened in a new process picks up the calls the last one left pending.

store := otel.Wrap(jsonlStore, otel.Tracer("my-agent"))
// ... record as usual; call store.Close(sessionID) when done ...

_, err := otel.Export(ctx, tracer, sess, sess.Leaf()) // a stored session, after the fact

Packages

package purpose
agentsession header, entries, tree, context algorithm, request hash, Store interface, in-memory store
jsonl the file store: one JSONL file per session with a sync policy, crash recovery and a per-session lock against a second writing process, reporting a dead holder's lock when it takes one over
sqlite a SQLite store, as a nested module so its driver stays out of the library, holding each open session against a second process
otel the OpenTelemetry projection, as a nested module: replay a session as spans, or wrap a store so a live run emits them
atif Go types for ATIF v1.8 with unknown-member passthrough and validation
export trajectories, ATIF conversion, redactors, writer
storetest the conformance suite every store runs
cmd/agentsession the command: show, verify, export and list session files

Interoperating

Three things a writer built elsewhere must agree on are written in the RFC rather than shared as code: the request hash, the link entry for subsessions, and the subsession ID, a UUIDv5 under the nil namespace over <parent session id>/<call_id> that SubsessionID derives. testdata/hash/vectors.json holds request documents and their hashes so another implementation can test itself against the same inputs. The Python one-liner json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False) reproduces the canonical form for requests whose numbers are integers.

Development

make check   # gofmt, vet, deps, staticcheck, govulncheck, race tests

See CONTRIBUTING.md. The format is specified in docs/rfcs/0001-agent-session-format.md and the library design in docs/plans/session-layer.md.

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. Entries form a tree through their id and parent members, so a branch is a child of an earlier entry, in place, and an abandoned branch stays in the file. 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.

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

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

View Source
const (
	RelSubsession  = "subsession"
	RelForkOf      = "fork_of"
	RelContinuedIn = "continued_in"
)

Relations a LinkEntry may carry.

View Source
const (
	OutcomeFeedback  = "feedback"
	OutcomeTest      = "test"
	OutcomeTask      = "task"
	OutcomeToolError = "tool_error"
	OutcomeEval      = "eval"
	OutcomeCustom    = "custom"
)

Kinds an OutcomeEntry may carry.

View Source
const (
	MediaInline  = "inline"
	MediaSidecar = "sidecar"
)

Media storage modes named by the header.

View Source
const (
	TypeRun      = "run"
	TypeDispatch = "dispatch"
	TypeDecision = "decision"
)

Record entry types added in format 0.2. They are never in context.

View Source
const (
	RunStart = "start"
	RunEnd   = "end"
)

Phases of a RunEntry.

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

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

View Source
const (
	VerdictProceed = "proceed"
	VerdictReject  = "reject"
	VerdictHold    = "hold"
)

Verdicts a DecisionEntry may carry, each defined by what follows the decision on the path: a dispatch, an output carrying the reason, or nothing.

View Source
const (
	ByHuman  = "human"
	ByPolicy = "policy"
	ByAgent  = "agent"
)

Deciders a DecisionEntry may name.

View Source
const (
	WorkspaceLocal     = "local"
	WorkspaceContainer = "container"
	WorkspaceRemote    = "remote"
)

Workspace kinds an EnvEntry may name.

View Source
const Format = "agentsession/0.2"

Format is the session format version this package writes.

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

View Source
const HashPrefix = "sha256:"

HashPrefix precedes the hexadecimal digest in a request hash.

View Source
const LeafLabel = "leaf"

LeafLabel is the reserved label that makes the leaf durable. A label entry carrying it names the entry the next append should hang from: Session.Append keeps the leaf at the label's target rather than moving it to the label entry, and Read restores the leaf from the last such label in the file rather than from the last line, so a branch survives a restart. A null label on the same target clears it. The format holds this as a library convention; see the RFC's open questions.

View Source
const Payload = "openresponses/" + openresponses.SpecVersion

Payload is the payload profile this package writes: Open Responses items at the specification version openresponses targets.

Variables

AllRecords are the three record entry types a harness that runs the loop itself can promise: run, dispatch and decision.

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

View Source
var ErrDuplicateEntry = errors.New("agentsession: duplicate entry id")

ErrDuplicateEntry is returned when an appended entry reuses an ID.

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

View Source
var ErrNoEntry = errors.New("agentsession: no such entry")

ErrNoEntry is returned when an entry ID is not in the session.

View Source
var ErrNoSession = errors.New("agentsession: no such session")

ErrNoSession is returned when a session ID is not in the store.

View Source
var ErrNotGit = errors.New("agentsession: not a git checkout")

ErrNotGit is returned by GitRevision when dir is not inside a git checkout.

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

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

View Source
var ErrSessionExists = errors.New("agentsession: session already exists")

ErrSessionExists is returned when Create is given an ID already in the store.

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

View Source
var ErrUnsupportedFormat = errors.New("agentsession: unsupported format")

ErrUnsupportedFormat is returned when a header names a format this package cannot read.

Functions

func ComputeReason added in v0.0.5

func ComputeReason(segment []Entry) string

ComputeReason recomputes a run's end reason from its segment, as the format defines it: the first of error, input_required, aborted, done and stopped whose shape the segment matches. 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.

func GitRevision

func GitRevision(dir string) (string, error)

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 HashBytes

func HashBytes(data []byte) string

HashBytes returns the content hash of data in the env entry's form.

func HashFile

func HashFile(path string) (string, error)

HashFile returns the content hash of a file in the form the env entry uses: "sha256:" followed by lowercase hex.

func HashFiles

func HashFiles(dir string, paths ...string) (map[string]string, error)

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

func HashRequestJSON(data []byte) (string, error)

HashRequestJSON computes the request hash of an already-encoded request. Member order and whitespace in data do not matter.

func InContext

func InContext(e Entry) bool

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

func IsExtension(typ string) bool

IsExtension reports whether typ is a namespaced extension type.

func MarshalEntry

func MarshalEntry(e Entry) ([]byte, error)

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 ParseFormat

func ParseFormat(format string) (major, minor int, err error)

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 SubsessionID added in v0.0.5

func SubsessionID(parentSessionID, callID string) string

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 Write

func Write(w io.Writer, s *Session) error

Write encodes the session as JSONL: the header, then every entry in file order, one per line.

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 dispatch entry, or nil when none is on the path.
	Dispatch *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 dispatch and its output.

func Calls added in v0.0.5

func Calls(path []Entry) []*Call

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) Args added in v0.0.5

func (c *Call) Args() string

Args returns the arguments the tool ran with: those of the last decision that rewrote them, else the call's own.

func (*Call) Held added in v0.0.5

func (c *Call) Held() bool

Held reports whether the call is waiting on an answer: its latest decision is a hold and no dispatch follows it.

func (*Call) ID added in v0.0.5

func (c *Call) ID() string

ID returns the call ID.

func (*Call) Pending added in v0.0.5

func (c *Call) Pending() bool

Pending reports whether the call has no output on the path.

func (*Call) Rejected added in v0.0.5

func (c *Call) Rejected() bool

Rejected reports whether a decision ended the call without running it.

func (*Call) State added in v0.0.5

func (c *Call) State(h Header) CallState

State returns what the path says happened to the call, reading the header's records to decide whether a missing dispatch means the call never started or means the file does not say.

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
)

func (CallState) String added in v0.0.5

func (s CallState) String() string

String names the state.

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"`
	// 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.

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"`
	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 (*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) EntryType

func (*ConfigEntry) EntryType() string

EntryType returns "config".

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
	// ItemEntries[0] is the compaction entry, whose summary is Items[0].
	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

func BuildContext(path []Entry) (Context, error)

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

func (c Context) Request() (openresponses.Request, error)

Request returns the canonical request for the context.

type CustomEntry

type CustomEntry struct {
	EntryBase `json:"-"`
	NS        string          `json:"ns"`
	Data      json.RawMessage `json:"data,omitempty"`
}

CustomEntry is application state that is not in context. State that the model should see is an ItemEntry holding a namespaced item.

func (*CustomEntry) EntryType

func (*CustomEntry) EntryType() string

EntryType returns "custom".

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.

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"`
}

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.

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.

type Entry

type Entry interface {
	Base() *EntryBase
	EntryType() string
}

Entry is one line of a session after the header. Concrete types are ItemEntry, ResponseEntry, ConfigEntry, CompactionEntry, BranchSummaryEntry, RunEntry, DispatchEntry, DecisionEntry, 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

func UnmarshalEntry(data []byte) (Entry, error)

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.
	Parent string
	// Timestamp is when the entry was written.
	Timestamp time.Time
	// Unknown holds envelope 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
}

EntryBase is the envelope every entry carries.

func (*EntryBase) Base

func (b *EntryBase) Base() *EntryBase

Base returns the envelope.

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

func NewEnvEntry(cwd string) *EnvEntry

NewEnvEntry starts an env entry for a working directory. Use EnvEntry.SetGit, EnvEntry.AddRead and EnvEntry.AddWritten to fill it in.

func (*EnvEntry) AddRead

func (e *EnvEntry) AddRead(path, hash string)

AddRead records the hash of a file the session read.

func (*EnvEntry) AddTool

func (e *EnvEntry) AddTool(name, version string)

AddTool records the version of a tool available to the session.

func (*EnvEntry) AddWritten

func (e *EnvEntry) AddWritten(path, hash string)

AddWritten records the hash of a file the session wrote.

func (*EnvEntry) EntryType

func (*EnvEntry) EntryType() string

EntryType returns "env".

func (*EnvEntry) MarshalJSON

func (e *EnvEntry) MarshalJSON() ([]byte, error)

MarshalJSON emits the entry as one JSON object.

func (*EnvEntry) SetGit

func (e *EnvEntry) SetGit(dir string, dirty bool) error

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

func (e *EnvEntry) SetWorkspace(kind, ref string)

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 in an unknown member.

func (*EnvEntry) UnmarshalJSON

func (e *EnvEntry) UnmarshalJSON(data []byte) error

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 Harness

type Harness struct {
	Name    string `json:"name"`
	Version string `json:"version,omitempty"`
}

Harness names the writer of a session.

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"`
	Media     string `json:"media,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

func (h Header) HasRecord(typ string) bool

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

func (h Header) MarshalJSON() ([]byte, error)

MarshalJSON emits the header with its type discriminator first and Extra flattened into the object.

func (*Header) UnmarshalJSON

func (h *Header) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes the header, keeping unknown fields in Extra.

func (Header) Validate

func (h Header) Validate() error

Validate checks the required header fields and that the format is one this package reads.

type InfoEntry

type InfoEntry struct {
	EntryBase `json:"-"`
	Name      string `json:"name,omitempty"`
}

InfoEntry carries display metadata such as a session name. Further members are kept in Unknown.

func (*InfoEntry) EntryType

func (*InfoEntry) EntryType() string

EntryType returns "info".

func (*InfoEntry) MarshalJSON

func (e *InfoEntry) MarshalJSON() ([]byte, error)

MarshalJSON emits the entry as one JSON object.

func (*InfoEntry) UnmarshalJSON

func (e *InfoEntry) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes the entry.

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"`
}

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 (*ItemEntry) EntryType

func (*ItemEntry) EntryType() string

EntryType returns "item".

func (*ItemEntry) IsVisible

func (e *ItemEntry) IsVisible() bool

IsVisible reports whether a renderer should show the item.

func (*ItemEntry) MarshalJSON

func (e *ItemEntry) MarshalJSON() ([]byte, error)

MarshalJSON emits the entry as one JSON object.

func (*ItemEntry) UnmarshalJSON

func (e *ItemEntry) UnmarshalJSON(data []byte) error

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

func (*LabelEntry) EntryType() string

EntryType returns "label".

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

func NewLinkEntry(rel, session string) *LinkEntry

NewLinkEntry builds a link to another session.

func NewSubsessionLink(session, callID string) *LinkEntry

NewSubsessionLink builds the link entry the format prescribes for a subagent session spawned by the function call callID.

func (*LinkEntry) EntryType

func (*LinkEntry) EntryType() string

EntryType returns "link".

func (*LinkEntry) MarshalJSON

func (e *LinkEntry) MarshalJSON() ([]byte, error)

MarshalJSON emits the entry as one JSON object.

func (*LinkEntry) UnmarshalJSON

func (e *LinkEntry) UnmarshalJSON(data []byte) error

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 NewMemoryStore

func NewMemoryStore() *MemoryStore

NewMemoryStore returns an empty store.

func (*MemoryStore) Append

func (m *MemoryStore) Append(ctx context.Context, sessionID string, e Entry) (string, error)

Append implements Store.

func (*MemoryStore) Create

func (m *MemoryStore) Create(_ context.Context, h Header) (*Session, error)

Create implements Store.

func (*MemoryStore) Delete

func (m *MemoryStore) Delete(_ context.Context, id string) error

Delete implements Store.

func (*MemoryStore) List

List implements Store.

func (*MemoryStore) Open

func (m *MemoryStore) Open(_ context.Context, id string) (*Session, error)

Open implements Store.

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 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"`
}

ResponseEntry is the envelope of one model call, written after its output items. It is not in context.

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 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
}

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

func Runs(path []Entry) []*Run

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) Calls added in v0.0.5

func (r *Run) Calls() []*Call

Calls returns the calls on the segment; see Calls.

func (*Run) Pending added in v0.0.5

func (r *Run) Pending() []string

Pending returns the IDs of the calls on the segment with no output on it.

func (*Run) RunID added in v0.0.5

func (r *Run) RunID() string

RunID returns the run's ID.

func (*Run) Verify added in v0.0.5

func (r *Run) Verify() error

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"`
	// 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

func NewRunEnd(runID, reason, ref string, pending []string) *RunEntry

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

func NewRunStart(runID, source, ref string) *RunEntry

NewRunStart builds the entry that opens a run. ref may be "".

func (*RunEntry) EntryType added in v0.0.5

func (*RunEntry) EntryType() string

EntryType returns "run".

func (*RunEntry) IsEnd added in v0.0.5

func (e *RunEntry) IsEnd() bool

IsEnd reports whether the entry closes a run.

func (*RunEntry) IsStart added in v0.0.5

func (e *RunEntry) IsStart() bool

IsStart reports whether the entry opens a run.

func (*RunEntry) MarshalJSON added in v0.0.5

func (e *RunEntry) MarshalJSON() ([]byte, error)

MarshalJSON emits the entry as one JSON object. A start entry omits pending; an end entry always carries it.

func (*RunEntry) UnmarshalJSON added in v0.0.5

func (e *RunEntry) UnmarshalJSON(data []byte) error

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 New

func New(h Header) *Session

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

func Read(r io.Reader) (*Session, error)

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

func (s *Session) Append(e Entry) (string, error)

Append adds e to the tree and makes it the leaf. An empty ID is assigned; an empty Parent is set to the current leaf, so an explicit Parent branches in place; a zero Timestamp is set to now. The parent must exist and the ID must be new. On success e is owned by the session and must not be modified.

func (*Session) Branch

func (s *Session) Branch(id string) error

Branch moves the leaf to id, so the next append becomes a child of that entry.

func (*Session) Calls added in v0.0.5

func (s *Session) Calls(leaf string) ([]*Call, error)

Calls returns the calls on the path to leaf; see Calls.

func (*Session) Children

func (s *Session) Children(id string) []string

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 or Usage if known, 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: the format keeps entries, and a compaction's summary 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) Context

func (s *Session) Context() (Context, error)

Context builds the context at the current leaf. With no leaf it is empty.

func (*Session) ContextAt

func (s *Session) ContextAt(id string) (Context, error)

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) EndRun added in v0.0.5

func (s *Session) EndRun(reason, ref string) (*RunEntry, error)

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.

func (*Session) Entries

func (s *Session) Entries() []Entry

Entries returns the entries in file order. The slice is a copy; the entries are shared.

func (*Session) Entry

func (s *Session) Entry(id string) (Entry, bool)

Entry returns the entry with the given ID.

func (*Session) Header

func (s *Session) Header() Header

Header returns a copy of the header.

func (*Session) ID

func (s *Session) ID() string

ID returns the session ID.

func (*Session) Labels

func (s *Session) Labels() map[string]string

Labels returns the current label of every labelled entry: the last label entry per target wins, and a null label clears.

func (*Session) Leaf

func (s *Session) Leaf() string

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

func (s *Session) Leaves() []string

Leaves returns the IDs of the entries that have no children, in file order.

func (*Session) Len

func (s *Session) Len() int

Len returns the number of entries.

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 from it rather than from the last line. Append it through the store after Branch; the leaf stays where it is. It returns an error when there is no leaf.

func (*Session) Name

func (s *Session) Name() string

Name returns the display name from the last info entry that set one.

func (*Session) OpenRun added in v0.0.5

func (s *Session) OpenRun(leaf string) (*Run, error)

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

func (s *Session) Path(id string) []Entry

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

func (s *Session) PendingCalls(leaf string) ([]*Call, error)

PendingCalls returns the calls on the path to leaf that have no output, which is what a resume answers.

func (*Session) RequestContext

func (s *Session) RequestContext(id string) (Context, error)

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. Output items are the item entries directly before the response entry whose ResponseID matches.

func (*Session) ResetLeaf

func (s *Session) ResetLeaf()

ResetLeaf clears the leaf, so the next append starts a new root.

func (*Session) Roots

func (s *Session) Roots() []string

Roots returns the IDs of the root entries in file order.

func (*Session) Runs added in v0.0.5

func (s *Session) Runs(leaf string) ([]*Run, error)

Runs returns the runs on the path to leaf; see Runs.

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.

func (*Session) SupersededBy added in v0.0.5

func (s *Session) SupersededBy() string

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

func (s *Session) Verify(id string) error

Verify rebuilds the request for the response entry id and checks that it hashes to the entry's RequestHash. A response without a recorded hash verifies trivially.

func (*Session) VerifyRecords added in v0.0.5

func (s *Session) VerifyRecords(leaf string) error

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 on the same call, and, when the header names dispatch in records, 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"`
	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

func (s Settings) ExtraValue(key string, v any) (bool, error)

ExtraValue decodes the passthrough member key into v. It reports false, and leaves v alone, when the settings carry no such member.

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.

type Store

type Store interface {
	// Create starts a new session from h, filling empty header fields.
	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.

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

func (t *TruncatedLine) Error() string

Error implements error.

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"`
	Ref  string `json:"ref,omitempty"`
}

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 richer goes in unknown members of the env entry.

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.
cmd
agentsession command
Command agentsession inspects, verifies, exports and lists Agent Session Format files from a shell.
Command agentsession inspects, verifies, exports and lists Agent Session Format files 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
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.

Jump to

Keyboard shortcuts

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