> This page is for version v4 (default).
> For other versions, use one of these documentation indexes:
> - v4 (default): https://docs-beta.getzep.com/v4/llms.txt
> - v3: https://docs-beta.getzep.com/v3/llms.txt
> - v2: https://docs-beta.getzep.com/v2/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs-beta.getzep.com/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 graph or another 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. The `project` methods address the project-wide default. The `graph` methods address one graph.

**`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({
  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"

    zep "github.com/getzep/zep-go/v4"
    zepclient "github.com/getzep/zep-go/v4/client"
    "github.com/getzep/zep-go/v4/option"
)

client := zepclient.NewClient(
    option.WithAPIKey(API_KEY),
)

// Set the project-wide default.
config, err := client.Project.SetObservationSteering(context.TODO(), &zep.ObservationSteering{
    Instruction: zep.String("Prioritize durable commitments and stated preferences. Keep observations concise."),
    Types: []*zep.ObservationType{
        {Name: zep.String("commitment"), Description: zep.String("A promise or planned action the user has committed to.")},
        {Name: zep.String("preference"), Description: zep.String("A durable preference the user has expressed.")},
    },
})

// Read the current project configuration.
config, err = client.Project.GetObservationSteering(context.TODO())
if config.Instruction != nil {
    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 two scopes:

* **Project**: the default configuration, applied to every graph in the project. Use the `project` methods.
* **Graph**: configuration for a single graph, addressed with the UUID of the graph. Use the `graph` methods. For a user graph, use the `graph_uuid` that `user.create` returns. For another graph, use the `uuid` that `graph.create` returns.

A graph configuration is a **complete override**, not a merge — when a graph has its own configuration, Zep uses it in full and ignores the project default. Reading a graph scope returns its effective configuration. When the graph has no configuration of its own, the response contains the project default and `inherited` is `true`.

**`Python`**

```python Python
# Steer observations for one user only.
# zep_graph_uuid is the graph_uuid of the user, from your application database.
client.graph.set_observation_steering(
    zep_graph_uuid,
    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's graph.
config = client.graph.get_observation_steering(zep_graph_uuid)
print(config.inherited)  # False: the graph has its own configuration
```

**`TypeScript`**

```typescript TypeScript
// Steer observations for one user only.
// zepGraphUuid is the graph UUID of the user, from your application database.
await client.graph.setObservationSteering(zepGraphUuid, {
  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's graph.
const config = await client.graph.getObservationSteering(zepGraphUuid);
console.log(config.inherited); // false: the graph has its own configuration
```

**`Go`**

```go Go
// Steer observations for one user only.
// zepGraphUUID is the graph UUID of the user, from your application database.
_, err := client.Graph.SetObservationSteering(context.TODO(), zepGraphUUID, &zep.ObservationSteering{
    Instruction: zep.String("Focus on support escalations and unresolved issues."),
    Types: []*zep.ObservationType{
        {Name: zep.String("escalation"), Description: zep.String("An issue the user escalated to support.")},
    },
})

// Read the effective configuration for the user's graph.
config, err := client.Graph.GetObservationSteering(context.TODO(), zepGraphUUID)
fmt.Println(*config.Inherited) // false: the graph has its own configuration
```

## 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_observations(
    zep_graph_uuid,
    query="commitments to the customer",
    filters=SearchFilters(
        property_filters=[
            PropertyFilter(
                operator="eq",
                property_name="observation_type",
                value="commitment",
            )
        ]
    ),
)

for obs in results.items or []:
    print(obs.name, obs.score)
```

**`TypeScript`**

```typescript TypeScript
const results = await client.graph.searchObservations(zepGraphUuid, {
  body: {
    query: "commitments to the customer",
    filters: {
      propertyFilters: [
        {
          operator: "eq",
          propertyName: "observation_type",
          value: "commitment",
        },
      ],
    },
  },
});

for (const obs of results.data) {
  console.log(obs.name, obs.score);
}
```

**`Go`**

```go Go
results, err := client.Graph.SearchObservations(context.TODO(), zepGraphUUID, &zep.GraphSearchObservationsRequest{
    Body: &zep.SearchRequest{
        Query: "commitments to the customer",
        Filters: &zep.SearchFilters{
            PropertyFilters: []*zep.PropertyFilter{
                {
                    Operator:     zep.PropertyFilterOperatorEq.Ptr(),
                    PropertyName: zep.String("observation_type"),
                    Value:        &zep.PropertyFilterValue{String: "commitment"},
                },
            },
        },
    },
})

for _, obs := range results.Results {
    fmt.Println(*obs.Name, *obs.Score)
}
```

## Clearing steering

Sending an empty configuration — no instruction and an empty `types` list — clears steering at the addressed scope. Clearing the project scope removes the default. Clearing a graph scope removes the override, so that graph falls back to the project default.

**`Python`**

```python Python
# Remove the override for a user's graph (falls back to the project default).
client.graph.set_observation_steering(
    zep_graph_uuid,
    types=[],
)
```

**`TypeScript`**

```typescript TypeScript
// Remove the override for a user's graph (falls back to the project default).
await client.graph.setObservationSteering(zepGraphUuid, { types: [] });
```

**`Go`**

```go Go
// Remove the override for a user's graph (falls back to the project default).
_, err := client.Graph.SetObservationSteering(context.TODO(), zepGraphUUID, &zep.ObservationSteering{
    Types: []*zep.ObservationType{},
})
```

## Configurable parameters

| Parameter     | Type   | Description                                                                                                                         | Default | Required                     |
| ------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- | ------- | ---------------------------- |
| `graph_uuid`  | string | The UUID of the graph. Only the `graph` methods take this parameter. For a user graph, use the `graph_uuid` of the user.            | -       | Yes, for the `graph` methods |
| `instruction` | string | Guidance for the wording and relevance of generated observations. Up to 2000 characters. An empty or omitted instruction clears it. | -       | No                           |
| `types`       | array  | Up to 10 custom observation types, each with a `name` and `description`.                                                            | `[]`    | No                           |

The `project` methods address the project scope and take no UUID.

### 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  | The configured instruction. The field is absent when no instruction is set.                          |
| `types`       | array   | The configured observation types, each with a `name` and `description`.                              |
| `inherited`   | boolean | `true` when the graph has no configuration of its own and the response contains the project default. |

Reading a graph scope returns its effective configuration, falling back to the project default when that graph 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.