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
- Variables
- type Detail
- type PendingInput
- type Store
- func (s *Store) Abort(ctx context.Context, userID, sessionID, reason string) error
- func (s *Store) ClearInterrupted(ctx context.Context, userID, sessionID string) error
- func (s *Store) Get(ctx context.Context, userID, sessionID string) (*Detail, error)
- func (s *Store) List(ctx context.Context, userID string) ([]Summary, error)
- func (s *Store) MarkInterrupted(ctx context.Context, userID, sessionID, reason string) error
- type Summary
Constants ¶
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 ¶
var ErrAlreadyAborted = errors.New("session already aborted")
ErrAlreadyAborted reports that an abort marker is already present.
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 ¶
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 ¶
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
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 ¶
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 ¶
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
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.