Documentation
¶
Overview ¶
Package govern is the Tier-2 (distributed / concurrent) compensating-agent layer: it integrates the gsm "Governed State Machine" library so that MULTIPLE concurrent agents mutating SHARED state converge to the same valid state regardless of interleaving — PROVABLY, via gsm's build-time normalization-confluence verification (WFC + CC).
This is the fundamentally different tier from the sequential/hierarchical saga in the core package: there is no single causal order to reverse, so correctness can't be "reverse-the-log." Instead each agent's action is a gsm Event, shared business state is gsm's finite-domain state, business rules are Invariants, and compensations are Repairs. gsm proves at build time that every interleaving reaches the same normal form.
It lives OUTSIDE the core (which imports no gsm) — an edge integration, so the lean hexagonal core is untouched.
Index ¶
- func AttestedEventTool(gov Applier, name, description, event, policyDigest string, ...) agent.Tool
- func EventTool(gov Applier, name, description, event string, safety agent.Safety) agent.Tool
- func FederatedEventTool(gov FederatedApplier, name, description, registry, event string, ...) agent.Tool
- type Applier
- type ConfluenceCertificate
- type EventLog
- type FederatedApplier
- type FederatedGovernor
- type Governor
- type MemEventLog
- type PersistentGovernor
- type QuorumResult
- type Vote
- type Voter
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AttestedEventTool ¶
func AttestedEventTool(gov Applier, name, description, event, policyDigest string, safety agent.Safety) agent.Tool
AttestedEventTool is EventTool that also records, in the tool's durable result, WHICH governed policy admitted the action: it embeds policyDigest (a stable identifier of the combinator policy, e.g. gsm.Registry.PolicyDigest) alongside the event. Because the result is part of the journaled record, any audit over the journal (a signed tree head, or a ProofBundle from ProveToolCall) then commits cryptographically to the policy the action ran under, not merely that the action happened. An auditor recomputes the digest from the published policy bytes and runs the external verified oracle on them (see `bide-audit verify-governance`), tying the cryptographic root (the log) to the mathematical root (the proof) over one artifact.
policyDigest is treated as an opaque string on purpose: the SDK does not depend on gsm's serialization format, it only records the identifier the policy's owner published.
func EventTool ¶
EventTool gives an agent a GOVERNED action: when the agent's LLM calls it, `event` is applied to the shared governed state (convergent + thread-safe). Multiple agents that share one governor converge regardless of how their governed calls interleave — the tool-boundary bridge from real agent loops to gsm's proven convergence. gsm Guards handle preconditions (a guarded-out event is a no-op).
This is the "governor middleware": the point where an agent's tool call becomes a verified event on shared state, rather than an unchecked side effect. Works with either Governor (in-memory) or PersistentGovernor (crash-recoverable).
func FederatedEventTool ¶
func FederatedEventTool(gov FederatedApplier, name, description, registry, event string, safety agent.Safety) agent.Tool
FederatedEventTool gives an agent a GOVERNED federated action: when the agent's LLM calls it, `event` is applied to component `registry` of the shared federation. Multiple agents (each owning a different registry) converge regardless of interleaving, with cross-registry conflicts resolved by the authority argument — no locking. This is the federated twin of EventTool: the point where an agent tool call becomes a verified event on shared, cross-organizational governed state.
Types ¶
type Applier ¶
type Applier interface {
Apply(ctx context.Context, event string) (gsm.State, error)
State() gsm.State
}
Applier applies a named governed event to shared state, returning the new normal form. Both the in-memory Governor and the crash-recoverable PersistentGovernor satisfy it, so EventTool works with either.
type ConfluenceCertificate ¶
type ConfluenceCertificate struct {
Machine string `json:"machine"` // name of the gsm machine the certificate is about
PolicyDigest string `json:"policy_digest"` // stable identifier of the policy this certificate certifies
// Converges is the headline: WFC && CC. True means every interleaving of events reaches the
// same normal form, proven exhaustively at build time.
Converges bool `json:"converges"`
// WFC (well-founded compensation): every repair chain terminates; MaxRepairLen is the longest
// one found. CC (compensation commutativity): independent event pairs commute.
WFC bool `json:"wfc"`
CC bool `json:"cc"`
MaxRepairLen int `json:"max_repair_len"`
// The CC evidence: how many independent pairs were checked, split into those discharged by
// footprint-disjointness and those checked by brute force over all valid states.
PairsTotal int `json:"pairs_total"`
PairsDisjoint int `json:"pairs_disjoint"`
PairsBrute int `json:"pairs_brute"`
// States is the size of the state space the check ran over exhaustively.
States int `json:"states"`
// CompensationFree marks the CRDT fragment. CRDT.v (sibling proof repo) establishes that
// CRDTs are exactly the compensation-free fragment of this theory: operations that always
// preserve every invariant, so normalization never needs to repair. MaxRepairLen == 0 means
// no state in the enumerated space ever triggered a repair, so the machine sits in that
// fragment: it is a CRDT, needs no coordinator, and its convergent state can merge with other
// CRDT infrastructure. A positive MaxRepairLen means the policy uses compensation and is
// therefore strictly more expressive than any CRDT (the reason to run it here at all). The
// classification is conservative: MaxRepairLen == 0 is a sound witness of the CRDT fragment;
// a positive value means compensation is defined and may fire.
CompensationFree bool `json:"compensation_free"`
}
ConfluenceCertificate is the machine-checkable evidence that a governed policy converges, carried out of gsm's build-time verification and into the SDK so it can be surfaced to a user and anchored in the audit trail. gsm.Registry.Build runs the normalization-confluence check EXHAUSTIVELY over the enumerated state space (not sampled): WFC proves every compensation chain terminates, and CC proves every independent event pair commutes to the same normal form. By Newman's lemma those two local facts give global confluence, so applying a set of events in ANY order reaches the same valid state. This certificate is that result made portable.
Without it the guarantee lived only inside gsm's build step and was discarded at the Bide boundary; a run could prove which policy was used (audit) but not that the policy was provably convergent. Anchoring the certificate next to the policy leaf (see audit.RecordConvergence) closes that: an auditor verifies not merely "policy digest X was used" but "policy digest X is provably convergent, checked over N states".
func CertifyConvergence ¶
func CertifyConvergence(rep *gsm.Report, policyDigest string) ConfluenceCertificate
CertifyConvergence translates a gsm build Report and the policy's digest into a portable certificate. Pass the Report returned by gsm.Registry.Build and the digest from Registry.PolicyDigest. A Report whose machine failed to build (WFC or CC false) still produces a certificate; it simply reports Converges == false, which a caller should refuse to deploy.
func (ConfluenceCertificate) Classification ¶
func (c ConfluenceCertificate) Classification() string
Classification names the fragment the machine lives in, for a human-facing report.
func (ConfluenceCertificate) Marshal ¶
func (c ConfluenceCertificate) Marshal() ([]byte, error)
Marshal serializes the certificate for anchoring as an audit leaf (see audit.RecordConvergence).
func (ConfluenceCertificate) String ¶
func (c ConfluenceCertificate) String() string
String renders the certificate for a build log or CLI.
type EventLog ¶
type EventLog interface {
Append(ctx context.Context, entity, event string) error
Events(ctx context.Context, entity string) ([]string, error)
}
EventLog is an append-only log of governed events per entity. Order is preserved on append but is NOT required for correctness — gsm convergence guarantees any replay order of the same event set reaches the same state. Implementations: MemEventLog (below), and any durable backend (SQLite/Postgres) with the same two methods.
type FederatedApplier ¶
type FederatedApplier interface {
Apply(ctx context.Context, registry, event string) (gsm.FedState, error)
State() gsm.FedState
}
FederatedApplier is the federated analogue of Applier: it applies a named event to a named component registry of a gsm federation, returning the new federated normal form.
type FederatedGovernor ¶
type FederatedGovernor struct {
// contains filtered or unexported fields
}
FederatedGovernor is a crash-recoverable governor over a gsm federation (multiple component registries linked by directed morphisms). Each applied event is durably appended to an EventLog as a (registry, event) pair; state is reconstructed by replaying the log through the federated machine.
Two guarantees carry over from the federated convergence theorem (paper §8):
- Replay is exact and, for events the network proved independent, order-independent — so a shared durable log lets multiple processes converge on the same federated state.
- Cross-registry conflicts resolve WITHOUT coordination via the authority argument (§8.3): a source registry deterministically fixes its targets' shared components, so a target-side event that collides with its source is overwritten on the next ρ_Fed. This is the "distributed compensating agents across ownership boundaries" tier: each component registry is an organization / agent subtree, morphisms are the cross-org constraints.
func NewFederated ¶
func NewFederated(ctx context.Context, m *gsm.FedMachine, log EventLog, entity string, initial gsm.FedState) (*FederatedGovernor, error)
NewFederated constructs a federated governor and reconstructs its state by replaying the entity's event log from the initial federated state.
func (*FederatedGovernor) Apply ¶
func (fg *FederatedGovernor) Apply(ctx context.Context, registry, event string) (gsm.FedState, error)
Apply validates the (registry, event) against the federation, durably records it, then advances the federated state. Validation runs before the append (via the pure ApplyNamed, which does not mutate), so a rejected event is never written to the log; if the append fails, the in-memory state is left untouched.
func (*FederatedGovernor) State ¶
func (fg *FederatedGovernor) State() gsm.FedState
State returns the current federated normal-form state.
type Governor ¶
type Governor struct {
// contains filtered or unexported fields
}
Governor applies agents' events to one in-memory, gsm-governed state. Apply is convergent and thread-safe: because the machine was verified at Build time (WFC + CC), any order of the same event set lands on the same valid normal form.
type MemEventLog ¶
type MemEventLog struct {
// contains filtered or unexported fields
}
MemEventLog is an in-memory EventLog for tests/local dev.
func NewMemEventLog ¶
func NewMemEventLog() *MemEventLog
NewMemEventLog returns an empty in-memory EventLog.
type PersistentGovernor ¶
type PersistentGovernor struct {
// contains filtered or unexported fields
}
PersistentGovernor is a crash-recoverable Governor: every applied event is durably appended to an EventLog, and the current state is reconstructed by REPLAYING the log through the machine on startup. Because gsm.Apply is convergent, replay is exact — and (for the events gsm proved independent) order-independent. Back it with a shared durable EventLog and multiple processes converge on the same governed state.
func NewPersistent ¶
func NewPersistent(ctx context.Context, m *gsm.Machine, log EventLog, entity string, initial gsm.State) (*PersistentGovernor, error)
NewPersistent constructs a governor and reconstructs its state by replaying the entity's event log from the initial state.
func (*PersistentGovernor) Apply ¶
Apply durably records the event, then advances the shared state. On a later restart, NewPersistent replays the log to the same state.
func (*PersistentGovernor) State ¶
func (pg *PersistentGovernor) State() gsm.State
State returns the current shared normal-form state.
type QuorumResult ¶
type QuorumResult struct {
Decision string `json:"decision"` // the plurality decision (the label the most voters chose)
VotesFor int `json:"votes_for"` // how many voters chose Decision
Total int `json:"total"` // number of voters that successfully voted
Agreed bool `json:"agreed"` // whether VotesFor >= k (statistical signal, not a proof)
Votes []Vote `json:"votes"` // every recorded vote, in voter order
}
QuorumResult is the tally over the votes. Decision is the plurality value (the decision the most voters chose); VotesFor is how many voters chose it; Total is the number of voters that voted; Agreed reports whether VotesFor >= k. Votes carries every recorded vote in voter order.
Agreed is the statistical signal, not a proof. The provable object is the gate a caller builds from VotesFor: seed votes_for into a gsm invariant that admits the commit only when votes_for >= k (see examples/quorum), and the k-of-n requirement is then machine-checked over every possible count. Whether the agreed Decision is correct is not certified here: correlated model errors mean agreement is not statistical independence, so a quorum lowers single-model risk without certifying the answer.
func Quorum ¶
func Quorum(ctx context.Context, store agent.Durable, runID string, k int, voters ...Voter) (QuorumResult, error)
Quorum fans the voters out concurrently and durably, tallies their normalized decisions, and returns the plurality decision with its count. It is a composition helper over existing seams, not a new agent type: the fan-out is agent.Parallel (each vote is a journaled Step, so it is at-most-once and replayable), each vote and the final tally are recorded as durable Step values (provable one by one via audit.ProveStep), and the k-of-n gate itself is left to the caller to express as a gsm invariant over VotesFor (see examples/quorum). Keeping the model call inside each Voter.Decide keeps this helper model-agnostic and the votes normalized.
Durability: the votes and the tally are memoized under runID by name. Re-running Quorum with the same runID returns the recorded votes without re-invoking any Voter.Decide, so a resumed run neither re-queries the models nor re-tallies. Voter names must therefore be unique within one call. The tally is recorded under the step name "quorum/tally".
Tally: votes are grouped by decision value; the plurality value wins (ties broken by decision string, deterministically, so the recorded tally is stable across replays). Agreed is VotesFor >= k. A voter that errors is not recorded, so on a later resume it re-runs while the voters that already voted are memoized; the joined error is returned and the tally reflects only the votes that succeeded (Total is the count of successful votes). k <= 0 is treated as no gate (Agreed is true whenever at least one voter voted).
The boundary, stated plainly: this returns a tally. The tally makes the k-of-n gate provable once a caller wires VotesFor into an invariant; the agreement itself is statistical and never a guarantee of correctness. Do not blur the two.
type Vote ¶
type Vote struct {
Voter string `json:"voter"` // the voter that cast this decision
Decision string `json:"decision"` // the normalized decision label the voter chose
}
Vote is one voter's recorded decision. It is the provenance the audit trail commits to.
type Voter ¶
type Voter struct {
Name string // identifies who voted; also the durable step key, so it must be unique per Quorum call
Decide func(context.Context) (string, error) // produces this voter's normalized decision label
}
Voter is one named unit that produces a normalized decision. Name identifies who voted (a model, a provider/version, or any principal), so the recorded vote shows not just the tally but who voted how; it doubles as the durable step key, so it must be unique across the voters in one Quorum call. Decide returns a discrete decision string; the caller keeps the model call and its parsing inside Decide, so Quorum stays model-agnostic and every vote is already a comparable, normalized value. Free-form prose cannot be quorumed: Decide must map to a small set of labels (an enum, a yes/no, a chosen action).