session

package
v0.66.0 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: AGPL-3.0 Imports: 11 Imported by: 0

Documentation

Overview

Package session owns agent-session lifecycle.

It is the sole authority over session creation, resumption, kind/channel policy, main-session resolution, review candidate selection, and the conversion from a validated session record to a memory operation scope.

Info is a session-domain type, not an alias to memory.SessionInfo. Every use boundary fails closed: the persistence mapping (Record / InfoFromRecord) and the memory-scope conversion (MemoryScope) validate the session invariant and return an error rather than emit an unchecked value. The memory scope is produced only by MemoryScope; production code must not hand-build memory.Session values.

Index

Constants

View Source
const MaxSynchronousOutputBytes = 64_000

MaxSynchronousOutputBytes bounds output retained while a caller drains a synchronous child Session stream. Draining continues after this limit so the producer can finish and release its busy guard.

View Source
const MaxTitleBytes = 1_000

Variables

View Source
var ErrArchived = errors.New("session is archived")

ErrArchived is returned when an attempt is made to write to an archived session.

View Source
var ErrForbidden = errors.New("session access forbidden")

ErrForbidden is returned when a caller attempts to access a session they do not own.

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

ErrNotFound is returned when a session cannot be located.

View Source
var ErrRotationOutcomeUnknown = memory.ErrRotationOutcomeUnknown

ErrRotationOutcomeUnknown is returned when a rotation failed at commit acknowledgement and may or may not have been persisted. See memory.ErrRotationOutcomeUnknown.

View Source
var ErrStaleRotation = memory.ErrStaleRotation

ErrStaleRotation is returned when a rotation's expected session is no longer the active one. It is the store-level sentinel so callers can match a stale rotation with one errors.Is check regardless of which layer detected it.

View Source
var ErrTitleTooLong = errors.New("session title exceeds maximum length")
View Source
var ErrWrongKind = errors.New("session kind mismatch")

ErrWrongKind is returned when a resumed session has a different kind than required.

Functions

func ValidateTitle added in v0.63.3

func ValidateTitle(title string) error

ValidateTitle enforces the canonical title write bound in UTF-8 bytes. A byte bound matches the serialized payloads titles are copied into and avoids allowing multibyte Unicode to exceed those budgets.

Types

type Channel

type Channel string

Channel is the typed originating channel.

const (
	ChannelWeb       Channel = "web"
	ChannelCLI       Channel = "cli"
	ChannelTelegram  Channel = "telegram"
	ChannelDelegate  Channel = "delegate"
	ChannelTask      Channel = "task"
	ChannelScheduler Channel = "scheduler"
	ChannelWebhook   Channel = "webhook"
)

type ChannelRequest added in v0.61.0

type ChannelRequest struct {
	UserID  string
	AgentID string
	// GuestID marks a persistent guest binding and must equal UserID.
	GuestID string
	// GroupID marks a group binding. When set it must equal UserID.
	GroupID string
	// Channel is the durable channel value the binding carries. It is part of the
	// binding predicate for private chat channels and informational for groups.
	Channel Channel
	// LegacyID is the pre-binding key-as-ID session for this chat. On a binding
	// miss it is resolved once and, when it is still a valid active session for
	// this binding, adopted (binding fields persisted) instead of superseded by a
	// new empty session. Empty disables the fallback.
	LegacyID string
	// ExpectedSessionID makes a rotation a compare-and-rotate: the binding's
	// current session must still be this one, otherwise RotateChannel reports
	// ErrStaleRotation and changes nothing. Ignored by ResolveChatChannel.
	ExpectedSessionID string
}

ChannelRequest describes a durable chat-channel session binding: the identity a channel chat resolves against instead of pinning itself to one session id.

A group binds on its owning group (UserID == GroupID) plus agent and kind=chat, because a group's channel string varies with the platform reply channel it arrives through. A private chat channel binds on user + agent + kind=chat + Channel, which is the channel adapter's stable session key.

type Info

type Info struct {
	ID                  string
	AgentID             string
	UserID              string
	GroupID             string
	GuestID             string
	Channel             string
	Kind                string
	ProjectID           string
	Title               string
	CreatedAt           time.Time
	LastActive          time.Time
	LastTurnStartedAt   time.Time
	LastTurnCompletedAt time.Time
	LastTurnResult      memory.SessionTurnResult
	LastViewedAt        time.Time
	Archived            bool
	// LatestSeq is populated by review listings and is not session metadata.
	LatestSeq int64
}

