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
- func Begin(ctx context.Context, s Store, id, repo, project string) error
- func PingRegistry(path, id, cwd string, now time.Time) error
- func RegistryPath() (string, error)
- func RewriteRegistry(path string, entries []RegistryEntry) error
- func SyntheticTaskID(id string) task.ID
- type CloseDetail
- type CloseInput
- type CloseResult
- type Commit
- type FactSource
- type GitLogScanner
- type GitScanner
- type GitScannerFunc
- type OpenDetail
- type PgrepOwner
- type ProcessOwner
- type RegistryEntry
- type SessionInfo
- type Store
- type SweepInput
- type SweepOutcome
Constants ¶
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 ¶
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
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
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 ¶
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 ¶
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.
type ProcessOwner ¶ added in v0.3.1
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.