Every published guide to buying agentic AI is a list of questions to put to the vendor. This page is the other half: the answers, for Monarch, written so a security architect can check them against a running instance rather than accept them. Where a control depends on a setting, the setting is named. Where a control has a boundary, the boundary is named.

Monarch is the product Galahad deploys: an ontology-driven applicative layer that reads across the systems an organisation already runs and stages every write for approval. It installs inside your own infrastructure. That single fact decides most of the answers below, so it comes first.

Where the software runs

Where does the software run, and who operates it?

On your machines, in your infrastructure. Monarch installs as a set of containers: the application backend, the frontend, its PostgreSQL database, a database backup service and an audit export service. Galahad operates no hosting for customer data. The instance runs under your accounts, your keys and your jurisdiction, which is why the accreditation that governs it is the one covering the environment you install it into.

Is the deployment shared with other customers?

No. It is mono-tenant by construction: one deployment, one ontology, one database. Isolation inside that deployment is expressed as purposes with column-level grants at the database level, not as row-level security policies. If your requirement is to separate two record populations from each other, it is met by declaring two purposes and granting them differently, and it is enforced by PostgreSQL.

How is the application exposed on the network?

In the enterprise profile, TLS terminates at an authenticating proxy at the edge, with plaintext listening explicitly disabled and the service published on 443. The application itself binds to loopback, so no application port answers in the clear. The API container is never published at all: it is reachable only on the internal container network, and the database publishes nothing.

Which third parties sit in the data path?

One, and only if you choose it: the model provider you configure. Two further outbound paths exist and both are opt in. Web search is off by default. A cloud key management service is available if you prefer one to a local key store, and in sovereign mode the provider builder refuses a cloud KMS before any SDK is even imported. Nothing else in the running system is a recipient of your data.

What reaches an external model

Does our data reach an external model provider?

In the shipped default configuration, yes: the default provider is an external API and it calls out. Sovereign mode is a supported configuration and a flag rather than a posture. With external model calls switched off in the deployment configuration, the platform refuses to construct any external client at all. It will accept only a loopback, RFC 1918 or internal-domain endpoint, it refuses a cloud KMS before the relevant library is even loaded, and it keeps web search dark. The choice is made at deployment, it is visible in the configuration, and it is testable from your own network.

Can it run with no external model key at all?

Yes. The model backbone is provider agnostic and the deployment runs against any OpenAI-compatible endpoint, a local one included, with no external key present anywhere in the environment.

Which parts of the system send data to a model, and which do not?

The chat surface sends the question and the rows it retrieves. The known decision path calls no model during execution: a registered compute transform emits SQL over base tables and applies rules in plain Python. The only model call on that path is the router that selects which decision to run. On that surface, the model chooses which code runs; it does not compute the figures.

How your data is read

Is our data copied out of our systems?

It depends on the access mode bound to each source, and the mode is recorded per source because it is the first thing an audit asks.

Access mode What happens to the data Where it lives
Live foreign table Read on every query, no copy Stays in your source system
Managed copy Landed into a raw schema by an operator-run sync Copied into the platform database
Coarse integration Allow or deny call, not a joinable table Nothing is landed
Time-bounded cache Declared, not implemented Not available

Which systems can be read live, without a copy?

PostgreSQL, through its own foreign data wrapper. The live path is Postgres to Postgres: provisioning fixes that wrapper, the admin API refuses any other kind, and the planner reads the source’s system catalogue to build the mapping. Anything that is not PostgreSQL is read through a managed copy into a raw schema in the platform database. Installing the foreign data wrapper extension is a one-time superuser action on your side, by design: nothing in the product grants itself that privilege.

Can a sensitive source be bound to a weak access mode?

No, and the refusal happens at load time rather than at query time. A source flagged sensitive can be bound only to a live foreign table or a managed copy. Bind it to a coarse allow-or-deny integration and the loader refuses the source, so that misconfiguration never reaches a running system.

How fresh is a copied source?

As fresh as the last sync you ran. Mirror refresh is operator scheduled on purpose: the sync is meant to be driven from your own scheduler and is never called in process by the agent, and no scheduler ships inside the deployment. A live foreign table has no freshness question at all, because it reads the source on every query.

Once a source is mapped, is it governed differently from internal data?

