{
  "schema_version": 2,
  "id": "develop/ai/context-engine/lens/instrument",
  "title": "Instrument an agent",
  "url": "https://redis.io/docs/latest/develop/ai/context-engine/lens/instrument/",
  "summary": "Record turns, retrievals, memory recalls, prompt assembly, model calls, and custom steps with the Redis Iris SDK.",
  "tags": [
    "docs",
    "develop",
    "ai"
  ],
  "last_updated": "2026-11-03T15:51:18-08:00",
  "page_type": "content",
  "content_hash": "f9bfb4039b909ba934a2561cfd4134bdfdf3aeb9864c339f4f219d94a049000c",
  "sections": [
    {
      "id": "code-examples",
      "title": "Code examples",
      "role": "example",
      "text": "These examples connect to Iris services using SDKs or HTTP requests with curl. Follow one language throughout each walkthrough.\n\n\nRecord each agent turn and the steps inside it with `redis_iris.lens`, the Lens module of the Redis Iris software development kit (SDK). These examples need the Lens connection values from the [quickstart](https://redis.io/docs/latest/develop/ai/context-engine/lens/quickstart#configure-the-sdk)."
    },
    {
      "id": "turn-lens-on",
      "title": "Turn Lens on",
      "role": "content",
      "text": "Call `lens.init()` once at startup. It turns Lens on, traces model calls, and sets the agent ID that Lens groups your turns under:\n\n**Available in:** Python (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n\n\nYou can set the agent ID with `IRIS_LENS_AGENT_ID` instead, and call `lens.init()` with no arguments. An `agent_id` you pass to `lens.init()` overrides the variable.\n\nWithout `lens.init()`, Lens starts at the first Lens or Iris service call when `IRIS_LENS_ENDPOINT` is set, and records everything except model calls. You can also pass Lens settings to `Iris(IrisSettings(lens=...))`. See [Configure Lens](https://redis.io/docs/latest/develop/ai/context-engine/lens/configure) for every setting."
    },
    {
      "id": "open-a-turn",
      "title": "Open a turn",
      "role": "content",
      "text": "Wrap each agent exchange in `lens.turn()` and pass a session ID. The Lens UI shows only turns that have one. Optionally, pass a thread ID, a user ID, and an agent ID that overrides the process default. Pass a user ID to filter sessions by user in the Lens UI and the read API:\n\n**Available in:** Python (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n\n\nUse `set_attribute()` to add your own fields to the turn, and `set_outcome()` to record how it ended. See [turn outcomes](https://redis.io/docs/latest/develop/ai/context-engine/lens/concepts#turn-outcomes) for the values.\n\n`lens.turn()` works the same in asynchronous code: open it with `with` around the `await` calls of one exchange."
    },
    {
      "id": "what-iris-services-record",
      "title": "What Iris services record",
      "role": "content",
      "text": "Agent Memory, Agent Playbooks, and Context Gateway calls record their own steps inside the open turn, in both their synchronous and asynchronous forms:\n\n| Service | Steps recorded |\n| ------- | -------------- |\n| Agent Memory | Memory recalls, session events, and memory writes and deletes. |\n| Agent Playbooks | Discovery, skill loads, skill resource reads, and proposals. |\n| Context Gateway | Tool listings and each tool call, with its upstream tool name. |\n\nThese steps come from the Redis Iris service objects, such as `AgentMemory` and `AgentPlaybooks`. To record a call on a service's generated client (`.client`), wrap it in `lens.memory_recall()`, `lens.retrieval()`, or `lens.span()`."
    },
    {
      "id": "trace-model-calls",
      "title": "Trace model calls",
      "role": "content",
      "text": "Install the `llm-tracing` extra, then call `lens.init()` at startup:\n\n[code example]\n\nLens then records calls made through the OpenAI, Anthropic, Amazon Bedrock, and LangChain client libraries, including LangGraph. Each model call records its prompt, completion, and token counts, and the turn records its token totals."
    },
    {
      "id": "record-your-own-retrievals",
      "title": "Record your own retrievals",
      "role": "content",
      "text": "Iris services record their own results. For other sources, such as your own vector search, wrap the call in `lens.retrieval()` and record its results as `(id, score, text)` tuples:\n\n**Available in:** Python (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n\n\nPass `None` as the score for a source that doesn't return one. Lens records it as no score rather than a score of zero."
    },
    {
      "id": "add-custom-steps",
      "title": "Add custom steps",
      "role": "content",
      "text": "Wrap any operation in `lens.span()` to record it in the turn. Pass attributes as keyword arguments, or set them on the step:\n\n**Available in:** Python (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n\n\nCustom steps are part of the turn's trace, and the read API returns them in the turn's `spans`."
    },
    {
      "id": "include-other-instrumentation",
      "title": "Include other instrumentation",
      "role": "content",
      "text": "Lens exports the spans other OpenTelemetry instrumentation creates inside a turn, apart from a default set of low-level scopes such as database drivers. To also export spans created outside a turn, or from an excluded scope, wrap the call in `lens.include()`. It works as a decorator or a context manager:\n\n**Available in:** Python (Iris)\n\n##### Python (Iris)\n\n[code example]"
    },
    {
      "id": "get-the-turn-id",
      "title": "Get the turn ID",
      "role": "content",
      "text": "Read the turn ID to link a turn to your own logs, or to [fetch the turn](https://redis.io/docs/latest/develop/ai/context-engine/lens/read-api#get-a-turn) with the read application programming interface (API). Inside a turn, call `lens.current_trace_id()`. After the turn, read `trace_id` from the turn handle:\n\n**Available in:** Python (Iris)\n\n##### Python (Iris)\n\n[code example]"
    },
    {
      "id": "carry-a-turn-across-processes",
      "title": "Carry a turn across processes",
      "role": "content",
      "text": "When a turn continues in another process, such as a queue worker, pass its context with the work. Call `lens.inject()` to write the turn context into a dictionary, and `lens.continue_from()` on the other side to resume the turn:\n\n**Available in:** Python (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n\n\nSteps recorded in the worker join the same turn. The dictionary carries the session, user, and agent IDs, so send it only to services you trust, and resume only from dictionaries your own services created.\n\nWithin one process, `lens.init()` with the `llm-tracing` extra carries the turn into threads your code starts."
    },
    {
      "id": "shut-down",
      "title": "Shut down",
      "role": "content",
      "text": "Before a short-lived process exits, call `lens.shutdown()`:\n\n**Available in:** Python (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n\n\n`shutdown()` waits for the export queue to flush and returns `True` when the flush completes. A failed export logs a warning on the `iris.lens` logger. `shutdown()` also turns Lens off for the rest of the process, so call it once, when your program is done tracing. Long-running services export continuously and call it only when they stop."
    }
  ],
  "examples": [
    {
      "id": "turn-lens-on-ex0",
      "language": "python",
      "code": "from redis_iris import lens\n\nlens.init(agent_id=\"support-bot\")",
      "section_id": "turn-lens-on"
    },
    {
      "id": "open-a-turn-ex0",
      "language": "python",
      "code": "import uuid\n\nsession_id = f\"session-{uuid.uuid4().hex[:8]}\"\n\nwith lens.turn(session_id, thread_id=\"thread-1\", user_id=\"user-1042\") as turn:\n    turn.set_attribute(\"app.channel\", \"web\")\n    answer = \"Your refund was issued on May 2.\"\n    turn.set_outcome(\"completed\")",
      "section_id": "open-a-turn"
    },
    {
      "id": "trace-model-calls-ex0",
      "language": "bash",
      "code": "python -m pip install 'redis-iris[llm-tracing]'",
      "section_id": "trace-model-calls"
    },
    {
      "id": "record-your-own-retrievals-ex0",
      "language": "python",
      "code": "question = \"How long do refunds take?\"\ndocuments = [\n    (\"doc-17\", 0.88, \"Refunds post within 5 to 7 business days.\"),\n    (\"doc-04\", 0.61, \"Store credit is available immediately.\"),\n]\n\nwith lens.turn(session_id) as turn:\n    with lens.retrieval(query=question, data_source=\"help-center\") as search:\n        search.results(documents)\n    turn.set_outcome(\"completed\")",
      "section_id": "record-your-own-retrievals"
    },
    {
      "id": "add-custom-steps-ex0",
      "language": "python",
      "code": "with lens.turn(session_id) as turn:\n    with lens.span(\"rank_documents\", candidates=len(documents)) as step:\n        ranked = sorted(documents, key=lambda d: d[1], reverse=True)\n        step.set_attribute(\"app.top_document\", ranked[0][0])\n    turn.set_outcome(\"completed\")",
      "section_id": "add-custom-steps"
    },
    {
      "id": "include-other-instrumentation-ex0",
      "language": "python",
      "code": "@lens.include()\ndef search_vector_store(query):\n    # Spans that another OpenTelemetry instrumentation creates in here are exported to Lens.\n    return documents",
      "section_id": "include-other-instrumentation"
    },
    {
      "id": "get-the-turn-id-ex0",
      "language": "python",
      "code": "with lens.turn(session_id) as turn:\n    print(f\"Turn ID: {lens.current_trace_id()}\")\n    turn.set_outcome(\"completed\")\nprint(f\"Turn ID after the turn: {turn.trace_id}\")",
      "section_id": "get-the-turn-id"
    },
    {
      "id": "carry-a-turn-across-processes-ex0",
      "language": "python",
      "code": "def enqueue(job):\n    print(f\"Queued job with {len(job['lens'])} Lens headers\")\n    return job\n\n\ndef worker(job):\n    with lens.continue_from(job[\"lens\"]):\n        with lens.span(\"summarize_case\"):\n            pass\n\n\nwith lens.turn(session_id) as turn:\n    job = enqueue({\"case\": \"case-881\", \"lens\": lens.inject()})\n    worker(job)\n    turn.set_outcome(\"completed\")",
      "section_id": "carry-a-turn-across-processes"
    },
    {
      "id": "shut-down-ex0",
      "language": "python",
      "code": "if not lens.shutdown(timeout_millis=10_000):\n    print(\"Lens did not finish exporting before the timeout\")",
      "section_id": "shut-down"
    }
  ]
}
