Manage Anectico
Read as MarkdownDecide what recorded content may be used for
The per-project content policy: six kinds of content against four boundaries, which cells are enforced today, and the one denial that quietly empties two other boundaries.
A content policy answers one question per project: what may this project’s recorded content be used for? It lives at Govern → Content Policy.
It is a grid. Six kinds of content:
- Model transcripts — prompt and completion bodies.
- Tool call arguments — the arguments and results of tool and MCP calls.
- Session snapshots — session-replay recordings.
- Person properties — the property bag on a person, such as an email address or plan.
- Account properties — the separate property bag on a business account or group, such as a contract value or account tier.
- Agent memory — what a memory operation itself wrote or returned. A retrieval step’s returned documents are a model input, not memory, and are governed by Model transcripts instead — see Memory provenance and quarantine for the identity and labelling workflow built on top of this class.
Against four boundaries:
- Recorded — written down at all.
- Shown to a reader — served to somebody asking for it.
- Sent outside Anectico — included in a bulk export or sent to a customer-controlled task
callback. The API calls this boundary
export. - Sent to a model — handed to an evaluation model or an investigation, which may be a third party.
Each cell is Allow, Metadata — you may know the content exists and how large it is, never its bytes — or Deny.
Read this before you rely on a cell
Eighteen of the twenty-four cells are enforced today; six have no flow to gate. This reference says which is which for every cell rather than leaving you to find out, and there are three possible states, not two:
| Marker | What it means |
|---|---|
| Enforced | A denial here changes what the product does. |
| Not enforced | Your choice is recorded and audited, but it is not yet the control deciding this path. |
| Nothing to gate | No content of this kind crosses this boundary at all, so the cell would change nothing however it were wired. |
Here is the whole grid as it stands:
| Recorded | Shown to a reader | Included in an export | Sent to a model | |
|---|---|---|---|---|
| Model transcripts | Enforced | Enforced | Enforced | Enforced |
| Tool call arguments | Enforced | Enforced | Enforced | Enforced |
| Session snapshots | Enforced | Enforced | Enforced | Nothing to gate |
| Person properties | Enforced | Enforced | Enforced | Enforced |
| Account properties | Enforced | Enforced | Nothing to gate | Enforced |
| Agent memory | Nothing to gate | Nothing to gate | Nothing to gate | Nothing to gate |
Agent memory has no producer at all, so every one of its cells is already true. Documents that a retrieval or search step pulled in are recorded as part of the model transcript and are governed by that row, not by agent memory.
Session snapshots and person properties reached this table last, and two details of how they bind are worth knowing before you set either to Deny:
- Denying session snapshots at “Recorded” stops recordings being kept at all for that project; the browser SDK’s uploads are refused rather than accepted and discarded. Denying them at “Shown to a reader” refuses playback with an explanation rather than showing an empty timeline. What a session was linked to — the errors and product events that happened during it, and the fact that a session of a given length happened for a given person — is not the recording and stays visible.
- Denying customer properties at “Recorded” drops the property values on an
identify()call. It does not stop Anectico identifying the person: the identity, the merge of an anonymous visitor into a known one, and every link from that person to their sessions, errors and traces all still happen. Clearing a property is never refused. Denying them at “Shown to a reader” leaves the property bag off each person, per person, according to the project that person belongs to.
“Recorded” reaches the context you attach to an error, too. Tags, extra data and captured request
headers on an error report are your own key/value bags. A field you name after a model-content
convention holds the same bytes the policy governs on a span, so a Deny at Recorded drops it
from the error and from its issue and says which fields it dropped. Everything else you sent is
untouched. anectico.content.* is a reserved key prefix in every bag Anectico stores — those are
the fields it uses to tell you what it withheld, so a key of your own with one of those names is
dropped rather than stored.
Both boundaries reach the properties on a product event. The property bag you send with an event is your own key/value bag on the same terms. Recorded drops the fields named after a model-content convention before the event is written. On Shown to a reader, a credential below the recorded-content tier receives only attribute keys Anectico knows are operational metadata; an unrecognized key is withheld because its value could be content from a convention introduced after this release. This applies per event and per project on both a person’s and an account’s activity timeline. The event itself, its name, time, person and session links stay visible.
A nested field is a field. Property bags nest, and so do attribute values — an object or a list under a key of your own. Both boundaries look inside them: a field named after a model-content convention is governed wherever it sits, not only at the top of the bag. The fields beside it survive, including the key it was nested under, so denying one nested field never costs you the object it lived in. A structure Anectico cannot read all the way down — nesting deeper than 32 levels — is refused rather than passed through, because a level it cannot inspect is a level it cannot decide.
The same read rule applies wherever raw telemetry attributes are returned: span attributes, resources, span events and links; log attributes and resources; metric exemplar attributes; custom metric labels; and metric labels on unified-search results. Known operational fields such as route, status, service, model name and token counts remain visible. An unfamiliar field is recoverably withheld rather than guessed safe.
Withholding is explicit in the returned bag. anectico.content.withheld names fields omitted because
the credential lacks the recorded-content permission;
anectico.content.policy_withheld names fields refused by this project’s policy; and
anectico.content.not_stored records fields that were never written. Each names the fields it
refused; a field that was nested is named by its full path from the top of the bag
(http.route.gen_ai.prompt.0.content), so you can see not only what is missing but where the hole
is. A policy that cannot be confirmed is treated as a policy refusal at read time. If the credential has the recorded-content
permission and the project permits both model transcripts and tool arguments at Shown to a
reader, an otherwise unfamiliar key may be returned.
Account properties have their own model-transfer decision. An investigation of an account may use the account bag only when the caller may read that account and the project’s Account properties → Sent to a model cell permits it. Denying Person properties does not deny account properties, and allowing it does not allow them: a person’s email address and an account’s contract value are independent disclosures.
A promoted evaluation case is refused rather than trimmed. Promoting a production agent run into
a dataset governs the run’s recorded content as usual. The expected and metadata documents you
send with it are treated differently: if either carries a field named after a model-content
convention your project refuses at Recorded, the whole promotion is refused and nothing is
written, naming the document and the field. A dataset version cannot be edited once written — that
permanence is what makes a result citing it mean something — so quietly dropping a field would leave
a specification you did not author, scoring differently for ever. Refusing costs you one edit; you
are still holding the document. Reading a case back, or replaying one in the sandbox, is governed at
Shown to a reader: the frozen run keeps its shape without the recorded bodies, and the two
documents you supplied are withheld whole with a note saying what was refused and why.
A task callback is an external transfer, even though it is not a bulk export. An investigation answer can restate a transcript, tool result, session snapshot, or customer property. Before every delivery attempt, including a retry, Anectico applies the current Sent outside Anectico decision for the content that could have contributed to that answer. A denial — or a policy that cannot be confirmed — still sends the task state and protocol metadata, but not the conclusion body. The answer stored in Anectico is unchanged, so a later permitted read remains possible.
Denying person properties at “Shown to a reader” has two knock-on effects the grid does not show:
people search stops matching on property values for that project (searching by any identifier
you sent with identify() still works), and audiences defined by a property value stop
refreshing, because computing one means reading those values. Audiences defined by behaviour are
unaffected.
When a cell moves between states, the product’s marker moves with it and this page changes in the same release.
Denying at “Shown to a reader” also empties two other boundaries
Included in an export and Sent to a model are both assembled from the same read. So a class you deny at “Shown to a reader” is already gone before either of their own decisions is consulted — whatever those cells say.
The product warns you at the moment you set it. The composition runs one way only: denying at “Included in an export” leaves reading untouched.
Leaving the downstream cells on Allow after denying the read is not harmful, but it records an intent the project will not actually have. Set them to match what you mean.
Recording is the boundary you cannot undo
The other three withhold a copy of content that is still recorded, so a denial there is reversible — widen the cell and the content is served again. Recorded means the bytes are never written, and nothing brings them back.
That asymmetry runs the other way too. If Anectico cannot read your policy at the moment content arrives, it keeps serving the last answer it successfully read, so an explicit refusal of yours stays in force through an outage. A project that has never been configured is answered from the platform default until its own policy is written — which happens within about fifteen minutes of the project being created — because refusing those minutes would destroy a new workspace’s content and answer the same project two different ways.
Editing it
The page saves the whole grid, not the cell you touched. That is deliberate: a partial save cannot express the removal of a permission, and “just change the one I care about” is how an older grant survives an edit nobody noticed. The page builds that grid from the policy it reads. If the API introduces a content class before this dashboard version has a specific name or enforcement description for it, the page still shows the row and sends its existing decisions back unchanged.
Every save carries the version you were looking at. If somebody else changed the policy while you had the page open, your save is refused and you are told to reload rather than silently discarding their tightening.
A change takes up to 30 seconds to apply everywhere.
The effect is retroactive at read boundaries. In particular, a finished export that contains recorded content is not a standing grant: every retry and download asks the current Sent outside Anectico cells again. Tightening them holds the existing file back without rewriting or deleting it; widening them can make that same file available again. If the current decision cannot be confirmed, the transfer is temporarily refused.
What the policy has refused
A refusal is invisible by design — the content simply does not travel — so the workspace keeps a record of them. Every time this project’s policy withholds content at a boundary, it writes one entry naming the kind of content, the boundary, why it was withheld, and who was asking. That is the answer to “why did my export come back short”, and to “has this control ever actually done anything”.
Three things about it are worth knowing before you read one.
It records refusals, not access. A permitted read writes no entry. Every successful view of a trace, a log line, a profile or a recording passes this control and is allowed, so recording those would mean an entry per view of anything — a second stream of data about the fact that nothing happened. So this is not an access log and it cannot tell you who has read a given person’s content; it tells you what your policy has stopped.
“Withheld everything” and “withheld the content but kept the summary” both count. The second looks like an allow in the entry, because the item’s existence and size did travel — but the bytes you asked for did not, and that is what you are looking for.
One refusal is one entry, however many times it was attempted. An export that was refused, failed for an unrelated reason and ran again is one refusal in this record, not two — so a count here is a count of what your policy stopped, not of how many times the workspace tried. A genuinely separate attempt on a different item is its own entry, and a refusal whose reason changes — your policy tightened between two runs, say — is a new entry too, so the record never keeps asserting a reason that has stopped being true.
Entries carry no content of their own. The kind is a name; the entry exists precisely because the content did not move.
Permissions
Reading a policy — and the record of what it has refused — needs governance:read, which every
role from viewer up holds: the people a disclosure control protects have to be able to audit it,
and neither the grid nor the refusal record discloses content of any kind.
Changing one needs governance:write, which is owner- and admin-only. Widening a content policy is
the broadest disclosure act the product offers: one change can make every transcript in a project
readable, exportable or sendable to a model, retroactively, for every credential that already holds
the matching content permission.
The policy and the permission compose by taking the stricter of the two. The policy answers whether
these bytes may cross this boundary at all; a credential’s content permission answers whether this
credential may see them. Neither stands in for the other, and governance:write grants no content
directly — it changes what the other checks decide. See
Permission scopes.
How long each kind of content is kept
The grid above answers what recorded content may be used for. A retention window answers a second question about the same six kinds: how long is it kept at all.
Each kind can carry its own window, in days, independently of the others — transcripts for seven days while replay recordings stay for thirty is a coherent posture and a common one. A kind with no window set is kept for as long as the workspace keeps that data anyway, which is the default and is what every project starts with.
Two properties are worth knowing before you set one.
A window only ever shortens. It brings deletion forward; it cannot extend how long anything is kept. The longest window you can set is 180 days, which is the workspace’s own storage limit — a longer number would be a promise the storage could not keep, so it is refused rather than saved. Your plan’s history window can be shorter than that, and where it is, it wins.
Shortening a window is the one setting here you cannot undo. Widening a denied cell in the grid above brings the content back, because the content is still there and was only withheld. Widening a retention window brings nothing back. Set one deliberately, and prefer trying it on one project before applying it across a workspace.
Every change is recorded — which kind, the new window, what it was before, who changed it and when — so an audit can answer “who set transcripts to one day” months later.
Editing it
Govern → Content Policy → Retention. You save the complete set of windows at once, the same way you save the grid: a kind you leave out has its window removed and goes back to the default. That is deliberate, because removing a window is how you undo a tightening, and a form that could only ever set numbers would have no way to express it.
governance:read shows the windows; governance:write changes them — the same two permissions as
the grid, because this is the same control along a different axis.
What a window actually does to your data
A window that has passed does not remove the record of what happened — it removes the content from it.
For transcripts and tool arguments that means the prompt, the completion, the tool arguments and the tool results are erased from the trace, and the trace itself stays: its timing, its status, its service, its token counts and every other attribute are untouched. That is deliberate. Expiring a transcript by deleting the trace would delete the operational record you set the window to trim, and you would lose the ability to see that the call happened at all.
The same erasure reaches everywhere else that kind of content is kept, not only the trace. If a prompt was carried on an agent step, on a metric’s exemplar, in an error’s tags, extra or request headers, in the tags shown on the issue that error rolls up into, or in a product event’s properties, the window removes it from each of those too, and each of those records likewise stays.
There is no longer an exception. A log line’s attributes, the resource a log was emitted from and a metric’s own labels used to sit outside the window and expire only on the workspace’s much longer storage schedule; they are now cleared on your window like everywhere else. The log line and the metric point themselves stay, with their message, severity, service, value and every non-content label intact, so a short window trims the content without costing you the record that it happened.
For session replay the recording IS the content, so the recording and its snapshots are removed outright.
Windows are enforced on a schedule rather than at the instant they pass, so content can survive for a few hours past its window before the next pass removes it. Once removed it is gone; there is no recovery.
Two kinds — customer properties on a person and on an account — accept a window today and are not yet enforced. They are not silently ignored: the workspace reports them as windows it could not act on, and they will be enforced when the property stores gain the capability. Set one if you want the intent recorded; do not rely on it as a deletion guarantee yet.
Erasing one person’s content now
A window is a schedule. When somebody asks you to erase what you hold about them, you do not want to wait for one.
DELETE /api/v1/projects/{projectId}/content/{class}/{subjectRef}
class is one of the six kinds; subjectRef identifies the person inside the project. The request
body must carry a reason, and it is required rather than optional — this is the one action here
that nothing undoes, and a record of who erased what without why answers only half of the question
an erasure review asks.
What it does:
- removes that person’s content of that kind, immediately, from every read surface — the API, the dashboard, the MCP tools and the CLI all see the same thing;
- leaves the surrounding records standing, exactly as a window does. Erasing a person’s transcripts does not delete the traces of the requests they made, which are your own operational record;
- leaves other people’s content alone, even where it is byte-for-byte identical. The erasure is by person, not by content;
- writes a tamper-evident receipt naming you, the kind, the person and how many records changed. It is the only record that survives the erasure, which is why it exists.
Repeating the call is safe and is the expected way to confirm: a second request answers success with
already_erased: true and changes nothing. It never answers “not found” — that would tell a
requester whether you hold anything about that person, which is itself a disclosure.
Session replay cannot be erased for one person this way. The recordings are selected by age across a project and there is no per-person selector, so the request is refused rather than deleting every recording in the project to be sure of covering one. Delete the individual recordings instead.
governance:write is what allows it, and it is bounded to your own workspace.
The same answers over the API
GET /api/v1/content-policies
GET /api/v1/projects/{projectId}/content-policy
PUT /api/v1/projects/{projectId}/content-policy
GET /api/v1/projects/{projectId}/content-policy/retention
PUT /api/v1/projects/{projectId}/content-policy/retention
GET /api/v1/projects/{projectId}/content-policy/access-audit
DELETE /api/v1/projects/{projectId}/content/{class}/{subjectRef}
Both reads return every cell, denied ones included, so you can render the grid without knowing
the vocabulary in advance. The PUT replaces the grid; pass the expected_version you last read to
have the write refused if somebody changed it meanwhile.
The retention routes answer only the kinds that HAVE a window — a kind with none is genuinely
absent rather than reported with a default number, because a number in this screen is an
instruction to delete and you should never be shown one you did not choose. The PUT replaces the
whole set. Full request and response shapes are in the
REST API reference.
The access-audit route lists what the policy REFUSED, newest first, and takes optional
content_class, sink, principal_id, since, until and limit filters. An omitted filter
means every value rather than the unset one, so a bare call returns the whole project. Remember
what the section above says about it: an empty page means nothing was withheld, never that nothing
was read.