Skip to content

Guardrails

deep_db_agents.guardrails

Hard guardrails enforced by the tool wrapper (not by the agent).

The defense hierarchy encoded here: limit and paginate, explore before extracting, estimate cost via EXPLAIN, allow only whitelisted operations, enforce a per-session row budget.

GuardrailConfig dataclass

GuardrailConfig(default_rows: int = 100, hard_max_rows: int = 1000, explain_row_threshold: int = 1000000, query_timeout_s: int = 30, allowed_statements: frozenset[str] = frozenset({'SELECT'}), row_budget: int | None = 50000)

Safety thresholds that cannot be bypassed by the agent.

Attributes:

Name Type Description
default_rows int

LIMIT applied automatically when the agent does not specify one.

hard_max_rows int

Hard cap on rows per single query, never exceedable.

explain_row_threshold int

If the row estimate from EXPLAIN exceeds this threshold, the query is blocked and the agent is asked to refine its filters or aggregate instead.

query_timeout_s int

Maximum execution timeout for a query, enforced by the driver/database.

allowed_statements frozenset[str]

Whitelist of allowed statement types (only SELECT by default).

row_budget int | None

Cumulative row budget returned per session (None = unlimited).

check_estimate

check_estimate(estimated_rows: int, metrics: SessionMetrics | None = None) -> None

Block the query if the EXPLAIN estimate exceeds the threshold.

Parameters:

Name Type Description Default
estimated_rows int

The estimated number of rows the query would return, typically obtained via EXPLAIN.

required
metrics SessionMetrics | None

Optional session counters to update when the query is blocked.

None

Raises:

Type Description
GuardrailError

If estimated_rows exceeds explain_row_threshold.

Source code in src/deep_db_agents/guardrails.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def check_estimate(self, estimated_rows: int, metrics: SessionMetrics | None = None) -> None:
    """Block the query if the EXPLAIN estimate exceeds the threshold.

    Args:
        estimated_rows: The estimated number of rows the query would return,
            typically obtained via ``EXPLAIN``.
        metrics: Optional session counters to update when the query is blocked.

    Raises:
        GuardrailError: If ``estimated_rows`` exceeds ``explain_row_threshold``.
    """
    if estimated_rows > self.explain_row_threshold:
        if metrics is not None:
            metrics.record_estimate_blocked()
        _logger.warning(
            "query blocked: estimate ~%d rows exceeds threshold %d",
            estimated_rows,
            self.explain_row_threshold,
        )
        raise GuardrailError(
            f"Query blocked: ~{estimated_rows:,} estimated rows exceed the "
            f"threshold of {self.explain_row_threshold:,}. Refine your filters "
            "or use aggregation."
        )

clamp_limit

clamp_limit(requested: int | None) -> int

Compute the effective LIMIT to apply, capped by hard_max_rows.

Parameters:

Name Type Description Default
requested int | None

The row limit requested by the agent, or None/non-positive to fall back to default_rows.

required

Returns:

Name Type Description
int int

The effective limit, never greater than hard_max_rows.

Source code in src/deep_db_agents/guardrails.py
44
45
46
47
48
49
50
51
52
53
54
55
56
def clamp_limit(self, requested: int | None) -> int:
    """Compute the effective LIMIT to apply, capped by ``hard_max_rows``.

    Args:
        requested: The row limit requested by the agent, or ``None``/non-positive
            to fall back to ``default_rows``.

    Returns:
        int: The effective limit, never greater than ``hard_max_rows``.
    """
    if requested is None or requested <= 0:
        requested = self.default_rows
    return min(requested, self.hard_max_rows)

SessionBudget dataclass

SessionBudget(budget: int | None = None, metrics: SessionMetrics | None = None)

Tracks row consumption in a session and enforces its budget.

If a SessionMetrics is associated, session counters are updated as well.

Attributes:

Name Type Description
budget int | None

Maximum cumulative rows allowed for the session, or None for unlimited.

metrics SessionMetrics | None

Optional session counters updated alongside the budget.

consumed int

Rows consumed so far in the session (not set at init).

charge

charge(rows: int) -> None

Charge rows against the session budget.

Parameters:

Name Type Description Default
rows int

Number of rows just returned, to add to the consumed total.

required

Raises:

Type Description
GuardrailError

If the cumulative consumed rows exceed budget.

Source code in src/deep_db_agents/guardrails.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
def charge(self, rows: int) -> None:
    """Charge ``rows`` against the session budget.

    Args:
        rows: Number of rows just returned, to add to the consumed total.

    Raises:
        GuardrailError: If the cumulative consumed rows exceed ``budget``.
    """
    if self.metrics is not None:
        self.metrics.record_query(rows)
    if self.budget is None:
        return
    self.consumed += rows
    if self.consumed > self.budget:
        if self.metrics is not None:
            self.metrics.record_budget_exhausted()
        _logger.warning("session row budget exhausted: %d/%d", self.consumed, self.budget)
        raise GuardrailError(
            f"Session row budget exhausted "
            f"({self.consumed:,}/{self.budget:,}). Start a new session or aggregate more."
        )