Repository navigation
Guardrails: make thresholds and heuristics configurable #16
Description
Context
Follow-up to #11 (Phase 4 guardrails/prompt library/agent scaffolding, implemented in PR #15). Raised while reviewing that PR: every guardrail behavior is currently a hardcoded constant in backend/app/guardrails/, with no env var, no DB-backed admin override, and no on/off switch. That was a deliberate scope cut at the time (see MAX_INPUT_LENGTH's docstring in input_guardrail.py, which explicitly argues it's a hard safety bound, not a per-deployment tunable) rather than an oversight — but it means an operator cannot tune, loosen, or disable guardrail behavior without a code change and redeploy, which doesn't fit this project's "10-year lifecycle" and admin-configurable posture (retention windows, LLM provider, etc. are all already DB-overridable via the Settings screen).
The gap
Input guardrail (backend/app/guardrails/input_guardrail.py):
MAX_INPUT_LENGTH(8000 chars) is a module constant.- The prompt-injection heuristic list (
_PROMPT_INJECTION_PATTERNS) is a fixed, unexposed set of regexes — an operator who finds a heuristic too aggressive (false-positiving on legitimate messages) or wants to add a deployment-specific pattern has no way to do either short of editing the module. - No way to disable the guardrail entirely (e.g. for a controlled internal pilot where the tradeoff is deliberately accepted).
Output guardrail (backend/app/guardrails/output_guardrail.py):
- The system-prompt-disclosure and credential-token redaction patterns are likewise fixed.
- No visibility toggle for whether redaction happened, beyond what's implicit in the response text.
Scope
- Decide what's genuinely a deployment tunable vs. a hard safety floor. Not everything here should become configurable — e.g. there may be a reasonable case for keeping a hard upper bound on
MAX_INPUT_LENGTHeven if an admin can lower it, similar to howapi_key_purge_days/conversation_retention_hourshave both an env default and a bounded DB override today. Make this decision explicit and document it, don't just expose every constant. - Follow the existing config-resolution pattern: env var default + nullable DB override via
SystemSettings, resolved per-call (is not None, not truthiness — seeAGENTS.md's Retention Field Semantics section) the same wayapp.services.retention_service.resolve_retention_configandapp.services.system_settings_service.resolve_llm_configalready do. Don't invent a second configuration mechanism. - Surface in the admin Settings UI, consistent with how retention windows are already exposed there, including the existing "confirm before applying a stricter/looser change" pattern if applicable.
- Decide on-off scope: a single guardrails kill switch, per-guardrail (input vs. output) switches, or neither — write down the reasoning.
- Prompt-injection pattern extensibility: decide whether custom/additional heuristic patterns should be admin-configurable text (e.g. a list of regex/phrases stored in
SystemSettingsor a dedicated table) or remain code-only with only the thresholds configurable. This is the highest-risk part of the scope — a badly configured custom pattern could create false negatives — so don't default to "make it fully configurable" without weighing that. - Audit the config changes themselves: per
AuditLogRepository, a change to guardrail configuration is itself a compliance-relevant event, same as retention changes (action="retention.update") — likely wants its ownactionvalue (e.g."guardrail.config_update").
Standards
AGENTS.md — TDD for all new deterministic logic, is not None semantics for nullable config fields (parametrize tests over [None, 0, 1, ...] boundary values), no premature abstraction (don't build a generic rule-engine for guardrails when the actual need is a handful of tunable values).
Done when
An operator can adjust input-guardrail length limits and enable/disable guardrail enforcement without a code change, changes are resolved the same way other DB-overridable settings are, and any change is audit-logged. Scope for making the heuristic pattern list itself configurable is explicitly decided (in scope with a concrete design, or explicitly deferred with rationale) rather than left ambiguous like PII detection was in #11.
Activity