nativeturn

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 20, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package nativeturn is the serve-level survival layer for native ACP chain turns: an in-flight turn runs on a serve-rooted Registry, so a dropped connection detaches a viewer without cancelling the turn. Every exported type is safe for concurrent use.

Index

Constants

View Source
const (
	// DefaultTurnDeadline is the fallback hard turn ceiling (belt 2).
	DefaultTurnDeadline = 60 * time.Minute
	// DefaultGraceWindow is the fallback last-viewer-detach survival window (belt 1).
	DefaultGraceWindow = 15 * time.Minute
	// DefaultJournalSize bounds the per-session replay journal (belt 3).
	DefaultJournalSize = 512
)
View Source
const (
	// StateRunning: the turn's chain is executing and at least one viewer is attached.
	StateRunning = "running"
	// StateGrace: the chain is still executing but no viewer is attached, with a grace
	// timer counting down (belt 1); a reattach returns it to running, expiry cancels it.
	StateGrace = "grace"
	// StateFinished: the chain has ended and the session awaits viewer
	// detach / reaper cleanup.
	StateFinished = "finished"
	// StateSuspended: the chain suspended on a pending human approval and checkpointed
	// durably; lifecycle-wise a finished turn whose distinct state tells the operator
	// to wait for the approval, not "done".
	StateSuspended = "suspended"
)

Turn states, the vocabulary of TurnStatus.State.

Variables

View Source
var ErrClosed = errors.New("nativeturn: registry is closed")

ErrClosed is returned by Start once the Registry has been Closed; a sentinel for errors.Is(err, ErrClosed).

Functions

This section is empty.

Types

type Config

type Config struct {
	// TurnDeadline is the hard wall-clock ceiling on a single turn (belt 2),
	// terminating a still-running turn with a context deadline; must be > 0.
	TurnDeadline time.Duration
	// GraceWindow is how long an in-flight turn survives with no viewer attached
	// before it is cancelled (belt 1); a reattach inside the window keeps it
	// alive, and it must be > 0.
	GraceWindow time.Duration
	// JournalSize bounds the per-session replay journal (belt 3) to the most
	// recent events, oldest dropped first; zero or negative resolves to
	// DefaultJournalSize in New.
	JournalSize int
}

Config holds the survival-layer tunables; TurnDeadline and GraceWindow are operator-facing (env-backed, see ParseEnv), JournalSize is overridable only in-process (tests).

func ParseEnv

func ParseEnv(turnMax, turnGrace string) (Config, error)

ParseEnv builds a Config from the raw CONTENOX_TURN_MAX (hard deadline) and CONTENOX_TURN_GRACE (last-viewer grace) env strings. An empty field takes the default; a set value must be a positive Go duration.

type Event

type Event struct {
	// Seq is the 1-based monotonic id of this event within its session's turn.
	Seq uint64
	// Update is the session/update notification as emitted by the turn, before any
	// per-viewer normalization.
	Update libacp.SessionNotification
}

Event is one captured session/update tagged with a per-session monotonic sequence number, for a future SSE transport's Last-Event-ID replay.

type Registry

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

Registry owns every native session's in-flight turn, off any single connection, keyed by ACP session id; every method is safe for concurrent use.

func New

func New(cfg Config) *Registry

New returns a Registry rooted on a fresh serve context (call Close at shutdown); a non-positive JournalSize, TurnDeadline, or GraceWindow is floored to its default.

func (*Registry) AttachIfRunning

func (r *Registry) AttachIfRunning(ctx context.Context, sid libacp.SessionID, viewer Viewer) (*Turn, bool, error)

AttachIfRunning attaches viewer to sid's in-flight turn if one exists, replaying the journal and joining the live fan-out; returns (nil, false, nil) when none exists (a finished turn is deliberately not attachable here).

func (*Registry) Cancel

func (r *Registry) Cancel(sid libacp.SessionID) bool

Cancel cancels sid's in-flight turn and tears it down (session/cancel), reporting whether one was present (no-op false if not); any viewer awaiting the turn unblocks with its cancelled result.

func (*Registry) Close

func (r *Registry) Close() error

Close tears every in-flight turn down and cancels the Registry's root context, after which Start returns ErrClosed. Idempotent.

func (*Registry) Get

func (r *Registry) Get(sid libacp.SessionID) (TurnStatus, bool)

Get returns the status of sid's active turn, or ok=false when none is active (never started, or already reaped).

func (*Registry) List

func (r *Registry) List() []TurnStatus

