Framework guide · LangChain.js
LangChain under Aarmos in 60 seconds.
Keep your existing LangChain agent exactly as-is. No aarmos import, no framework wrapper, no monkey-patch. A thin wrapper script routes every outbound HTTP call through the local Aarmos proxy, where the policy gate decides and a signed receipt is written.
Runnable example:
examples/langchain/ in the repo. Mirrored below.0 · Initialize the workspace (once)
aarmos init
✓ policy.aarmos.toml
✓ avar.config.json
✓ signing key: .aarmos/keys/signing.key1 · Your existing LangChain agent — unchanged
// src/agent.mjs — stock LangChain.js. No aarmos imports.
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage } from "@langchain/core/messages";
const model = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 });
const reply = await model.invoke([new HumanMessage("what is a receipt?")]);
console.log(reply.content);2 · Wrap the launch (one small script)
Two Node realities the wrapper handles: aarmos run does not inherit shell env into the child, and Node's built-in fetch ignores HTTPS_PROXY unless you install an undici ProxyAgent.
# run-agent.sh
#!/usr/bin/env bash
set -euo pipefail
: "${OPENAI_API_KEY:?set OPENAI_API_KEY}"
export OPENAI_API_KEY
exec node --require ./setup-proxy.cjs src/agent.mjs "$@"
# setup-proxy.cjs
const proxy = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
if (proxy) {
const { setGlobalDispatcher, ProxyAgent } = require("undici");
setGlobalDispatcher(new ProxyAgent(proxy));
}3 · Run it under Aarmos
aarmos run --policy ./policy.aarmos.toml --intent "answer a question" ./run-agent.sh
▸ proxy: reusing daemon at http://127.0.0.1:7681
▸ agent: ./run-agent.sh
▸ policy: ./policy.aarmos.toml (15daa3b482ab…)
✓ AVAR receipt: .aarmos/avar/receipt-…json4 · Verify the receipt
aarmos verify .aarmos/avar/receipt-….json
✓ receipt parsed
spec_version: 0.1
signature_alg: ed25519Open /audit in the PWA to see the same session with the policy digest, verb, latency, and the signed receipt.
What actually happens
- The undici
ProxyAgentmakes Node'sfetchhonorHTTPS_PROXYand route the OpenAI call through127.0.0.1:7681(the default loopback port; use--portto change it). - The Aarmos proxy sees the outbound
CONNECT, matches the destination hostname against your policy's FQDN allow-list, and forwards the connection to the original destination. - A signed receipt lands in
.aarmos/avar/. Verification runs locally against your workspace key — no network required.
Honest limits
- Hostname-only proxy (ADR-0001). The proxy inspects only the outbound hostname — it does not open TLS sessions or read request/response payloads. Receipts prove which hosts the agent talked to, signed by your local key — not what was said on the wire.
- JSON receipts, proxy-only sessions. Today the JSON receipt shows
0 entriesbecause per-CONNECTtuples aren't folded into the chain yet. For per-call entries, use an official adapter (e.g.@aarmos/adapter-langchain) instead of the raw proxy. - Proxy-honoring clients only. Node
fetch/undici, axios with env, Pythonrequestswith env. Native SDKs that ignore proxy env need per-SDK wiring. - Local tool calls bypass the proxy. LangChain
Tools that go throughfetchare gated; in-process Node function tools are not — that's on the roadmap.