No. A mapped client table is governed identically to a native one: same purposes, same grants, same audit.

Are documents governed like records?

Yes. A document can be served as extracted text or as the original file, and both are gated exactly like a record read. A separate extraction package fills an existing ontology from PDF, DOCX, XLSX and PPTX through a verbatim-provenance gate, so an extracted value carries the place it came from.

Can we see which systems and columns feed a given answer?

A lineage map serves a deterministic graph of sources, connectors, tables, ontology objects and their usage. It is PBAC scoped and audited like any other read, and it has a kill switch for an organisation that considers the physical schema map itself sensitive.

What the agent is allowed to do

Can an AI agent be given database permissions instead of prompt-level restrictions?

That is the design, and it is the answer to most of what follows. Effective permissions are the permissions the user’s roles allow, intersected with the permissions the active purpose allows, enforced redundantly at five layers: the tool filter, the ontology filter, the SQL validator, the PostgreSQL grants and the audit. The fourth layer is the non-negotiable floor. There is one PostgreSQL role per purpose, with column-level grants, and every data read switches to that role for the life of the transaction, so the executor is never the table owner. A read with no purpose routes to a role that holds no grants and cannot log in, and it is refused for insufficient privilege. A test in the build fails on any hand-rolled role switch written anywhere else, so new code cannot quietly open a second path around the floor.

How do you technically enforce which purpose an AI system is acting under?

Purpose is an access-control primitive here, not a paragraph in a policy. Each purpose is a PostgreSQL role with its own column-level grants, and the permissions a user holds are intersected with the permissions of the purpose they are acting under, so a role that would allow a read is not sufficient if the active purpose does not. Escalating to another purpose inside a session is its own recorded event, not a silent widening. When a DPO asks whether purpose limitation is enforced or merely declared, this is the enforcement: a grant that PostgreSQL applies, checkable by connecting as the role and trying.

What happens if a prompt injection makes the model attempt a query it should not run?

It gets a database error, not a polite decline. On the chat surface the model authors SQL, and that is stated plainly rather than hidden: the guard is a statement-class check on the tool, plus the connection it runs on, which carries the grants of the active purpose and nothing else. A query outside those grants fails with an insufficient-privilege error from PostgreSQL. The control does not depend on the model behaving, on the system prompt surviving, or on an injected instruction being detected.

What is an agent’s own identity, and what can it reach?

An agent is declared in the ontology, not conjured by a prompt. It is selected deterministically from the objects named in a directive, so the model does not choose its own boundary. It runs under its own PostgreSQL role, granted the intersection of its purpose and its specialty, and an empty specialty grants nothing. It fails closed: a missing agent role never falls back to the user’s purpose. And it is bounded by default rather than on request: a daily call quota plus a per-run budget over calls, rows, writes and wall-clock time. An agent that declares none of them inherits the deployment’s defaults, so no agent is unbounded by omission. Removing the bound is an explicit choice someone has to make and record, and a quota of zero blocks the agent outright.

What happens when permission is denied part way through a task?

On a permission denial the known decision path degrades to a sanitized read rather than failing the turn, and the denial itself is written to the audit trail. A hard deny raises an event the user can file as an access request, which means a refusal produces a governed request rather than a workaround.

What stands before a write

Can the platform write to our production systems?

Only where you turn it on, and only to a source declared writable. Write-back is off in the runtime by default and enabled per deployment. When it is on, the write lands in the real table in your own database, live and reversibly, not in a separate writeback dataset that someone has to reconcile later, and it runs on the same permission-scoped transaction as the write that triggered it, never on a connection of its own.

Writability itself is default-deny. Every write resolves its target table back to the source that owns it, and the write is refused unless that source is declared writable. A managed copy and a time-bounded cache are never writable, whatever anyone declares, because a write to a copy is silent data loss: it lands in the copy, the next sync overwrites it, and nothing tells you. A live foreign table is written to only where it has been declared writable, and a table native to the platform’s own database is unaffected. The check runs on every write, in every deployment profile, before the savepoint is opened, and a refusal rolls back and records a failed execution in the trail naming the source as read-only.

What stands between the model and an irreversible write?