Info holds validated metadata about an agent session. It is a session-domain value distinct from the memory-layer persistence type memory.SessionInfo; callers cross the persistence boundary through Record and InfoFromRecord.

GroupID identifies the group a group session belongs to. It is now durable (ctx_conversation.group_id) and load/save round-trip it. The durable invariant is that a group session is owned by the group itself: UserID equals GroupID, and GroupID is the canonical ctx_group_state UUID. That equality is what makes group read, write, and compaction resolve one memory partition; it is enforced by Validate at every use boundary rather than left as an undeclared convention.

func InfoFromRecord added in v0.60.0

func InfoFromRecord(r memory.SessionInfo) (Info, error)

InfoFromRecord maps a memory-layer persistence record back to a session-domain Info. It is the persistence→domain half of the session store boundary and the only sanctioned way to turn an unvalidated memory.SessionInfo into an Info; it validates the resulting Info and fails closed on a record that violates the session invariant.

func NewInfo

func NewInfo(id, agentID, userID, channel string, kind Kind, projectID string, now time.Time) Info

NewInfo constructs a fresh Info with the required fields set.

func (Info) MemoryScope added in v0.60.0

func (i Info) MemoryScope() (memory.Session, error)

MemoryScope is the single canonical conversion from a validated session Info to a memory.Session operation scope. It fails closed: an Info that violates the session invariant yields an error, never a half-formed scope.

For a private session, read (Assemble), write (Append/Bootstrap), and compaction all derive their scope here and land on the same partition. A group session shares one durable canonical scope for read and write (partition keyed on session/user/agent, with GroupID selecting the group assembly/isolation path); group compaction is a separate matter and is unsupported (rejected by agent.Service.CompactSession), because group history is assembled from the group event log rather than the LCM conversation.

func (Info) Record added in v0.60.0

func (i Info) Record() (memory.SessionInfo, error)

Record maps a validated session-domain Info onto the memory-layer persistence type. It is the domain→persistence half of the session store boundary and fails closed on an invalid Info.

func (Info) Validate added in v0.60.0

func (i Info) Validate() error

Validate enforces the durable session-scope invariant. Every session needs an ID, an agent, and a user owner. A group session is owned by its group: UserID must equal GroupID and GroupID must be a canonical ctx_group_state UUID. This is the single fail-closed gate the persistence and memory-scope boundaries run.

type Kind

type Kind string

Kind is the typed session kind.

const (
	KindMain      Kind = "main"
	KindChat      Kind = "chat"
	KindDelegate  Kind = "delegate"
	KindTask      Kind = "task"
	KindScheduler Kind = "scheduler"
)

type ListOptions

type ListOptions struct {
	// Kinds filters by session kind; empty = all kinds.
	Kinds []Kind
	// IncludeArchived includes archived sessions.
	IncludeArchived bool
	// ExcludeInternal omits task/delegate worker sessions from human lists.
	ExcludeInternal bool
	// ProjectID filters to one project when non-empty.
	ProjectID string
	Limit     int
	Offset    int
}

ListOptions controls session listing.

type MainRequest

type MainRequest struct {
	UserID  string
	AgentID string
	// ExpectedSessionID makes a rotation a compare-and-rotate: the current main
	// must still be this session, otherwise RotateMain reports ErrStaleRotation
	// and changes nothing. Empty means "rotate whatever is current"; it is
	// ignored by ResolveMain.
	ExpectedSessionID string
}

MainRequest describes a main-session resolution or rotation request.

type OutputCollector added in v0.63.3

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

OutputCollector keeps a UTF-8-safe prefix while allowing callers to drain an arbitrarily large stream without retaining it all in memory.

func (*OutputCollector) String added in v0.63.3

func (c *OutputCollector) String() string

func (*OutputCollector) Truncated added in v0.63.3

func (c *OutputCollector) Truncated() bool

func (*OutputCollector) Write added in v0.63.3

func (c *OutputCollector) Write(text string)

Write retains as much of text as fits and records whether any bytes were omitted.

