Skip to content

Guardrails: make thresholds and heuristics configurable #16

Closed

Description

@prometheusgr

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

  1. 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_LENGTH even if an admin can lower it, similar to how api_key_purge_days/conversation_retention_hours have both an env default and a bounded DB override today. Make this decision explicit and document it, don't just expose every constant.
  2. Follow the existing config-resolution pattern: env var default + nullable DB override via SystemSettings, resolved per-call (is not None, not truthiness — see AGENTS.md's Retention Field Semantics section) the same way app.services.retention_service.resolve_retention_config and app.services.system_settings_service.resolve_llm_config already do. Don't invent a second configuration mechanism.
  3. 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.
  4. Decide on-off scope: a single guardrails kill switch, per-guardrail (input vs. output) switches, or neither — write down the reasoning.
  5. Prompt-injection pattern extensibility: decide whether custom/additional heuristic patterns should be admin-configurable text (e.g. a list of regex/phrases stored in SystemSettings or 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.
  6. 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 own action value (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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions