feat(harness): per-run auto-approval of layer-2 proposals for model measurement

런 옵션 auto_approve_after_s(runs 열·POST /runs)를 추가한다. 값이 양수면 하네스가 제안
등록 뒤 그 지연만큼 기다렸다가 스스로 승인(actor auto)하고 HITL 버튼 경로와 같은 방식으로
조치를 실행(actor ai-approved, MarkProposalExecuted)한다. 사람 없이 모델 비교 캠페인을
반복하기 위한 측정 모드로, MTTA는 지연값으로 고정되고 판정 로직은 그대로다(2층 판정은
ai-approved 조치에서 나온다). 이미 결정·만료된 제안과 기록 전용 조치는 API 경로와 같이
처리한다.
This commit is contained in:
seon committed 2026-10-15 23:39:39 +09:00
1 parent f671e2419f
commit ca933a646e
4 files changed
+82 -26

No files matched your search

+27 -21
View File
@@ -24,27 +24,29 @@ type RunManager struct {
}
type activeRun struct {
RunID string
Campaign string
Model string // LLM for this run's diagnoses ("" = AI VM default); passed to the AI VM on every /diagnose
ScenarioID string // "" between scenarios
Scenario *Scenario
TInject time.Time
cancel context.CancelFunc
RunID string
Campaign string
Model string // LLM for this run's diagnoses ("" = AI VM default); passed to the AI VM on every /diagnose
AutoApproveAfter time.Duration // >0: measurement mode, the harness approves and executes layer-2 proposals after this delay
ScenarioID string // "" between scenarios
Scenario *Scenario
TInject time.Time
cancel context.CancelFunc
}
type RunRequest struct {
Campaign string `json:"campaign"`
Model string `json:"model"` // optional: LLM id for the diagnoses (e.g. gpt-6-luna, claude-sonnet-5-5); "" = AI VM default
ScenarioIDs []string `json:"scenario_ids"`
BaselineS *int `json:"baseline_s"` // default 300 (design 8)
CooldownS *int `json:"cooldown_s"` // default 300
ObserveTimeoutS *int `json:"observe_timeout_s"` // default 1800
MinObserveS *int `json:"min_observe_s"` // default 180: earliest t_recover (12 probe successes after inject)
DriftBefore *int `json:"drift_before"`
GitSHA string `json:"git_sha"`
Notes string `json:"notes"`
SkipRevert bool `json:"skip_revert"`
Campaign string `json:"campaign"`
Model string `json:"model"` // optional: LLM id for the diagnoses (e.g. gpt-6-luna, claude-sonnet-5-5); "" = AI VM default
ScenarioIDs []string `json:"scenario_ids"`
BaselineS *int `json:"baseline_s"` // default 300 (design 8)
CooldownS *int `json:"cooldown_s"` // default 300
ObserveTimeoutS *int `json:"observe_timeout_s"` // default 1800
MinObserveS *int `json:"min_observe_s"` // default 180: earliest t_recover (12 probe successes after inject)
DriftBefore *int `json:"drift_before"`
GitSHA string `json:"git_sha"`
Notes string `json:"notes"`
SkipRevert bool `json:"skip_revert"`
AutoApproveAfterS *int `json:"auto_approve_after_s"` // measurement mode (model comparison without a human in the loop): nil/0 = off
}
// modelRe bounds the model id (empty = AI VM default). The AI VM routes by prefix: claude-* -> Anthropic, else OpenAI.
@@ -98,9 +100,13 @@ func (a *App) handleRunCreate(w http.ResponseWriter, r *http.Request) {
}
runID := newID("run-")
ctx, cancel := context.WithCancel(context.Background())
m.active = &activeRun{RunID: runID, Campaign: req.Campaign, Model: req.Model, cancel: cancel}
var autoAfter time.Duration
if req.AutoApproveAfterS != nil && *req.AutoApproveAfterS > 0 {
autoAfter = time.Duration(*req.AutoApproveAfterS) * time.Second
}
m.active = &activeRun{RunID: runID, Campaign: req.Campaign, Model: req.Model, AutoApproveAfter: autoAfter, cancel: cancel}
m.mu.Unlock()
if err := a.store.CreateRun(r.Context(), runID, req.Campaign, req.DriftBefore, req.GitSHA, req.Notes, req.Model); err != nil {
if err := a.store.CreateRun(r.Context(), runID, req.Campaign, req.DriftBefore, req.GitSHA, req.Notes, req.Model, req.AutoApproveAfterS); err != nil {
m.mu.Lock()
m.active = nil
m.mu.Unlock()
@@ -110,7 +116,7 @@ func (a *App) handleRunCreate(w http.ResponseWriter, r *http.Request) {
}
mRuns.Set(1)
go m.execute(ctx, runID, req)
writeJSON(w, 202, map[string]any{"run_id": runID, "scenario_ids": req.ScenarioIDs, "model": req.Model})
writeJSON(w, 202, map[string]any{"run_id": runID, "scenario_ids": req.ScenarioIDs, "model": req.Model, "auto_approve_after_s": req.AutoApproveAfterS})
}
func (a *App) handleRunAbort(w http.ResponseWriter, r *http.Request) {