session

package
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: Oct 6, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package session bridges INTERACTIVE crush sessions into the same close-out pipeline pool agents get. Pool work is reviewed and reported on because the journal sees its completions; interactive sessions leave no task trail, so their commits could silently skip the second opinion. The bridge closes that gap: `tq session begin` records the session's opening in the journal, and `tq session close` attributes the session's commits (a `Crush-Session: <id>` git footer written by the committer), then directly enqueues ONE review task over the attributed commit range and ONE done-prompt status task. Both ride the ordinary pool: they are ordinary "review"/"status" tasks, drained by whichever agent-capable pool shares the database, dedup-keyed so re-closing a session never duplicates work.

Close is deliberately enqueue-only (the crush SessionEnd hook budget is ~2s): git attribution scan, two idempotent enqueues, one journal fact. The pool does everything else.

Loop safety is structural: session facts carry the synthetic "session:<id>" task identity, which no task row ever uses, so sweepers and workers cannot mistake session lifecycle for task state; the minted review is type "review" and the status task type "status", both exempt from re-review by the sweeper's agent-only rule.

Index

Constants

View Source
const Trailer = "Crush-Session"

Trailer is the git-footer key that attributes a commit to an interactive session: committers end their commit message with `Crush-Session: <id>` (same convention as the Task-Queue-ID footer pool agents write). Close attributes exactly the commits whose trailer value equals the session id.

Variables

This section is empty.

Functions

func Begin

func Begin(ctx context.Context, s Store, id, repo, project string) error

Begin records the session's opening as a session.opened fact. Refusing a double begin keeps one open epoch per id: begin, work, close — re-running begin for an already-open session is a mistake, not idempotent noise.

func PingRegistry added in v0.3.1

func PingRegistry(path, id, cwd string, now time.Time) error

PingRegistry appends one observation for id/cwd at now. The line is a single small write on an O_APPEND handle, so concurrent hook processes never interleave mid-line on POSIX.

func RegistryPath added in v0.3.1

func RegistryPath() (string, error)

RegistryPath resolves the registry file: $TQ_SESSION_REGISTRY if set, else <user cache dir>/tq/session-registry.jsonl.

func RewriteRegistry added in v0.3.1

func RewriteRegistry(path string, entries []RegistryEntry) error

RewriteRegistry replaces the file with entries (an empty slice empties it). The sweeper uses it to drop the sessions it closed.

func SyntheticTaskID

func SyntheticTaskID(id string) task.ID

SyntheticTaskID returns the session's task-ID-shaped lineage key: the TaskID on session facts and the ReviewedTask / Completed.TaskID lineage in the minted review and status payloads. It is namespaced so it can never collide with a real task ID ("session:" is not a hex ULID prefix).

Types

type CloseDetail

type CloseDetail struct {
	SessionID   string   `json:"session_id"`
	Repo        string   `json:"repo"`
	Project     string   `json:"project,omitempty"`
	Summary     string   `json:"summary,omitempty"`
	AllowDirty  bool     `json:"allow_dirty"`
	ReviewRange string   `json:"review_range,omitempty"`
	Commits     []Commit `json:"commits"`
	ReviewTask  string   `json:"review_task,omitempty"`
	StatusTask  string   `json:"status_task,omitempty"`
}

CloseDetail is the session.closed fact's detail: what the session

committed (as far as attribution can see), what close minted, and the clean-tree decision + review range it acted on (forensics: the fact answers "why did close tolerate a dirty tree / which commits did the reviewer see" without re-deriving them from the payload).

type CloseInput

type CloseInput struct {
	// ID is the interactive session's id (CRUSH_SESSION_ID). Required.
	ID string
	// Repo is the absolute repository path the session worked in. Required.
	Repo string
	// Project is the queue project the minted tasks belong to (convention:
	// the repo directory's base name).
	Project string
	// Summary is the operator's one-paragraph account of what the session
	// did; it becomes the review's bar and the status entry's item. Empty
	// falls back to a default text.
	Summary string
	// AllowDirty mirrors the pool's --allow-dirty stance into the minted
	// payloads' RequireClean: a multi-agent repo is effectively always
	// dirty, and a clean-tree-required task there requeues forever.
	AllowDirty bool
}

CloseInput carries one Close invocation's parameters.

type CloseResult

type CloseResult struct {
	// Commits are the session-attributed commits, oldest first.
	Commits []Commit
	// ReviewTask / StatusTask are the minted tasks; zero TaskIDs when no
	// commits were attributed (nothing minted).
	ReviewTask task.Task
	StatusTask task.Task
	// ReviewFresh / StatusFresh are false when the enqueue hit the dedup
	// key (the task already existed from an earlier close of this session).
	ReviewFresh bool
	StatusFresh bool
}

CloseResult reports what Close recorded and minted.

func Close

func Close(ctx context.Context, s Store, scanner GitScanner, in CloseInput) (CloseResult, error)

