transcript

package
v0.1.1 Latest Latest
Warning

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

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

Documentation

Overview

Package session is the operator-facing read/inspect surface over ADK's session store (docs/durable-execution-design.md, "Operator-facing surface"). It answers the questions durability raises for an operator: which sessions exist, which are paused waiting for input, on what interrupt, and with what response schema.

The pause model it inspects is the one verified in spike 2 (docs/spike-findings.md): a paused session carries an event with a non-nil RequestedInput; the matching resume is a later user turn whose FunctionResponse.ID equals that RequestedInput's InterruptID. A RequestedInput with no such later FunctionResponse is therefore a *pending* interrupt, and a session with pending interrupts is paused.

State labels are derived strictly from what the store can prove:

  • StatePaused: at least one pending (unresolved) RequestedInput.
  • StateAborted: an operator abort marker is present (written by Store.Abort to the session's companion ops row — see opsSuffix; v0.1.0 markers in the primary row's state are still honored).
  • StateInterrupted: a daemon shutdown cut a turn short — the daemon wrote an interruption marker before draining (Store.MarkInterrupted) and no clean completion cleared it (Store.ClearInterrupted). This is still strictly log-proven: the process that WAS running the turn recorded the fact durably before it stopped.
  • StateIdle: everything else. The store cannot distinguish "a turn is in flight right now" from "the last turn completed" — that is in-process runner state, not event-log state — so this package deliberately does not claim "running" or "completed".

Precedence: aborted > paused > interrupted > idle.

Index

Constants

View Source
const (
	StatePaused      = "paused"
	StateAborted     = "aborted"
	StateInterrupted = "interrupted"
	StateIdle        = "idle"
)

Session states derived from the event log. See the package doc for why there is no "running" or "completed".

Variables

View Source
var ErrAlreadyAborted = errors.New("session already aborted")

ErrAlreadyAborted reports that an abort marker is already present.

View Source
var ErrNotFound = errors.New("session not found")

