Skip to content

DP-001 — Data protection

For a customer’s data protection officer, security reviewer or legal team, answering the questions asked before a pilot rather than after an incident: what this installation stores, what leaves it, what can be erased, and what this platform cannot do for you.

Every statement below names the mechanism it rests on. Where a property is a consequence of how something is built, it says so; where it depends on a decision the customer makes, it says that instead. Section 8 lists what this document does not establish.

Written in English because this repository is public and its documents are reviewed in English. If it is to be handed to a DPO who reads Portuguese, it needs a translation — that is a deliverable, not a formality, and pretending otherwise is how a document gets approved by people who did not read it.


The installation keeps two things, and the difference between them is what makes erasure possible at all.

The chain. run_steps records what happened, step by step, each step sealed against the one before it by a hash. It is append-only and the database enforces it: there is no UPDATE and no DELETE. A correction is a new step.

The content. run_content holds the bulk of what a run touched — the text of a ticket the agent read, the arguments a tool was called with, a tool’s result. For these, the step carries a reference and a digest, and the bytes live where they can be erased.

This is why an erasure does not break anything. Deleting that content leaves the chain untouched and still verifiable: every hash still checks, because no hash covered the bytes — it covered the reference to them.

The split is not absolute, and the exception matters

Section titled “The split is not absolute, and the exception matters”

Some text is written into the step itself, and therefore into the append-only table. It is not covered by erasure and not covered by retention, because neither touches run_steps. It is permanent.

Written into the stepWhere it comes from
RunFinishedPayload.Outcome on older runsThe model’s final answer, verbatim
ApprovalDecidedPayload noteTyped by the person approving
AbandonedPayload.ReasonTyped by the person abandoning the run
Resume noteTyped by the person resuming

The first row is the one a reviewer must weigh. It was a defect: an agent’s closing answer is free text produced by the model, and it can restate anything the agent read on the way — a name, an address, the body of a ticket. New runs store that answer in run_content behind OutcomeRef and OutcomeDigest. Older runs cannot be rewritten without breaking the hash chain, so their inline answers remain permanent. A customer with historical runs should assume those old model answers survive erasure.


Roughly thirty tables. The ones that can hold personal data:

WhereWhat it can hold
run_contentEverything an agent read or wrote: ticket text, email bodies, tool arguments, tool results and failure diagnostics, model replies
run_stepsWho asked, which agent, which tool, what the Gate decided; data-flow labels for company, area, untrusted or personal origin; references and digests for bulk content and shared context artifacts — and the free text listed in section 1, permanently
runs.last_errorOperational error text for the latest failed turn; classified model provider failures store only a stable code, but unclassified local or integration failures can still carry free text
principals, sessions, role_grantsThe people who use the console: identity from the customer’s own provider
channel_inbox, channel_deliveries, channel_delivery_failuresMessages exchanged on a connected channel, what was sent back, optional thread context supplied to a run, and operational delivery failures tied to a run and conversation
mcp_egress_denialsStdio MCP proxy failures by configured server, host, port, stable code and timestamps; no URL path, query string, header, body, token or tool argument
memory_assertions, memory_suggestions, memory_assertion_eventsHuman-reviewed or agent-suggested structured memory claims, source run/artifact/digest evidence, labels, actor, reason and timestamps
admin_events, audit recordsWho changed what configuration, and when

agent_specs, policies, areas, scopes, settings and the trigger tables hold configuration written by the customer’s own staff. They can hold personal data only if somebody writes it into an instruction.

The runs row is a projection, not the audit chain and not the content store. Runtime dashboards read it because folding the chain during an outage would make the database do archival work at the worst possible time. That projection therefore has to stay low-cardinality where it can: typed model provider failures write codes such as model_auth_failed, not the provider’s response body. Errors the platform has not classified can still leave their text in runs.last_error, outside content retention and erasure.

Data-flow labels are not message content. They are stored with steps so the Gate can refuse a later action when content from one company or area reaches a run outside that scope. They are also what makes shared context between agents auditable by reference instead of by copied prose: the ledger carries artifact name, source run, digest and labels, while the artifact bytes remain in run_content.

Everything is in the customer’s PostgreSQL, in the customer’s environment. Nothing is stored by the vendor, because there is no vendor-side component.


This is the section that matters most, and the honest answer is that data does leave — deliberately, to places the customer chooses.

The model provider. An agent works by sending text to a large language model. That text is whatever the agent has read. The providers this platform can be pointed at are configured by the customer, and the built-in list spans several jurisdictions — api.openai.com, api.anthropic.com, api.mistral.ai, api.groq.com, api.x.ai, api.together.xyz, api.deepseek.com, api.moonshot.cn. The last two are outside the EU and the US, which is a fact a DPO needs before choosing, not after.

Two further presets — vllm and ollama — carry no vendor endpoint. They exist for the customer that cannot let prompts leave its network at all: the base URL is the installation’s own, and nothing goes to a third party.

FuseOne bundles no inference runtime of its own, so running a model locally means the customer runs it. The path is supported and configured; it is not the default, and choosing it is the decision that settles this section.

Remote MCP servers. A tool server reached over HTTP is a third party the customer connected, and tool arguments go to it. A tool server run locally over stdio does not leave the machine — and does not inherit the process environment: the child receives an allowlist (PATH, HOME, TMPDIR, LANG, LC_ALL, TZ) rather than the worker’s secrets.

Channels. A connected channel — Slack, for instance — carries messages to that provider under the customer’s own workspace agreement.

