Content policies
Available to Enterprise Plan customers only.
Overview
A content policy tells Zep what derived information must not enter a Context Graph. You define the categories and the rules. Zep evaluates every artifact that ingestion derives from an episode against the rules of the graph before the artifact is published. An artifact that matches a rule is dropped. The source episode stays stored.
Use a content policy when your application must keep a class of information out of agent memory, for example health conditions in a banking assistant or compensation in a support agent. The policy applies to the graph content that Zep derives. The policy does not filter, redact, or rewrite the text you send.
A content policy separates three surfaces:
- The project policy is a set of categories and rules that you write at project scope. Each write creates a new immutable revision.
- The bound policy is the policy of one graph. Zep copies the current project revision when the graph is created. The bound policy never changes after creation.
- The episode state is the
content_policyobject on each episode of a protected graph. It records the evaluation status and what Zep dropped.
What Zep evaluates
Zep evaluates the artifacts that ingestion derives from an episode: entities, entity attributes, entity summaries, facts, thread summaries, document summaries, and observations. Zep does not evaluate the source episode text.
A classifier that Zep operates answers one question per artifact and rule: does the artifact state or imply the prohibited meaning about a named or identifiable person? Zep converts each answer into one of three decisions.
Zep never rewrites, redacts, or replaces a dropped artifact. A drop is complete:
- A match on a fact drops the fact. The entities at each end stay under evaluation on their own.
- A match on an entity drops the entity and every fact of the same episode that connects to that entity.
- A match on one entity attribute drops that attribute only.
- A match on an entity summary drops the summary. The entity is published without a summary update.
- An entity that the episode creates is dropped when a match drops its summary and no published edge connects to it. An entity that existed before the episode stays.
- An invalidation, expiration, or contradiction that depends on a dropped fact is not applied. Existing facts keep their state.
Permitted artifacts of the same episode are published. One episode can publish permitted facts and drop prohibited ones.
An artifact over 4000 characters is not truncated. Zep drops the artifact without a flag. A summary over 4000 characters is evaluated in segments, and a match on any segment drops the whole summary.
Source episodes stay stored as written, under the existing retention and deletion behavior and BYOK controls. A content policy is not a promise that prohibited text never reaches Zep. It prevents the derived graph content. An application that must filter source text does so before it sends data to Zep.
The policy model
A policy set has categories and rules.
- A category has a
keyand an optionaldescription. The key is a customer-defined code that matches^[a-z][a-z0-9_]{1,63}$and is unique in the policy set. You filter episodes and audit events by key. The description has at most 500 characters. Zep does not send the description to the classifier. - A rule belongs to one category through
category_key. The rule has adescriptionof at most 1000 characters that states the prohibited meaning and the subject scope. It has 1 to 8prohibited_examplesand 0 to 8permitted_examples. Each example has at most 500 characters. The description and the examples are classifier inputs.
A policy set has at most 16 categories and at most 32 rules. A category without a rule is valid. A rule whose category_key names no category in the same body is rejected. An empty policy set has zero categories and zero rules and means “no content policy”.
Zep rejects a write with 400 invalid_request when a key repeats, a key does not match the pattern, a category_key names no category, a list or a string exceeds its limit, or prohibited_examples is empty. The param field of the error names the failing path, for example rules[2].prohibited_examples.
Write rules as you would brief a careful reviewer. Name the subject scope, such as “a named or identifiable person”. Write prohibited examples that read as extracted facts, and permitted examples that are close to the rule but do not state the prohibited meaning.
The project policy
The project policy is the default for every graph that you create later. The ContentPolicy response carries the server-owned fields uuid, revision, categories[].id, rules[].id, and created_at. A write body omits them. A write that echoes them is accepted, and Zep ignores the echoed values.
The content policy methods are in the v4 SDKs after this release. The current v3 SDKs do not include them. The examples below use the v4 method names.
Set the project policy
PUT /project/content-policy replaces the full policy set and returns the new revision with 200. There is no partial update. A body that equals the current revision still creates a new revision.
The response is the new ContentPolicy:
categories[].id and rules[].id are stable across revisions. Zep matches a category across revisions by key, and a rule by the pair (category_key, description). A category or rule that does not change keeps its identifier.
To clear the project policy, write the empty policy set. No body, {}, and {"categories": [], "rules": []} are the same write. Graphs that you create later bind the empty set unless the create body adds rules. Graphs that already exist keep their bound policy.
Read the project policy and its revisions
GET /project/content-policy returns the current revision. A project without a write returns revision 0, the empty policy set.
GET /project/content-policy/revisions lists revisions in descending revision order with limit and cursor. The list carries total_size. Zep stores every revision that a graph binds until the last graph that binds it is deleted. A revision that no graph binds and that is not current can be removed by retention, so a client accepts a gap in the list.
GET /project/content-policy/revisions/{revision_uuid} returns one revision, or 404 not_found.
Bind a policy at graph creation
A graph copies the current project revision when you create it. graph.create and user.create accept an optional content_policy object with three fields:
project_revisionis optional. When present, the create fails with409 conflictand the codecontent_policy_revision_staleif the current project revision is different. Send it when the policy that an operator reviewed must be the policy that the graph binds.categoriesandrulesadd to the project revision. Akeythat repeats a project category adds rules to that category. A rule that duplicates a project rule by(category_key, description)is ignored.- The combined set must stay inside the limits of the policy model. A combined set over a limit is
400 invalid_requestwithparamset tocontent_policy.rules.
The create body cannot remove a project rule. Additions are additive only.
The create and the binding are one atomic operation. The 201 response carries the graph, and the bound policy is readable from that moment. A create that cannot persist the binding writes no graph and returns 503 service_unavailable.
A graph is never created unprotected when a policy exists. A create on an account without the content policy entitlement, when the project revision or the create body has at least one rule, returns 403 content_policy_not_entitled and writes no graph.
The bound policy is read-only
There is no update and no delete for the bound policy of a graph. A later write to the project policy does not change a graph that already exists. When a graph needs a different policy, create a new graph.
GET /graphs/{graph_uuid}/content-policy returns the bound set as a ContentPolicy with two more fields:
revision and project_revision are the number of the project revision that the bound set includes, and they are always equal. The bound set has its own uuid when the graph adds a category or a rule, and uuid equals the uuid of that project revision when the graph adds nothing. Project categories and rules keep the project identifiers. Graph additions get their own identifiers.
Cloning a graph binds the source graph’s policy to the clone. The clone operation does not accept content_policy. The clone copies each source episode with its content_policy object unchanged. It does not copy pending or failed episodes.
Episode state
Every episode of a protected graph carries a content_policy object. An episode of a graph without a bound rule has no content_policy object.
processed keeps its existing meaning. An episode is processed only after content_policy.status reaches complete. A pending or failed evaluation leaves processed: false.
violated, category_keys, and dropped_count can grow after complete. Zep sometimes extracts one artifact from several episodes at once. When that artifact matches a rule, Zep attributes the match to every episode in the group, including an episode that is already complete. status and processed do not change in that case.
Ingestion evaluates every artifact of an episode before it publishes any of them. Retrieval never observes a protected graph state where an artifact is published before its evaluation completed.
Pending and failed episodes
An episode stays pending while ingestion evaluates it. When the classifier fails, Zep retries under the existing ingestion retry policy, and the episode stays pending. When the retry policy is exhausted, status becomes failed and processed stays false. A match that was persisted before the failure stays in violated and category_keys.
No artifact of a failed episode is published. A failure is never recorded as “no match”. A pending or failed episode is excluded from every summary and observation, and from context assembly.
Task results
The graph.add, thread.add_messages, and batch.process tasks report the policy outcome in result. A policy violation is an expected outcome, and the task reaches succeeded.
graph.addcarries oneepisode_uuidand onecontent_policyobject.thread.add_messagescarriesepisodes, an array of{episode_uuid, content_policy}with one entry per message.batch.processcarries the aggregateviolated_countandfailed_countinresult.content_policy. Each batch item carries the episodecontent_policyobject once the item completes.
The task stays processing while the episode is pending, including while Zep retries a classifier failure. A classifier failure that exhausts the retry policy is a processing failure. The task reaches failed with error.code set to content_policy_evaluation_failed. The error message names no source text, no artifact text, and no rule text. A thread.add_messages or batch.process task where some episodes completed and some failed is partial, and result carries the completed episodes.
The ingestion status recipe covers task and episode polling.
Flagged episodes in retrieval
A flagged episode is an episode where at least one artifact matched a rule. The project setting include_policy_violating_episodes on GET /project and PATCH /project controls whether four operations return flagged episodes. The default is false.
In both settings:
- The four operations return
pendingandfailedepisodes withprocessed: false. Such an episode has no published artifact, so the list exposes only the source episode. graph.episode.getreturns a flagged episode. A direct read is an explicit request for one known identifier.graph.search_episodesmatches on source text.
The setting never changes the result of graph.get_context, thread.get_context, agent.get_context, summaries, or observations. Context assembly excludes flagged, pending, and failed episodes from source text and from evidence expansion. A permitted fact from a mixed episode stays searchable, and default responses do not expand it to the source episode text. A flagged episode is never an input to a thread summary, a document summary, a user summary, an entity summary, or an observation. When an episode is flagged after a summary that used it was published, Zep marks that summary unavailable and rebuilds it from eligible inputs.
Zep does not derive observations on a protected graph in this release. The observation writer does not evaluate its output against the bound policy, so it does not run.
Filter by violation
graph.episode.list and graph.search_episodes accept content_policy_violated as an exact-match filter in filters. The filter is supported only while include_policy_violating_episodes is true. While the setting is false, the filter is rejected with 400 unsupported_filter.
Cursors
The value of include_policy_violating_episodes is part of the pagination cursor of the four operations. A cursor that was issued under one value and used under the other value returns 400 invalid_cursor. Restart the page walk after you change the setting.
Audit events
POST /graphs/{graph_uuid}/content-policy/events/list lists one event per dropped artifact and one per flagged episode. Events carry identifiers only. An event never contains source text, artifact text, spans, probabilities, questions, person names, or entity names.
An episode-level event has artifact_kind: "episode", drop_reason: "match", and every matched key.
The list orders by decided_at descending and paginates with limit and cursor. It carries total_size. filters accepts episode_uuid and drop_reason as exact-match filters. Any other filter returns 400 unsupported_filter. The operation is available on an unprotected graph and returns an empty list. Events follow the retention of the audit store, so a client accepts that old events are absent.
Permissions
Policy reads map to the ABAC action project.get at project scope and graph.get at graph scope. PUT /project/content-policy and the include_policy_violating_episodes field of PATCH /project map to project.update. content_policy in a create body needs graph.create or user.add, the same as the create itself. An ingestion-only API key cannot write a policy or change the retrieval setting, and receives 403 permission_denied. Policy-based access control describes how actions attach to API keys.
Entitlement and availability
Content policies need the content policy entitlement on your account. Without the entitlement:
- A
PUT /project/content-policywith at least one rule returns403 content_policy_not_entitled. - A create whose bound policy, the current project revision plus the
content_policyin the body, has at least one rule returns403 content_policy_not_entitledand writes no graph. A body that adds categories only does not need the entitlement. - A
PUTof the empty policy set is accepted. A project can always clear its policy and create graphs again.
A project that loses the entitlement keeps existing bound graphs protected. A new graph on that account is rejected until the project policy is cleared or the entitlement returns. Removing protection from an existing graph is never a side effect of a plan change.
The classifier call sends the derived text and the rule text to a language model that Zep operates through its model gateway. Zep selects the model, and you cannot change it. In this release, an account under a strict data residency policy and a BYOC deployment cannot receive the entitlement. The Security & Compliance overview describes the deployment models. Contact Zep sales for the entitlement.
Error codes
Related
- Create Graph and Users and User Graphs describe the create operations that accept
content_policy. - Episodes describes how episodes are stored and retrieved.
- Cloning Graphs describes how a clone binds the source policy.
- Policy-based access control describes the ABAC actions that policy operations map to.