type Registry

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

Registry is the sole owner of agent-session lifecycle. It creates, resumes, lists, and archives sessions; it also converts validated session records into memory operation scopes.

Registry has no knowledge of LLM execution, runners, or tools.

func NewRegistry

func NewRegistry(mem memory.Provider, agentID string) (*Registry, error)

NewRegistry creates a Registry backed by the given memory.Provider. mem must implement memory.SessionManager; if it does not, an error is returned. agentID may be empty for a cross-agent registry.

func NewRegistryWithStore

func NewRegistryWithStore(s store, agentID string) *Registry

NewRegistryWithStore creates a Registry with an explicit store (for testing).

func (*Registry) Archive

func (r *Registry) Archive(ctx context.Context, scope Scope, id string) error

Archive marks a session as archived.

func (*Registry) Ensure

func (r *Registry) Ensure(ctx context.Context, req Request) (Info, error)

Ensure finds or creates a session according to the request policy.

- If req.ID is set and the session exists: validates kind and user, returns it. - If req.ID is set but missing and AllowExactIDCreate: creates with that ID. - If req.ID is empty and CreateIfMissing: generates a new ID and creates. - Otherwise: returns ErrNotFound.

func (*Registry) Get

func (r *Registry) Get(ctx context.Context, scope Scope, id string) (Info, error)

Get returns session metadata by ID. The session must belong to the given scope (userID + agentID match).

func (*Registry) List

func (r *Registry) List(ctx context.Context, scope Scope, opts ListOptions) ([]Info, error)

List returns sessions matching the scope and options.

func (*Registry) ListForAdmin added in v0.63.0

func (r *Registry) ListForAdmin(ctx context.Context, userID, agentID string, opts ListOptions) ([]Info, error)

ListForAdmin returns an admin's own sessions plus agent-scoped guest sessions. The caller must enforce administrator authority and per-row authorization.

func (*Registry) ListForReview

func (r *Registry) ListForReview(ctx context.Context, req ReviewRequest) ([]Info, error)

ListForReview returns sessions that are candidates for reflect review.

func (*Registry) MemoryScope

func (r *Registry) MemoryScope(info Info) (memory.Session, error)

MemoryScope converts a validated session Info into a memory.Session scope. This is the ONLY authorised way to produce memory.Session for production agent sessions; it delegates to the canonical Info.MemoryScope conversion. Private read, write, and compaction resolve one partition; group read and write share the durable canonical group scope (group compaction is unsupported, so MemoryScope makes no claim about it). It fails closed on an invalid Info.

func (*Registry) ResolveChatChannel added in v0.61.0

func (r *Registry) ResolveChatChannel(ctx context.Context, req ChannelRequest) (Info, error)

ResolveChatChannel returns the chat session a channel chat is currently bound to, creating one when the binding has none yet. Unlike Ensure it never uses a derived key as the session id, so the binding can rotate onto a successor while staying the same chat.

func (*Registry) ResolveMain

func (r *Registry) ResolveMain(ctx context.Context, req MainRequest) (Info, error)

ResolveMain returns the main session for a user+agent pair. It first looks for an existing main-kind session, then promotes the most recent candidate, and finally creates a new one if CreateIfMissing is set.

func (*Registry) RotateChannel added in v0.61.0

func (r *Registry) RotateChannel(ctx context.Context, req ChannelRequest) (Info, error)

RotateChannel archives the chat channel's current session and returns its successor, so the next turn starts with an empty context while the old session remains stored but leaves the recall corpus. The archive and the create are one store-level transaction, so the binding is never left without an active session.

When req.ExpectedSessionID is set it must still be the binding's current session; otherwise the rotation is stale (another `/new` already ran) and reports ErrStaleRotation without changing anything.

func (*Registry) RotateMain added in v0.61.0

func (r *Registry) RotateMain(ctx context.Context, req MainRequest) (Info, error)

RotateMain archives the user's current main session and returns its successor, so the next turn starts with an empty context while the old session remains stored but leaves the recall corpus. The archive and the create are one store-level transaction: the binding is never left without an active main.

When req.ExpectedSessionID is set it must still be the current main; otherwise the rotation is stale (another /new already ran) and reports ErrStaleRotation without changing anything. Rotating when no main exists is just a create.