Close attributes the session's commits, records the session.closed fact, and directly enqueues the close-out: ONE review over the attributed range and ONE status report, both idempotent by dedup key. A session with no attributed commits records only the fact — there is nothing to review and nothing to report.

type Commit

type Commit = executor.Commit

The commit-attribution scanner lives in internal/executor (gitscan.go, shared with outcome derivation); these aliases keep the bridge's signatures stable. Aliases, not copies: the types are identical.

type FactSource added in v0.3.1

type FactSource interface {
	Facts(ctx context.Context, after int64, limit int) ([]journal.Fact, error)
}

FactSource is List's read side: page through journal facts in Seq order.

type GitLogScanner

type GitLogScanner = executor.GitLogScanner

The commit-attribution scanner lives in internal/executor (gitscan.go, shared with outcome derivation); these aliases keep the bridge's signatures stable. Aliases, not copies: the types are identical.

type GitScanner

type GitScanner = executor.GitScanner

The commit-attribution scanner lives in internal/executor (gitscan.go, shared with outcome derivation); these aliases keep the bridge's signatures stable. Aliases, not copies: the types are identical.

type GitScannerFunc

type GitScannerFunc = executor.GitScannerFunc

The commit-attribution scanner lives in internal/executor (gitscan.go, shared with outcome derivation); these aliases keep the bridge's signatures stable. Aliases, not copies: the types are identical.

type OpenDetail

type OpenDetail struct {
	SessionID string `json:"session_id"`
	Repo      string `json:"repo"`
	Project   string `json:"project,omitempty"`
}

OpenDetail is the session.opened fact's detail.

type PgrepOwner added in v0.3.1

type PgrepOwner struct{}

PgrepOwner is the production ProcessOwner: a session id counts as owned while any process whose full command line mentions it is alive.

func (PgrepOwner) Owns added in v0.3.1

func (PgrepOwner) Owns(ctx context.Context, id string) (bool, error)

Owns reports whether pgrep -f finds the session id on any live command line. A missing pgrep is an error, not a silent "unowned": closing a live session behind the sweeper's back is the failure mode this check exists to prevent.

type ProcessOwner added in v0.3.1

type ProcessOwner interface {
	Owns(ctx context.Context, id string) (bool, error)
}

ProcessOwner reports whether a live crush process owns a session id. The default implementation shells out to pgrep; tests stub it.

type RegistryEntry added in v0.3.1

type RegistryEntry struct {
	ID       string    `json:"id"`
	CWD      string    `json:"cwd"`
	LastSeen time.Time `json:"last_seen"`
}

RegistryEntry is one ping's observation: which session, working where, seen when. Snake_case keys: the file is a machine-payload wire format, appended by hooks and consumed by the sweeper (tagliatelle exclusion below).

func LoadRegistry added in v0.3.1

func LoadRegistry(path string) ([]RegistryEntry, error)

LoadRegistry folds the file into the latest entry per session id. Missing file means an empty registry, not an error. Blank and unparseable lines are skipped (a torn append must not poison the whole registry).

type SessionInfo added in v0.3.1

type SessionInfo struct {
	// ID is the session's CRUSH_SESSION_ID.
	ID string
	// Repo / Project mirror the session.opened fact's detail.
	Repo string
	// Project may be empty (older opens without the field).
	Project string
	// OpenedAt is the opened fact's timestamp.
	OpenedAt time.Time
}

SessionInfo describes one journal-recorded interactive session.

func List added in v0.3.1

func List(ctx context.Context, src FactSource) ([]SessionInfo, error)

List returns every session that has an opened fact but no closed fact — the crash-recovery surface: sessions that began and never closed, whether the process died or the close hook never ran. Newest opens first.

type Store

type Store interface {
	Enqueue(ctx context.Context, n task.New) (task.Task, error)
	AppendFact(ctx context.Context, f journal.Fact) error
	FactsForTask(ctx context.Context, id string, limit int) ([]journal.Fact, error)
}

Store is the bridge's view of the queue core: mint tasks, append the session's lifecycle facts, and read one identity's facts (the double-begin guard).

type SweepInput added in v0.3.1

type SweepInput struct {
	RegistryPath string
	StaleAfter   time.Duration
	Owner        ProcessOwner
	AllowDirty   bool
	Summary      string
	Now          time.Time
}

SweepInput configures one sweep pass.

type SweepOutcome added in v0.3.1

type SweepOutcome struct {
	Entry  RegistryEntry
	Closed bool
	Reason string
	Err    error
}

SweepOutcome records what the sweeper did to one registry session.

func Sweep added in v0.3.1

func Sweep(ctx context.Context, store Store, scanner GitScanner, input SweepInput) ([]SweepOutcome, error)

Sweep closes every registry session that is both quiet for StaleAfter and not owned by a live crush process, minting the ordinary close (replay-safe via the dedup keys) and rewriting the registry without the closed ids. Entries that are fresh, still owned, or whose close failed stay in the file.

Jump to

Keyboard shortcuts

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