The write tool never executes inline. A proposed write persists as a pending approval, opens a savepoint under the purpose captured at proposal time, computes a dry-run diff, and holds. Approving commits it, rolling back discards it, extending resets the hold. The purpose used at execution is the one captured when the write was proposed, so a permission change between proposal and approval cannot be used to widen what the write touches.

What happens if nobody answers the approval in time?

The hold has a deadline and it is five minutes. An extension resets it, up to three times. If the deadline passes unanswered and unextended, the expiry sweep takes the rollback branch: the savepoint is rolled back, the pending approval is marked expired, and nothing reaches the target table. Inaction cannot commit, and it does not leave a transaction open either, so a stalled proposal resolves to no change at all. The expiry is written to the trail as its own event like every other step of the lifecycle, and the proposal keeps its dry-run diff, so an auditor reads what was proposed and confirms it was never applied. Recovering the write means proposing it again, under a fresh hold.

Are the actions declared, or does the model invent them?

Declared. The ontology carries named actions and functions, and applying one emits parameterised SQL against the target through the same approval and savepoint flow. Each is gated on the permission for that specific action plus the mutation permission for the object it touches. There is no path where a model composes a novel write and executes it.

Does a human validate before anything executes at all, not only writes?

Yes. A plan gate stops the turn before anything runs: the planner announces its plan, raises a plan-required event, and nothing executes until the user validates it. Separate events cover a mutation approval, an in-session purpose escalation, and a hard deny. When a deployer has to demonstrate effective human oversight, this is the mechanism to point at: not a checkbox in a settings page, but an execution that does not start.

What the audit trail proves

How do I prove to an auditor which data an agent accessed and who approved the action?

Audit completeness is stated as a contract rather than as a log level, and three things it captures are the three ordinary application logging misses.

Event When it is written Why it matters in an audit
The verbatim user query At ingress, before any planning What was asked survives even when the plan is rejected
The full mutation lifecycle Execute, rollback, commit, extend, including the rollback the expiry sweep performs The write has a history, not just an outcome
Record disclosures Both granted and denied A refusal is the evidence that a control worked

The table itself is append-only through a PostgreSQL trigger. With owner separation on, which is the default, the server refuses to boot while the runtime still owns the audit log, so the process writing the trail is not the process that could alter it.

Is the log tamper evident, and what does that mean concretely?

Each audit row carries a SHA-256 written by an insert trigger that overwrites whatever the caller supplied, so an application bug or a compromised application cannot forge a row hash. Anchors chain ranges of rows together. A bundled verification command recalculates the chain and exits with a failure code on alteration, which makes verification something your own pipeline runs on a schedule rather than something we assert on a slide.

Anchors ship out, and that is the default rather than a setting someone has to remember. The product ships hardened: the trail writes to a durable file sink, a scheduled seal exports the anchors for both chains, what an agent did and what it looked at, and the server refuses to boot if anchoring cannot be turned on. The default sink is a local file, so a fully disconnected deployment is anchored with no egress at all. To put the anchors somewhere the operators of the box cannot reach, point it at one of the other sinks: syslog RFC 5424 over TLS, an HTTP collector, or object storage with Object Lock in COMPLIANCE mode, which refuses a bucket whose lock is not real.

What does the tamper evidence not cover?

Three boundaries, named, because a control whose edge is undeclared is not a control.

  • Destruction of the host is not prevented, it is made undeniable. The chain proves rows are missing. It does not bring them back. Off-host anchors are what turn that into evidence rather than a gap.
  • Retention is yours. The platform does not decide how long the trail is kept, and no default retention is imposed on your behalf.
  • The hashes are not notarised by a third party. There is no HSM signature and no RFC 3161 qualified timestamp. The trail is tamper evident against the application and its operators, which is a different and narrower claim than a countersigned one.

What happens if the audit write fails?

The write is refused, and it is a transaction guarantee rather than an application check. The commit record is inserted on the mutation’s own connection, inside the mutation’s own transaction, before the commit. If that insert fails, the transaction rolls back and nothing reaches the target table. There is no state in which the write was committed and the record of the commit was not.

The boundary is worth naming, because it is a design decision rather than an oversight. Only the commit record is inside the transaction. The execute and rollback records around it are written best effort, outside it, deliberately: a rollback is discarding the very transaction its record would have to be written inside, so binding it there would mean a rollback left no trace at all. The property to hold us to is the one that governs a persisted change. A write that committed always carries its commit record, and a lifecycle record that fails to write leaves a gap in the history of a write, never a write with no history.

