> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs-beta.getzep.com/v3/steering-observations/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs-beta.getzep.com/_mcp/server. # Steering observations > **Experimental API** > > Observation steering is an experimental feature. The API may change in future releases. > **Info** > > Available to [Flex Plus and Enterprise](https://www.getzep.com/pricing) customers — the same plans that generate [observations](/observations). ## Why use observation steering Zep automatically derives [observations](/observations) — durable, evidence-backed patterns — from a graph. By default, Zep decides how each observation is worded and labels every observation with the type `generic`. Observation steering lets you guide that generation without changing the underlying evidence. Steering influences generation along two axes. You can set either one on its own, or both together: * **Instruction**: free-text guidance that shapes the wording and relevance of the observations Zep writes. The instruction cannot introduce facts, choose which evidence is used, or change how many observations are produced — evidence always comes from the graph. * **Observation types**: up to ten named categories Zep may assign to an observation. Zep writes the selected type to the `observation_type` property on the observation node, which you can [filter on](/searching-the-graph) during graph search. When none of your types fit, Zep falls back to `generic`. Steering applies only to observations generated *after* you configure it. It does not enqueue a job or regenerate existing observations; those update on their own as new evidence arrives. ### Use cases * **Domain vocabulary**: Label observations with categories that match your product — `commitment`, `risk`, `preference`, `churn_signal` — then filter graph search by type. * **Relevance tuning**: Steer wording and emphasis toward what your agent needs, without post-processing. * **Per-user or per-graph control**: Apply distinct guidance to a specific user or graph while keeping a project-wide default. ## Basic usage Use `set_observation_steering` to replace the configuration at a scope, and `get_observation_steering` to read it back. Both return the resulting configuration. With no `user_id` or `graph_id`, the request addresses the project-wide default. **`Python`** ```python Python from zep_cloud import Zep, ObservationType client = Zep( api_key=API_KEY, ) # Set the project-wide default. config = client.project.set_observation_steering( instruction="Prioritize durable commitments and stated preferences. Keep observations concise.", types=[ ObservationType(name="commitment", description="A promise or planned action the user has committed to."), ObservationType(name="preference", description="A durable preference the user has expressed."), ], ) # Read the current project configuration. config = client.project.get_observation_steering() print(config.instruction) for t in config.types or []: print(f"{t.name}: {t.description}") ``` **`TypeScript`** ```typescript TypeScript import { ZepClient } from "@getzep/zep-cloud"; const client = new ZepClient({ apiKey: API_KEY, }); // Set the project-wide default. let config = await client.project.setObservationSteering({ body: { instruction: "Prioritize durable commitments and stated preferences. Keep observations concise.", types: [ { name: "commitment", description: "A promise or planned action the user has committed to." }, { name: "preference", description: "A durable preference the user has expressed." }, ], }, }); // Read the current project configuration. config = await client.project.getObservationSteering(); console.log(config.instruction); for (const t of config.types ?? []) { console.log(`${t.name}: ${t.description}`); } ``` **`Go`** ```go Go import ( "context" "fmt" v3 "github.com/getzep/zep-go/v3" v3client "github.com/getzep/zep-go/v3/client" "github.com/getzep/zep-go/v3/option" ) client := v3client.NewClient( option.WithAPIKey(API_KEY), ) // Set the project-wide default. config, err := client.Project.SetObservationSteering(context.TODO(), &v3.SetObservationSteeringRequest{ Body: &v3.ObservationSteeringConfig{ Instruction: v3.String("Prioritize durable commitments and stated preferences. Keep observations concise."), Types: []*v3.ObservationType{ {Name: "commitment", Description: "A promise or planned action the user has committed to."}, {Name: "preference", Description: "A durable preference the user has expressed."}, }, }, }) // Read the current project configuration. config, err = client.Project.GetObservationSteering(context.TODO(), &v3.GetObservationSteeringRequest{}) fmt.Println(*config.Instruction) for _, t := range config.Types { fmt.Printf("%s: %s\n", t.Name, t.Description) } ``` ## Scoping to a user or graph Steering resolves at one of three scopes: * **Project**: the default configuration, applied to every user and graph in the project. Addressed when you pass neither `user_id` nor `graph_id`. * **User**: configuration for a single user's graph, addressed with `user_id`. * **Graph**: configuration for a single named graph, addressed with `graph_id`. Pass at most one of `user_id` or `graph_id`; passing both returns a `400`. A user or graph configuration is a **complete override**, not a merge — when a user or graph has its own configuration, Zep uses it in full and ignores the project default. Reading a user or graph scope returns its effective configuration, falling back to the project default when that scope has none of its own. **`Python`** ```python Python # Steer observations for one user only. client.project.set_observation_steering( user_id="alice", instruction="Focus on support escalations and unresolved issues.", types=[ ObservationType(name="escalation", description="An issue the user escalated to support."), ], ) # Read the effective configuration for the user (falls back to the project default). config = client.project.get_observation_steering(user_id="alice") ``` **`TypeScript`** ```typescript TypeScript // Steer observations for one user only. await client.project.setObservationSteering({ userId: "alice", body: { instruction: "Focus on support escalations and unresolved issues.", types: [ { name: "escalation", description: "An issue the user escalated to support." }, ], }, }); // Read the effective configuration for the user (falls back to the project default). const config = await client.project.getObservationSteering({ userId: "alice" }); ``` **`Go`** ```go Go // Steer observations for one user only. _, err := client.Project.SetObservationSteering(context.TODO(), &v3.SetObservationSteeringRequest{ UserID: v3.String("alice"), Body: &v3.ObservationSteeringConfig{ Instruction: v3.String("Focus on support escalations and unresolved issues."), Types: []*v3.ObservationType{ {Name: "escalation", Description: "An issue the user escalated to support."}, }, }, }) // Read the effective configuration for the user (falls back to the project default). config, err := client.Project.GetObservationSteering(context.TODO(), &v3.GetObservationSteeringRequest{ UserID: v3.String("alice"), }) ``` ## Observation types Each entry in `types` defines a category Zep may assign to an observation. When Zep generates an observation, it selects the best-fitting type and writes the name to the `observation_type` property on the observation node. If none of your configured types fit — or you configure no types — Zep uses `generic`. | Field | Rules | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `name` | Must match `^[a-z][a-z0-9_]*$` (start with a lowercase letter, then lowercase letters, digits, or underscores); up to 50 characters; unique within the list. `generic` is reserved and cannot be configured. | | `description` | Required; 1–500 characters. Describes when the type applies, which guides Zep's selection. | You can configure up to 10 types. Because the selected type is stored as the `observation_type` node property, you can retrieve observations of a given type with [graph search](/searching-the-graph) property filters: **`Python`** ```python Python from zep_cloud.types import SearchFilters, PropertyFilter results = client.graph.search( user_id="alice", query="commitments to the customer", scope="observations", search_filters=SearchFilters( property_filters=[ PropertyFilter( comparison_operator="=", property_name="observation_type", property_value="commitment", ) ] ), ) for obs in results.observations or []: print(obs.name, obs.score) ``` **`TypeScript`** ```typescript TypeScript const results = await client.graph.search({ userId: "alice", query: "commitments to the customer", scope: "observations", searchFilters: { propertyFilters: [ { comparisonOperator: "=", propertyName: "observation_type", propertyValue: "commitment", }, ], }, }); for (const obs of results.observations ?? []) { console.log(obs.name, obs.score); } ``` **`Go`** ```go Go results, err := client.Graph.Search(context.TODO(), &v3.GraphSearchQuery{ UserID: v3.String("alice"), Query: "commitments to the customer", Scope: v3.GraphSearchScopeObservations.Ptr(), SearchFilters: &v3.SearchFilters{ PropertyFilters: []*v3.PropertyFilter{ { ComparisonOperator: v3.ComparisonOperatorEquals, PropertyName: "observation_type", PropertyValue: "commitment", }, }, }, }) for _, obs := range results.Observations { fmt.Println(obs.Name, obs.Score) } ``` ## Clearing steering Sending an empty configuration — a null instruction and an empty `types` list — clears steering at the addressed scope. Clearing the project scope removes the default; clearing a user or graph scope removes the override, so that scope falls back to the project default. **`Python`** ```python Python # Remove the override for a user (falls back to the project default). client.project.set_observation_steering( user_id="alice", instruction=None, types=[], ) ``` **`TypeScript`** ```typescript TypeScript // Remove the override for a user (falls back to the project default). await client.project.setObservationSteering({ userId: "alice", body: { types: [] }, }); ``` **`Go`** ```go Go // Remove the override for a user (falls back to the project default). _, err := client.Project.SetObservationSteering(context.TODO(), &v3.SetObservationSteeringRequest{ UserID: v3.String("alice"), Body: &v3.ObservationSteeringConfig{Types: []*v3.ObservationType{}}, }) ``` ## Configurable parameters | Parameter | Type | Description | Default | Required | | ------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- | | `user_id` | string | Address the steering configuration for a single user's graph. | - | No\* | | `graph_id` | string | Address the steering configuration for a named graph. | - | No\* | | `instruction` | string \| null | Guidance for the wording and relevance of generated observations. Up to 2000 characters. An empty or omitted instruction clears it. | `null` | No | | `types` | array | Up to 10 custom observation types, each with a `name` and `description`. | `[]` | No | \*Pass at most one of `user_id` or `graph_id`. Passing both returns a `400`; passing neither addresses the project scope. In the TypeScript and Go SDKs, `instruction` and `types` are set on the request's `body` field. ### Observation type fields | Field | Type | Description | | ------------- | ------ | ------------------------------------------------------------------------------------------------------------------ | | `name` | string | Category name. Must match `^[a-z][a-z0-9_]*$`, up to 50 characters, unique within the list. `generic` is reserved. | | `description` | string | When the type applies. Required, 1–500 characters. | ## Response Both `get_observation_steering` and `set_observation_steering` return the resulting configuration at the addressed scope: | Field | Type | Description | | ------------- | -------------- | ----------------------------------------------------------------------- | | `instruction` | string \| null | The configured instruction, or `null` if none is set. | | `types` | array | The configured observation types, each with a `name` and `description`. | Reading a user or graph scope returns its effective configuration, falling back to the project default when that scope has no configuration of its own. ## Related * [Observations](/observations) — the durable, evidence-backed context that steering shapes. * [Searching the Graph](/searching-the-graph) — filter observations by the `observation_type` property. * [Shape the Graph](/customizing-context) — the other ways to tailor how Zep builds the Context Graph. > Guide how Zep generates observations with a custom instruction and observation types