feat(guardrails): context-aware flock slot + dispatch guardrail integration (#28) #35

Merged
jacisjake merged 2 commits from task/28-dispatch-guardrails into main 2026-10-28 12:48:07 +00:00
2 changed files with 36 additions and 10 deletions
Showing only changes of commit acc09331b8 - Show all commits

No files matched your search

+16 -2
View File
@@ -128,6 +128,17 @@ func (f *Flock) pick(nest Nest, n int) []string {
return out return out
} }
// reserve takes a flock slot, honoring ctx: a saturated flock waits for a
// slot, but gives up when the context ends instead of wedging the caller.
func (f *Flock) reserve(ctx context.Context) (func(), bool) {
select {
case f.sem <- struct{}{}:
return func() { <-f.sem }, true
case <-ctx.Done():
return nil, false
}
}
// ask is the only way any crow touches the model: budget, flock cap, timeout, log. // ask is the only way any crow touches the model: budget, flock cap, timeout, log.
func (f *Flock) ask(ctx context.Context, w Watcher, crow, system, prompt string, budgets ...*Budget) (string, error) { func (f *Flock) ask(ctx context.Context, w Watcher, crow, system, prompt string, budgets ...*Budget) (string, error) {
for _, b := range budgets { for _, b := range budgets {
@@ -136,8 +147,11 @@ func (f *Flock) ask(ctx context.Context, w Watcher, crow, system, prompt string,
return "", err return "", err
} }
} }
f.sem <- struct{}{} release, slot := f.reserve(ctx)
defer func() { <-f.sem }() if !slot {
return "", fmt.Errorf("flock at cap: %w", ctx.Err())
}
defer release()
if w != nil { if w != nil {
w.Flying(crow) w.Flying(crow)
+20 -8
View File
@@ -200,8 +200,11 @@ func (f *Flock) ClassifyMode(ctx context.Context, text string, budgets ...*Budge
return "", false return "", false
} }
} }
f.sem <- struct{}{} release, slot := f.reserve(ctx)
defer func() { <-f.sem }() if !slot {
return "", false
}
defer release()
cctx, cancel := context.WithTimeout(ctx, f.g.CrowTimeout) cctx, cancel := context.WithTimeout(ctx, f.g.CrowTimeout)
defer cancel() defer cancel()
start := time.Now() start := time.Now()
@@ -295,8 +298,11 @@ func (f *Flock) ClassifyDivergence(ctx context.Context, state any, budgets ...*B
return 0, false return 0, false
} }
} }
f.sem <- struct{}{} release, slot := f.reserve(ctx)
defer func() { <-f.sem }() if !slot {
return 0, false
}
defer release()
cctx, cancel := context.WithTimeout(ctx, f.g.CrowTimeout) cctx, cancel := context.WithTimeout(ctx, f.g.CrowTimeout)
defer cancel() defer cancel()
start := time.Now() start := time.Now()
@@ -318,8 +324,11 @@ func (f *Flock) ClassifyKind(ctx context.Context, text string, budgets ...*Budge
return "", nil, false return "", nil, false
} }
} }
f.sem <- struct{}{} release, slot := f.reserve(ctx)
defer func() { <-f.sem }() if !slot {
return "", nil, false
}
defer release()
cctx, cancel := context.WithTimeout(ctx, f.g.CrowTimeout) cctx, cancel := context.WithTimeout(ctx, f.g.CrowTimeout)
defer cancel() defer cancel()
start := time.Now() start := time.Now()
@@ -346,8 +355,11 @@ func (f *Flock) classify(ctx context.Context, w Watcher, item string, budgets ..
return JevCall{}, err return JevCall{}, err
} }
} }
f.sem <- struct{}{} release, slot := f.reserve(ctx)
defer func() { <-f.sem }() if !slot {
return JevCall{}, fmt.Errorf("flock at cap: %w", ctx.Err())
}
defer release()
if w != nil { if w != nil {
w.Flying("Sentry") w.Flying("Sentry")
} }