type Request

type Request struct {
	// ID is the exact session ID to resume. If empty, a new ID is generated.
	ID string
	// UserID and AgentID are required for user-scoped operations.
	UserID  string
	AgentID string
	// GroupID marks this as a group session owned by the group. When set it is
	// the canonical ctx_group_state UUID and must equal UserID; it is persisted
	// so the group identity survives reload. Empty for private sessions.
	GroupID string
	// ProjectID scopes the session to a project when non-empty.
	ProjectID string
	// Kind is the session kind to use when creating a new session.
	// If the session already exists, it must match RequireKind when set.
	Kind Kind
	// Channel is the originating channel (e.g. ChannelWeb, ChannelDelegate).
	Channel Channel
	// Title is optional; pool_chat auto-derives it from the first message.
	Title string
	// CreateIfMissing creates a new session if none matches.
	CreateIfMissing bool
	// AllowExactIDCreate allows creating a new session with the explicit ID
	// when that ID does not yet exist. Required for delegate session persistence.
	AllowExactIDCreate bool
	// RequireKind enforces that a resumed session must have this kind.
	// Empty means any kind is acceptable.
	RequireKind Kind
}

Request describes what session to find or create.

type ReviewPolicy

type ReviewPolicy struct {
	// ExcludeKinds lists session kinds to skip.
	ExcludeKinds []Kind
	// RequirePrivateMain, when true, includes only sessions on private user channels.
	RequirePrivateMain bool
}

ReviewPolicy controls which sessions are included in reflect review.

func DefaultReviewPolicy

func DefaultReviewPolicy() ReviewPolicy

DefaultReviewPolicy returns the policy used by the reflect system. Delegate, task, and scheduler sessions are excluded by default.

func (ReviewPolicy) Includes

func (p ReviewPolicy) Includes(kind Kind) bool

Includes reports whether a session with the given kind passes this policy.

func (ReviewPolicy) IsZero

func (p ReviewPolicy) IsZero() bool

IsZero reports whether no review policy was supplied.

type ReviewRequest

type ReviewRequest struct {
	AgentID string
	// Policy controls inclusion/exclusion rules.
	Policy ReviewPolicy
}

ReviewRequest describes which sessions are candidates for reflect review.

type RotationOutcome added in v0.61.0

type RotationOutcome int

RotationOutcome is what a re-read of the chat's session binding can say about a rotation whose commit acknowledgement was lost (ErrRotationOutcomeUnknown): the transaction may have landed, so the error alone is not an answer. The binding is the answer — it is what the rotation moves, and it never moves back.

const (
	// RotationUncertain means the verification read itself failed, so nothing is
	// known. A caller must say so rather than invite a retry: retrying a
	// rotation that did commit archives the fresh context instead of the old.
	RotationUncertain RotationOutcome = iota
	// RotationCommitted means the binding has left the expected session, so the
	// rotation did happen and should be reported as success.
	RotationCommitted
	// RotationNotExecuted means the binding still points at the expected
	// session, so the transaction rolled back. Retrying is safe.
	RotationNotExecuted
)

func VerifyRotation added in v0.61.0

func VerifyRotation(ctx context.Context, expectedSessionID string, resolve func(context.Context) (Info, error)) RotationOutcome

VerifyRotation classifies a lost-acknowledgement rotation by re-resolving the chat's session binding. expectedSessionID is the session the rotation was asked to replace; resolve reads the binding's current session.

A resolve that succeeds but names nothing is treated as uncertain: an empty id is not evidence that the binding moved.

type Scope

type Scope struct {
	UserID  string
	AgentID string
	// System marks this as a background-only scope (e.g. reflect).
	// System scopes bypass user requirement but still require AgentID.
	System bool
}

Scope identifies a caller's access scope for registry queries.

Directories

Path Synopsis
Package access is the authoritative Session and Workspace application service.
Package access is the authoritative Session and Workspace application service.
Package inbox persists Agent-originated Session inputs without owning their execution.
Package inbox persists Agent-originated Session inputs without owning their execution.
Package turnqueue serializes synchronous agent-originated turns per Session.
Package turnqueue serializes synchronous agent-originated turns per Session.

Jump to

Keyboard shortcuts

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