Skip to content
Redline
Esc
navigateopen⌘Jpreview
On this page

Monitoring

The same agent, watched in production — observe, create_monitor, what lands on the run versus the session, and what the platform does with a session on its own.

Monitoring is the other half of the product: the agent doing real work for real users, reported as it happens and read by the same rules as an experiment. Sessions, Violations and Intents in the console fill from production instead of from a lab. One integration, two doorways — this page is the second one.

Two records, and which one you are looking at

This is the thing that confuses people first, so it goes first.

record opened by filled by where
the run redline dev claiming an experiment ctx.thinking / ctx.tool / ctx.tool_result / ctx.log in your doorway, plus spans Experiments → the run
the session @observe in production, or the worker during a dev conversation conversation turns, plus spans only Monitor → Sessions

ctx.* writes to the run. It never reaches a session. So a doorway that maps every tool call onto ctx.tool and emits no spans produces a run with a full transcript and a session with none — both correct, and easy to read as a bug. What fills a session’s waterfall is OpenTelemetry spans, from your framework’s instrumentation or from your own loop.

Under redline dev — nothing to write

While the worker is up, monitoring is on (REDLINE_MONITOR=1). Every conversation the agent works — its user message, model calls with token counts, both halves of every tool call, reasoning, the final answer — also streams to Monitor → Sessions as a live session. The worker records the turns; the spans supply everything between.

What decides whether it sees anything is the instrumentation. The SDK only listens; the framework has to emit. Most emit nothing until their package is in the venv, and redline dev activates whatever is installed at start:

framework install notes
Pydantic AI nothing the SDK calls Agent.instrument_all()
LangChain / LangGraph openinference-instrumentation-langchain
OpenAI Agents SDK openinference-instrumentation-openai-agents
CrewAI openinference-instrumentation-crewai and openinference-instrumentation-openai CrewAI 1.x calls the model through the OpenAI client
Agno openinference-instrumentation-agno
Vercel AI SDK v7 nothing
Vercel AI SDK ≤6 nothing to install experimental_telemetry: { isEnabled: true } on the agent or each call
your own loop nothing to install emit your own — see below

Verify after the first run: the session must show model calls and a token count. One line per turn and nothing between means the package is missing or was installed after redline dev started. Install and restart.

In production — observe

For the deployed agent — real users, no redline dev — one decorator on the function that handles a message:

from redline import observe

@observe(agent="my-agent", session_arg="conversation_id",
         user_arg="user_id", input_arg="text")
async def handle_message(conversation_id: str, user_id: str, text: str) -> str:
    return await your_agent(text)
  • session_arg — the argument carrying the conversation id. Calls sharing it land in one transcript; without it every call is its own session.
  • user_arg — the argument naming the end user, shown on the session.
  • input_arg — the argument carrying the user’s message; default is the call’s single string argument.
import { observe } from "@redlineai/sdk";

export const handleMessage = observe(
  async (conversationId: string, userId: string, text: string) => yourAgent(text),
  { agent: "my-agent", sessionArg: 0, userArg: 1, inputArg: 2 },
);

Same options, by argument index.

The decorator records the input and the returned answer itself; every span the call emits lands in the same session, keyed to the call’s trace. Config is environment only — REDLINE_API_KEY, REDLINE_URL, REDLINE_AGENT — so a deploy needs no code. Without the key it is inert and the handler runs untouched. Errors are reported and re-raised; nothing in it can take the handler down.

Wiring it into an existing app

A decorator only records calls to the function it decorates. If the repository already handles inbound messages — a webhook, a queue worker, a websocket loop — that pipeline has to call handle_message. It will not find it on its own.

That is one call-site change in existing code, and it is the only one the integration asks for. Keep the pipeline’s own signals intact: if it read something off the stream besides the reply (an “ended” flag, a step count), pass a state dict the handler fills, so nothing downstream changes.

Without this, experiments work, redline dev sessions work, and production sessions never appear. An integration reviewed as “done” with this missing is the commonest shape we see.

create_monitor — the client underneath

For a handler shape the decorator cannot wrap, the explicit client:

from redline import create_monitor
monitor = create_monitor(agent="my-agent")          # env for key and url

s = monitor.session(id=conversation_id, user=user_id)
s.input(text)
s.thinking("…")
s.tool("lookup", args={"q": "x"}, result="…", ok=True, latency_ms=120)
s.output(reply, tokens=439)
s.end("closed")

Same span types the ingest API accepts — input, output, thinking, tool, retrieval, error, event. Batched and flushed on a timer; end() closes the trace.

Emitting your own spans

For a hand-rolled loop there is no package to install; the loop emits its own, in the OTel GenAI convention the SDK reads:

span attributes
one per user turn (the parent) none — a container carries no gen_ai.*
one per model call gen_ai.operation.name="chat", gen_ai.request.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens
one per tool call gen_ai.operation.name="execute_tool", gen_ai.tool.name, gen_ai.tool.call.arguments, gen_ai.tool.call.result

Parent the model and tool spans to the turn span explicitly (start_span(..., context=set_span_in_context(turn))), not by making the turn current — a loop that yields leaks a current span into whatever runs next. Without the parent, every span is read as its own turn.

The full, working version is on the own tool loop page.

What the platform does with a session

With no further code:

  • It appears in Monitor → Sessions as a readable conversation, with the waterfall beside it.
  • Rule detectors match every span as it is written — a violation on a tool call is raised the moment the call lands. Within minutes the monitor agent re-examines the whole session against every rule the project defines (Monitor → Violations). Rules are written in the console, in plain language, not in the repository.
  • When the session closes, the intent miner reads it for recurring user behaviour (Monitor → Intents). Custom instructions on that page steers it, also in plain language.

Arrival is the event. Post the session and the rest follows.

Was this page helpful?