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:
2026-08-29 05:37:15 -05:00
parent 9dbb20489c
commit fc518c475e
21 changed files with 2904 additions and 20 deletions
+26 -1
View File
@@ -15,6 +15,7 @@ import (
"time"
"ukrrs.com/mopac/harness/internal/models"
"ukrrs.com/mopac/harness/internal/quota"
"ukrrs.com/mopac/harness/internal/task"
"ukrrs.com/mopac/harness/internal/writeback"
)
@@ -69,6 +70,9 @@ func (c *Conductor) RunLoop(ctx context.Context, opts LoopOpts) error {
fmt.Fprintf(c.out, "harness: loop: vertical=%s poll=%s state=%s redmine=%s (SIGINT to stop)\n",
c.cfg.Vertical, interval, c.cfg.Loop.StateDir, c.redmineHost())
if status := c.gateStatusLine(ctx); status != "off" {
fmt.Fprintf(c.out, "harness: loop: gate: %s\n", status)
}
if gitea != nil {
fmt.Fprintf(c.out, "harness: loop: gitea report commit on (%s/%s)\n", c.cfg.Gitea.Owner, c.cfg.Gitea.Repo)
}
@@ -121,6 +125,13 @@ func (c *Conductor) scanOnce(ctx context.Context, state *loopState, writer *writ
fmt.Fprintf(c.out, "harness: loop: would dispatch %s (updated %s): %q\n", t.ID, t.UpdatedOn, t.Subject)
continue
}
// Back-pressure: quota buckets + peak schedule + host resources. A
// defer skips the turn WITHOUT consuming the task — the next scan
// reconsiders it (the 19:00-wall class of failure becomes a logged
// throttle instead of dead turns).
if !c.gateConsult(ctx, state, t) {
continue
}
c.dispatchTask(ctx, state, writer, gitea, t)
}
return nil
@@ -136,9 +147,23 @@ func (c *Conductor) dispatchTask(ctx context.Context, state *loopState, writer *
run, err := c.runTask(ctx, t)
if run != nil && run.turn != nil && run.reportPath != "" {
// Usage accounting: tokens + estimated z.ai credits, per class,
// into the JSONL (feeds the Discourse usage reports) and the
// shared quota state (all instances' estimates).
credits := 0.0
if c.gate != nil {
credits = c.gate.RecordTurn(quota.EstimateTurnInput{
Model: run.decision.Model,
PromptTokens: run.turn.PromptTokens,
CompletionTokens: run.turn.CompletionTokens,
Peak: c.gate.InPeak(),
})
}
_ = state.log(loopEvent{
Type: evReport, TaskID: t.ID, Model: run.decision.Model,
Type: evReport, TaskID: t.ID, Model: run.decision.Model, Class: t.Class,
ReportPath: run.reportPath, StopReason: run.turn.StopReason,
PromptTokens: run.turn.PromptTokens, CompletionTokens: run.turn.CompletionTokens,
TotalTokens: run.turn.TotalTokens, Credits: credits,
})
}
if err != nil {