Repository navigation
Conversation
|
@adel-pplx a couple of suggestions on the provider config:
For example, the proposed header syntax could look like: provider:
endpoint: https://openrouter.ai/api/alpha/decisions
model: perplexity/pplx-decider-v1.2
headers:
Authorization: "Bearer ${OPENROUTER_API_KEY}"
HTTP-Referer: "https://example.com"
X-Title: "My application"If |
Align the module minimum and CI/release toolchains with the fix for GO-2026-6809.
Keep model assessments and findings invocation-scoped, bind history to original tool arguments, and leave rejected state permissions unchanged. Propagate collector cancellation with a shared export deadline, deduplicate evidence gaps, and keep inactive decision selectors out of deterministic analysis. Clarify audit records and schema migration.
| }{ | ||
| Model: profile.Model, State: state, | ||
| Provider: profile.Routing, | ||
| Questions: map[string]map[string]any{"policy": { |
There was a problem hiding this comment.
I think it would be better to pack questions. questions is a map, but the client always sends exactly one ("policy") and answers is rejected unless it contains only that key (L275-279). So N decision rules on the same event = N sequential requests, each re-sending the same state.
If rules with the same view were grouped into one request keyed by rule name, latency and payload would stay ~1x as rules are added instead of growing linearly inside the shared 1 s budget. Each questions still returns a probability, also.
There was a problem hiding this comment.
Keeping questions separate because provider docs say grouping can change answers. 7b6bc53 uses bounded concurrency instead; grouped policies need separate accuracy validation.
There was a problem hiding this comment.
Grouping helps when questions are facets of one decisions. Here evaluation is on one tool-call, so likely it would help. That being said, some benchmarking can resolve this empirically.
There was a problem hiding this comment.
Agreed that same-action policies are a plausible grouping case. Kept independent requests for this revision; the follow-up benchmark should use distinct policies and fixed groups, checking answer completeness, threshold crossings, latency, and tokens.
Keep model questions independent and bound concurrent requests to two under one deadline, finalizing freshness before native control. Move collector inference to a bounded service-owned queue with receipt-time snapshots and scoped output failures. Add explicit CONNECT proxy support, rate-limit reporting, and guidance for selective monitored use.
| Provider: c.Provider.Protocol, ProviderModel: c.Provider.Model, | ||
| ConfigDigest: c.Digest(), BindingMode: b.Mode, | ||
| } | ||
| if err := em.EmitAssessment(record); err != nil || !a.Matched { |
There was a problem hiding this comment.
Selected decision rules still emit skipped assessments after a deterministic deny. If one optional assessment or finding write fails, global RecordErrors makes runHookEvent fail open and removes that clean deterministic deny. Can decision-record errors be isolated here as they are in the collector?
There was a problem hiding this comment.
I kept the documented clean-output gate: an undelivered assessment is actual audit loss, distinct from an inference failure, so it still suppresses a local deny. Added an explicit regression and clarified that distinction in 36d5ee1 rather than silently ignoring failed records.
| finished := <-done | ||
| active-- | ||
| p.rules[finished.index].result = finished.result | ||
| if denied { |
There was a problem hiding this comment.
After a sibling deny, this overwrites even a reply that already completed as prior_semantic_deny; delivery order can therefore erase a paid-for matched finding. Can completed replies be preserved and only unstarted or canceled work be marked skipped?
There was a problem hiding this comment.
Fixed in 36d5ee1. Cancelling remaining provider work no longer replaces completed replies or terminal errors; completed siblings retain the original deadline and freshness checks and cannot replace the winning deny.
| if sinks.http { | ||
| sinkBudget = httpTimeout | ||
| } | ||
| if time.Duration(c.BudgetMS)*time.Millisecond+time.Second+sinkBudget > 9*time.Second { |
There was a problem hiding this comment.
This budgets one HTTP sink timeout, but --http-batch-size=1 flushes each assessment and finding synchronously. Multiple selected rules can spend several sink timeouts after inference and exceed the native-hook deadline. Can enforcement require one batch or budget every possible flush?
There was a problem hiding this comment.
Fixed in 36d5ee1: native HTTP delivery now shares one absolute nine-second deadline across flushes and Close, rather than budgeting one request. Also recheck the winning contextual decision after delivery, since session state can change during those writes.
| c.decisions.Observe(ctx, ev) | ||
| // Immediate skip records are optional too. This callback and | ||
| // background writes share processMu, so the delta is scoped. | ||
| c.advisoryErrors += cfg.emit.Stats().RecordErrors - before |
There was a problem hiding this comment.
advisoryErrors captures only immediate Observe failures. Later background assessment or finding writes increment RecordErrors but not this counter, so runCollect exits 1 on shutdown after an optional evaluator write failure. Can the background writer account for its own failures too?
There was a problem hiding this comment.
Kept shutdown exit 1 for actual audit-record loss; that is separate from accepting an OTLP export and from best-effort model execution. Clarified the distinction in 36d5ee1 rather than hiding background write failures.
| t.Fatalf("exit=%d requests=%d for %s control write", code, lab.hits.Load(), tc.name) | ||
| } | ||
| var sawReceipt atomic.Bool | ||
| lab.check = func(_ *http.Request, raw []byte) { |
There was a problem hiding this comment.
lab.check is reassigned after the first invocation launches two provider requests. A canceled straggler can enter the handler afterward, racing this assignment and skewing lab.hits; the focused race test reproduced it. Can the checker be installed before the first call and filter the second command?
There was a problem hiding this comment.
Fixed in 36d5ee1: the checker is installed before any requests and filters/counts the second action by structured input, removing the callback reassignment and aggregate-hit baseline. Focused race regressions pass.
Adds opt-in, model-backed decision rules for agent actions that need content or session context, without replacing deterministic rules or native hook enforcement. The feature remains experimental and explicitly configured.
What changes
decision:uses the ordinary rule catalog, with per-rule selectors, instructions, choices and thresholds. One provider configuration supports the Perplexity and OpenRouter Decisions APIs, using either an environment variable or a protected token file.examples/decision-rules/can be enabled without site-specific policy or allowlists. It is outside the embedded catalog and reviews observed tool results, not whether an attack succeeded.Operator impact
All records use schema
0.6.0, even without decision evaluation enabled. Update strict downstream validators before upgrading; assessments and their links to findings/enforcement are new, and case tools do not migrate earlier record schemas. Builds require Go 1.26.10 or later.--decision-configexplicitly enables sending projected tool evidence to the configured provider. Raw content requires a separate opt-in; redaction is not a data-loss-prevention guarantee. Shared sequence/history state must satisfy the stricter private-state checks when decision evaluation is configured.Claude hook child identities now split timeline groups even without decision evaluation; payloads without
agent_idretain their existing grouping. Contextual evidence gaps remain visible and prevent contextual blocking, not advisory findings.Recommended use is selective, tested local enforcement plus a few narrowly selected model policies, initially monitored. Broader correlation belongs in the SIEM/data lake. Questions are not automatically batched because grouping can change model answers; concurrency reduces serial waiting, not request count or capacity requirements.
Try it
Build with
go build ./cmd/numbat, then follow the decision rule quick start or rule-authoring guide. Start in monitor mode with synthetic data.Synthetic checks exercise single-call and contextual read/upload flows, native response generation, slow-provider isolation, bounded scheduling and proxy routing over real HTTP. These are functional checks, not production accuracy claims; probability thresholds still need calibration.
Installed-host blocking and fleet behavior remain unverified. Cross-platform tests do not establish installed-host acceptance, and a logged deny request does not prove that the host blocked the action.