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 refusesastream()withNotImplementedError— stream synchronously, as above, with a plaindef. If the graph is async, useasync def runandastream— and neverasyncio.run()inside it. recursion_limit: 250. LangGraph defaults to 25 steps and a real task needs a few hundred; without it the run stops withGraphRecursionError.idis a kebab-case slug.task.promptis 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_URLis the address of the Redline platform — nothing in this repository hosts it.REDLINE_API_KEYis 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.pyexports no@agentfunction. - Registration rejected → id collides; pick another slug.
- The agent says it has no tools → §5a, or the graph was built before
redline devpatched (see §4 callout). - The run dies on the first tool error → §5b.
GraphRecursionError/ stops mid-build →recursion_limit(§3).NotImplementedErrorfromastream→ sync-only middleware; stream synchronously (§3).- Session shows only User/Agent rows, no model calls → §8a package missing
or installed after
redline devstarted. Install and restart. ask_userENDS 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.