How do I replay a decision months later and get the same answer?

A saved decision stores its parameters plus an immutable issued record: verdict, rules, codes, scope and hash. It never stores the output. Re-opening it re-derives the decision under the current purpose, with zero model calls, and flags hash divergence when the result has changed, for instance because a narrower purpose now sees fewer findings.

That is the property an audit actually needs. A stored answer only proves what was displayed once. A re-derivation tells you whether the decision still holds today, and when it does not, it says so instead of quietly returning something different.

An agent wrote wrong data to a production system. How do we roll it back and prove what happened?

Before commit, rolling the proposal back discards the savepoint and nothing lands. After commit, the trail carries the whole lifecycle of that write: the proposal, the dry-run diff shown at the time, who approved it, under which purpose, and when. Any correction is itself a write, so it goes through the same proposal, diff and approval path and lands in the trail beside the original, which means the remediation is as evidenced as the mistake.

Determinism and model risk

Will the same question give the same answer twice?

The honest answer has two halves and both matter to a model risk function.

On the app surface, identical parameters return identical figures. The execution path calls no model and compiles its SQL from a declared retrieval contract: the same contract, the same ontology and the same parameters produce byte-identical SQL, values are bound as parameters and never interpolated, and a conformance check asserts that the emitted SQL equals the contract. A validator therefore reviews the contract once, rather than reviewing a query generated afresh on every run.

The chat surface is stochastic by design, and the model writes the SQL there. The router that selects which decision to run on the app surface is itself one model call. So “type this sentence, get the same application every time” is not guaranteed. “Run this application with these parameters, get the same figures every time” is, and that is the claim to hold us to.

Is the response byte-identical between runs?

No, and the difference is recorded rather than papered over. The payload carries a wall-clock generation timestamp, which is excluded from the integrity hash, so the envelope differs while the decision record stays stable. Findings are re-sorted in code on a key that is not total, so row arrays can permute between two runs. The figures do not change.

Identity and access to the platform

How do users authenticate, and does it integrate with our identity provider?

Four paths ship, each on its own switch and dark until you enable it: SSO through an identity-aware proxy, native OIDC with PKCE, SCIM 2.0 push provisioning, and native TOTP multi-factor authentication. The step-up is the part worth noting: MFA is demanded on any purpose flagged as requiring it, so the second factor is required when the work is sensitive rather than once at the door.

How are credentials stored and how is brute force handled?

Passwords are stored with bcrypt. Account lockout is database backed and keyed per account: five failed attempts, then a fifteen-minute lock. It is persistent, so it survives a restart, and TOTP guessing draws on the same counter rather than getting a fresh budget.

Is there a machine-to-machine path, and can a browser call the API directly?

A machine API key exists for server-to-server use. There is no CORS allowlist and no CORS middleware is mounted, and the content security policy restricts the page to calling its own origin, so there is no browser-direct path to the API from another origin. The application is same-origin through its own proxy.

Certification, and what you can verify yourselves

What certification does the product carry?

We assert none, and the reason is architectural. Monarch runs inside your infrastructure, on your machines, under your keys and your jurisdiction. The accreditation that governs the running system is therefore the one covering the environment you install it into, and it is yours to hold. What we put in front of an assessor is the control and its enforcement point: the trigger, the grant, the flag, the exit code. Every answer on this page is written to be checked against a running instance rather than accepted on our word.

What can we verify ourselves, before signing anything?

Four checks, all runnable on your own instance, none of which requires trusting a document.

  1. Run the audit verification command against your own trail, then alter a row and watch it fail.
  2. Attempt a read with no purpose active and observe an insufficient-privilege error from PostgreSQL, not a message from a model.
  3. Propose a write, read the dry-run diff, roll it back, then confirm the target table is untouched and the whole lifecycle is in the trail.
  4. Narrow a purpose, re-open a saved decision, and watch the hash divergence flag fire.

A rehearsal of a use-case pack additionally writes a machine-generated proof recording the pack fingerprint, the commit hash and the timestamp of a green run, so the artefact that says an instance passed is produced by the run itself rather than typed afterwards.