List returns a snapshot of every active turn, sorted by session id; a point-in-time report, not a transaction.

func (*Registry) ReapIdle

func (r *Registry) ReapIdle(_ context.Context) error

ReapIdle sweeps every session and tears down the ones that must not linger (belt 4): finished with no viewers, unwatched past its grace deadline, or past its hard deadline plus one grace window; always returns nil.

func (*Registry) Start

func (r *Registry) Start(sid libacp.SessionID, fn TurnFunc, viewer Viewer) (*Turn, bool, error)

Start ensures a turn is running for sid and attaches viewer to it: an in-flight turn is joined (started false), otherwise a fresh turn is started on a serve-rooted, deadline-bounded context (started true).

func (*Registry) Stop

func (r *Registry) Stop(sid libacp.SessionID) bool

Stop is the operator-surface twin of Cancel: it ends sid's turn and tears it down, reporting whether one was present. Idempotent.

type Result

type Result struct {
	// StopReason is the ACP stop reason the connected client's prompt resolves with.
	StopReason libacp.StopReason
	// Err is a genuine execution failure the client must see as a JSON-RPC error; nil
	// for a clean end or a cancellation (which resolves as StopReasonCancelled with no
	// error).
	Err error
	// Suspended marks a turn whose chain parked on a pending human approval:
	// not a failure (Err is nil), and status reads StateSuspended instead of
	// StateFinished until reaped.
	Suspended bool
	// ApprovalID is the durable approval a Suspended turn is parked on, communicated to
	// the client since StopReason cannot express it; empty unless Suspended.
	ApprovalID string
	// DroppedContentKinds are the prompt content kinds the turn could not forward to
	// the model, ridden on Result so a reattaching client still sees them.
	DroppedContentKinds []string
}

Result is the outcome of one turn, returned by a TurnFunc and read back by the connected viewer to resolve its libacp session/prompt RPC.

type Turn

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

Turn is a caller's handle to one session's turn: how a connected viewer awaits completion, detaches on connection drop, and (for session/cancel) cancels.

func (*Turn) Await

func (t *Turn) Await(ctx context.Context) (Result, bool)

Await blocks until the turn completes (Result, true) or ctx is done first (zero Result, false, without disturbing the turn); a convenience over selecting on Done.

func (*Turn) Cancel

func (t *Turn) Cancel()

Cancel ends this turn now and tears it down (the explicit session/cancel); any viewer awaiting completion unblocks with the cancelled result. Idempotent.

func (*Turn) Detach

func (t *Turn) Detach(viewerID string)

Detach removes viewerID from this turn's fan-out, never cancelling the turn while another viewer remains; detaching the last one starts the grace window (Belt 1). Idempotent.

func (*Turn) Done

func (t *Turn) Done() <-chan struct{}

Done is closed when the turn's chain ends; Result is then readable.

func (*Turn) Result

func (t *Turn) Result() Result

Result returns the turn's outcome, only meaningful after Done is closed.

func (*Turn) SessionID

func (t *Turn) SessionID() libacp.SessionID

SessionID is the ACP session this turn serves.

type TurnFunc

type TurnFunc func(ctx context.Context, emit func(ctx context.Context, n libacp.SessionNotification)) Result

TurnFunc runs one turn's work: ctx is the serve-rooted, hard-deadline-bounded turn context (belt 2), outliving the client that started it, and emit journals a session/update and fans it out to every attached viewer exactly once and in order.

type TurnStatus

type TurnStatus struct {
	SessionID libacp.SessionID `json:"sessionId"`
	StartedAt time.Time        `json:"startedAt"`
	// Deadline is the hard wall-clock time this turn is terminated at (belt 2).
	Deadline time.Time `json:"deadline"`
	// Viewers is how many viewers are attached right now (0 in StateGrace).
	Viewers int `json:"viewers"`
	// State is one of StateRunning / StateGrace / StateFinished.
	State string `json:"state"`
}

TurnStatus is a point-in-time snapshot of one active turn (Registry.List/Get); a value copy, mutating it never affects the live turn.

type Viewer

type Viewer interface {
	// ID uniquely identifies this viewer within a session; two viewers on one session
	// must not share an ID.
	ID() string

	// Deliver receives one turn event, in order (replayed backlog then live); it must
	// not block since it runs under the session lock, and its returned error is
	// advisory only.
	Deliver(ctx context.Context, ev Event) error
}

Viewer is a consumer attached to one session's live (and replayed) turn stream.

Jump to

Keyboard shortcuts

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