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
- func QuorumConfigStep(name string) string
- func QuorumTallyStep(name string) string
- func QuorumVoteStep(name, voter string) string
- type Applied
- type Applier
- type ConfluenceCertificate
- type EventLog
- type FedApplied
- type FederatedApplier
- type FederatedGovernor
- func (fg *FederatedGovernor) Apply(ctx context.Context, registry, event string) (FedApplied, error)
- func (fg *FederatedGovernor) ApplyOnce(ctx context.Context, id, registry, event string) (FedApplied, error)
- func (fg *FederatedGovernor) State() gsm.FedState
- func (fg *FederatedGovernor) Sync(ctx context.Context) (gsm.FedState, error)
- type Governor
- type MemEventLog
- type PersistentGovernor
- func (pg *PersistentGovernor) Apply(ctx context.Context, event string) (Applied, error)
- func (pg *PersistentGovernor) ApplyOnce(ctx context.Context, id, event string) (Applied, error)
- func (pg *PersistentGovernor) State() gsm.State
- func (pg *PersistentGovernor) Sync(ctx context.Context) (gsm.State, error)
- 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).
Inside a run, the event is applied with ApplyOnce keyed by the tool call and the apply's number within it (agent.NextOnceKey), so a call that runs again (a retry-safe tool whose process died before its result was recorded) applies its event once and reports the original position, and a composite tool that calls EventTool several times in one call applies each of them.
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.
Inside a run, the event is applied with ApplyOnce keyed by the tool call, as for EventTool.
func QuorumConfigStep ¶ added in v0.7.0
QuorumConfigStep is the step name under which the quorum named name records its k and voters.
func QuorumTallyStep ¶ added in v0.7.0
QuorumTallyStep is the step name of the tally of the quorum named name.
func QuorumVoteStep ¶ added in v0.7.0
QuorumVoteStep is the step name of voter's vote in the quorum named name. Because a quorum name contains no '/', the name and voter are recoverable from it: everything after "quorum/<name>/vote/" is the voter.
Types ¶
type Applied ¶ added in v0.5.0
type Applied struct {
// State is the shared normal form after the event.
State gsm.State
// Position is the event's place in the order State is a replay of: its position in the shared
// EventLog for a log-backed governor, or in the governor's own sequence for the in-memory
// Governor. Replaying the events at positions [0, Position] from the initial state through the
// same policy reproduces State exactly, which is what lets an auditor check a recorded state.
Position int64
}
Applied is the outcome of applying one governed event.
type Applier ¶
type Applier interface {
// Apply applies event as a new action, once per call.
Apply(ctx context.Context, event string) (Applied, error)
// ApplyOnce applies event at most once per id. A later call with the same id applies nothing
// and returns what the first call applied: the same position, and the state replayed through
// it. Reusing an id with a different event, or an empty id, is an ErrConfig error. EventTool
// keys it by the tool call, so a call that runs again (its process died after applying the
// event but before the call's result was recorded) does not apply its event twice.
ApplyOnce(ctx context.Context, id, event string) (Applied, error)
// State returns the governor's current view of the shared state. For a log-backed governor
// it is as of the last Apply or Sync; other processes' later appends are folded in by the
// next one.
State() gsm.State
}
Applier applies a named governed event to shared state. Both the in-memory Governor and the log-backed 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 records event at the end of entity's log under id and returns its position: the
// number of the entity's events recorded before it. If entity's log already holds an append
// with this id, Append records nothing and returns that append's position; if that append's
// event differs from event, or id is empty, it returns an error wrapping agent.ErrConfig. It
// must be safe to call concurrently from many processes, which must never be assigned the
// same position, and concurrent appends with the same id must record one event.
Append(ctx context.Context, entity, id, event string) (int64, error)
// Events returns entity's events at positions from onward, in log order.
Events(ctx context.Context, entity string, from int64) ([]string, error)
}
EventLog is an append-only, totally ordered log of governed events per entity: the durable shared record several processes' governors act on (MemEventLog below; govern/sqlitelog, govern/postgreslog, and govern/redislog for durable backends).
Positions are dense and never change. The first event appended for an entity is at position 0, and once a reader has seen the events at positions [0, n), every later read returns those same events first. Governors rely on this to fold other processes' events into their state in exactly the order an auditor will replay them.
Appends are idempotent by id. A governor gives every append an id (a random one for Apply, the caller's for ApplyOnce) and may send the same append again: a transport retries a request whose reply was lost, or a tool call runs again after its process died. The log records each (entity, id) once, so a repeated append cannot record its event twice.
type FedApplied ¶ added in v0.5.0
FedApplied is the outcome of applying one federated event: the federated normal form after it, and the event's position in the shared EventLog. Replaying the log's events at positions [0, Position] from the initial federated state reproduces State.
type FederatedApplier ¶
type FederatedApplier interface {
// Apply applies event to registry as a new action, once per call.
Apply(ctx context.Context, registry, event string) (FedApplied, error)
// ApplyOnce applies event to registry at most once per id, like Applier.ApplyOnce: a later
// call with the same id applies nothing and returns the first call's position and the state
// replayed through it.
ApplyOnce(ctx context.Context, id, registry, event string) (FedApplied, error)
// State returns the governor's view of the federated state as of its last Apply or Sync.
State() gsm.FedState
}
FederatedApplier is the federated analogue of Applier: it applies a named event to a named component registry of a gsm federation.
type FederatedGovernor ¶
type FederatedGovernor struct {
// contains filtered or unexported fields
}
FederatedGovernor is a governor over a gsm federation (multiple component registries linked by directed morphisms), backed by a shared, durable EventLog. Each applied event is appended to the log as a (registry, event) pair, and the governor's state is the log replayed through the federated machine: before answering, Apply folds in every event the log holds up to and including its own, in log order, including other processes' events. So any number of processes sharing the log act on the same federated state, and a recorded state matches an auditor's replay of the log.
Cross-registry conflicts resolve WITHOUT coordination via the authority argument (paper §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 or agent subtree, and 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 over the entity's log and reconstructs its state by replaying the log from the initial federated state. A log holding an entry the federation cannot apply is an ErrProtocol error here and on every later fold.
func (*FederatedGovernor) Apply ¶
func (fg *FederatedGovernor) Apply(ctx context.Context, registry, event string) (FedApplied, error)
Apply validates the (registry, event) against the federation, appends it to the shared log, then folds the log into the state up to and including its position. Validation runs before the append (via the pure ApplyNamed, which does not mutate), so an unknown registry or event is never written to the log. If the append succeeds but reading the log fails, the event is still recorded (the error says at which position); the next Apply or Sync folds it in. The append carries a fresh random id, so a log adapter whose transport retries it still records it once.
func (*FederatedGovernor) ApplyOnce ¶ added in v0.7.0
func (fg *FederatedGovernor) ApplyOnce(ctx context.Context, id, registry, event string) (FedApplied, error)
ApplyOnce is Apply at most once per id, across every process sharing the log (see FederatedApplier). A repeated id returns the first append's position and the federated state an auditor gets by replaying the log through it.
func (*FederatedGovernor) State ¶
func (fg *FederatedGovernor) State() gsm.FedState
State returns the governor's view of the federated state as of its last Apply or Sync.
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. Its state lives only in this process; use a PersistentGovernor over a shared EventLog when several processes act on the same state.
func (*Governor) Apply ¶
Apply advances the shared state by one event (O(1) table lookup; compensation to a valid normal form is baked in). Position is the event's place in this governor's sequence. An event the machine does not declare is rejected with an ErrConfig error and Position -1, and the state is unchanged.
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 governor over a shared, durable EventLog. Every event is appended to the log, and the governor's state is the log replayed through the machine, so any number of processes sharing the log act on the same state: before answering, Apply folds in every event the log holds up to and including its own, in log order, including other processes' events. The state an Apply returns is therefore exactly what an auditor gets by replaying the shared log through that event's position.
func NewPersistent ¶
func NewPersistent(ctx context.Context, m *gsm.Machine, log EventLog, entity string, initial gsm.State) (*PersistentGovernor, error)
NewPersistent constructs a governor over the entity's log and reconstructs its state by replaying the log from the initial state. A log holding an event the machine does not declare (written by an older policy or a foreign writer) is an ErrProtocol error here and on every later fold.
func (*PersistentGovernor) Apply ¶
Apply appends the event to the shared log, then folds the log into the state up to and including the event's position (so every earlier event, from any process, is applied first, in log order), and returns that state and position. Events appended after it by other processes are folded in by the next Apply or Sync. If the append succeeds but reading the log fails, the event is still recorded (the error says at which position); the next Apply or Sync folds it in.
The event is validated before the append: a name the machine does not declare is rejected with an ErrConfig error and Position -1, and nothing is written, so no process sharing the log ever reads it. The append carries a fresh random id, so a log adapter whose transport retries it still records it once.
func (*PersistentGovernor) ApplyOnce ¶ added in v0.7.0
ApplyOnce is Apply at most once per id (see Applier), across every process sharing the log: the id travels with the append, and the log records each id once. A repeated id returns the first append's position and the state an auditor gets by replaying the log through it, even if this governor has since folded in later events.
func (*PersistentGovernor) State ¶
func (pg *PersistentGovernor) State() gsm.State
State returns the governor's view of the shared state as of its last Apply or Sync.
type QuorumResult ¶
type QuorumResult struct {
Decision string `json:"decision"` // the plurality decision; on a tie, the lexically smallest tied label (a placeholder, and Agreed is false)
VotesFor int `json:"votes_for"` // how many voters chose Decision
Total int `json:"total"` // number of voters that successfully voted
Agreed bool `json:"agreed"` // VotesFor >= k and no tie for the most votes (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 and no other decision has as many votes (a tie for the most votes is never agreement). 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, name 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.
Naming: name identifies this quorum within the run, so one run can hold several quorums, even over the same voters. It must be non-empty and contain no '/'. The quorum's steps are QuorumConfigStep(name) (its k and voter names, recorded first), QuorumVoteStep(name, voter) for each vote, and QuorumTallyStep(name) for the tally.
Durability: the votes and the tally are memoized under runID by step name. Re-running Quorum with the same runID and name returns the recorded votes without re-invoking any Voter.Decide, so a resumed run neither re-queries the models nor re-tallies. A re-run must pass the same k and the same voter names in the same order: any other call under a recorded name is an agent.ErrConfig error, and nothing is cast or tallied, so one quorum's record never answers for another's.
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 with no tie for the most votes: a tie is never agreement. 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 and the top count is not tied).
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; part of the vote's step name, so non-empty and 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 is part of the vote's durable step name, so it must be non-empty and 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). Decide must have no side effects: a vote not yet recorded when the process dies is cast again on resume.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package eventlogtest checks that an implementation satisfies the govern.EventLog contract, so every adapter (in-memory, SQLite, Postgres, Redis, or your own) is held to the same guarantees a governor relies on: dense, unique positions under concurrent appends from separate processes, appends that are idempotent by id (so a retried append is recorded once), and a log that only ever grows at the end.
|
Package eventlogtest checks that an implementation satisfies the govern.EventLog contract, so every adapter (in-memory, SQLite, Postgres, Redis, or your own) is held to the same guarantees a governor relies on: dense, unique positions under concurrent appends from separate processes, appends that are idempotent by id (so a retried append is recorded once), and a log that only ever grows at the end. |
|
postgreslog
module
|
|
|
redislog
module
|
|
|
sqlitelog
module
|