quota: z.ai credit-bucket back-pressure + resource gate + usage accounting (Redmine 490+491)
The loop now consults quota and host state before every dispatch and
DEFERS gated work with a logged reason instead of letting turns die at
the provider (the 2026-08-28 19:00 quota-wall failure mode, replayed as
a test). Adds internal/quota: 5h/weekly credit buckets (provider poll
when z.ai ships an endpoint - fake-server tested - else locally
estimated from the documented credit formula), TZ-aware peak window
(default 01:00-05:00 America/Chicago weekdays, matching the documented
z.ai peak Mon-Fri 14:00-18:00 Singapore), block/defer thresholds, a
read-only load/mem/disk/IO-PSI monitor, an optional redis shared-state
hop (stdlib RESP2 mini-client) so all instances of an account
coordinate, per-class token+credit accounting in loop.jsonl, and
`harness quota status|probe|gate`. Config: [quota] + [resources]
sections; README runbook covers the redis container and deploy-time
cgroup enforcement.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
+13
-1
@@ -17,6 +17,7 @@ import (
|
||||
"ukrrs.com/mopac/harness/internal/intake"
|
||||
"ukrrs.com/mopac/harness/internal/llm"
|
||||
"ukrrs.com/mopac/harness/internal/models"
|
||||
"ukrrs.com/mopac/harness/internal/quota"
|
||||
"ukrrs.com/mopac/harness/internal/task"
|
||||
"ukrrs.com/mopac/harness/internal/tools"
|
||||
"ukrrs.com/mopac/harness/internal/writeback"
|
||||
@@ -35,6 +36,9 @@ type Conductor struct {
|
||||
bash *tools.BashTool
|
||||
keys *config.KeyResolver
|
||||
out io.Writer
|
||||
gate *quota.Gate
|
||||
// deferLogged dedups defer log lines per (task, updated_on, reason).
|
||||
deferLogged map[string]bool
|
||||
}
|
||||
|
||||
// New validates routing config and prepares the tool palette.
|
||||
@@ -57,9 +61,17 @@ func New(cfg *config.Config, out io.Writer) (*Conductor, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &Conductor{cfg: cfg, router: router, bash: bash, keys: config.NewKeyResolver(cfg), out: out}, nil
|
||||
keys := config.NewKeyResolver(cfg)
|
||||
gate, err := buildGates(cfg, keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Conductor{cfg: cfg, router: router, bash: bash, keys: keys, out: out, gate: gate, deferLogged: map[string]bool{}}, nil
|
||||
}
|
||||
|
||||
// Gate exposes the quota gate (nil when off) for `harness quota status`.
|
||||
func (c *Conductor) Gate() *quota.Gate { return c.gate }
|
||||
|
||||
// OnceOpts controls a single iteration.
|
||||
type OnceOpts struct {
|
||||
DryRun bool // intake + plan only, no LLM call
|
||||
|
||||
Reference in New Issue
Block a user