Documentation
¶
Overview ¶
Package metadata defines the Projmux resource metadata model: the Project -> Window -> (Pane | Agent) ownership tree, its opaque identity (`metadata.uid`), its stable query key (`metadata.name`), and the atomic name-suffix allocator that backs both.
The package is pure. It performs no file, tmux, or process I/O; every environment-dependent input (clock, uid source, root-directory existence) arrives through the injected Mutator. Persistence and the tmux transport mirror live in internal/integrations/metadata.
Identity boundary: tmux raw identity (`$N`, `@N`, `%N`, `pane_title`, `window_name`) is adapter/status transport and is deliberately absent from this model. tmux ids are resolved to a uid by the adapter, never stored as identity here.
Field spelling in the on-disk registry follows the resource-model contract (`apiVersion`, `schemaVersion`, `metadata`, `displayName`, `ownerRef`, `anchorPaneRef`, `defaultShellPaneRef`, `spec`, `status`) rather than the snake_case spelling used by the older projmux state files. See docs/architecture.md.
Index ¶
- Constants
- Variables
- func AgentNameBase(explicit, provider string) string
- func AgentPanePromotionRefusal(reg *Registry, windowUID, paneUID string, observed LegacyPane) string
- func CanTransitionAgent(from, to AgentPhase) bool
- func ControlSessionNameBase(session string) string
- func DerivePaneDisplayTitle(agent, topic, command, rawTitle string) string
- func EqualTeardownPlans(left, right TeardownDecision) bool
- func IsUsageError(err error) bool
- func LegacyPaneNameSeed(pane LegacyPane, defaultShell string) string
- func LegacyWindowNameSeed(_ LegacyWindow) string
- func ManagedPaneNameBase(agentName string) string
- func MigrateRegistryWithEnvironment(set MigrationSet, reg Registry, env MigrationEnvironment) (Registry, bool, MigrationReport, error)
- func NeedsTerminationProjection(reg Registry, paneUID string) bool
- func NewGeneration() (string, error)
- func NewUID(kind Kind) (string, error)
- func NormalizeProvider(provider string) string
- func PaneNameBase(command, shell string) string
- func ProjectDisplayName(root string) string
- func ProjectNameBase(root string) string
- func SanitizeNameBase(seed string) string
- func ValidAgentActivationReason(reason string) bool
- func ValidAgentInteractionKind(kind AgentInteractionKind) bool
- func ValidAgentInteractionSource(source string) bool
- func ValidAgentPhase(phase AgentPhase) bool
- func ValidAgentProgressActivity(activity AgentProgressActivity) bool
- func ValidTerminationClassification(classification TerminationClassification) bool
- func ValidTerminationSource(source TerminationSource) bool
- func ValidateName(name string) error
- func WindowNameBase(explicit, command, shell string) string
- type AdoptionKind
- type AdoptionMatch
- type Agent
- type AgentActivation
- type AgentActivationState
- type AgentExit
- type AgentInteraction
- type AgentInteractionKind
- type AgentInteractionSource
- type AgentLinkKind
- type AgentLinkage
- type AgentPaneAuthority
- type AgentPhase
- type AgentProgress
- type AgentProgressActivity
- type AgentSessionObservation
- type AgentSessionRef
- func (r *AgentSessionRef) Clone() *AgentSessionRef
- func (r *AgentSessionRef) ConversationID() string
- func (r *AgentSessionRef) Empty() bool
- func (r *AgentSessionRef) Fields() [][2]string
- func (r *AgentSessionRef) SameConversation(other *AgentSessionRef) bool
- func (r *AgentSessionRef) Summary() string
- type AgentSpec
- type AgentStatus
- type AgentWorkspace
- type AntigravitySessionRef
- type AssetDisposition
- type BindingMatcher
- type BootstrapPane
- type BootstrapWindow
- type ClaudeSessionRef
- type CodexActivationBinding
- type CodexActivationObservation
- type CodexSessionRef
- type Condition
- type ControlSession
- type ControlSessionBinding
- type ControlSessionObservation
- type ControlSessionPane
- type ControlSessionSpec
- type ControlSessionStatus
- type ControlSessionWindow
- type CreateAgentOptions
- type ExternalAssetOutcome
- type ImportOrigin
- type ImportResult
- type ImportedAgent
- type ImportedPane
- type ImportedWindow
- type InputError
- type Kind
- type LegacyPane
- type LegacySession
- type LegacyWindow
- type Migration
- type MigrationEnvironment
- type MigrationRepair
- type MigrationReport
- type MigrationSet
- type Mutator
- func (m Mutator) AddPane(reg *Registry, windowUID string, declared BootstrapPane, ...) (Pane, error)
- func (m Mutator) AddWindow(reg *Registry, projectUID string, declared BootstrapWindow, ...) (Window, []Pane, error)
- func (m Mutator) AddWindowToManagedRoot(reg *Registry, ownerKind Kind, ownerUID string, declared BootstrapWindow, ...) (Window, []Pane, error)
- func (m Mutator) AdoptWindowDefaultShell(reg *Registry, windowUID, paneUID string) (Pane, bool, error)
- func (m Mutator) AttachAgentPane(reg *Registry, agentUID string, declared BootstrapPane, operationID string) (Pane, error)
- func (m Mutator) Begin(reg *Registry, operationID string) *Transaction
- func (m Mutator) BindCodexActivation(reg *Registry, obs CodexActivationObservation) (bool, error)
- func (m Mutator) BindControlSession(reg *Registry, observed ControlSessionObservation, ...) (ControlSessionBinding, error)
- func (m Mutator) BindProjectSession(reg *Registry, projectUID, sessionName string, live bool) (Project, error)
- func (m Mutator) ClearTermination(reg *Registry, paneUID, operationID string) (bool, error)
- func (m Mutator) CreateAgent(reg *Registry, windowUID string, opts CreateAgentOptions) (Agent, error)
- func (m Mutator) DeleteAgent(reg *Registry, agentUID string) error
- func (m Mutator) DeletePane(reg *Registry, paneUID string) error
- func (m Mutator) DeleteProject(reg *Registry, projectUID string) error
- func (m Mutator) DeleteWindow(reg *Registry, windowUID string) error
- func (m Mutator) EnsureWindowDefaultShell(reg *Registry, windowUID, defaultShell, operationID string) (Pane, bool, error)
- func (m Mutator) ImportLegacySession(reg *Registry, legacy LegacySession, defaultShell, operationID string, ...) (ImportResult, error)
- func (m Mutator) ImportOrphanPane(reg *Registry, windowUID string, observed LegacyPane, operationID string) (Pane, error)
- func (m Mutator) LinkAgentPane(reg *Registry, windowUID, paneUID string, observed LegacyPane, ...) (AgentLinkage, error)
- func (m Mutator) ObservePaneActivationRuntime(reg *Registry, paneUID, generation, runtimeID string) (bool, error)
- func (m Mutator) ObserveProjectRoots(reg *Registry) error
- func (m Mutator) ObserveRuntimeBindings(reg *Registry, observed RuntimeObservation)
- func (m Mutator) ObserveWindowDisplayName(reg *Registry, windowUID, displayName string) (Window, error)
- func (m Mutator) ObserveWindowRuntimeBinding(reg *Registry, windowUID, sessionID, runtimeID string) (Window, error)
- func (m Mutator) ProjectTermination(reg *Registry, in TerminationProjectionInput) (TerminationProjection, error)
- func (m Mutator) RebindAgentPane(reg *Registry, agentUID, paneUID string) (Pane, error)
- func (m Mutator) RebindProjectRoot(reg *Registry, projectUID, newRoot string) (Project, error)
- func (m Mutator) RecordAgentSessionRef(reg *Registry, agentUID string, obs AgentSessionObservation) (Agent, bool, error)
- func (m Mutator) RecordPaneActivation(reg *Registry, paneUID string, opts PaneActivationOptions) (Pane, error)
- func (m Mutator) RecordTermination(reg *Registry, receipt TerminationEvidence) (TerminationOutcome, error)
- func (m Mutator) RefineCodexActivation(reg *Registry, obs CodexActivationObservation) (bool, error)
- func (m Mutator) RegisterProject(reg *Registry, opts RegisterProjectOptions) (RegisterProjectResult, error)
- func (m Mutator) ReleaseAgentPane(reg *Registry, agentUID string, exit AgentExit, reason string) (Agent, error)
- func (m Mutator) RenameAgent(reg *Registry, agentUID, name string) (Agent, error)
- func (m Mutator) RenamePane(reg *Registry, paneUID, name string) (Pane, error)
- func (m Mutator) RenameProject(reg *Registry, projectUID, name string) (Project, error)
- func (m Mutator) RenameWindow(reg *Registry, windowUID, name string) (Window, error)
- func (m Mutator) SetAgentActivation(reg *Registry, agentUID string, state AgentActivationState, ...) (Agent, error)
- func (m Mutator) SetAgentInteraction(reg *Registry, agentUID string, kind AgentInteractionKind, source string) (Agent, error)
- func (m Mutator) SetAgentProgress(reg *Registry, agentUID, currentTurn string, progress AgentProgress) (Agent, bool, error)
- func (m Mutator) SetAgentTopic(reg *Registry, agentUID, topic string) (Agent, error)
- func (m Mutator) SetPaneDisplayTitle(reg *Registry, paneUID, agent, topic, command, rawTitle string) (Pane, error)
- func (m Mutator) TransitionAgent(reg *Registry, agentUID string, phase AgentPhase, reason string) (Agent, error)
- type NameReservation
- type ObjectMeta
- type OwnerRef
- type Pane
- type PaneActivation
- type PaneActivationOptions
- type PaneAgentCascadeDeletePlan
- type PaneRole
- type PaneSpec
- type PaneStatus
- type PaneTeardownEvidence
- type PaneTeardownEvidencePlan
- type Project
- type ProjectCascadeDeletePlan
- type ProjectDescendantUIDOutcome
- type ProjectFreshReplacementPlan
- type ProjectLifecycleAction
- type ProjectLifecycleOperation
- type ProjectLifecyclePlan
- type ProjectLifecyclePreconditions
- type ProjectLifecycleState
- type ProjectOpenAction
- type ProjectOpenPlan
- type ProjectOpenReason
- type ProjectOpenSource
- type ProjectReopenState
- type ProjectSpec
- type ProjectStartupWrite
- type ProjectStatus
- type ProjectUIDOutcome
- type RegisterProjectOptions
- type RegisterProjectResult
- type Registry
- func (r *Registry) Agent(uid string) (*Agent, bool)
- func (r *Registry) AgentsOf(windowUID string) []Agent
- func (r Registry) Clone() Registry
- func (r *Registry) ControlSession(uid string) (*ControlSession, bool)
- func (r *Registry) ControlSessionBySession(session string) (*ControlSession, bool)
- func (r Registry) Normalize() Registry
- func (r *Registry) Pane(uid string) (*Pane, bool)
- func (r Registry) PaneInWindow(windowUID, paneUID string) (*Pane, bool)
- func (r *Registry) PanesOf(ownerUID string) []Pane
- func (r *Registry) Project(uid string) (*Project, bool)
- func (r *Registry) ProjectByName(name string) (*Project, bool)
- func (r *Registry) ProjectByRoot(root string) (*Project, bool)
- func (r Registry) Validate() error
- func (r *Registry) Window(uid string) (*Window, bool)
- func (r Registry) WindowAnchor(windowUID string) (*Pane, bool)
- func (r Registry) WindowDefaultShell(windowUID string) (*Pane, bool)
- func (r *Registry) WindowsOf(projectUID string) []Window
- type ReopenIdentity
- type RootTeardownAction
- type RuntimeObservation
- type SchemaAction
- type SessionProjection
- type SnapshotProjectionPlan
- type StateError
- type TeardownAction
- type TeardownDecision
- type TeardownEvent
- type TeardownEventKind
- type TeardownGeneration
- type TeardownObservation
- type TeardownOwnerChain
- type TeardownReason
- type TerminationClassification
- type TerminationDisposition
- type TerminationEvidence
- type TerminationOutcome
- type TerminationProjection
- type TerminationProjectionInput
- type TerminationSource
- type Transaction
- type Window
- type WindowRootCascadeDeletePlan
- type WindowSpec
- type WindowStatus
Constants ¶
const ( // TerminationReasonIntentional is a canonical control action's own record. TerminationReasonIntentional = "managed pane was ended by a control action" // TerminationReasonNormal is a supervised exit status 0. TerminationReasonNormal = "managed process exited with status 0" // TerminationReasonKilled is an externally requested hangup with no paired // canonical control-action receipt. TerminationReasonKilled = "managed process was killed externally" // TerminationReasonAbnormal is a supervised non-zero exit or a signal. TerminationReasonAbnormal = "managed process exited abnormally" // TerminationReasonUnknown is an absence no receipt explains. // // It is the pre-existing sweep clause, kept verbatim. The sweep applied it // to every disappearance including the ones that are now proven; narrowing // it to the case it was always literally true of needs no new words. TerminationReasonUnknown = "managed pane is no longer live" )
Termination reason clauses. Each classification gets its own, because status.reason is the one line an operator reads next to the phase, and two classifications sharing a clause would make the phase the only thing separating them -- which is exactly the information the phase collapses.
const ( // ConditionMissingRoot marks a Project whose spec.root has disappeared. // The Project is never deleted or re-identified while it is set. ConditionMissingRoot = "MissingRoot" // ConditionMissingRuntime marks a Window or Pane whose uid is mirrored on // no live tmux object. It is the recorded *reason* a runtime object went // away, and it is deliberately not a deletion: the resource keeps its uid, // its name reservation, and its place in the owner tree, exactly the way // ConditionMissingRoot preserves a Project whose root vanished. // // It is never the source of a status read. Status is derived from a live // observation taken by the reading invocation (see selector.ObservedStatus); // this condition is the durable note the reconciler leaves behind so // `describe` can say why an object is offline long after the observation // that noticed it has been discarded. ConditionMissingRuntime = "MissingRuntime" ConditionTrue = "True" ConditionFalse = "False" )
const ( FallbackProjectNameBase = "project" FallbackWindowNameBase = "window" FallbackPaneNameBase = "pane" FallbackAgentNameBase = "agent" // FallbackControlSessionNameBase is used when the bound tmux session name // sanitizes to nothing usable as a query key. FallbackControlSessionNameBase = "control" )
Fallback name bases used when no better seed exists.
const ( AgentProgressPlanCap = 99 AgentProgressFilesCap = 999 AgentProgressItemsCap = 32 AgentProgressSource = "provider-control-plane" )
const APIVersion = "projmux.io/v1alpha1"
APIVersion is the resource API version string stamped on every resource.
const AgentInteractionFreshFor = 30 * time.Minute
AgentInteractionFreshFor is the maximum age at which a durable observation may be presented as current after a process restart. Provider hooks refresh active states; an abandoned response must not leave a live badge forever.
const AnnotationAgentTopic = "projmux.io/agent-topic"
AnnotationAgentTopic is the non-identifying annotation that carries an AI topic. Topics are never a name or a selector input.
const DeletedPaneMirrorPrefix = "deleted:"
DeletedPaneMirrorPrefix marks a live Pane whose Registry resource was durably deleted before its self-target kill could be queued. It is transport state, not a resource uid: every binding/import path must refuse it so the deleted Pane cannot be minted back under a new identity.
const ReasonRuntimeUnbound = "RuntimeUnbound"
ReasonRuntimeUnbound is the ConditionMissingRuntime reason. It records what was observed -- no live tmux object mirrors this uid -- and nothing about why, because nothing about why is observable: a window or pane that is gone leaves no exit status behind for a later inventory read to recover.
const SchemaVersion = 2
SchemaVersion is the current registry envelope version. A registry file carrying a higher value is rejected fail-closed; see schema.go.
Variables ¶
var ( // ErrNameConflict is an explicit --name / rename collision. It never gets // an implicit suffix; the operation fails with zero mutations. ErrNameConflict = errors.New("name is already in use") // ErrNameExhausted means the automatic suffix space for a base is full. ErrNameExhausted = errors.New("automatic name suffix space is exhausted") // ErrInvalidName marks a name that cannot be a stable query key. ErrInvalidName = errors.New("invalid resource name") // ErrInvalidRoot marks a root that is not an existing absolute directory. ErrInvalidRoot = errors.New("invalid project root") // ErrRootConflict marks a rebind onto a root already bound to another // Project uid. ErrRootConflict = errors.New("project root is already bound to another project") // ErrNotFound marks an unresolvable uid. ErrNotFound = errors.New("resource not found") // ErrInvalidPhase marks an Agent phase value or transition that is not in // the closed lifecycle model. ErrInvalidPhase = errors.New("invalid agent phase") // ErrInvalidRegistry marks a registry that violates a structural invariant. ErrInvalidRegistry = errors.New("invalid resource registry") // ErrSchemaTooNew marks a registry envelope newer than this build. It is // handled fail-closed: the caller refuses to read it as valid and performs // no write at all. ErrSchemaTooNew = errors.New("resource registry schema version is newer than supported") // ErrSchemaUnsupported marks an envelope version with no migration path. ErrSchemaUnsupported = errors.New("unsupported resource registry schema version") )
Sentinel causes. Every metadata failure wraps exactly one of these so callers classify with errors.Is instead of string matching.
Functions ¶
func AgentNameBase ¶
AgentNameBase applies the Agent naming order: explicit --name, normalized provider id, then "agent" when the provider is unknown.
func AgentPanePromotionRefusal ¶ added in v0.13.0
func AgentPanePromotionRefusal(reg *Registry, windowUID, paneUID string, observed LegacyPane) string
AgentPanePromotionRefusal returns the reason a runtime Agent marker cannot change one direct Window-owned shell Pane into an Agent-owned managed Pane.
defaultShellPaneRef is Registry ownership authority, not a runtime hint. A marker can link an additional Pane that projmux previously launched, but it cannot re-parent the canonical default shell that ref preserves. Anchor-only promotion semantics belong to a separate contract and remain unchanged. Refusing here is deliberately zero-write: no Agent is minted, no reservation changes scope, and the Pane uid, owner, role, and Window refs remain exact.
func CanTransitionAgent ¶
func CanTransitionAgent(from, to AgentPhase) bool
CanTransitionAgent reports whether from -> to is a permitted transition.
func ControlSessionNameBase ¶ added in v0.13.0
ControlSessionNameBase derives the ControlSession name base from the tmux session name it is bound to.
The session name is the only seed there is: a control session owns no root to take a basename from, and inventing one from $HOME would be the very path-derived identity this kind exists to avoid.
func DerivePaneDisplayTitle ¶
DerivePaneDisplayTitle computes the secondary, derived pane title from the Agent topic, a known interactive shell command, or the raw pane title. It is never a selector, an identity, or a Window name source, and the Pane metadata.name is deliberately not an input.
func EqualTeardownPlans ¶ added in v0.13.0
func EqualTeardownPlans(left, right TeardownDecision) bool
EqualTeardownPlans exists for property tests and controller callers that need to compare two delivery orders without depending on serialization.
func IsUsageError ¶
IsUsageError reports whether err was caused by invalid user input. The app layer converts these into the CLI usage-error exit code 2.
func LegacyPaneNameSeed ¶
func LegacyPaneNameSeed(pane LegacyPane, defaultShell string) string
LegacyPaneNameSeed derives the one-time Pane name base for a legacy pane: the existing @projmux_pane_label, then the command basename, then the configured shell basename, then "pane".
func LegacyWindowNameSeed ¶
func LegacyWindowNameSeed(_ LegacyWindow) string
LegacyWindowNameSeed returns the stable allocator base for a newly imported Window. Nothing observed from tmux -- window_name, Pane label, provider, command, shell, topic, or title -- is an identity input. The observed window_name is projected separately onto metadata.displayName.
func ManagedPaneNameBase ¶
ManagedPaneNameBase is the "<agent-name>-pane" base used for a Pane managed by an Agent.
func MigrateRegistryWithEnvironment ¶ added in v0.13.0
func MigrateRegistryWithEnvironment(set MigrationSet, reg Registry, env MigrationEnvironment) (Registry, bool, MigrationReport, error)
MigrateRegistryWithEnvironment lifts reg with an explicit migration set, filesystem and uid inputs, and the complete repair report.
func NeedsTerminationProjection ¶ added in v0.13.0
NeedsTerminationProjection reports whether one absent Pane still has work left for the projection.
It is the pre-transaction dirty check, and its job is to make a repeat pass cost zero write transactions rather than one that happens to change nothing. There are exactly two kinds of outstanding work: evidence that has not been recorded, and an Agent still bound to the dead Pane that can still move. A Pane with its evidence stored and no live binding left is finished.
func NewGeneration ¶ added in v0.13.0
NewGeneration mints one opaque activation generation.
A generation names one *materialization* of a Pane, not the Pane. The uid survives kill/recreate and resume; the generation does not, and that is the whole point: a receipt carries the generation it was launched with, so a receipt from the process a resume replaced can be recognized as stale instead of being applied to the Pane that now holds the uid.
func NewUID ¶
NewUID mints an opaque Projmux identity for kind. The value is independent of tmux lifecycle, of the resource's name, and of its root path, so it survives snapshot/restore and rebind unchanged.
Entropy comes from crypto/rand; callers that need determinism inject their own generator through Mutator.NewUID.
func NormalizeProvider ¶
NormalizeProvider maps a provider spelling onto its registered id (codex, claude, antigravity) and returns "" when it is unknown.
func PaneNameBase ¶
PaneNameBase applies the shell Pane naming order: command basename, configured shell basename, then "pane".
func ProjectDisplayName ¶
ProjectDisplayName derives the duplicate-allowed Project display name.
func ProjectNameBase ¶
ProjectNameBase derives the Project name base from its root basename. Project displayName seeds from the same value and may duplicate.
func SanitizeNameBase ¶
SanitizeNameBase turns an arbitrary seed into a valid name base. Runes that cannot appear in a name collapse into a single `-`. An empty result means the caller must fall back to the next seed in its priority order.
func ValidAgentActivationReason ¶ added in v0.11.1
func ValidAgentInteractionKind ¶ added in v0.11.1
func ValidAgentInteractionKind(kind AgentInteractionKind) bool
ValidAgentInteractionKind reports whether kind is in the public closed set.
func ValidAgentInteractionSource ¶ added in v0.11.1
ValidAgentInteractionSource reports whether source is safe durable metadata. Empty remains readable for registries written before sources were introduced, but every new mutation must choose one of the closed values.
func ValidAgentPhase ¶
func ValidAgentPhase(phase AgentPhase) bool
ValidAgentPhase reports whether phase is in the closed phase set.
func ValidAgentProgressActivity ¶ added in v0.13.0
func ValidAgentProgressActivity(activity AgentProgressActivity) bool
func ValidTerminationClassification ¶ added in v0.13.0
func ValidTerminationClassification(classification TerminationClassification) bool
ValidTerminationClassification reports whether classification is in the closed set.
func ValidTerminationSource ¶ added in v0.13.0
func ValidTerminationSource(source TerminationSource) bool
ValidTerminationSource reports whether source is in the closed set.
func ValidateName ¶
ValidateName rejects names that cannot serve as a stable, unambiguous query key. Case is preserved: `Projmux` and `projmux` are distinct names.
func WindowNameBase ¶
WindowNameBase applies the one-time Window naming order: explicit name, initial command basename, configured shell basename, then "window". Agent topic and raw pane title are deliberately excluded as name seeds.
Types ¶
type AdoptionKind ¶
type AdoptionKind string
AdoptionKind is the outcome of matching one live tmux object against the registry.
const ( // AdoptionRebind means the live object already carries a uid the registry // knows and the scope owns. Nothing is adopted and nothing is // re-identified; the caller reapplies the binding it already had. AdoptionRebind AdoptionKind = "rebind" // AdoptionAdopt means the live object carries no uid and the next eligible // unbound registry object of the scope, in creation order, takes it. AdoptionAdopt AdoptionKind = "adopt" // AdoptionUnmatched means the live object carries no uid and the scope has // no eligible registry object left. Both write paths create here, but not // for the same kinds. The import path mints whichever object is missing, // Window or Pane, because its `@projmux_project_path` anchor is what makes // minting a whole topology safe. The binding-repair path has no anchor, so // it mints a Pane only, and only inside a Window it has already paired -- // a live window it could not match is still left exactly as it was found. AdoptionUnmatched AdoptionKind = "unmatched" // AdoptionForeign means the live object carries a uid this registry has // never heard of. It is never adopted -- pointing an existing registry // object at it would be re-identification on the strength of a blank // lookup, which is precisely the heuristic uid merge the contract forbids. // // It is treated as unmatched by whichever path is allowed to create there, // which mints a fresh object for it rather than reusing one. That is not a // re-identification: no // registry uid changes, a new one is allocated. And it is the only reading // that does not strand the machine, because projmux itself produces // unknown uids -- a reconcile whose transaction is later rolled back by a // pre-create hook refusal has already written its allocated uids onto tmux, // and tmux options are not transactional. Refusing those outright would // leave the operator's windows permanently unmanageable. AdoptionForeign AdoptionKind = "foreign" // AdoptionRefused means the pairing is ambiguous in a way that has a real // registry object on the other side of it. The caller must neither adopt // nor create nor write: a refusal is a decision to leave a live tmux object // exactly as it was found, because claiming it would take a binding that // belongs to something else. AdoptionRefused AdoptionKind = "refused" )
type AdoptionMatch ¶
type AdoptionMatch struct {
Kind AdoptionKind
UID string
}
AdoptionMatch is one matching decision. UID is set only for AdoptionRebind and AdoptionAdopt.
func (AdoptionMatch) Matched ¶
func (m AdoptionMatch) Matched() bool
Matched reports whether the decision names a registry object the caller should write a binding for.
type Agent ¶
type Agent struct {
APIVersion string `json:"apiVersion"`
Kind Kind `json:"kind"`
Metadata ObjectMeta `json:"metadata"`
Spec AgentSpec `json:"spec"`
Status AgentStatus `json:"status"`
}
Agent is owned by a Window and owns its current managed Pane.
func (Agent) EffectiveInteraction ¶ added in v0.11.1
func (a Agent) EffectiveInteraction(now time.Time) AgentInteraction
EffectiveInteraction returns the current read model without erasing durable history. Non-running lifecycle, missing pane binding, and stale observations are all unknown and therefore cannot retain a response-complete badge.
type AgentActivation ¶ added in v0.11.1
type AgentActivation struct {
State AgentActivationState `json:"state"`
ObservedAt time.Time `json:"observedAt,omitzero"`
Source string `json:"source,omitempty"`
Reason string `json:"reason,omitempty"`
}
AgentActivation is bounded launch metadata. It never contains prompt text, provider credentials, or pane content.
func (AgentActivation) IsZero ¶ added in v0.11.1
func (a AgentActivation) IsZero() bool
type AgentActivationState ¶ added in v0.11.1
type AgentActivationState string
AgentActivationState separates Pane creation from initial-task activation.
const ( ActivationNotRequested AgentActivationState = "not_requested" ActivationPending AgentActivationState = "pending" ActivationAcknowledged AgentActivationState = "acknowledged" ActivationUnconfirmed AgentActivationState = "unconfirmed" ActivationReasonTimedOut = "provider activation acknowledgement timed out" ActivationReasonFailed = "provider activation acknowledgement failed" )
type AgentExit ¶
type AgentExit string
AgentExit classifies why an Agent stopped owning its managed Pane.
const ( // AgentExitNormal is a normal managed-Pane exit. It resolves to Offline. AgentExitNormal AgentExit = "normal" // AgentExitDeleted is an explicit pane deletion. It resolves to Offline. AgentExitDeleted AgentExit = "deleted" // AgentExitAbnormal is an abnormal managed-Pane exit. It resolves to Failed. AgentExitAbnormal AgentExit = "abnormal" // AgentExitLaunchFailure is a failure to launch. It resolves to Failed. AgentExitLaunchFailure AgentExit = "launch-failure" // AgentExitUnknown is a managed-Pane disappearance no receipt explains. It // resolves to Offline. // // Offline rather than Failed is a deliberate asymmetry. The phase is what an // operator reads to decide whether to resume, and an unproven Failed is // worse for that decision than an honest Offline: the evidence that the // answer is unproven is carried by status.lastTermination, where it can be // read without being mistaken for a diagnosis. AgentExitUnknown AgentExit = "unknown" )
func (AgentExit) Phase ¶
func (e AgentExit) Phase() (AgentPhase, bool)
Phase maps an exit classification onto the resulting Agent phase.
type AgentInteraction ¶ added in v0.11.1
type AgentInteraction struct {
Kind AgentInteractionKind `json:"kind"`
ObservedAt time.Time `json:"observedAt,omitzero"`
Source string `json:"source,omitempty"`
}
AgentInteraction is the last durable semantic observation. Readers use Agent.EffectiveInteraction to invalidate it for an Offline/Failed Agent or after the bounded freshness window; history may remain durable without being presented as current state.
func (AgentInteraction) IsZero ¶ added in v0.11.1
func (i AgentInteraction) IsZero() bool
type AgentInteractionKind ¶ added in v0.11.1
type AgentInteractionKind string
AgentInteractionKind is the provider-neutral closed interaction vocabulary. It is intentionally separate from AgentPhase: interaction describes what a live provider is waiting on, while phase describes whether the Agent has a live managed Pane at all.
const ( InteractionUnknown AgentInteractionKind = "unknown" InteractionIdle AgentInteractionKind = "idle" InteractionInProgress AgentInteractionKind = "in_progress" InteractionApprovalRequired AgentInteractionKind = "approval_required" InteractionInputRequired AgentInteractionKind = "input_required" InteractionResponseComplete AgentInteractionKind = "response_complete" )
func AgentInteractionKinds ¶ added in v0.11.1
func AgentInteractionKinds() []AgentInteractionKind
AgentInteractionKinds returns the closed semantic set in neutral order.
type AgentInteractionSource ¶ added in v0.11.1
type AgentInteractionSource string
AgentInteractionSource is the closed provenance vocabulary for durable semantic observations. It is intentionally not caller supplied: arbitrary strings here would turn status metadata into a prompt/credential sink.
const ( InteractionSourceManual AgentInteractionSource = "manual" InteractionSourceCompatibilityAI AgentInteractionSource = "compatibility-ai" InteractionSourceProviderHook AgentInteractionSource = "provider-hook" InteractionSourceProviderControl AgentInteractionSource = "provider-control-plane" InteractionSourceLifecycle AgentInteractionSource = "lifecycle" )
func AgentInteractionSources ¶ added in v0.11.1
func AgentInteractionSources() []AgentInteractionSource
type AgentLinkKind ¶
type AgentLinkKind string
AgentLinkKind is the outcome of linking one live agent pane to an Agent.
const ( // AgentLinkNone means the pane carries no agent authorship marker, or the // registry state refused the link. Nothing was written. AgentLinkNone AgentLinkKind = "none" // AgentLinkRebound means the Pane was already owned by an Agent. Only the // reverse pointer, status.paneRef, was repaired. AgentLinkRebound AgentLinkKind = "rebound" // AgentLinkAttached means an existing Agent recording the same provider // conversation took the Pane. AgentLinkAttached AgentLinkKind = "attached" // AgentLinkMinted means no Agent existed for this conversation and one was // created. AgentLinkMinted AgentLinkKind = "minted" )
type AgentLinkage ¶
type AgentLinkage struct {
Kind AgentLinkKind
// AgentUID is set for every kind except AgentLinkNone.
AgentUID string
// Promoted reports whether the Pane moved from Window-owned shell to
// Agent-owned managed.
Promoted bool
}
AgentLinkage reports one linkage decision.
func (AgentLinkage) Linked ¶
func (l AgentLinkage) Linked() bool
Linked reports whether the decision named an Agent.
type AgentPaneAuthority ¶ added in v0.13.0
type AgentPaneAuthority string
AgentPaneAuthority is the closed authorship table for one observed Pane. Presentation/provider markers are observations; only the distinct canonical launch receipt authorizes a Window-shell promotion.
const ( AgentPaneAuthorityNoMarker AgentPaneAuthority = "no-marker" AgentPaneAuthorityHookOnly AgentPaneAuthority = "hook-only" AgentPaneAuthorityLaunch AgentPaneAuthority = "launch-authorship" AgentPaneAuthorityAmbiguous AgentPaneAuthority = "ambiguous" )
func ResolveAgentPaneAuthority ¶ added in v0.13.0
func ResolveAgentPaneAuthority(observed LegacyPane) AgentPaneAuthority
ResolveAgentPaneAuthority classifies the complete launch/provider marker pair without consulting command, title, cwd, or names.
type AgentPhase ¶
type AgentPhase string
AgentPhase is the closed Agent lifecycle state set.
const ( // PhasePending is an Agent whose managed Pane has not started yet. PhasePending AgentPhase = "Pending" // PhaseRunning is an Agent with a live managed Pane. PhaseRunning AgentPhase = "Running" // PhaseOffline follows a normal managed-Pane exit or an explicit pane // deletion. The Agent survives as a resumable resource. PhaseOffline AgentPhase = "Offline" // PhaseFailed follows a launch failure or an abnormal exit. PhaseFailed AgentPhase = "Failed" )
func AgentPhases ¶
func AgentPhases() []AgentPhase
AgentPhases returns the closed phase set in lifecycle order.
type AgentProgress ¶ added in v0.13.0
type AgentProgress struct {
TurnRef string `json:"turnRef"`
Activity AgentProgressActivity `json:"activity,omitempty"`
PlanCompleted uint8 `json:"planCompleted,omitempty"`
PlanInProgress uint8 `json:"planInProgress,omitempty"`
PlanTotal uint8 `json:"planTotal,omitempty"`
PlanTruncated bool `json:"planTruncated,omitempty"`
ChangedFiles uint16 `json:"changedFiles,omitempty"`
FilesTruncated bool `json:"filesTruncated,omitempty"`
ActiveItemCount uint8 `json:"activeItemCount,omitempty"`
StartedAt time.Time `json:"startedAt,omitzero"`
ObservedAt time.Time `json:"observedAt,omitzero"`
Source string `json:"source"`
}
AgentProgress is the current exact-turn projection. It intentionally has no history and no fields capable of retaining provider content. TurnRef is not an independent identity: write sites must prove it is the exact turn in the current Pane activation binding before committing this value.
func (AgentProgress) IsZero ¶ added in v0.13.0
func (p AgentProgress) IsZero() bool
type AgentProgressActivity ¶ added in v0.13.0
type AgentProgressActivity string
AgentProgressActivity is the provider-neutral, content-free activity vocabulary for one active Agent turn. Providers may only select one of these values from a wire discriminator; names, commands, paths, and payload text never participate in the selection.
const ( ProgressPlanning AgentProgressActivity = "planning" ProgressCommand AgentProgressActivity = "command" ProgressFileChange AgentProgressActivity = "file-change" ProgressTool AgentProgressActivity = "tool" ProgressWebSearch AgentProgressActivity = "web-search" ProgressDelegation AgentProgressActivity = "delegation" ProgressImage AgentProgressActivity = "image" ProgressReview AgentProgressActivity = "review" ProgressCompaction AgentProgressActivity = "compaction" ProgressOther AgentProgressActivity = "other" )
func AgentProgressActivities ¶ added in v0.13.0
func AgentProgressActivities() []AgentProgressActivity
AgentProgressActivities returns the closed activity set in presentation order. Empty is reserved for a progress value with no current safe item.
type AgentSessionObservation ¶
type AgentSessionObservation struct {
Provider string
SessionID string
ThreadID string
TranscriptPath string
}
AgentSessionObservation is the raw identifier set one provider hook handed over. It is the ingest-side input shape; folding it onto the right union member is this package's job so the app layer never encodes provider shape knowledge of its own.
type AgentSessionRef ¶
type AgentSessionRef struct {
// Provider is the normalized provider id and is the union discriminator.
// It always matches the populated member.
Provider string `json:"provider"`
// ObservedAt is when the reporting hook was ingested. It is an observation
// timestamp about projmux, not a timestamp the provider supplied.
ObservedAt time.Time `json:"observedAt"`
Claude *ClaudeSessionRef `json:"claude,omitempty"`
Codex *CodexSessionRef `json:"codex,omitempty"`
Antigravity *AntigravitySessionRef `json:"antigravity,omitempty"`
}
AgentSessionRef is the durable pointer from an Agent to the provider conversation that Agent belongs to.
It lives in status, not spec, because nothing declares it. A provider hook reports it after the fact and the Agent keeps whatever was last observed — exactly the contract status.paneRef already has. The difference between the two is the question each answers: status.paneRef is the *current* managed Pane binding and is cleared the moment that Pane is released, while this ref answers "which conversation is this Agent" and deliberately survives ReleaseAgentPane, so an Offline Agent still knows what it was.
This is also not a duplicate of the tmux pane option `@projmux_ai_session_id`. That option is a *live routing index*: hook ingest scans the live pane list and matches on it to decide which pane an incoming event belongs to, so following pane lifetime is correct for it. This field is the durable conversation pointer and must outlive the Pane.
The shape is a per-provider discriminated union rather than one flat string or one shared bag of ids. Providers do not agree on what identifies a conversation: Claude reports a session id plus a transcript path, Codex reports a thread id and a session id, Antigravity reports a conversation id. Flattening them would either assert a false equivalence between a Codex thread id and a Claude session id, or leave a reader holding an opaque string with no way to tell which spelling it has. Provider is the discriminator and exactly one member is populated.
func NewAgentSessionRef ¶
func NewAgentSessionRef(obs AgentSessionObservation, observedAt time.Time) (*AgentSessionRef, bool)
NewAgentSessionRef folds one hook observation onto the provider member that owns it.
It reports false — rather than returning a half-populated ref — when the provider is not a known provider id or when the observation carries no usable conversation identifier at all. A hook that fires before the provider has a conversation id is normal, and recording an empty pointer would be worse than recording nothing.
func (*AgentSessionRef) Clone ¶
func (r *AgentSessionRef) Clone() *AgentSessionRef
Clone returns a deep copy. Every member is a pointer, so a shallow copy would let two registry snapshots alias the same conversation record.
func (*AgentSessionRef) ConversationID ¶
func (r *AgentSessionRef) ConversationID() string
ConversationID returns the identifier that names the conversation for the populated provider. It is the one value a human reads to recognize which conversation an Agent belongs to.
func (*AgentSessionRef) Empty ¶
func (r *AgentSessionRef) Empty() bool
Empty reports whether the ref points at no conversation at all.
func (*AgentSessionRef) Fields ¶
func (r *AgentSessionRef) Fields() [][2]string
Fields returns the populated provider's identifier set as ordered display key/value pairs. The keys differ per provider on purpose: that difference is the whole reason the shape is a union rather than one flat record.
func (*AgentSessionRef) SameConversation ¶
func (r *AgentSessionRef) SameConversation(other *AgentSessionRef) bool
SameConversation reports whether two refs point at the same conversation. ObservedAt is deliberately excluded: it records when projmux last saw the conversation, not which conversation it is, so a re-observation of the same ids is not a change and must not trigger a registry write.
func (*AgentSessionRef) Summary ¶
func (r *AgentSessionRef) Summary() string
Summary renders the provider-qualified conversation pointer for a one-line human projection.
type AgentSpec ¶
type AgentSpec struct {
Provider string `json:"provider,omitempty"`
Workspace AgentWorkspace `json:"workspace,omitzero"`
}
AgentSpec records the normalized provider id the Agent was created for.
type AgentStatus ¶
type AgentStatus struct {
Phase AgentPhase `json:"phase"`
PaneRef string `json:"paneRef,omitempty"`
SessionRef *AgentSessionRef `json:"sessionRef,omitempty"`
Interaction AgentInteraction `json:"interaction,omitzero"`
Activation AgentActivation `json:"activation,omitzero"`
Progress AgentProgress `json:"progress,omitzero"`
Reason string `json:"reason,omitempty"`
// LastTermination mirrors the receipt recorded against the Agent's current
// managed Pane, so the evidence survives the Pane resource a canonical
// delete removes.
LastTermination *TerminationEvidence `json:"lastTermination,omitempty"`
LastTransitionAt time.Time `json:"lastTransitionAt"`
}
AgentStatus tracks the lifecycle phase, the current managed Pane uid, and the durable pointer to the provider conversation the Agent belongs to.
PaneRef and SessionRef have deliberately different lifetimes. PaneRef is the *current* binding and is cleared by ReleaseAgentPane, DeletePane, and every non-Running transition. SessionRef is the durable conversation pointer and is never cleared by any of them: an Offline Agent that has lost its Pane still knows which conversation it is.
SessionRef is an optional pointer with omitempty. That is the whole read-compatibility story for registry files written before it existed: an absent key decodes to nil, a nil ref re-encodes to an absent key, and the document round-trips byte-identically. It was additive inside schemaVersion 1 and needed no migration step — bumping the envelope would have made every already installed build reject the file fail-closed with ErrSchemaTooNew, which is a hard downgrade break bought for nothing.
One conversation may be pointed at by more than one Agent. That is NOT prevented, and the registry treats it as legal state; see the note on NewAgentSessionRef and the Agent section of docs/agent-workflow.md for the reasoning.
type AgentWorkspace ¶ added in v0.11.1
type AgentWorkspace struct {
CWD string `json:"cwd"`
AdditionalWritableRoots []string `json:"additionalWritableRoots,omitempty"`
}
AgentWorkspace is the provider-neutral effective filesystem contract of one Agent launch. Project ownership remains on the owning Window; these paths only describe where the provider process is allowed to work.
func (AgentWorkspace) IsZero ¶ added in v0.11.1
func (w AgentWorkspace) IsZero() bool
IsZero lets old registry documents omit the additive workspace block.
type AntigravitySessionRef ¶
type AntigravitySessionRef struct {
ConversationID string `json:"conversationId"`
TranscriptPath string `json:"transcriptPath,omitempty"`
}
AntigravitySessionRef is Antigravity's conversation identity as its hook reports it. Antigravity carries a single conversation id and reports it in both the thread and session slots of the shared hook seam.
type AssetDisposition ¶ added in v0.13.0
type AssetDisposition string
AssetDisposition makes every external-asset cell explicit.
const ( AssetPreserve AssetDisposition = "preserve" AssetNotApplicable AssetDisposition = "not-applicable" )
type BindingMatcher ¶
type BindingMatcher struct {
// contains filtered or unexported fields
}
BindingMatcher pairs the live tmux objects seen during one reconciliation pass with registry objects.
It is pass-scoped, not session-scoped, and that is load bearing: a registry Window may be the runtime of exactly one live tmux window, so once a walk has claimed it -- by rebinding it or by adopting it -- no later walk in the same pass may claim it again, even from a different session that resolves to the same Project.
The zero value is not usable; build one with NewBindingMatcher.
func NewApprovedOrphanBindingMatcher ¶ added in v0.13.0
func NewApprovedOrphanBindingMatcher(runtime RuntimeObservation) *BindingMatcher
NewApprovedOrphanBindingMatcher permits only an exact unknown mirrored uid to reach AdoptionForeign. Blank D2 objects remain refused.
func NewBindingMatcher ¶
func NewBindingMatcher(runtime RuntimeObservation) *BindingMatcher
NewBindingMatcher builds a matcher over one pre-pass live-tmux observation.
An empty observation is the fail-closed reading: nothing is known to be bound elsewhere, so adoption is decided purely by scope, ordinal, and what this pass has already claimed. That is the same tolerance the rest of reconcile extends to a tmux server that is down -- it can never invent a binding, only decline to protect one that no longer exists.
func NewRepairBindingMatcher ¶
func NewRepairBindingMatcher(runtime RuntimeObservation) *BindingMatcher
NewRepairBindingMatcher builds the fail-closed matcher used by explicit resource reconciliation. Unknown live uids are diagnostic evidence, not an invitation to mint a replacement identity.
func (*BindingMatcher) Claim ¶
func (b *BindingMatcher) Claim(uid string)
Claim marks a registry uid as paired for the rest of the pass.
The import path calls it for every object it mints. Without that, a Window created for the third live tmux window of a session would still be an unclaimed candidate when the fourth is matched, and the fourth would adopt the Window that was just created for the third.
func (*BindingMatcher) Claimed ¶
func (b *BindingMatcher) Claimed(uid string) bool
Claimed reports whether this pass already paired a registry uid.
Agent runtime linkage reads it for the same reason the pane and window walks keep the set at all: one registry object is the runtime of at most one live tmux object, so an Agent a previous pane of this pass already attached to is no longer a candidate for the next one. Agent uids share this set with Window and Pane uids because uids are globally unique across kinds, which the registry's own Validate enforces.
func (*BindingMatcher) MatchPane ¶
func (b *BindingMatcher) MatchPane(reg *Registry, windowUID, observedUID string) AdoptionMatch
MatchPane decides which registry Pane of windowUID a live tmux pane is the runtime of. It is only ever called for a Window that was itself matched: a live tmux window nobody adopted contributes none of its panes.
func (*BindingMatcher) MatchWindow ¶
func (b *BindingMatcher) MatchWindow(reg *Registry, projectUID, observedUID string) AdoptionMatch
MatchWindow decides which registry Window of projectUID a live tmux window is the runtime of. observedUID is the `@projmux_window_uid` the live window already carries, empty when it carries none.
type BootstrapPane ¶
type BootstrapPane struct {
// Name is an explicit pane name. Empty means automatic naming.
Name string
// Command is the one-time name-derivation source for the Pane.
Command string
// CWD is the declared pane working directory.
CWD string
// Labels are creation-time key/value classification metadata. They are a
// creation option, never a selector input for the resource being created.
Labels map[string]string
}
BootstrapPane declares one Pane of a Project startup topology.
type BootstrapWindow ¶
type BootstrapWindow struct {
Name string
Command string
// Labels are creation-time key/value classification metadata.
Labels map[string]string
Panes []BootstrapPane
}
BootstrapWindow declares one Window of a Project startup topology. Every Window owns an initial Pane; an empty Panes list gets one default shell Pane.
type ClaudeSessionRef ¶
type ClaudeSessionRef struct {
SessionID string `json:"sessionId"`
TranscriptPath string `json:"transcriptPath,omitempty"`
}
ClaudeSessionRef is Claude's conversation identity as its hook reports it.
TranscriptPath is stored as a path only. Nothing in projmux reads the transcript contents to populate this ref, and nothing may start: parsing another tool's conversation store is permanently out of scope. Only what the hook hands over is recorded.
type CodexActivationBinding ¶ added in v0.13.0
type CodexActivationBinding struct {
ThreadID string `json:"threadId"`
TurnID string `json:"turnId,omitempty"`
}
CodexActivationBinding is the content-free native identity returned by the local app-server. TurnID is optional for an interactive create/resume that has not started a turn yet.
type CodexActivationObservation ¶ added in v0.13.0
type CodexActivationObservation struct {
AgentUID string
PaneUID string
Generation string
ThreadID string
TurnID string
}
CodexActivationObservation is the complete identity a native app-server create/resume or later exact event is allowed to commit. It is deliberately separate from AgentSessionObservation: turn identity is scoped to one Pane generation and never becomes a durable conversation pointer.
type CodexSessionRef ¶
type CodexSessionRef struct {
ThreadID string `json:"threadId,omitempty"`
SessionID string `json:"sessionId,omitempty"`
}
CodexSessionRef is Codex's conversation identity as its hook reports it.
Codex also reports a turn id. It is deliberately absent: a turn id addresses one turn inside the conversation and changes on every hook event, so it does not point at the conversation. Storing it would give the Agent a durable field that is stale the moment it is written.
type Condition ¶
type Condition struct {
Type string `json:"type"`
Status string `json:"status"`
Reason string `json:"reason,omitempty"`
Message string `json:"message,omitempty"`
FirstObservedAt time.Time `json:"firstObservedAt"`
LastTransitionAt time.Time `json:"lastTransitionAt"`
}
Condition records an observed resource condition with its first-observed timestamp preserved across repeat observations.
type ControlSession ¶ added in v0.13.0
type ControlSession struct {
APIVersion string `json:"apiVersion"`
Kind Kind `json:"kind"`
Metadata ObjectMeta `json:"metadata"`
Spec ControlSessionSpec `json:"spec"`
Status ControlSessionStatus `json:"status,omitzero"`
}
ControlSession is the app-owned control session as a Registry root.
It is the second thing a Window may be owned by, and the only root kind that carries no filesystem path. The tmux session it is bound to is named in spec.session and is matched exactly, never by basename, cwd, or a name heuristic: an operator's session that happens to be called `home` on a server projmux does not own is not this resource, and the marker read in internal/core/resourcegraph is what decides that at observation time.
It deliberately has no status.session projection either. A Project's session projection exists so an offline Project remembers which session name it will come back as; a control session's whole identity *is* that name, so a second copy of it in status would be a field that can disagree with spec.
func (ControlSession) Clone ¶ added in v0.13.0
func (c ControlSession) Clone() ControlSession
Clone returns a deep copy of the ControlSession.
type ControlSessionBinding ¶ added in v0.13.0
type ControlSessionBinding struct {
ControlSession ControlSession
// Reused is true when the exact session name already had a ControlSession.
Reused bool
Windows []ImportedWindow
Panes []ImportedPane
OperationID string
Created []string
}
ControlSessionBinding is the outcome of one control-session bind.
Windows and Panes report every object that was bound to a live tmux object, created or not, because the adapter owes all of them a mirror write. Created reports only what the transaction minted, so a rollback removes exactly what this operation brought into existence and never an adopted object that predates it. Both properties are the ImportResult contract, restated here so the two paths can never drift into different adapter expectations.
type ControlSessionObservation ¶ added in v0.13.0
type ControlSessionObservation struct {
// Session is the exact tmux session name. It is the control session's
// identity, matched verbatim, never by basename or cwd.
Session string
// Windows are the session's live windows in window_index ascending order,
// which is the ordinal adoption aligns against.
Windows []ControlSessionWindow
}
ControlSessionObservation is one live observation of an app-owned control session: the exact session name plus its windows and panes in tmux order.
It is a value, not a reader, for the same reason resourcegraph.Inventory is: the binding decision stays pure and a test can state a machine state directly instead of scripting tmux output.
The caller is responsible for having proved the session is a control session before it builds one of these. Two facts decide that and neither is observable from this struct: the server carries `@projmux_app=1`, and the exact session's `@projmux_session_role` is exactly `control`. See internal/app/control_session.go for the writer-side guard and internal/core/resourcegraph for the reader-side one.
type ControlSessionPane ¶ added in v0.13.0
type ControlSessionPane struct {
// UID is the `@projmux_pane_uid` the live pane already carries, empty when
// it carries none.
UID string
// Name is the `@projmux_pane_label` mirror, the highest-priority name seed
// for a minted Pane.
Name string
// Command is `pane_current_command`, the one-time name-derivation source.
Command string
// Title is `pane_title`, a derived display source only.
Title string
// CWD is `pane_current_path`. It is recorded verbatim; a control session has
// no root to fall back to, so an unreadable cwd stays empty rather than
// borrowing $HOME.
CWD string
}
ControlSessionPane is one observed pane of a control session window.
type ControlSessionSpec ¶ added in v0.13.0
type ControlSessionSpec struct {
Session string `json:"session"`
}
ControlSessionSpec names the tmux session this control session is bound to.
There is no Root field and there must never be one. See KindControlSession.
type ControlSessionStatus ¶ added in v0.13.0
type ControlSessionStatus struct {
Conditions []Condition `json:"conditions,omitempty"`
}
ControlSessionStatus carries the observed conditions of one control session.
It is `omitzero` for the same read-compatibility reason WindowStatus is: a control session that has never carried a condition serializes without the key, so the block was additive when introduced in schemaVersion 1.
type ControlSessionWindow ¶ added in v0.13.0
type ControlSessionWindow struct {
// DisplayName is the tmux window_name. It is projected onto the
// duplicate-allowed metadata.displayName and is never a name seed.
DisplayName string
RuntimeSessionID string
RuntimeID string
// UID is the `@projmux_window_uid` the live window already carries, empty
// when it carries none.
UID string
// Panes are the window's live panes in pane order.
Panes []ControlSessionPane
}
ControlSessionWindow is one observed window of a control session.
type CreateAgentOptions ¶
type CreateAgentOptions struct {
// Name is an explicit --name. A collision fails with ErrNameConflict.
Name string
// Provider is the raw provider spelling; it is normalized to codex,
// claude, or antigravity, and falls back to the "agent" name base when
// unknown.
Provider string
DisplayName string
Labels map[string]string
Annotations map[string]string
Workspace AgentWorkspace
Activation AgentActivationState
OperationID string
}
CreateAgentOptions is the input to offline Agent creation.
type ExternalAssetOutcome ¶ added in v0.13.0
type ExternalAssetOutcome struct {
RootDirectory AssetDisposition
GitMetadata AssetDisposition
Worktrees AssetDisposition
SnapshotBytes AssetDisposition
}
ExternalAssetOutcome is the non-Registry boundary of a root decision.
type ImportOrigin ¶
type ImportOrigin string
ImportOrigin records how one reported Window or Pane came to be reported.
The distinction matters to exactly two callers and for opposite reasons. The tmux adapter treats all three identically -- every reported object gets its binding written through the one mirror write path, because a created object has no binding yet and an adopted or rebound one has a binding that may have been wiped. The transaction ledger treats only ImportCreated as created: an adopted object existed before this operation, so rolling the operation back must not delete it.
const ( // ImportCreated is a Window or Pane this import minted. ImportCreated ImportOrigin = "created" // ImportAdopted is a pre-existing registry object this import paired with a // live tmux object that carried no uid. ImportAdopted ImportOrigin = "adopted" // ImportRebound is a pre-existing registry object whose live tmux object // still carried its uid. Reported so its binding is reapplied. ImportRebound ImportOrigin = "rebound" )
type ImportResult ¶
type ImportResult struct {
Project Project
ProjectReused bool
Windows []ImportedWindow
Panes []ImportedPane
Agents []ImportedAgent
OperationID string
Created []string
}
ImportResult is the outcome of one legacy session import.
Windows and Panes report every object the import bound to a live tmux object, created or not, because the adapter owes all of them a mirror write. Created reports only what the transaction minted, so a rollback still removes exactly what this operation brought into existence and never an adopted object that predates it.
type ImportedAgent ¶
ImportedAgent reports one Agent created by a legacy import.
type ImportedPane ¶
type ImportedPane struct {
UID string
Name string
WindowIndex int
PaneIndex int
// Origin distinguishes a minted Pane from an adopted or rebound one.
Origin ImportOrigin
}
ImportedPane reports one Pane a legacy import bound to a live tmux pane.
type ImportedWindow ¶
type ImportedWindow struct {
UID string
Name string
SourceIndex int
// NeedsAutomaticRenameOff is true for every managed Window, so a
// focused-Pane change can never overwrite the Window name.
NeedsAutomaticRenameOff bool
// Origin distinguishes a minted Window from an adopted or rebound one.
Origin ImportOrigin
}
ImportedWindow reports one Window a legacy import bound to a live tmux window, together with the transport work the tmux adapter still owes it.
type InputError ¶
InputError is a typed metadata error caused by invalid user input. It carries the operation, a human-readable detail, and the sentinel cause.
func (*InputError) Error ¶
func (e *InputError) Error() string
Error renders "<op>: <detail>" with the sentinel cause appended when the detail does not already state it.
func (*InputError) MetadataUsageError ¶
func (e *InputError) MetadataUsageError() bool
MetadataUsageError marks this error as a usage error for IsUsageError.
func (*InputError) Unwrap ¶
func (e *InputError) Unwrap() error
Unwrap exposes the sentinel cause to errors.Is.
type Kind ¶
type Kind string
Kind is the closed set of Projmux resource kinds. A persistent tmux Session is intentionally absent: it is a 1:1 runtime projection of a Project stored in Project.status.session and owns no uid, name, or ownerRef of its own.
const ( KindProject Kind = "Project" KindWindow Kind = "Window" KindPane Kind = "Pane" KindAgent Kind = "Agent" // KindControlSession is the app-owned control session -- the Home session // `projmux shell` opens -- as an OWNER of Windows and Panes. // // It is a fifth root kind rather than a Project with a special role for one // reason that no flag can reproduce: a control session owns no filesystem // path, and the type below has no field to put one in. Every path-based // surface projmux has (managed roots, trust, rebind, cwd defaults, // ProjectByRoot) reads Project.Spec.Root, so a control session cannot leak // into any of them by omission, by a forgotten filter, or by a later // refactor -- the leak would not compile. $HOME is therefore permanently // incapable of becoming a Project or a managed root through this kind. KindControlSession Kind = "ControlSession" )
type LegacyPane ¶
type LegacyPane struct {
Label string
Provider string
// LaunchAuthorship is the raw canonical Projmux launch receipt. Only the
// exact value "1" together with a provider authorizes topology promotion.
// It is deliberately independent of hook/provider presentation markers.
LaunchAuthorship string
// Topic is carried for the derived display title only. It is never a name
// seed for a Pane, a Window, or an Agent.
Topic string
Command string
Title string
CWD string
// UID is the `@projmux_pane_uid` the live tmux pane already carries, empty
// when it carries none. It is a binding, never a name seed: adoption reads
// it to tell "already ours" from "blank", and refuses either way to
// re-identify anything.
UID string
// SessionID and ThreadID are the provider conversation identifiers the AI
// routes wrote onto the live pane. They are read for exactly one decision --
// whether an Agent that already records the same conversation in
// status.sessionRef is the one this live pane belongs to -- and are never a
// name seed, a selector, or a uid.
SessionID string
ThreadID string
}
LegacyPane is one observed pre-v2 tmux pane. Label is the existing @projmux_pane_label value, which is the migration seed for the Pane *name*; in v2 vocabulary it is a name, not a label. metadata.labels stays reserved for key/value classification.
type LegacySession ¶
type LegacySession struct {
Session string
Root string
Windows []LegacyWindow
}
LegacySession is one observed pre-v2 tmux session anchored at a project root.
type LegacyWindow ¶
type LegacyWindow struct {
Name string
// RuntimeSessionID and RuntimeID are the exact live $N/@N owner binding.
RuntimeSessionID string
RuntimeID string
// AutomaticRename is the observed window-scoped automatic-rename value.
AutomaticRename bool
// UID is the `@projmux_window_uid` the live tmux window already carries,
// empty when it carries none. Same role as LegacyPane.UID.
UID string
Panes []LegacyPane
}
LegacyWindow is one observed pre-v2 tmux window.
type Migration ¶
type Migration func(*Registry, MigrationEnvironment, *MigrationReport) error
Migration lifts one registry document from an envelope version to the next.
type MigrationEnvironment ¶ added in v0.13.0
type MigrationEnvironment struct {
DirectoryExists func(string) (bool, error)
NewUID func(Kind) (string, error)
}
MigrationEnvironment supplies the only machine-dependent inputs a schema repair may use. Keeping them injected leaves this package pure and gives the production adapter and byte-fixed golden tests the exact same algorithm.
type MigrationRepair ¶ added in v0.13.0
type MigrationRepair struct {
Action string `json:"action"`
Kind Kind `json:"kind"`
UID string `json:"uid"`
Field string `json:"field"`
From string `json:"from,omitempty"`
To string `json:"to,omitempty"`
InformationLoss bool `json:"informationLoss"`
}
MigrationRepair is one stable, operator-reportable change made while an old envelope is lifted. InformationLoss distinguishes replacement of stale declared data from additive repair.
type MigrationReport ¶ added in v0.13.0
type MigrationReport struct {
FromVersion int
ToVersion int
Repairs []MigrationRepair
}
MigrationReport records every repair made by a migration in Registry order.
func (MigrationReport) InformationLossCount ¶ added in v0.13.0
func (r MigrationReport) InformationLossCount() int
InformationLossCount returns the number of repairs that replaced declared legacy information rather than only adding a missing invariant field.
func (MigrationReport) String ¶ added in v0.13.0
func (r MigrationReport) String() string
String renders a deterministic report suitable for an operator-facing migration result or archived test evidence.
type MigrationSet ¶
MigrationSet maps a source envelope version onto the step that lifts it to the next version.
func ProductionMigrationSet ¶ added in v0.13.0
func ProductionMigrationSet() MigrationSet
ProductionMigrationSet returns a copy of the shipped migration registry. Integration tests extend this copy with a pre-v1 fixture step without replacing the production v1 repair they are meant to exercise.
type Mutator ¶
type Mutator struct {
// Now supplies operation timestamps. Defaults to time.Now.
Now func() time.Time
// NewUID mints opaque identities. Defaults to the crypto/rand generator.
NewUID func(Kind) (string, error)
// DirExists reports whether a path is an existing directory. It has no
// default: root lifecycle operations require the caller to supply the
// filesystem probe explicitly.
DirExists func(string) (bool, error)
}
Mutator applies metadata operations to a registry. Every environment dependency is injected so the core stays free of I/O and tests stay deterministic.
func (Mutator) AddPane ¶
func (m Mutator) AddPane(reg *Registry, windowUID string, declared BootstrapPane, defaultShell, operationID string) (Pane, error)
AddPane creates one offline shell Pane inside an existing Window.
func (Mutator) AddWindow ¶
func (m Mutator) AddWindow(reg *Registry, projectUID string, declared BootstrapWindow, defaultShell, operationID string) (Window, []Pane, error)
AddWindow creates one offline Window plus its initial Pane below an existing Project. No tmux window is created.
func (Mutator) AddWindowToManagedRoot ¶ added in v0.13.0
func (m Mutator) AddWindowToManagedRoot(reg *Registry, ownerKind Kind, ownerUID string, declared BootstrapWindow, defaultShell, defaultCWD, operationID string) (Window, []Pane, error)
AddWindowToManagedRoot creates one offline Window plus its initial Pane below an exact Project or ControlSession owner. Generated Window intents use this owner-explicit entrypoint; the public AddWindow contract remains Project-only.
func (Mutator) AdoptWindowDefaultShell ¶ added in v0.13.0
func (m Mutator) AdoptWindowDefaultShell(reg *Registry, windowUID, paneUID string) (Pane, bool, error)
AdoptWindowDefaultShell installs paneUID as a Window's optional default shell only when the ref is empty. The candidate must be a direct Window-owned shell Pane; the role-agnostic anchor is never inferred or changed.
func (Mutator) AttachAgentPane ¶
func (m Mutator) AttachAgentPane(reg *Registry, agentUID string, declared BootstrapPane, operationID string) (Pane, error)
AttachAgentPane creates the managed Pane owned by an Agent and moves the Agent to Running. The Pane name uses the "<agent-name>-pane" base inside the Agent's owner scope.
func (Mutator) Begin ¶
func (m Mutator) Begin(reg *Registry, operationID string) *Transaction
Begin opens a transaction against reg with the supplied operation id.
func (Mutator) BindCodexActivation ¶ added in v0.13.0
func (m Mutator) BindCodexActivation(reg *Registry, obs CodexActivationObservation) (bool, error)
BindCodexActivation commits the first native binding for one exact running Agent materialization. It also records the returned thread as the Agent's durable Codex sessionRef. A create may fill an empty ref; a resume may only reuse the already stored same thread.
func (Mutator) BindControlSession ¶ added in v0.13.0
func (m Mutator) BindControlSession(reg *Registry, observed ControlSessionObservation, defaultShell, operationID string, binder *BindingMatcher) (ControlSessionBinding, error)
BindControlSession converts one observed app-owned control session into Registry resources and reattaches the ones that already exist.
It is convergent by construction, which is what makes it safe to run on every `projmux shell` entry:
- The exact session name reuses the same ControlSession uid. Nothing else merges uids, and no observation ever renames or re-roots an existing one.
- Windows and Panes resolve through the shared adoption matcher, so a live object that still carries a known uid is rebound, an unmirrored live object adopts the next unbound Registry object of the same owner in creation order, and only a genuinely surplus live object mints a new resource. A mirror write that failed halfway through a previous pass therefore repairs itself instead of duplicating a Window on the next pass.
- Nothing is deleted, pruned, renamed, or re-identified. A Window whose tmux window is gone keeps its uid and its name reservation exactly as a MissingRoot Project does.
binder carries the adoption decision across a whole reconciliation pass so one Registry Window is never handed to two live tmux windows. A nil binder gets a private one over an empty observation, which is the correct reading for a caller binding a single session in isolation.
func (Mutator) BindProjectSession ¶
func (m Mutator) BindProjectSession(reg *Registry, projectUID, sessionName string, live bool) (Project, error)
BindProjectSession records the 1:1 persistent tmux session projection. It never changes metadata.uid, so a Project keeps its identity across runtime creation, teardown, and recreation.
func (Mutator) ClearTermination ¶ added in v0.13.0
ClearTermination removes a receipt this exact operation wrote.
It is the compensating half of a control action that recorded its intent and then failed before carrying it out. The operation id guard is what makes the compensation safe: a receipt written by anything else -- another delete, a supervisor observing a real exit in the meantime -- is left alone, so a failed delete can never erase evidence it did not produce.
func (Mutator) CreateAgent ¶
func (m Mutator) CreateAgent(reg *Registry, windowUID string, opts CreateAgentOptions) (Agent, error)
CreateAgent creates an Agent owned by windowUID in the Pending phase. The managed Pane is attached separately.
func (Mutator) DeleteAgent ¶
DeleteAgent removes an Agent and its managed Panes.
func (Mutator) DeletePane ¶
DeletePane removes one Pane resource. Deleting the managed Pane of a running Agent moves that Agent to Offline; the Agent resource survives.
func (Mutator) DeleteProject ¶
DeleteProject removes a Project and every descendant resource.
func (Mutator) DeleteWindow ¶
DeleteWindow is the explicit canonical Window delete. It removes a Window and every descendant Pane and Agent while preserving its Project or ControlSession root. A Project with no remaining Window keeps its uid, root, session name, pin, and snapshot ownership as a valid closed identity.
func (Mutator) EnsureWindowDefaultShell ¶ added in v0.13.0
func (m Mutator) EnsureWindowDefaultShell(reg *Registry, windowUID, defaultShell, operationID string) (Pane, bool, error)
EnsureWindowDefaultShell returns the Window's existing optional default shell or allocates one direct Window-owned shell without changing a valid anchor. The allocation is intended for a shell-required transaction that already owns its Registry working copy; callers remain responsible for runtime creation and outer rollback.
func (Mutator) ImportLegacySession ¶
func (m Mutator) ImportLegacySession(reg *Registry, legacy LegacySession, defaultShell, operationID string, binder *BindingMatcher) (ImportResult, error)
ImportLegacySession converts one observed pre-v2 tmux session into Projmux resources, and reattaches the ones that already exist.
Name collisions during import are automatic, not explicit, so they receive the lowest free suffix rather than failing: two projects whose roots share a basename become `name` and `name-1`, and two agents of the same provider in one window become `codex` and `codex-1`. An exact saved root that reappears reuses the same Project uid; nothing else merges uids.
binder carries the adoption decision across the whole reconciliation pass so one registry Window is never handed to two live tmux windows. A nil binder gets a private one over an empty observation, which is the right reading for a caller importing a single session in isolation: adoption still happens, and nothing is known to be bound elsewhere.
func (Mutator) ImportOrphanPane ¶
func (m Mutator) ImportOrphanPane(reg *Registry, windowUID string, observed LegacyPane, operationID string) (Pane, error)
ImportOrphanPane mints the shell Pane a live tmux pane has never had, inside a Window that is already paired with a live tmux window.
Adoption alone cannot reach this state. Adoption pairs a live tmux object with a registry object that already exists, and a pane produced by a route that registers nothing -- the non-resource `projmux create agent` bridge is the measured one -- has no registry counterpart to pair with. Those panes stayed permanently unbound, so `projmux delete pane` with no selector kept refusing with "carries no @projmux_pane_uid" in the operator's own active pane. Something has to be created before a uid exists to mirror back.
The name base is FallbackPaneNameBase, uniquified by the registry's own allocator, and deliberately *not* LegacyPaneNameSeed. That seed reads `pane_current_command`, which changes the moment the operator runs something else in the pane, and the product contract is that metadata.name is never derived from a runtime attribute. What the runtime reported goes to status.displayTitle instead -- the duplicate-allowed field that exists for exactly this, and the same field the import path fills.
No Agent is minted, whatever the pane happens to be running. Reading the pane options or its title to decide that a Window owes an Agent resource would be a content heuristic deciding registry topology, which is the judgment bindLegacyPaneTx already records for an adopted pane. Agent phase belongs to its own track.
Nothing existing is touched: no uid is changed, merged, or reassigned, and no Window spec is rewritten. This adds one Pane and stops.
func (Mutator) LinkAgentPane ¶
func (m Mutator) LinkAgentPane(reg *Registry, windowUID, paneUID string, observed LegacyPane, binder *BindingMatcher, operationID string) (AgentLinkage, error)
LinkAgentPane connects one already-bound registry Pane to the Agent resource whose runtime the live tmux pane is.
It is idempotent by construction. The second pass over the same machine finds the Pane already Agent-owned and does nothing but reassert the reverse pointer, so a reconciler that runs on every mutation route converges instead of accumulating Agents.
windowUID is the Window the Pane was bound inside. It is the whole scope: the candidate Agents are that Window's Agents and no others, so an Agent can never be pulled across a Project boundary. The caller reached this Window by pairing it against a live tmux window inside one resolved Project, so the boundary is structural here rather than checked.
func (Mutator) ObservePaneActivationRuntime ¶ added in v0.13.0
func (m Mutator) ObservePaneActivationRuntime(reg *Registry, paneUID, generation, runtimeID string) (bool, error)
ObservePaneActivationRuntime records the exact tmux handle one activation generation was materialized onto.
It is guarded by the generation rather than by the Pane uid alone: a handle observed for a generation the registry has already replaced describes a process that no longer holds the Pane, and writing it would make the diagnostic point at the wrong runtime object. A mismatch is a no-op.
func (Mutator) ObserveProjectRoots ¶
ObserveProjectRoots refreshes the MissingRoot condition for every Project. A missing root never deletes or re-identifies a Project and never releases its name reservations; a returning root recovers the same uid and clears the condition.
func (Mutator) ObserveRuntimeBindings ¶
func (m Mutator) ObserveRuntimeBindings(reg *Registry, observed RuntimeObservation)
ObserveRuntimeBindings refreshes the MissingRuntime condition of every Window and Pane against one live-tmux observation.
This is the reconciler's half of the observation contract. It records *why* a runtime object is gone so the reason survives the invocation that noticed it; the live/offline answer itself is derived per read from the observation and never from what this writes. That split is deliberate: a stored liveness bool is what made Windows and Panes report live against nothing at all.
It is an inventory diff, not an event handler, which is what lets it converge with no hook firing at all: an object whose uid is mirrored on no live tmux object is judged orphan on the very next pass, and a re-bound object clears the condition on the pass after that.
Nothing here deletes, prunes, or re-identifies a resource, and nothing releases a name reservation. A Window or Pane whose tmux object disappeared stays queryable forever, exactly like a MissingRoot Project.
func (Mutator) ObserveWindowDisplayName ¶
func (m Mutator) ObserveWindowDisplayName(reg *Registry, windowUID, displayName string) (Window, error)
ObserveWindowDisplayName projects one live tmux window_name onto the duplicate-allowed, non-identifying metadata.displayName field. It never changes the Window uid, stable name, owner, or name reservation.
func (Mutator) ObserveWindowRuntimeBinding ¶ added in v0.13.0
func (m Mutator) ObserveWindowRuntimeBinding(reg *Registry, windowUID, sessionID, runtimeID string) (Window, error)
ObserveWindowRuntimeBinding records the exact live tmux owner pair for one managed Window. The binding is retained when a later inventory reports the Window missing: teardown hooks need the last positive $N/@N observation to pair pane-exited with window-unlinked without consulting current client context after the Window is gone.
func (Mutator) ProjectTermination ¶ added in v0.13.0
func (m Mutator) ProjectTermination(reg *Registry, in TerminationProjectionInput) (TerminationProjection, error)
ProjectTermination is the one lifecycle transition of the exit reconciler.
It consumes the receipt the Pane already stores, or records an unknown one when it stores none, and applies the single transition that evidence implies:
- a shell Pane keeps its logical existence and gains the evidence. A runtime object going away is not a statement about desired topology, and deleting the resource here would make a crashed shell indistinguishable from one the operator asked to remove.
- an Agent's *current* managed Pane is unbound by status only: the Agent moves to the phase the evidence implies, drops status.paneRef, and keeps both status.sessionRef and the Pane row. Runtime observation is evidence of disappearance, not canonical delete authority.
- an Agent-owned Pane the Agent no longer binds is evidence only. The Agent has since been relaunched onto another Pane, and touching it here would apply a dead process's exit to a live one.
It is idempotent by construction. The unknown receipt it writes is the thing that makes it so: a second pass over the same disappearance finds that document already stored, RecordTermination reports it a duplicate, and nothing is written. An absence with no stored value would be re-projected forever.
It performs no runtime call of any kind and can start nothing.
func (Mutator) RebindAgentPane ¶ added in v0.13.0
RebindAgentPane makes a retained Agent-owned Pane the Agent's current managed Pane again without changing either UID. Runtime disappearance retains Pane rows as evidence; materialization may therefore reuse the exact row instead of deleting the Window anchor and allocating a replacement identity.
func (Mutator) RebindProjectRoot ¶
RebindProjectRoot changes only spec.root of exactly one Project. It never moves files and never changes metadata.uid.
func (Mutator) RecordAgentSessionRef ¶
func (m Mutator) RecordAgentSessionRef(reg *Registry, agentUID string, obs AgentSessionObservation) (Agent, bool, error)
RecordAgentSessionRef stores one hook-observed provider session ref on an Agent and reports whether the registry actually changed.
The write is deliberately narrow. It touches status.sessionRef and nothing else: not the phase, not lastTransitionAt, not paneRef. Observing which conversation an Agent belongs to is not a lifecycle event, and letting an ingest hook move an Agent through the phase machine would make the closed transition table answerable by an external tool.
A re-observation of the same conversation returns changed=false so a hook that fires on every turn does not rewrite the registry file each time.
A conversation already claimed by another Agent is NOT refused. Uniqueness is not an invariant this model can honestly hold: the same provider conversation really can be attached twice (a manual resume of the same session id in a second pane already does exactly that today), an Offline Agent keeps its ref forever, and refusing the write would make the registry describe a world that does not exist rather than the one that does. Choosing between several Agents that point at one conversation is a resume-time decision and belongs to the resume materialization Phase, not to this observation write.
func (Mutator) RecordPaneActivation ¶ added in v0.13.0
func (m Mutator) RecordPaneActivation(reg *Registry, paneUID string, opts PaneActivationOptions) (Pane, error)
RecordPaneActivation stamps a new activation generation onto one Pane.
Issuing a generation clears any receipt the previous materialization left behind. A Pane that has just been relaunched has no termination evidence, and keeping the old receipt visible would present a dead process's exit status as the current one's.
func (Mutator) RecordTermination ¶ added in v0.13.0
func (m Mutator) RecordTermination(reg *Registry, receipt TerminationEvidence) (TerminationOutcome, error)
RecordTermination applies one receipt under the activation generation guard.
The guards, in order, are the whole contract of this transport:
- the named Pane must still exist;
- the receipt's generation must be the Pane's *current* generation;
- a receipt naming an Agent must name the Agent that owns the Pane, and that Agent's current pane binding must still be this Pane, except that a same-generation supervisor receipt may refine the reconcile/unknown evidence that already cleared this exact binding;
- a receipt the registry already stores verbatim changes nothing;
- recorded intent is sticky for its generation.
The last one is not an optimization. A canonical delete records intent and then kills the live Pane; the supervisor watching that Pane sees its child die on a signal and reports abnormal. Letting the observation overwrite the intent would turn every deliberate deletion into a crash report.
Nothing here changes an Agent phase, a paneRef, or a Pane's existence. Consuming this evidence is a separate concern with its own review.
func (Mutator) RefineCodexActivation ¶ added in v0.13.0
func (m Mutator) RefineCodexActivation(reg *Registry, obs CodexActivationObservation) (bool, error)
RefineCodexActivation updates only the turn of an already exact native binding. Late generations, foreign Panes/Agents, and another thread are normal rejected observations: they return changed=false and write nothing.
func (Mutator) RegisterProject ¶
func (m Mutator) RegisterProject(reg *Registry, opts RegisterProjectOptions) (RegisterProjectResult, error)
RegisterProject registers a Project and creates its offline Window/Pane topology with no tmux involvement. The exact saved root reappearing reuses the same uid; nothing else ever merges uids.
func (Mutator) ReleaseAgentPane ¶
func (m Mutator) ReleaseAgentPane(reg *Registry, agentUID string, exit AgentExit, reason string) (Agent, error)
ReleaseAgentPane removes the managed Pane and moves the Agent to the phase implied by exit. The Agent itself survives as an offline/resumable resource.
func (Mutator) RenameAgent ¶
RenameAgent sets only an Agent's stable metadata.name inside its owning Window scope. Provider, topic annotations, lifecycle state, and managed Pane metadata are independent and remain unchanged.
func (Mutator) RenamePane ¶
RenamePane sets a Pane metadata.name inside its owner scope. The adapter mirrors the new name into the legacy @projmux_pane_label option and never writes the raw tmux pane_title.
func (Mutator) RenameProject ¶
RenameProject sets a Project metadata.name. An explicit collision fails with ErrNameConflict and zero mutations.
func (Mutator) RenameWindow ¶
RenameWindow sets a Window metadata.name inside its owning Project scope.
func (Mutator) SetAgentActivation ¶ added in v0.11.1
func (m Mutator) SetAgentActivation(reg *Registry, agentUID string, state AgentActivationState, source, reason string) (Agent, error)
SetAgentActivation records bounded launch acknowledgement metadata.
func (Mutator) SetAgentInteraction ¶ added in v0.11.1
func (m Mutator) SetAgentInteraction(reg *Registry, agentUID string, kind AgentInteractionKind, source string) (Agent, error)
SetAgentInteraction stores one semantic observation on exactly one Agent. Transport vocabulary is normalized by callers before reaching this mutator.
func (Mutator) SetAgentProgress ¶ added in v0.13.0
func (m Mutator) SetAgentProgress(reg *Registry, agentUID, currentTurn string, progress AgentProgress) (Agent, bool, error)
SetAgentProgress commits one already-bounded exact-turn projection. The caller supplies the current turn from the same activation binding checked in its transaction; a mismatch is refused without turning progress into a second conversation authority.
func (Mutator) SetAgentTopic ¶ added in v0.11.1
SetAgentTopic mutates only the non-identifying topic annotation.
func (Mutator) SetPaneDisplayTitle ¶
func (m Mutator) SetPaneDisplayTitle(reg *Registry, paneUID, agent, topic, command, rawTitle string) (Pane, error)
SetPaneDisplayTitle stores the derived secondary pane title.
func (Mutator) TransitionAgent ¶
func (m Mutator) TransitionAgent(reg *Registry, agentUID string, phase AgentPhase, reason string) (Agent, error)
TransitionAgent applies an explicit phase transition.
type NameReservation ¶
type NameReservation struct {
Scope string `json:"scope,omitempty"`
Kind Kind `json:"kind"`
Name string `json:"name"`
UID string `json:"uid"`
}
NameReservation is the persisted record of one allocated name. Reservations are the authority for suffix allocation: the allocator never recomputes a suffix from resource scan order.
Scope is "" for the registry-wide Project scope and the owner uid for Window, Pane, and Agent names.
type ObjectMeta ¶
type ObjectMeta struct {
UID string `json:"uid"`
Name string `json:"name"`
DisplayName string `json:"displayName,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Annotations map[string]string `json:"annotations,omitempty"`
OwnerRef *OwnerRef `json:"ownerRef,omitempty"`
CreatedAt time.Time `json:"createdAt"`
}
ObjectMeta is the metadata block shared by every resource.
- UID is opaque, immutable, and independent of tmux lifecycle.
- Name is the stable unique-within-scope query key. Project names are unique across the registry; Window/Pane/Agent names are unique within their ownerRef scope.
- DisplayName may duplicate and is never a selector, ownerRef, or identity.
- Labels are key/value classification input.
- Annotations are non-identifying metadata (AI topic, provider context).
func (ObjectMeta) Clone ¶
func (m ObjectMeta) Clone() ObjectMeta
Clone returns a deep copy of the metadata block.
func (ObjectMeta) OwnerUID ¶
func (m ObjectMeta) OwnerUID() string
OwnerUID returns the owning uid, or "" for a root resource.
type OwnerRef ¶
OwnerRef points at the owning resource by opaque uid. It never carries a displayName, a tmux target, or any other non-identifying value.
type Pane ¶
type Pane struct {
APIVersion string `json:"apiVersion"`
Kind Kind `json:"kind"`
Metadata ObjectMeta `json:"metadata"`
Spec PaneSpec `json:"spec"`
Status PaneStatus `json:"status"`
}
Pane is owned by a Window (shell role) or by an Agent (agent role).
type PaneActivation ¶ added in v0.13.0
type PaneActivation struct {
Generation string `json:"generation"`
RuntimeID string `json:"runtimeID,omitempty"`
AgentUID string `json:"agentUID,omitempty"`
OperationID string `json:"operationID,omitempty"`
StartedAt time.Time `json:"startedAt,omitzero"`
// Codex is the provider-native conversation/turn observed for exactly this
// materialization. The durable conversation pointer remains on the Agent;
// this refinement is nested under the generation so replacement clears it.
Codex *CodexActivationBinding `json:"codex,omitempty"`
}
PaneActivation binds one Pane materialization to an opaque generation.
RuntimeID is recorded for operator diagnostics only. tmux recycles `%N` handles, so the generation -- not the handle -- is what a receipt is matched against.
func (PaneActivation) Clone ¶ added in v0.13.0
func (a PaneActivation) Clone() PaneActivation
Clone returns an activation record whose provider binding cannot alias the source Registry snapshot.
func (PaneActivation) IsZero ¶ added in v0.13.0
func (a PaneActivation) IsZero() bool
IsZero lets registry documents written before activation generations existed re-encode without the additive block.
type PaneActivationOptions ¶ added in v0.13.0
type PaneActivationOptions struct {
// Generation is the opaque value the launched process will quote back.
Generation string
// RuntimeID is the exact tmux pane handle at issue time, for diagnostics.
RuntimeID string
// AgentUID is set for an Agent-managed Pane and empty for a shell Pane.
AgentUID string
// OperationID labels the create/resume transaction that issued it.
OperationID string
}
PaneActivationOptions is the input to one generation issue.
type PaneAgentCascadeDeletePlan ¶ added in v0.13.0
type PaneAgentCascadeDeletePlan struct {
Decision TeardownDecision
Desired Registry
Changed bool
PaneUID string
AgentUID string
DeletedPanes int
DeletedAgents int
Evidence *TerminationEvidence
}
PaneAgentCascadeDeletePlan is the desired-Registry plan for one qualifying exact pane-exited event. The Pane row is released while its owning Agent and Window identities survive. When it was the last descendant, the plan adds the minimum replacement shell in the same desired graph.
func PlanPaneAgentCascadeDelete ¶ added in v0.13.0
func PlanPaneAgentCascadeDelete(registry Registry, event TeardownEvent, now time.Time) (PaneAgentCascadeDeletePlan, error)
PlanPaneAgentCascadeDelete converts one exact lifecycle decision into a schema-valid desired Registry without mutating the source document.
The complete owner chain is revalidated here even when the caller already derived it from a fresh observation. This is the locked generation/owner guard: a late receipt or a resumed Agent cannot delete the binding that replaced the event's materialization.
type PaneRole ¶
type PaneRole string
PaneRole distinguishes a plain shell Pane from a Pane managed by an Agent.
type PaneSpec ¶
type PaneSpec struct {
Role PaneRole `json:"role"`
CWD string `json:"cwd,omitempty"`
Command string `json:"command,omitempty"`
}
PaneSpec records the declared pane recipe inputs. Command is the one-time name-derivation source; it is never re-read to rename an existing Pane.
type PaneStatus ¶
type PaneStatus struct {
DisplayTitle string `json:"displayTitle,omitempty"`
Conditions []Condition `json:"conditions,omitempty"`
// Activation names the current materialization of this Pane. It is the
// value a launched supervisor quotes back, and the only thing that
// separates the running process from the one a resume replaced.
Activation PaneActivation `json:"activation,omitzero"`
// LastTermination is the durable receipt of why the *current* generation's
// managed process stopped. Issuing a new generation clears it.
LastTermination *TerminationEvidence `json:"lastTermination,omitempty"`
// Teardown records the bounded exact-socket topology evidence supplied by a
// qualifying pane-exited hook. It is not inferred from liveness or content.
// A matching window-unlinked hook consumes it by deleting this Pane's owner
// chain; issuing a new activation clears it as stale generation state.
Teardown *PaneTeardownEvidence `json:"teardown,omitempty"`
}
PaneStatus carries the derived secondary display title and the observed conditions of one Pane. DisplayTitle is never a selector, an identity, or a Window name source.
As with WindowStatus there is no stored liveness field: liveness is derived from a live observation at read time, and Conditions only preserves the reason a runtime object went away.
type PaneTeardownEvidence ¶ added in v0.13.0
type PaneTeardownEvidence struct {
SocketIdentity string `json:"socketIdentity"`
RuntimeSessionID string `json:"runtimeSessionID"`
RuntimePaneID string `json:"runtimePaneID"`
RuntimeWindowID string `json:"runtimeWindowID"`
WindowUID string `json:"windowUID"`
RootKind Kind `json:"rootKind"`
RootUID string `json:"rootUID"`
Generation string `json:"generation"`
Classification TerminationClassification `json:"classification"`
ObservedAt time.Time `json:"observedAt"`
}
PaneTeardownEvidence pairs one exact pane-exited event with a later exact window-unlinked event without parsing Pane content or treating absence as authority. Every identity is current-generation and socket scoped.
func (*PaneTeardownEvidence) Clone ¶ added in v0.13.0
func (e *PaneTeardownEvidence) Clone() *PaneTeardownEvidence
Clone returns an independent teardown record.
type PaneTeardownEvidencePlan ¶ added in v0.13.0
type PaneTeardownEvidencePlan struct {
Decision TeardownDecision
Desired Registry
Changed bool
Evidence PaneTeardownEvidence
}
PaneTeardownEvidencePlan persists the exact causal half of a last-Pane cascade while retaining the complete Registry graph. It performs no parent deletion until the matching window-unlinked event arrives.
func PlanPaneTeardownEvidence ¶ added in v0.13.0
func PlanPaneTeardownEvidence(registry Registry, event TeardownEvent, now time.Time) (PaneTeardownEvidencePlan, error)
PlanPaneTeardownEvidence records the exact clean last-Pane half while retaining the complete Window subtree. The matching window-unlinked event is the only consumer allowed to turn this receipt into Window deletion.
type Project ¶
type Project struct {
APIVersion string `json:"apiVersion"`
Kind Kind `json:"kind"`
Metadata ObjectMeta `json:"metadata"`
Spec ProjectSpec `json:"spec"`
Status ProjectStatus `json:"status"`
}
Project is the canonical Projmux root resource.
type ProjectCascadeDeletePlan ¶ added in v0.13.0
type ProjectCascadeDeletePlan struct {
ProjectUID string
Root string
Desired Registry
Changed bool
DeletedProjects int
DeletedWindows int
DeletedPanes int
DeletedAgents int
DeletedReservations int
ExternalAssets ExternalAssetOutcome
ReopenIdentity ReopenIdentity
}
ProjectCascadeDeletePlan is a pure desired-Registry plan. The source Registry is never mutated; applying the composite write is a later phase.
func PlanProjectCascadeDelete ¶ added in v0.13.0
func PlanProjectCascadeDelete(registry Registry, projectUID string, now time.Time) (ProjectCascadeDeletePlan, error)
PlanProjectCascadeDelete removes one Project graph on a clone and returns the schema-valid desired Registry without touching filesystem or snapshot data.
type ProjectDescendantUIDOutcome ¶ added in v0.13.0
type ProjectDescendantUIDOutcome string
const ( ProjectDescendantUIDsPreserved ProjectDescendantUIDOutcome = "preserved" ProjectDescendantUIDsCreated ProjectDescendantUIDOutcome = "created" ProjectDescendantUIDsReplaced ProjectDescendantUIDOutcome = "replaced" ProjectDescendantUIDsRemoved ProjectDescendantUIDOutcome = "removed" ProjectDescendantUIDsAbsent ProjectDescendantUIDOutcome = "absent" )
type ProjectFreshReplacementPlan ¶ added in v0.13.0
type ProjectFreshReplacementPlan struct {
Operation ProjectLifecycleOperation
OldProjectUID string
NewProjectUID string
Preimage Registry
Desired Registry
WriteSet []ProjectStartupWrite
}
ProjectFreshReplacementPlan is the complete Registry preimage and desired result for one Fresh commit. The caller commits Desired atomically; any plan or store failure retains Preimage byte-for-byte.
func PlanProjectFreshReplacement ¶ added in v0.13.0
func PlanProjectFreshReplacement(registry Registry, projectUID string, opts RegisterProjectOptions, mutator Mutator) (ProjectFreshReplacementPlan, error)
PlanProjectFreshReplacement removes one exact Project graph from a clone and registers a new canonical Project/Window/shell graph for the same root. It never mutates registry, filesystem, Git/worktree state, or snapshots.
type ProjectLifecycleAction ¶ added in v0.13.0
type ProjectLifecycleAction string
ProjectLifecycleAction is the complete user-intent vocabulary at this boundary. Keeping one action on one plan makes stop, ordinary Window close, Project unregister, and Fresh replacement impossible to conflate.
const ( ProjectLifecycleStop ProjectLifecycleAction = "stop" ProjectLifecycleContinue ProjectLifecycleAction = "continue" ProjectLifecycleFresh ProjectLifecycleAction = "fresh" ProjectLifecycleDeleteProject ProjectLifecycleAction = "delete-project" )
type ProjectLifecycleOperation ¶ added in v0.13.0
type ProjectLifecycleOperation string
ProjectLifecycleOperation is the single mutation class carried by an executable plan. close-window is produced only by the causal Window plan; the four Project actions can therefore be compared without sharing effects.
const ( ProjectLifecycleOperationNone ProjectLifecycleOperation = "none" ProjectLifecycleOperationStop ProjectLifecycleOperation = "stop" ProjectLifecycleOperationContinue ProjectLifecycleOperation = "continue" ProjectLifecycleOperationFresh ProjectLifecycleOperation = "fresh" ProjectLifecycleOperationDeleteProject ProjectLifecycleOperation = "delete-project" ProjectLifecycleOperationCloseWindow ProjectLifecycleOperation = "close-window" )
type ProjectLifecyclePlan ¶ added in v0.13.0
type ProjectLifecyclePlan struct {
State ProjectLifecycleState
Action ProjectLifecycleAction
Operation ProjectLifecycleOperation
Available bool
ProjectUID ProjectUIDOutcome
DescendantUIDs ProjectDescendantUIDOutcome
AtomicWriteSet []ProjectStartupWrite
ExternalAssets ExternalAssetOutcome
Reason string
}
ProjectLifecyclePlan is one cell of the retained/zero/deleted × Stop/Continue/Fresh/delete table. AtomicWriteSet names Registry/runtime effects; a no-op is explicit rather than represented by a blank cell.
func DecideProjectLifecycle ¶ added in v0.13.0
func DecideProjectLifecycle(state ProjectLifecycleState, action ProjectLifecycleAction, preconditions ProjectLifecyclePreconditions) ProjectLifecyclePlan
DecideProjectLifecycle returns the single lifecycle/startup state table. Ordinary clean Window close is intentionally not an action in this table: it is owned by the causal Window-close plan and never appears in these write sets.
type ProjectLifecyclePreconditions ¶ added in v0.13.0
type ProjectLifecyclePreconditions struct {
UsableSnapshot bool
}
ProjectLifecyclePreconditions carries external evidence required by one cell without widening the closed three-state table. A usable snapshot is relevant only to deleted+Continue; runtime absence is deliberately not a precondition because it never grants Project identity authority.
type ProjectLifecycleState ¶ added in v0.13.0
type ProjectLifecycleState string
ProjectLifecycleState is the desired Project shape at the lifecycle/startup boundary. Runtime liveness is deliberately absent: a missing tmux server is observation, never Project-delete authority.
const ( ProjectLifecycleRetainedWindows ProjectLifecycleState = "retained-window" ProjectLifecycleZeroWindows ProjectLifecycleState = "zero-window" ProjectLifecycleDeleted ProjectLifecycleState = "deleted" )
type ProjectOpenAction ¶ added in v0.13.0
type ProjectOpenAction string
ProjectOpenAction is the user's one-step startup choice.
const ( ProjectOpenContinue ProjectOpenAction = "continue" ProjectOpenFresh ProjectOpenAction = "open-fresh" )
type ProjectOpenPlan ¶ added in v0.13.0
type ProjectOpenPlan struct {
Available bool
Source ProjectOpenSource
AtomicWriteSet []ProjectStartupWrite
Reason ProjectOpenReason
NewProjectUID bool
AdditionalConfirm bool
ExternalAssets ExternalAssetOutcome
}
ProjectOpenPlan is one total startup state-table cell.
func DecideProjectOpen ¶ added in v0.13.0
func DecideProjectOpen(state ProjectReopenState, action ProjectOpenAction) ProjectOpenPlan
DecideProjectOpen is the compatibility projection used by the older live/closed/deleted-with-snapshot UI classifier. It adds runtime/source labels only; Fresh and Continue identity outcomes and write sets always come from DecideProjectLifecycle. Unavailable Continue never silently falls back to Fresh.
type ProjectOpenReason ¶ added in v0.13.0
type ProjectOpenReason string
ProjectOpenReason makes unavailable and invalid cells non-ambiguous.
const ( ProjectOpenReasonAttachLive ProjectOpenReason = "attach-live-project" ProjectOpenReasonMaterializeClosed ProjectOpenReason = "materialize-closed-project" ProjectOpenReasonRestoreSnapshot ProjectOpenReason = "restore-usable-snapshot" ProjectOpenReasonNoSnapshot ProjectOpenReason = "no-usable-snapshot" ProjectOpenReasonFreshReplace ProjectOpenReason = "replace-existing-project" ProjectOpenReasonFreshCreate ProjectOpenReason = "create-fresh-project" ProjectOpenReasonInvalid ProjectOpenReason = "invalid-state-or-action" )
type ProjectOpenSource ¶ added in v0.13.0
type ProjectOpenSource string
ProjectOpenSource is the authority from which topology is opened.
const ( ProjectOpenSourceLiveRuntime ProjectOpenSource = "live-runtime" ProjectOpenSourceRegistryTopology ProjectOpenSource = "registry-topology" ProjectOpenSourceSnapshot ProjectOpenSource = "snapshot" ProjectOpenSourceRoot ProjectOpenSource = "filesystem-root" ProjectOpenSourceNone ProjectOpenSource = "none" )
type ProjectReopenState ¶ added in v0.13.0
type ProjectReopenState string
ProjectReopenState is the closed startup state table.
const ( ProjectReopenLive ProjectReopenState = "live" ProjectReopenClosed ProjectReopenState = "closed" ProjectReopenDeletedWithSnapshot ProjectReopenState = "deleted-with-snapshot" ProjectReopenDeletedWithoutSnapshot ProjectReopenState = "deleted-without-snapshot" )
type ProjectSpec ¶
type ProjectSpec struct {
Root string `json:"root"`
PrimaryWindowRef string `json:"primaryWindowRef"`
}
ProjectSpec holds the absolute filesystem root the Project is bound to and the exact Window that owns its canonical shell Pane.
type ProjectStartupWrite ¶ added in v0.13.0
type ProjectStartupWrite string
ProjectStartupWrite is one member of an atomic startup write set.
const ( ProjectStartupWriteNone ProjectStartupWrite = "no-write" ProjectStartupWriteStopRuntime ProjectStartupWrite = "stop-managed-runtime" ProjectStartupWriteMaterializeRegistry ProjectStartupWrite = "materialize-registry-topology" ProjectStartupWriteDeleteProjectGraph ProjectStartupWrite = "delete-existing-project-graph" ProjectStartupWriteCreateProject ProjectStartupWrite = "create-project-with-new-uid" ProjectStartupWriteCreateCanonicalWindow ProjectStartupWrite = "create-canonical-window" ProjectStartupWriteCreateCanonicalShell ProjectStartupWrite = "create-canonical-shell" ProjectStartupWriteRestoreSnapshotGraph ProjectStartupWrite = "restore-snapshot-topology" )
type ProjectStatus ¶
type ProjectStatus struct {
Session *SessionProjection `json:"session,omitempty"`
Conditions []Condition `json:"conditions,omitempty"`
}
ProjectStatus carries the runtime session projection and observed conditions.
type ProjectUIDOutcome ¶ added in v0.13.0
type ProjectUIDOutcome string
const ( ProjectUIDPreserved ProjectUIDOutcome = "preserved" ProjectUIDReplaced ProjectUIDOutcome = "replaced" ProjectUIDCreated ProjectUIDOutcome = "created" ProjectUIDRemoved ProjectUIDOutcome = "removed" ProjectUIDAbsent ProjectUIDOutcome = "absent" )
type RegisterProjectOptions ¶
type RegisterProjectOptions struct {
// Root is the absolute project root. It must already exist.
Root string
// Name is an explicit --name. A collision fails with ErrNameConflict and
// zero mutations; it never receives an implicit suffix.
Name string
// DisplayName defaults to the root basename and may duplicate.
DisplayName string
Labels map[string]string
Annotations map[string]string
// Topology is the configured project startup topology. Empty means one
// default Window named after the configured shell basename with one
// initial shell Pane.
Topology []BootstrapWindow
// DefaultShell is the configured shell path; its basename seeds default
// Window and Pane names.
DefaultShell string
// SessionName optionally records the persistent tmux session projection.
SessionName string
// OperationID labels the transaction ledger.
OperationID string
}
RegisterProjectOptions is the input to offline Project registration.
type RegisterProjectResult ¶
type RegisterProjectResult struct {
Project Project
Windows []Window
Panes []Pane
Reused bool
OperationID string
Created []string
}
RegisterProjectResult reports the resources an offline registration created.
type Registry ¶
type Registry struct {
APIVersion string `json:"apiVersion"`
SchemaVersion int `json:"schemaVersion"`
UpdatedAt time.Time `json:"updatedAt"`
Projects []Project `json:"projects,omitempty"`
// ControlSessions is `omitempty`, which is the whole no-migration story for
// this slice: a registry written before control sessions existed decodes the
// absent key to a nil slice and re-encodes it as an absent key, so the
// document round-trips byte-identically and schemaVersion stays 1. Bumping
// the envelope would make every already installed build reject the file
// fail-closed with ErrSchemaTooNew -- a hard downgrade break bought for
// nothing.
ControlSessions []ControlSession `json:"controlSessions,omitempty"`
Windows []Window `json:"windows,omitempty"`
Panes []Pane `json:"panes,omitempty"`
Agents []Agent `json:"agents,omitempty"`
NameReservations []NameReservation `json:"nameReservations,omitempty"`
}
Registry is the whole persisted resource set plus its name reservations. Slice order is insertion order and is preserved verbatim so serialization is deterministic without a sort pass.
func MigrateRegistry ¶
MigrateRegistry lifts reg to the current schema version using the production migration set.
func NewRegistry ¶
func NewRegistry() Registry
NewRegistry returns an empty registry stamped with the current envelope.
func (Registry) Clone ¶
Clone returns a deep copy of the registry so a failed mutation can never leak a partially applied change back to the caller.
func (*Registry) ControlSession ¶ added in v0.13.0
func (r *Registry) ControlSession(uid string) (*ControlSession, bool)
ControlSession returns the ControlSession with uid.
func (*Registry) ControlSessionBySession ¶ added in v0.13.0
func (r *Registry) ControlSessionBySession(session string) (*ControlSession, bool)
ControlSessionBySession returns the ControlSession bound to an exact tmux session name.
Matching is exact and never heuristic, for the same reason ProjectByRoot's is: the session name is the identity, so a trimmed, cased, or prefixed variant is a different session and must not merge onto this uid.
func (Registry) Normalize ¶
Normalize returns the registry with the current envelope stamped and timestamps canonicalized to UTC.
func (Registry) PaneInWindow ¶ added in v0.13.0
PaneInWindow reports whether paneUID belongs to windowUID through the exact Registry owner chain. It does not consult names, cwd, command, or runtime order, and it does not require an Agent Pane to be the Agent's current pane; callers that need the stricter anchor invariant use WindowAnchor.
func (*Registry) PanesOf ¶
PanesOf returns the Panes owned by ownerUID in insertion order. The owner is a Window for shell panes and an Agent for managed panes.
func (*Registry) ProjectByName ¶
ProjectByName returns the Project with the registry-unique name.
func (*Registry) ProjectByRoot ¶
ProjectByRoot returns the Project bound to an exact cleaned root path. Root matching is exact and never heuristic: basename, git origin, inode, and scan order are deliberately not consulted.
func (Registry) Validate ¶
Validate checks every structural invariant of the registry: envelope spelling, uid uniqueness, in-scope name uniqueness, ownerRef integrity, anchor/default-shell validity, Agent phase membership, and reservation consistency.
func (Registry) WindowAnchor ¶ added in v0.13.0
WindowAnchor resolves the required role-agnostic anchor of windowUID.
It is deliberately stricter than a bare Pane lookup: a direct shell must be owned by the Window, while an Agent Pane must be owned by an Agent of that Window and remain that Agent's managed Pane. Consumers use this helper before selecting a runtime target so a dangling or cross-Window ref can never turn into an inferred sibling Pane.
func (Registry) WindowDefaultShell ¶ added in v0.13.0
WindowDefaultShell resolves the optional direct Window-owned shell. Empty is a valid absence and is therefore reported as (nil, false).
type ReopenIdentity ¶ added in v0.13.0
type ReopenIdentity string
ReopenIdentity states which Registry identity a subsequent open may use.
const ( ReopenIdentitySameProjectUID ReopenIdentity = "same-project-uid" ReopenIdentityNewProjectUID ReopenIdentity = "new-project-uid" ReopenIdentityNotApplicable ReopenIdentity = "not-applicable" )
type RootTeardownAction ¶ added in v0.13.0
type RootTeardownAction string
RootTeardownAction states what happens at the owner-root boundary.
const ( RootTeardownRetainProject RootTeardownAction = "retain-project" RootTeardownDeleteProject RootTeardownAction = "delete-project" RootTeardownRetainControlSession RootTeardownAction = "retain-control-session" )
type RuntimeObservation ¶
type RuntimeObservation struct {
// Windows is the set of Window uids a live tmux window still mirrors.
Windows map[string]bool
// Panes is the set of Pane uids a live tmux pane still mirrors.
Panes map[string]bool
}
RuntimeObservation is one live-tmux inventory of mirrored Projmux uids.
It is the machine half of a registry-versus-machine diff: a uid the registry holds but this observation does not is an object whose tmux window or pane is gone. It is a value taken once per process invocation and thrown away, never a cache and never persisted, which is what makes closing a pane visible to the very next query without any hook firing.
The empty observation means "nothing is bound", which is the fail-closed reading: it can only ever downgrade a resource to offline, never invent a live one.
func (RuntimeObservation) BoundPane ¶
func (o RuntimeObservation) BoundPane(uid string) bool
BoundPane reports whether a live tmux pane still mirrors uid.
func (RuntimeObservation) BoundWindow ¶
func (o RuntimeObservation) BoundWindow(uid string) bool
BoundWindow reports whether a live tmux window still mirrors uid.
There is deliberately no generic Bound(kind, uid) accessor. Only Window and Pane have a tmux object of their own: a Project's runtime is a tmux session with no @projmux uid, and an Agent has no tmux object at all. A kind-dispatch accessor would have to answer something for those two, and every available answer is a lie waiting to be trusted.
func (RuntimeObservation) Clone ¶
func (o RuntimeObservation) Clone() RuntimeObservation
Clone returns a deep copy so a resolver can never observe its snapshot changing under it.
type SchemaAction ¶
type SchemaAction int
SchemaAction is the read decision for one on-disk registry envelope.
const ( // SchemaCurrent means the envelope already matches SchemaVersion. SchemaCurrent SchemaAction = iota // SchemaMigrate means a known older envelope must be migrated forward // before it can be used. SchemaMigrate // SchemaReject means the envelope cannot be read. The caller must fail // closed and perform no write at all. SchemaReject )
func ClassifySchemaVersion ¶
func ClassifySchemaVersion(version int) (SchemaAction, error)
ClassifySchemaVersion decides how to read an envelope version using the production migration set.
func ClassifySchemaVersionWith ¶
func ClassifySchemaVersionWith(set MigrationSet, version int) (SchemaAction, error)
ClassifySchemaVersionWith decides how to read an envelope version against an explicit migration set. A nil set means the production set.
Everything that is not the current version and not covered by a registered migration step is rejected fail-closed: the caller must not quarantine, reset, truncate, or otherwise write the file. A newer version would destroy state a newer build owns; an unversioned or otherwise unknown document is not proven to be a projmux registry at all. Downgrade writes are unsupported.
func (SchemaAction) String ¶
func (a SchemaAction) String() string
String renders the action for diagnostics.
type SessionProjection ¶
SessionProjection is the 1:1 runtime projection of a persistent tmux session onto its Project. Auto-attach ephemeral sessions are never recorded here; they live only in runtime inventory, outside the Project hierarchy.
type SnapshotProjectionPlan ¶ added in v0.13.0
type SnapshotProjectionPlan struct {
ProjectUID string
Desired Registry
Changed bool
ReplacedWindows int
ReplacedPanes int
ReplacedAgents int
PreservedUIDs int
DeletedWindows int
DeletedPanes int
DeletedAgents int
LostSessionRefs int
}
SnapshotProjectionPlan is the pure, scoped replacement of one Project's descendants. Desired is safe to commit as one Registry transaction.
func PlanSnapshotProjection ¶ added in v0.13.0
func PlanSnapshotProjection(registry Registry, targetProjectUID string, snap sessionstate.Snapshot, now time.Time, newUID func(Kind) (string, error)) (SnapshotProjectionPlan, error)
PlanSnapshotProjection translates a v1 session snapshot into the desired Registry subtree of targetProjectUID. It performs no I/O and never mutates registry or snapshot.
type StateError ¶
StateError is a typed metadata error caused by inconsistent persisted state or an unavailable resource. It is not a usage error: it maps to exit 1.
func (*StateError) Unwrap ¶
func (e *StateError) Unwrap() error
Unwrap exposes the sentinel cause to errors.Is.
type TeardownAction ¶ added in v0.13.0
type TeardownAction string
TeardownAction is the bounded Registry action an authority decision permits.
const ( TeardownRetain TeardownAction = "retain" TeardownDeletePaneAgent TeardownAction = "delete-pane-agent" TeardownDeleteWindow TeardownAction = "delete-window" TeardownRefuse TeardownAction = "refuse" )
type TeardownDecision ¶ added in v0.13.0
type TeardownDecision struct {
Action TeardownAction
RootAction RootTeardownAction
Reason TeardownReason
ExternalAssets ExternalAssetOutcome
ReopenIdentity ReopenIdentity
}
TeardownDecision is one total decision-table cell.
func AggregateTeardownEvents ¶ added in v0.13.0
func AggregateTeardownEvents(events []TeardownEvent) TeardownDecision
AggregateTeardownEvents folds the two causal event kinds for one exact owner chain. It is intentionally insensitive to event delivery order.
func DecideTeardownEvent ¶ added in v0.13.0
func DecideTeardownEvent(event TeardownEvent) TeardownDecision
DecideTeardownEvent evaluates one event without mutating Registry or runtime state. A window-unlinked event is never sufficient by itself: aggregation must pair it with the exact causal pane-exited event.
type TeardownEvent ¶ added in v0.13.0
type TeardownEvent struct {
Kind TeardownEventKind
Classification TerminationClassification
Generation TeardownGeneration
Observation TeardownObservation
Chain TeardownOwnerChain
LiveSiblingPane bool
LiveSiblingRootWindow bool
}
TeardownEvent is one bounded input to the pure authority kernel.
type TeardownEventKind ¶ added in v0.13.0
type TeardownEventKind string
TeardownEventKind is the closed set of runtime topology events accepted by the automatic teardown authority kernel. Provider commands, pane contents, prompts, shell history, and transcripts are deliberately not inputs.
const ( TeardownEventPaneExited TeardownEventKind = "pane-exited" TeardownEventWindowUnlinked TeardownEventKind = "window-unlinked" )
func TeardownEventKinds ¶ added in v0.13.0
func TeardownEventKinds() []TeardownEventKind
TeardownEventKinds returns the closed event vocabulary.
type TeardownGeneration ¶ added in v0.13.0
type TeardownGeneration string
TeardownGeneration classifies an event's generation guard.
const ( TeardownGenerationCurrent TeardownGeneration = "current" TeardownGenerationStale TeardownGeneration = "stale" )
type TeardownObservation ¶ added in v0.13.0
type TeardownObservation string
TeardownObservation says what the final inventory pass proved about the exact tmux server named by an event. Only ExactSocket is positive authority; every other value is an explicit fail-closed observation, not an absence.
const ( TeardownObservationExactSocket TeardownObservation = "exact-socket" TeardownObservationEmpty TeardownObservation = "empty" TeardownObservationNoServer TeardownObservation = "no-server" TeardownObservationPermissionDenied TeardownObservation = "permission-denied" TeardownObservationSiblingSocket TeardownObservation = "sibling-socket" TeardownObservationForeignHost TeardownObservation = "foreign-host" )
func TeardownObservations ¶ added in v0.13.0
func TeardownObservations() []TeardownObservation
TeardownObservations returns the closed final-observation vocabulary.
type TeardownOwnerChain ¶ added in v0.13.0
type TeardownOwnerChain struct {
SocketIdentity string
SessionHandle string
PaneHandle string
WindowHandle string
PaneUID string
WindowUID string
RootKind Kind
RootUID string
Generation string
}
TeardownOwnerChain is the exact Registry chain an event claims.
type TeardownReason ¶ added in v0.13.0
type TeardownReason string
TeardownReason is a closed diagnostic vocabulary for the decision table.
const ( TeardownReasonInvalidInput TeardownReason = "invalid-input" TeardownReasonStaleGeneration TeardownReason = "stale-generation" TeardownReasonEmptyObservation TeardownReason = "empty-observation" TeardownReasonNoServer TeardownReason = "no-server" TeardownReasonPermissionDenied TeardownReason = "permission-denied" TeardownReasonSiblingSocket TeardownReason = "sibling-socket" TeardownReasonForeignHost TeardownReason = "foreign-host" TeardownReasonNonCausalTermination TeardownReason = "non-causal-termination" TeardownReasonPaneTeardown TeardownReason = "pane-teardown" TeardownReasonAwaitingPaneExit TeardownReason = "awaiting-pane-exit" TeardownReasonAwaitingWindowUnlink TeardownReason = "awaiting-window-unlink" TeardownReasonLiveSiblingPane TeardownReason = "live-sibling-pane" TeardownReasonWindowTeardown TeardownReason = "window-teardown" TeardownReasonProjectTeardown TeardownReason = "project-teardown" TeardownReasonMixedOwnerChain TeardownReason = "mixed-owner-chain" TeardownReasonConflictingOwnerFacts TeardownReason = "conflicting-owner-facts" TeardownReasonStaleOwnerBinding TeardownReason = "stale-owner-binding" // TeardownReasonDeadPaneCleanupRetry is the exact retryable boundary after // a clean current-generation decision but before the dead tmux Pane and its // managed Pane resource have converged. It never grants authority by itself; // the next pass must re-prove the same supervisor evidence, generation, // owner chain, socket, and dead mirror. TeardownReasonDeadPaneCleanupRetry TeardownReason = "exact-dead-pane-cleanup-retry" )
type TerminationClassification ¶ added in v0.13.0
type TerminationClassification string
TerminationClassification is the closed evidence vocabulary.
The five values are not a severity ladder, they are five different *kinds of proof*. Intentional means a canonical control action said so in writing before it acted. Normal and Abnormal mean a supervisor actually reaped the child and read its wait status. Unknown means nothing proved anything, and it is a legal, expected answer rather than a failure.
const ( // TerminationIntentional is a canonical control-plane action's own record // of its intent. Only TerminationSourceControlAction may carry it. TerminationIntentional TerminationClassification = "intentional" // TerminationNormal is an observed exit status 0. // // It is emphatically NOT intent. A provider that exits 0 because the // operator typed a quit command and a provider that exits 0 because it // finished a batch produce byte-identical wait statuses, so promoting // exit 0 to "intentional" would invent evidence nobody produced. TerminationNormal TerminationClassification = "normal" // TerminationKilled is an observed external hangup. It says the managed // process was killed from outside its own exit path, but it deliberately // says nothing about whose intent caused that kill. A matching canonical // control-action receipt remains the only authority that can say // intentional. TerminationKilled TerminationClassification = "killed" // TerminationAbnormal is an observed non-zero exit status or a death by // signal. TerminationAbnormal TerminationClassification = "abnormal" // TerminationUnknown is an explicitly evidence-free record: the runtime // object is gone and no receipt explains why. // // It is a value rather than an absence precisely so that an absence is // never re-read as normality. Only TerminationSourceReconcile may write it, // and writing it is what makes the reconciliation idempotent: the second // pass over the same disappearance finds the same document already stored // and changes nothing. TerminationUnknown TerminationClassification = "unknown" )
func ClassifyProcessExit ¶ added in v0.13.0
func ClassifyProcessExit(exitCode int, signal string) TerminationClassification
ClassifyProcessExit maps one reaped wait status onto observed evidence.
signal is the empty string for a child that exited on its own. A signalled child is abnormal regardless of the code the platform reports alongside it, and exit status 0 is the only normal outcome.
type TerminationDisposition ¶ added in v0.13.0
type TerminationDisposition struct {
// Exit is the Agent exit classification, meaningful only for an
// Agent-owned Pane.
Exit AgentExit
// Reason is the status.reason clause for this classification.
Reason string
}
TerminationDisposition is the lifecycle decision one classification implies.
It is a total function of the classification and of nothing else. Not of the role, not of whether an Agent owns the Pane, not of which host answered: those decide *what gets written*, never *what the evidence means*.
func DispositionFor ¶ added in v0.13.0
func DispositionFor(classification TerminationClassification) TerminationDisposition
DispositionFor maps one classification onto its lifecycle decision.
An unrecognized classification is treated as unknown rather than refused. A registry written by a build with a wider vocabulary must still converge here, and "this build cannot interpret the evidence" is precisely what unknown means.
type TerminationEvidence ¶ added in v0.13.0
type TerminationEvidence struct {
Source TerminationSource `json:"source"`
Classification TerminationClassification `json:"classification"`
ObservedAt time.Time `json:"observedAt,omitzero"`
PaneUID string `json:"paneUID,omitempty"`
AgentUID string `json:"agentUID,omitempty"`
Generation string `json:"generation,omitempty"`
// ExitCode is a pointer so "exited with status 0" and "never exited on its
// own" are different documents rather than the same zero value.
ExitCode *int `json:"exitCode,omitempty"`
Signal string `json:"signal,omitempty"`
OperationID string `json:"operationID,omitempty"`
}
TerminationEvidence is the minimal durable record of why one managed process stopped.
It is a pointer field with omitempty everywhere it is stored, which is the entire read-compatibility story: a registry written before this field existed decodes to nil, a nil value re-encodes to an absent key, and the document round-trips byte-identically. It was additive inside schemaVersion 1 and needed no migration step -- bumping the envelope would have made every already installed build reject the file fail-closed with ErrSchemaTooNew.
It carries no command text, no pane content, and no provider conversation data. Everything here is either a closed vocabulary value, a uid this build minted, or a numeric wait status.
func (*TerminationEvidence) Clone ¶ added in v0.13.0
func (t *TerminationEvidence) Clone() *TerminationEvidence
Clone returns a deep copy, including the exit-code pointer.
func (TerminationEvidence) IsZero ¶ added in v0.13.0
func (t TerminationEvidence) IsZero() bool
IsZero reports an entirely empty receipt.
func (*TerminationEvidence) Summary ¶ added in v0.13.0
func (t *TerminationEvidence) Summary() string
Summary renders one stored receipt as a single compact operator-facing clause: the classification, the provenance that produced it, and the wait status when one was actually read.
It carries no timestamp. Every surface that renders this already dates it in the unit that surface uses -- describe prints the absolute UTC instant, the columnar reads print a relative age -- and a clause that embedded one form would force the other surface to strip it back out.
type TerminationOutcome ¶ added in v0.13.0
type TerminationOutcome struct {
// Applied reports whether the registry now stores this receipt.
Applied bool
// Duplicate marks a receipt the registry already stores verbatim.
Duplicate bool
// Stale marks a receipt that lost a guard check.
Stale bool
// Reason is the operator-facing diagnostic for a refused receipt.
Reason string
}
TerminationOutcome is the result of offering one receipt to the registry.
A refused receipt is not an error. Duplicate delivery, a receipt from a replaced generation, and a receipt naming a Pane that has since been deleted are all *expected* on this transport, and the honest answer to each of them is "nothing changed, here is why".
type TerminationProjection ¶ added in v0.13.0
type TerminationProjection struct {
// PaneUID is the projected Pane.
PaneUID string
// AgentUID is the Agent released, empty for a shell Pane and for a Pane its
// Agent no longer binds.
AgentUID string
// Classification is the evidence the projection acted on.
Classification TerminationClassification
// Phase is the phase the Agent landed in, empty when no Agent moved.
Phase AgentPhase
// PaneRetained reports whether the logical Pane resource survived. A shell
// Pane always survives a runtime disappearance; an Agent's current managed
// Pane is released with the Agent.
PaneRetained bool
// Changed reports whether the registry now differs.
Changed bool
// Reason is the diagnostic for a projection that changed nothing.
Reason string
}
TerminationProjection is what one projection did, or why it did nothing.
type TerminationProjectionInput ¶ added in v0.13.0
type TerminationProjectionInput struct {
// PaneUID is the Pane the caller observed to have no runtime object.
PaneUID string
// Generation is an optional guard. When it is set it must equal the Pane's
// current activation generation, so an event describing a materialization
// the registry has already replaced projects nothing.
Generation string
// ObservedAt is when the absence was observed. A zero value means now.
ObservedAt time.Time
}
TerminationProjectionInput names one absent Pane for projection.
type TerminationSource ¶ added in v0.13.0
type TerminationSource string
TerminationSource is the closed provenance vocabulary of one receipt.
It is deliberately not caller-supplied free text. Provenance decides whether a receipt may claim intent, and an open string here would let anything that can write the registry manufacture "the operator asked for this".
const ( // TerminationSourceSupervisor is the managed process supervisor that // actually reaped the child and read its wait status. TerminationSourceSupervisor TerminationSource = "supervisor" // TerminationSourceControlAction is a canonical control-plane lifecycle // action recording its own intent before it mutates anything live. TerminationSourceControlAction TerminationSource = "control-action" // TerminationSourceReconcile is the exit reconciliation pass reporting that // it re-observed the exact host, found a managed runtime object gone, and // found no receipt that explains it. // // It is the only source that may claim TerminationUnknown, and unknown is // the only thing it may claim. That pairing is what keeps "nobody proved // anything" from being writable by the two sources that *do* observe // something: a supervisor that reaped a child and a control action that // stated its intent both know more than this, and a build that let either // of them file an unknown receipt could erase real evidence with it. TerminationSourceReconcile TerminationSource = "reconcile" )
type Transaction ¶
type Transaction struct {
// contains filtered or unexported fields
}
Transaction records the resources one operation created so a post-create failure can be rolled back in reverse order. It never touches resources that existed before the operation started or that another operation created.
A pre-create failure records nothing, so rolling back is a no-op and the registry is left with zero mutations.
func (*Transaction) Commit ¶
func (t *Transaction) Commit()
Commit closes the transaction successfully and clears the ledger.
func (*Transaction) Created ¶
func (t *Transaction) Created() []string
Created returns the ledger entries in creation order.
func (*Transaction) ID ¶
func (t *Transaction) ID() string
ID returns the operation id recorded for this transaction.
func (*Transaction) Rollback ¶
func (t *Transaction) Rollback()
Rollback removes, in reverse creation order, only the resources this operation created that still carry the same uid. Pre-existing resources and resources created by another operation are never touched.
type Window ¶
type Window struct {
APIVersion string `json:"apiVersion"`
Kind Kind `json:"kind"`
Metadata ObjectMeta `json:"metadata"`
Spec WindowSpec `json:"spec"`
Status WindowStatus `json:"status,omitzero"`
}
Window is owned by exactly one Project or ControlSession and always owns an initial Pane. The two owner kinds are structurally identical from a Window's point of view -- ownerRef is an opaque uid plus a kind -- so nothing below a Window needs to know which root it hangs from.
func (Window) DisplayName ¶
DisplayName returns the duplicate-allowed user-facing Window name. Registry files written before Window display names were projected leave the field empty, so those Windows safely fall back to their stable metadata.name until a runtime observation supplies the tmux window_name.
type WindowRootCascadeDeletePlan ¶ added in v0.13.0
type WindowRootCascadeDeletePlan struct {
Operation ProjectLifecycleOperation
Decision TeardownDecision
Desired Registry
Changed bool
PaneUID string
AgentUID string
WindowUID string
RootKind Kind
RootUID string
ProjectRoot string
DeletedProjects int
DeletedWindows int
DeletedPanes int
DeletedAgents int
DeletedReservations int
}
WindowRootCascadeDeletePlan is the schema-valid desired Registry produced by one exact pane-exited/window-unlinked pair.
func PlanWindowRootCascadeDelete ¶ added in v0.13.0
func PlanWindowRootCascadeDelete(registry Registry, paneEvent, unlinkEvent TeardownEvent, now time.Time) (WindowRootCascadeDeletePlan, error)
PlanWindowRootCascadeDelete consumes one stored exact Pane receipt together with its matching window-unlinked event. It deletes only that Window subtree; Project and ControlSession roots always survive.
type WindowSpec ¶
type WindowSpec struct {
AnchorPaneRef string `json:"anchorPaneRef"`
DefaultShellPaneRef string `json:"defaultShellPaneRef,omitempty"`
// contains filtered or unexported fields
}
WindowSpec separates the stable role-agnostic Window anchor from the optional direct shell used by shell-requiring compatibility consumers.
func (WindowSpec) CompatibilityShellPaneRef ¶ added in v0.13.0
func (s WindowSpec) CompatibilityShellPaneRef() string
CompatibilityShellPaneRef preserves the pre-Phase-1 shell consumer result: prefer the explicit default shell and otherwise fall back to the anchor. It is pure and never changes role, ownership, or Registry bytes.
func (*WindowSpec) UnmarshalJSON ¶ added in v0.13.0
func (s *WindowSpec) UnmarshalJSON(data []byte) error
UnmarshalJSON records raw field presence so schemaVersion 2 can distinguish the unpublished primaryPaneRef shape from final-v2 without guessing from values. Mixed authorities remain representable only long enough for the schema normalizer to reject the whole document fail-closed.
type WindowStatus ¶
type WindowStatus struct {
Conditions []Condition `json:"conditions,omitempty"`
RuntimeSessionID string `json:"runtimeSessionID,omitempty"`
RuntimeID string `json:"runtimeID,omitempty"`
}
WindowStatus carries the observed conditions of one Window.
There is deliberately no stored liveness field here, and there never will be one: a stored bool is exactly the defect this block replaces. Liveness is derived from a live observation at read time; what is stored is only the preserved *reason* an object stopped being bound.
The field is `omitzero` rather than `omitempty` so a Window that has never carried a condition serializes byte-identically to a registry written before this field existed. That kept the addition inside schemaVersion 1: an older build reading a newer file simply ignores a key it does not know, and a newer build reading an older file decodes the absent key to the zero value.