A channel can also be configured to include prior Slack thread messages when a person mentions the agent inside a thread. That is not just “the message that mentioned the bot”: it can include text written by other people in the same thread. Those messages become run input, are stored as content behind a reference, are marked untrusted, and may be sent to the configured model provider along with the rest of the prompt. The checkbox that enables this is therefore a data-protection decision, not only an authoring convenience.

Sign-in. OIDC reaches the customer’s own identity provider: it fetches the issuer’s discovery document, and exchanges an authorisation code for a token on every sign-in. The destination is whichever provider the customer configured.

There is no telemetry to the vendor, no usage reporting, no phone-home — the vendor receives nothing, because there is no vendor-side component to receive it. But “no outbound at all” is not something this platform can claim: a signed-in console has already talked to an identity provider, and a connected channel talks to its own.


Two mechanisms, deliberately distinct.

On request. Erase(owner, reason) clears the content of the runs an operator identified. The bytes become null; erased_at and erased_reason remain as a tombstone. Something that later resolves that reference gets a distinct error meaning this was deliberately erased — never confused with a reference that was always wrong.

The erasure is itself recorded, as an admin event carrying the number of runs, the number of objects and the reason. That is on purpose: an erasure nobody can account for is indistinguishable from data loss, and a DPO asked to prove a request was honoured needs a record that it was.

Finding the runs is the operator’s work. The platform erases what it is given. It does not offer “everything about this person” as a query, because it does not index by data subject — a run is identified by agent, scope and time, not by whose data it touched.

What survives an erasure. The chain: that a run happened, which agent, which tool, what the Gate decided, and the digest of content that no longer exists. That much is the deliberate trade — a record of processing is what the platform is for.

Operational rows survive a per-subject erasure too. They are not the chain, but they are not found by subject and most are not rewritten by the erasure job: channel_inbox, channel_deliveries, channel_delivery_failures and mcp_egress_denials age out through retention instead. Memory is the exception: an active assertion whose evidence points to an erased run is marked source_erased, so it remains auditable but is no longer served as intact knowledge.

And, for older runs, more than that. The free text of section 1 survives too, including model answers recorded before OutcomeRef existed. That part was not a trade anybody chose; it is historical behaviour that cannot be repaired by rewriting the chain.

If a regulator requires that no trace of the processing survive, this platform does not meet that requirement, and no configuration changes it.


Content ages out on a window the customer sets. A sweep runs daily and erases everything older, with the reason retention, recording content.expired when it erased anything.

The window is read on every sweep rather than cached, so shortening retention takes effect on the next run.

The same sweep deletes operational channel rows older than the window: channel_inbox by arrival time, channel_deliveries by post time, and channel_delivery_failures by the last time that failure was seen. Open channel debts are kept until answered: a refused ask that still needs a reply, or an opened ask whose finished run still owes its closing answer, is current work even if the original message is older than the retention window. The failure case follows the same rule. A channel failure that began months ago but still failed this morning is current operational evidence, not expired history. Stdio MCP egress denials are also deleted by last-seen time, so a still-active egress problem remains visible until it stops recurring and then ages out. Memory assertions, memory suggestions and memory assertion events age out through the same sweep; expired assertions and suggestions are removed even when they were updated recently. A suggestion whose source evidence is erased is marked source_erased, so it remains auditable until retention removes it and is no longer offered for review as intact knowledge.

There is a floor. A window below 24 hours is refused, and it is checked twice — when the setting is written and again when the sweep reads it back. The second check is what stops a corrupted row from becoming an installation-wide delete.

The chain is not swept. Retention governs content and channel operational records, not the record that processing happened.


Credentials — provider keys, tool server tokens, channel secrets — are sealed with AES-256-GCM under a master key supplied as an environment variable and never stored in the database.

Sealing is bound to context: the key identifier and the credential’s context are authenticated alongside the ciphertext, so a sealed value lifted from one row and pasted into another fails to open rather than opening as something else.

Sealed credentials are never returned by any endpoint that lists them. Revealing one is a separate, deliberate act.

The master key is not in the backup. A database backup restored without it yields an installation that cannot open any credential it holds.


Stated plainly, because each of these gets assumed:

  • It does not narrow a credential. A token issued with more reach than an agent needs, reaches that far. Scope belongs where the credential is issued — in GitHub, in SAP, in the customer’s own systems.
  • It does not classify personal data. It does not know which field is a name. The taint mechanism marks content by origin, not by sensitivity.
  • It does not index by data subject. See section 4.
  • It does not log tool arguments — and cannot speak for what it runs. FuseOne’s own tool execution path emits no log lines at all. But a local MCP server is a separate process whose standard error is wired to the worker’s, so anything that integration chooses to print lands in the worker’s log, outside the retention window and outside this platform’s control. A remote MCP server logs whatever its operator decided. What a tool server logs is the integration’s responsibility, and has to be assessed per server.
  • It does not anonymise or pseudonymise. What an agent reads is stored as it was read, until erased.

  • No independent audit. Everything here was verified by reading this repository. No third party has assessed it.
  • No DPIA, and no legal-basis analysis. Whether a given agent’s processing is lawful depends on what the customer configures it to read, and this document does not assess it.
  • Erasure has not been rehearsed at volume. The mechanism is tested; a subject request across a large installation has not been carried out.
  • No certification is claimed. Not ISO 27001, not SOC 2, not any other.
  • Historical inline outcomes have not been erased. New runs store the model’s closing answer where retention and erasure reach it. Old runs that already wrote that answer into run_steps keep it there, because rewriting the chain would destroy the audit property the platform depends on.
  • The provider list may drift. Section 3 names what the built-in list carries today. An installation’s configuration is what actually governs where data goes, and it is the customer’s to read.