ErrNotFound reports that no session with the requested ID exists in the store (under the store's app name).

Functions

This section is empty.

Types

type Detail

type Detail struct {
	Summary
	EventCount int            `json:"event_count"`
	Pending    []PendingInput `json:"pending,omitempty"`
}

Detail is the show-view projection: Summary plus event count and the full pending-interrupt records.

type PendingInput

type PendingInput struct {
	// InterruptID is the resume correlation key: the resume turn's
	// FunctionResponse.ID must equal it (spike-2 verified contract).
	InterruptID string `json:"interrupt_id"`
	// Message is the human-readable prompt from the pausing node.
	Message string `json:"message,omitempty"`
	// Author is the agent that raised the interrupt.
	Author string `json:"author,omitempty"`
	// RaisedAt is the timestamp of the pausing event.
	RaisedAt time.Time `json:"raised_at"`
	// ResponseSchema, when non-nil, is the JSON schema the resume
	// response payload must conform to.
	ResponseSchema *jsonschema.Schema `json:"response_schema,omitempty"`
	// Payload is optional context the pausing node attached.
	Payload any `json:"payload,omitempty"`
}

PendingInput is a RequestedInput interrupt that has not been resolved by a later matching FunctionResponse. It carries everything an operator needs to script a resume.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store wraps an ADK session.Service with the operator-facing projections. It works over any Service implementation — the SQLite / Postgres database service and the in-memory service alike.

func NewStore

func NewStore(svc adksession.Service, appName string) *Store

NewStore wraps an already-open session service (the path the daemon uses: same service instance the runner writes through).

func Open

func Open(path, appName string) (*Store, error)

Open opens the SQLite session DB at path read-through (the path the CLI uses: `mast sessions list/show --session-db=...` against a DB a daemon owns or owned). The file must already exist — opening a missing path would silently create an empty store and report zero sessions for what is actually an operator typo.

func (*Store) Abort

func (s *Store) Abort(ctx context.Context, userID, sessionID, reason string) error

Abort appends a durable operator-abort marker for the session.

Semantics contract (minimal and honest — read before relying on it):

  • Abort is a marker, not preemption. It does NOT cancel a turn that is in flight in some daemon process; it appends an event whose StateDelta records the abort reason and time in the session's companion ops row (see opsSuffix — writing to the primary row would invalidate a live runner handle and kill the turn, the opposite of this contract; issue #46). The abort event is therefore NOT part of the primary transcript the model sees — previously incidental, since the daemon refuses resumes on aborted sessions anyway.
  • ADK's workflow reconstruction does not read the marker: as far as the engine is concerned, a pending RequestedInput is still resumable. It is mast's surface that treats the marker as terminal — List/Get report StateAborted with pending interrupts cleared, and the daemon's /resume handler refuses aborted sessions (cmd/mast). A real engine-level terminal state is the v0.2 programmatic-pause/abort work (docs/durable-execution-design.md, Phasing).
  • Idempotency: a second Abort returns ErrAlreadyAborted rather than stacking markers. Legacy v0.1.0 abort markers (written to the primary row's state) count.

func (*Store) ClearInterrupted added in v0.1.1

func (s *Store) ClearInterrupted(ctx context.Context, userID, sessionID string) error

ClearInterrupted resolves a MarkInterrupted marker after the turn completed inside the drain window. Clearing an unmarked session is a harmless no-op event (shutdown-path callers cannot atomically check).

func (*Store) Get

func (s *Store) Get(ctx context.Context, userID, sessionID string) (*Detail, error)

Get returns the detail view for one session. An empty userID is resolved by scanning List for the session ID (the CLI knows session IDs, not the daemon-internal user ID).

func (*Store) List

func (s *Store) List(ctx context.Context, userID string) ([]Summary, error)

List returns summaries for all sessions under the store's app name, most recent last-event first. userID narrows to one user; empty lists all users.

Note: ADK's Service.List returns sessions without events, and paused state is an event-log property, so List issues one Get per session. Fine at operator-CLI scale; a paged/indexed path is a v0.2+ concern alongside the eventlog query surface (docs/fork-design.md P1.3).

func (*Store) MarkInterrupted added in v0.1.1

func (s *Store) MarkInterrupted(ctx context.Context, userID, sessionID, reason string) error

MarkInterrupted appends a durable interrupted-by-shutdown marker for the session (docs/durable-execution-design.md, "Shutdown contract").

The daemon writes it for every session with a turn in flight when a shutdown begins, BEFORE draining — so a SIGKILL mid-drain leaves the marker on disk — and clears it via ClearInterrupted when the turn completes inside the drain window. The marker lives in the companion ops row (see opsSuffix): writing it to the primary row would invalidate the live runner handle and kill the very turn being marked (issue #45). Like the abort marker it is state, not preemption: the engine ignores it, and a later turn on the session proceeds normally (reconstruct-and-re-execute); it exists so operators can see which sessions a restart cut short.

The primary session need not exist yet (a turn interrupted before the runner's auto-create): the marker parks in the ops row and surfaces if/when the primary appears. userID must then be explicit — with userID == "" resolution scans primaries and returns ErrNotFound. Re-marking overwrites (last write wins) — a second shutdown racing the first is not worth an error.

type Summary

type Summary struct {
	ID            string    `json:"id"`
	AppName       string    `json:"app_name"`
	UserID        string    `json:"user_id"`
	LastEventTime time.Time `json:"last_event_time"`
	State         string    `json:"state"`
	// PendingInterruptIDs are the unresolved interrupt IDs (empty
	// unless State is StatePaused).
	PendingInterruptIDs []string `json:"pending_interrupt_ids,omitempty"`
	// AbortReason is set when State is StateAborted.
	AbortReason string `json:"abort_reason,omitempty"`
	// InterruptReason is set when State is StateInterrupted: the reason
	// recorded by the daemon whose shutdown cut the session's turn short.
	InterruptReason string `json:"interrupt_reason,omitempty"`
}

Summary is the list-view projection of one session.

Jump to

Keyboard shortcuts

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