Agents & automation#

h5i-db needs no MCP server or custom protocol to be driven by AI agents, schedulers, or CI: agents use the same CLI and Python API as everyone else. What makes that safe is a deliberate machine contract: structured output, structured errors, hard resource limits, and a write path that policy can gate behind human review.

$ h5i-db query market.db "SELECT symbol, vwap(price, size) FROM trades GROUP BY symbol" \
    --format json --max-rows 1000 --timeout 30s        # machine formats + hard limits

$ h5i-db delete-range market.db trades --start 09:30… --end 09:31… --plan
{"plan_id": "5c41…", "summary": {"rows_affected": 12481, "segments_reused": 127}}

$ h5i-db policy set market.db direct_delete=false      # agents must preview; humans can too

Machine-readable everything#

In Python the same envelope arrives as a typed exception hierarchy: every H5iError carries .code, .hint, and .retryable.

try:
    db.append("trades", batch)
except h5i_db.ConflictError:
    ...                      # retryable: another writer won; re-read and retry
except h5i_db.InvalidInputError as e:
    print(e.hint)            # not retryable: fix the call

Resource limits as flags#

A supervisor can hard-cap any call without touching the database:

CLI Python Effect
--max-rows N sql(max_rows=N) Stop as soon as the result exceeds N rows, with a clean limit_exceeded error instead of silent truncation
--timeout 30s sql(timeout=30) Deadline; cancels execution on expiry
--memory-limit-mb N sql(memory_limit=N) Memory budget with disk spilling under pressure
--max-bytes N Cap output bytes at batch boundaries
(open read-only) Database(path, read_only=True) Reject every write at the handle level

The agent output profile#

Passing a limit on every call only works if the caller remembers, and one forgotten flag can end a session. H5I_DB_PROFILE=agent moves the budget into the environment instead:

$ export H5I_DB_PROFILE=agent
$ h5i-db query market.db "SELECT * FROM trades" --format jsonl
{"ts":"2026-07-01T09:30:00Z","symbol":"AAPL","price":210.5}
… 1000 rows …

stdout stops at 1000 rows or 1 MiB, and a JSON summary on stderr reports what was withheld:

{"profile":"agent","truncated":true,"total_rows":2841193,"returned_rows":1000,
 "full_result_path":"/tmp/h5i-db-results/result-….parquet","full_result_rows":2841193}

Nothing is lost, only withheld: the rows that did not fit are in that Parquet file. H5I_DB_RESULT_DIR moves where those land. Two properties hold in this mode. Output content never depends on whether stdout is a terminal, so a piped run produces the same bytes as an interactive one. And an explicit --max-bytes you pass yourself stays a hard limit_exceeded error rather than a soft truncation.

Policy-gated review#

The mutation policy forces chosen operations through the previewable plan/apply flow:

$ h5i-db policy set market.db direct_delete=false direct_write=false direct_replace=false

An agent that then tries a direct delete gets a policy_violation error whose hint points at --plan. The staged plan carries exact affected-row counts and before/after samples; a human reviews it in the UI (h5i-db ui market.db) or via plan show, and applies or discards it. Every committed manifest records its execution_mode and plan hash, so the audit trail distinguishes reviewed from direct writes forever.

Where the mutation policy gates who may write directly, a per-table data policy gates what data may be written: typed constraints (not_null, compare, in_set, composed with and/or/not) checked fail-closed on every write and at plan time. A violating batch is refused with data_policy_violation before it can land, so an agent can't quietly commit malformed rows.

Patterns that work#