Skip to content
Redline
Esc
navigateopen⌘Jpreview
On this page

LangChain / LangGraph

Connect a LangChain or LangGraph agent to Redline — experiments, production monitoring and runtime protection. The complete document, written to be followed by a coding agent inside the repository.

You are connecting a repository whose agent is built on LangChain or LangGraph to the Redline platform: experiments AND production monitoring. Follow these steps exactly and do not modify the repository’s existing agent code — the whole integration is ONE new file plus ONE instrumentation package.

0. Confirm the shape

Grep for langgraph.prebuilt, create_react_agent, langchain.agents, create_tool_calling_agent. If present, this is Shape B and this is the page. LangChain is PATCHED by the SDK: the experiment’s attached tools appear in the agent by themselves. Its spans need one package (§8a).

1. Install and scaffold

At the repository root, inside its virtualenv:

pip install redlineai-sdk openinference-instrumentation-langchain
redline init

The second package is for monitoring; install it now, before the first redline dev, so the first session already has a waterfall. redline init writes agents.py — never redline.py, which shadows the SDK.

2. Find the agent’s entry point

Locate the compiled graph or agent and how it is called: .invoke(, .stream(, .ainvoke(, .astream(, with {"messages": [...]} and a config. Read the signature and note whether it is sync or async. Do NOT modify it.

3. Wire it into agents.py

import uuid
from redline import agent
import my_app.support_graph as subject                 # unchanged

def _run(task, ctx) -> str:
    config = {
        "configurable": {"thread_id": str(uuid.uuid4())},
        "recursion_limit": 250,        # the default of 25 stops a real build mid-way
    }
    final, buffered = "", []

    def flush():
        if buffered:
            ctx.thinking("\n".join(buffered)); buffered.clear()

    for event in subject.agent.stream(
        {"messages": [{"role": "user", "content": task.prompt}]},
        config, stream_mode="updates",
    ):
        for update in (event or {}).values():
            for message in (update or {}).get("messages", []) or []:
                text = getattr(message, "content", "") or ""
                calls = getattr(message, "tool_calls", []) or []
                if calls:
                    flush()                                  # 5c
                    for call in calls:
                        ctx.tool(call.get("name", "?"), call.get("args", {}))
                elif getattr(message, "type", "") == "tool":
                    ctx.tool_result(getattr(message, "name", "?"), text)
                elif text:
                    buffered.append(text); final = text
    flush()
    return final

@agent(id="device-support", name="Device Support",
       description="A support agent whose tools and instructions change as it moves warranty check → classification → resolution.")
def run(task, ctx):
    return _run(task, ctx)
  • Sync or async follows the graph. If your graph’s middleware implements only the sync wrap_model_call, LangChain refuses astream() with NotImplementedError — stream synchronously, as above, with a plain def. If the graph is async, use async def run and astream — and never asyncio.run() inside it.
  • recursion_limit: 250. LangGraph defaults to 25 steps and a real task needs a few hundred; without it the run stops with GraphRecursionError.
  • id is a kebab-case slug. task.prompt is the instructions. RETURN the final answer as a string.

4. Attached tools — do NOTHING

redline dev patches langgraph.prebuilt.create_react_agent and langchain.agents.create_tool_calling_agent before it imports your files. The order matters: from langgraph.prebuilt import create_react_agent binds the function into your module, and a patch applied afterwards cannot reach that binding — which is why the CLI patches first.

Do not call redline_tools — that connects everything twice. Verify by counting the agent’s tools. Attached SKILLS arrive inside task.prompt.

5. Three failures that are nearly guaranteed if you skip them

5a. Tell the agent its tools exist. The prompt for a narrow job says “I have no way to do that” while machine_run sits unused. Add one line to the system prompt saying attached tools may be used when the task calls for them.

5b. A tool that raises must not kill the run. LangGraph’s default ToolNode handler re-raises, which turns “you have no app yet” into a dead run. Build with handle_tool_errors=True (or your own handler) so an exception becomes the observation text the agent reads and recovers from.

5c. Do not drop the reasoning. Map the stream onto ctx as in §3. Buffer streamed prose and flush it as one block at each tool call — a transcript of four hundred one-word entries is unreadable.

6. Connect and verify

The agent’s own environment must load the way the repo normally loads it. Then, in .env at the repo root or exported:

REDLINE_URL=https://tryredlineai.co
REDLINE_API_KEY=rl_…          # Agents page → Runner key
redline dev

redline dev reads that .env itself — every variable in it.

redline dev must be running on this machine at all times: experiments execute inside it, the agent shows ONLINE only while it runs, asset snapshots for Protect upload through it, policies and honeypots re-arm through it every 20 seconds, dev sessions stream to Monitor through it.

  • REDLINE_URL is the address of the Redline platform — nothing in this repository hosts it.
  • REDLINE_API_KEY is the runner key from the Agents page. Do not commit it.

On a server or VM:

nohup redline dev > ~/redline-dev.log 2>&1 &
disown

Outbound only, no port. Success: connected — device-support registered and online.

7. When something is wrong

  • “no agents found”agents.py exports no @agent function.
  • Registration rejected → id collides; pick another slug.
  • The agent says it has no tools → §5a, or the graph was built before redline dev patched (see §4 callout).
  • The run dies on the first tool error → §5b.
  • GraphRecursionError / stops mid-buildrecursion_limit (§3).
  • NotImplementedError from astream → sync-only middleware; stream synchronously (§3).
  • Session shows only User/Agent rows, no model calls → §8a package missing or installed after redline dev started. Install and restart.
  • ask_user ENDS the run — return the questions as the answer.

Everything else: known issues.

8. Monitoring — the same agent, watched in production

Nothing extra to write for dev: while redline dev is up, every conversation ALSO streams to Monitor → Sessions.

8a. REQUIRED — pip install openinference-instrumentation-langchain. The SDK only LISTENS for OpenTelemetry spans; LangChain emits none until this package is in the venv. redline dev finds and activates it by itself — but only if it was installed before redline dev started. VERIFY after the first run: model calls and a token count on the session. One line per turn and nothing between means the package is missing or arrived late.

For the DEPLOYED agent — real users, no redline dev:

from redline import observe

@observe(agent="device-support", session_arg="conversation_id",
         user_arg="user_id", input_arg="text")
def handle_message(conversation_id: str, user_id: str, text: str) -> str:
    result = subject.agent.invoke(
        {"messages": [{"role": "user", "content": text}]},
        {"configurable": {"thread_id": conversation_id}, "recursion_limit": 250},
    )
    return result["messages"][-1].content

thread_id = conversation_id keeps a multi-turn conversation on one LangGraph thread and one Redline session. Config is environment only; without REDLINE_API_KEY the decorator is inert.

8b. Your application has to CALL handle_message. A decorator only sees calls to the function it is on. The repository’s existing message pipeline must call it — one call-site change. Without it, production sessions never appear.

8c. It must return, not yield. @observe does not wrap async generators. If the entry point streams, drain it and return the text.

What the platform does with a session: Monitor → Sessions with the waterfall; violations matched per span and re-judged within minutes; intents mined when the session closes. See monitoring.

9. Runtime protection — what to expect once connected (nothing to write)

LangChain’s model calls go through the OpenAI client (or litellm), which the SDK patches, so policies, the guard classifier and honeypots configured in the console (Protect → the agent → Policies) reach the running process within ~20 seconds. A denied tool call is stripped from the model’s response before ToolNode can execute it and replaced with [redline] The call to <tool> was denied by policy: <reason>. The guard scores every user message and tool result; honeypots are injected beside the agent’s own tools. Streaming passes through unjudged; everything fails OPEN.

Was this page helpful?