AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗A pure-Go (no cgo) restart / reconcile state machine. Feed it a stream of
down / up / unhealthy / healthy signals for named units, and it decides
when to respawn after a death — honouring a grace period (anti-flap),
max_restarts within a sliding window (anti-thrash), and optional exponential
backoff — then effects the respawn through a caller-supplied Actions
interface.
Extracted from a microVM agent and made fully dependency-free: the
RespawnPolicy is a local value type (no external schema), health is a local
SignalKind (no probe/health-checker import), and side effects go through an
interface, so the whole machine is unit-testable with a fixed clock and a fake.
A pure decision core.
Decideis an allocation-light pure function:(policy, history, attempt, signal, now) → Plan. TheReconcileris the concurrent shell around it — one goroutine per watched unit, lazily started, draining a buffered signal channel — but every scheduling decision is a value you can assert against a fixed clock.
down/unhealthy signal parks in grace_wait for
grace_period_ms first, so a transient flap that recovers never triggers a
restart.max_restarts restarts are allowed per rolling
window_ms; exceeding it moves the unit to cooldown until the window rolls.constant or exponential (doubling initial_delay_ms, capped
at 5 minutes) before each respawn.Stop then Start, not a bare Start.Reconciler fans out across many units by
name; distinct units never serialise on each other.CGO-free, dependency-free, 100% test coverage (including every error
branch), race-tested, gofmt + go vet clean, and green across the six 64-bit
Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x).
go get github.com/go-proc/respawn
package main
import (
"context"
"time"
"github.com/go-proc/respawn"
)
// myActions effects the respawn. A real one wraps your process/VM driver;
// tests supply a fake to assert state-machine behaviour.
type myActions struct{ /* ... */ }
func (myActions) StartVM(ctx context.Context, name string) error { /* ... */ return nil }
func (myActions) StopVM(ctx context.Context, name string) error { /* ... */ return nil }
func main() {
r := respawn.New(myActions{}, nil)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
policy := &respawn.RespawnPolicy{
Enabled: true,
GracePeriodMs: 2000, // debounce flaps
MaxRestarts: 5, // within...
WindowMs: 60000, // ...a 60s window
Backoff: "exponential", // 1s, 2s, 4s, ... capped 5m
InitialDelayMs: 1000,
}
r.Watch(ctx, "web-1", policy)
// Feed signals from your event source:
r.Send(respawn.Signal{VMName: "web-1", Kind: respawn.SignalDown, When: time.Now()})
// On shutdown: cancel ctx, then drain.
cancel()
r.Wait()
}
Or drive the pure core directly, no goroutines:
plan := respawn.Decide(policy, history, attempt, respawn.Signal{Kind: respawn.SignalDown}, time.Now())
// plan.State / plan.Action / plan.DelayFor / plan.Reason
type RespawnPolicy struct {
Enabled bool
GracePeriodMs int64
MaxRestarts int32
WindowMs int64
Backoff string // "constant" | "exponential"
InitialDelayMs int64
}
type SignalKind int // SignalDown | SignalUp | SignalUnhealthy | SignalHealthy
type Signal struct { VMName string; Kind SignalKind; When time.Time }
// Actions is the side-effect surface the reconciler drives.
type VMActions interface {
StartVM(ctx context.Context, name string) error
StopVM(ctx context.Context, name string) error
}
// Reconciler — the concurrent shell.
func New(actions VMActions, log *slog.Logger) *Reconciler
func (r *Reconciler) Watch(ctx context.Context, name string, policy *RespawnPolicy)
func (r *Reconciler) Unwatch(name string)
func (r *Reconciler) Send(sig Signal)
func (r *Reconciler) Wait()
// Pure decision core.
type Plan struct { State State; Action Action; DelayFor time.Duration; Reason string }
func Decide(policy *RespawnPolicy, h *History, attempt int, sig Signal, now time.Time) Plan
func DecideAfterGrace(policy *RespawnPolicy, h *History, attempt int, kind SignalKind, now time.Time) Plan
Naming. The
Actionsmethods andSignal.VMNamekeep theVMnames of the origin codebase; the package itself is unit-agnostic — a "VM" here is any named thing you supervise.
The pure Decide core is asserted against a fixed clock across every policy
branch; the concurrent Reconciler is driven through a fake Actions with
injected clock/sleep seams to cover backoff, cooldown, grace, and every error
path deterministically.
COVERPKG=$(go list ./... | paste -sd, -)
go test -race -coverpkg="$COVERPKG" -coverprofile=cover.out ./...
go tool cover -func=cover.out | tail -1 # 100.0%
BSD-3-Clause — see LICENSE. Copyright the go-proc/respawn authors.