Repository navigation
bide v0.9.0
Correction (2026-10-02). This release's README overstated gsm's convergence guarantee: gsm v0.11.0 could certify some machines as convergent when they aren't (when one event reads a variable another event writes), and the proof-extracted checkers didn't run in
Buildor CI. The proof is correct; the implementation skipped a precondition. Fixed in gsm#2; details in bide#142.
A claim protocol checked by a model checker, on a core built for 1.0.
v0.9.0 is the release where bide's coordination protocols are checked, not only tested. TLA+ models of claims, approvals, flows, spend and the bide protocol run with the TLC model checker on every pull request, and the engine underneath them is rebuilt around a storage port and a journal with its own format. Model calls are typed values with exact spend accounting, flows run on durable Steps, proofs commit to the bytes the journal stores, and every audit entry point works under any signature scheme.
- The claim protocol, model-checked in CI. TLA+ models (written in PlusCal) cover attempt claims, not-started records, numbered retries, the resume gate and halt resolution (model 1); the approval gate with 1-of-1 and m-of-n tallies and approvers' key sets (model 1b); flow semantics (model 7); spend accounting (model 8); and the bide protocol's claim rules for remote tool calls (model 2). TLC explores every interleaving within each configuration's bounds, and Models is a required check on every pull request. Each counterexample the checker finds becomes a deterministic Go regression test, the Go fault-schedule exploration of concurrent claims is deterministic too (with a nightly full-bound run), and the model's findings are now properties the engine holds: each effect fires at most once, at most one attempt of a call is live, a resolver that never overrides a live driver, and a claim's winner that never halts on a loser's behalf. See spec/tla.
- A Store/Journal core.
agent.Storeis a small storage port (Insert,Get,Load) with its requirements numbered A1 to A8 on the type, andagent.Journalowns everything above it: memoization, the record encoding and salt, attempt claims, not-started records and recording an outcome after the caller's context ends. Every journal opens with an@journalheader naming its format, so a run in another format is refused before anything is read or written.agent/storetestchecks any store against every requirement,RunFilterlets SQL stores list only unfinished runs, and a counting store holds the engine to an exact budget of store round trips per operation. Journals in one process share in-flight steps, and a claim whose write may not have committed is recorded as not started, so a re-drive re-attempts the effect rather than halting over one that never ran. - A sealed pause contract. Every pause satisfies
agent.Pause, with five kinds (ApprovalPending,InterruptPending,SignalPending,TimerPending,OutcomeUnknown) and verbs named for the pause they answer (SubmitDecision,AnswerInterrupt,Enqueue).agent.ResolveHaltRefresolves tool and Step halts alike, knows whether a halt was crashed or contended, and refuses to resolve an effect a live driver may still be running. - Typed model calls and exact spend. A model call is an
agent.ModelCallvalue and returns anagent.ModelResponse, so middleware passes the call on instead of threading engine state through the context, andagent.CallModelsends one outside an agent through the same checks. Hooks are append-only with exactly oneAfterperBefore, the spend meter sits beneath every middleware, and requests are numbered across retries and hedged targets. A run that ends waits briefly for requests still in flight and journals their usage as late spend, so hedge losers and abandoned requests count towardResult.Spend, the budget andReplay. Each turn journals its finish reason, the model that answered, and digests of the prompt and tool set it was sent. The stream sink is claimed by one request per turn, so a hedged turn streams live from its first target. - Flows on durable Steps, with conformance. Every
plannode runs as anagent.Step, so flows get the Step's claim protocol, pause guard and halt resolution (plan.Flow.ResolveHalt). A flow run records what it is (RunKindFlow, its name and canonical input) andrun:completewhen it finishes, so recovery skips finished flows and a later drive returns the recorded output with one point read. Steps inside a loop body are scoped per iteration, andConformreplays a run's routing from its recorded choices. A fault-schedule exploration of the lowering (crashes, store errors and cancellations at every write, across processes) runs as a permanent test. - Proofs over the bytes the journal stores. A journal leaf is its tag followed by the record's stored bytes (
Record.Raw), so a record written by a later release verifies unchanged, andaudit.ExportJournalhands an auditor exactly those bytes. Every artifact names its format (proofs v3, evidence v5, STH v5), andaudit.UnmarshalStrictchecks it at every depth. Projections refuse a journal they cannot represent exactly, withErrRedactedorErrMalformed, and verifiers return anerrorthat wrapsErrNotVerified,ErrFormatorErrMalformed. - Signature agility and post-quantum hybrid. Every audit entry point takes an
audit.Signeroraudit.Verifier, so ed25519, ML-DSA-65 (FIPS 204) and the ed25519 plus ML-DSA-65 hybrid work throughout: tree heads, evidence, run certificates, approvals, grants, the standaloneaudit/verifypackage andbide-audit. The scheme is part of the signed bytes, keys have one text form (<alg>:<hex>), and each half of a hybrid signature signs its own label, so neither half verifies on its own. An m-of-n decision is journaled with the scheme it was signed under. - Approval key identity. An approver's verifier reports the identities of its signing keys (
KeyIDs), and the m-of-n gate counts one seat per signing key: a policy whose approvers share a key is refused on every evaluation, by the gate,TallyApprovals,audit.VerifyApprovalsandbide-audit verify-approvalsalike. Ed25519 keys are checked for canonical encoding and full order (audit.CheckEd25519PublicKey), so every accepted key is exactly one key. - The bide protocol, designed for SDKs.
bide.protocol.v1is the accepted design for using bide from other languages: the Go engine stays the only writer of the journal, claims and proofs, while Python and TypeScript SDKs run tools and answer pauses, as long-lived workers or serverless functions. Its claim rules pass model 2. Implementation follows the engine hardening of the pre-1.0 redesign. - Postgres stores that hold nothing between round trips, in a schema you pin. Every
store/postgreslease call and journal insert, and everygovern/postgreslogappend, is one statement that Postgres commits before it replies. An insert takes its position from anext_seqplpgsql function under a transaction-level advisory lock that ends with the statement, so a stalled client holds no lock and another node takes a run over one TTL after its last committed renewal. Every name the stores send is qualified: tables andnext_seqwith the store's schema, and every function, type and operator withpg_catalog(operators asOPERATOR(pg_catalog.<op>)), and a static check holds every statement to that rule.WithSchemapins the schema so the search path plays no part, and pinning it is the recommended setup.Openchecks that the tables carry the unique indexes the statements rely on, and thatnext_seqhas the expected definition, owner andsearch_path. - Recovery that drives only unfinished runs. Holding a run's lease,
RecoverandRecoverLoopcheck its end-of-run markers again before callingresume, so a run finished by another lease holder after the pass listed it is left alone, for three point reads per driven run. - Performance. On a standard 4-vCPU GitHub runner (AMD EPYC 7763, median of 21, run 36802470752), the overhead scenario runs ~25,400 runs/s at a mean run latency of 10.1 ms (p90 22 ms, p99 50 ms), and 20,000 runs with 5,000 in flight and 50 ms model calls finish in about 1.01 s (~19,900 runs/s, mean 251 ms, p90 321 ms, p99 447 ms). A same-ref run on the same CPU model put the noise within 2.7% on every one of these metrics. Throughput is flat to slightly up against v0.8.0: the Store/Journal core (#92) gained about 10%, and typed model calls (#104) gave back 3 to 8% as
Recordgrew. The fan-out p99 is higher than v0.8.0's (447 vs 419 ms) while throughput and the mean improved: #92's sixth journal record per run (the@journalheader) reshapes the latency distribution, and theRecordshrink planned after P12 is expected to recover some of it. Latency is reported as the mean, p90 and p99, since the closed-loop harness's p50 is bimodal, sitting on a scheduling cliff (cmd/bench).
API changes:
- Every journal starts with an
@journalheader (formatbide.journal.v1-dev), soHistoryreturns it first and record indices shift by one; journals written by v0.8.0 and earlier are refused, not resumed. store/sqlitenames its tablesbide_steps,bide_leasesandbide_schema_version, andstore/postgresits leases tablebide_leases; stop every v0.8.0 node before starting v0.9.0.agent.Lister.Runstakes aRunFilterand returns an iterator;agent.Capabilitytakes aStore;Record.SaltandRecord.Claimare read throughSalt()andClaimID().- The pause types are
ApprovalPending,InterruptPending,SignalPending,TimerPendingandOutcomeUnknown, each embeddingRunRef; the old names remain as deprecated aliases.ApproveAsis removed; useSubmitDecision. Waker.Schedulereturns an error, andResolveHaltRef(with its wrappers) refuses to resolve an effect a driver may still be running (*HaltInFlight), a conflicting outcome (*HaltAlreadyResolved) and an operation that never halted (ErrNoLiveAttempt).- A
Stepthat is not retry-safe and returns a pause isErrConfig; step names must be non-empty, andnode:,switch:andflow:are reserved prefixes. plannodes are journaled as Steps undernode:<name>(node:iter:<n>:<name>in a loop), a flow recordsrun:startandrun:complete, flow inputs compare as canonical JSON, andplan.HaltAmbiguousis removed; flows started under v0.8.0 do not resume.agent.ModelHandlerisfunc(ctx, ModelCall) (ModelResponse, error), hooks are added withModelCall.AddHook, andDetachModelSink,EmitMessage,WithModel(ctx, m)andWithModelCallHook(ctx, h)are removed.- Spend records are keyed
@spend/<id>,agent.Finishneeds keyed literals, and an empty finish reason is recorded asstop. middleware.CostMeterreports throughSnapshot();trace.WithSystemandtrace.WithModelare removed.- Proofs carry
RecordBytesinstead ofRecord, artifact formats move to proof v3, evidence v5 and STH v5 (re-create older artifacts from the journal), andTreeHead.TimestampisTimestampNanos. - Audit signing and verification take an
audit.Signeroraudit.Verifier, verifiers returnerror,EvidencePackagenames its key as{alg, public_key}, andNewAuditedStorereturns an error. agent.ApproverVerifierhasAlg()andKeyIDs(), andSubmitDecisionrequiresDecision.Alg.Grant.NotAfterisNotAfterUnix, stream and model event JSON is snake_case, andaudit.PoliciesUsed,audit.AbsenceRootandaudit.KeyFuncreturn errors or key lists.bide-auditreads keys as<alg>:<hex>and journals as aJournalExport.- The Postgres stores'
Openrefuses existing tables without the unique indexes their statements rely on, and anext_seqfunction with another definition, owner orsearch_path.
Journal format and upgrades: every pre-release, v0.9.0 included, writes the one format tag bide.journal.v1-dev. From known limitations: "Before 1.0, a run is not promised to resume across releases. Every pre-release writes the same journal format tag, bide.journal.v1-dev, and the tag is not bumped when the keys or the record shape change between pre-releases, so a later pre-release does not refuse an earlier one's journal even where it reads it differently. Finish or resolve runs before upgrading between pre-releases. At 1.0 the tag becomes bide.journal.v1, and 1.0 refuses every pre-release journal." Upgrading from v0.8.0 is not a rolling deploy: finish or resolve its runs, stop every v0.8.0 node, then start v0.9.0. On Postgres, pin the store's schema with WithSchema.
Install:
go get github.com/bide-ai/bide@v0.9.0
Library modules, at the same version:
go get github.com/bide-ai/bide/store/sqlite@v0.9.0
go get github.com/bide-ai/bide/store/postgres@v0.9.0
go get github.com/bide-ai/bide/mcp@v0.9.0
go get github.com/bide-ai/bide/trace@v0.9.0
go get github.com/bide-ai/bide/codec/gcf@v0.9.0
go get github.com/bide-ai/bide/govern@v0.9.0
go get github.com/bide-ai/bide/govern/sqlitelog@v0.9.0
go get github.com/bide-ai/bide/govern/redislog@v0.9.0
go get github.com/bide-ai/bide/govern/postgreslog@v0.9.0
Docs: https://bide-ai.com. Formal models: spec/tla.