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를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 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.