Integrate: nest calls under the call that caused them
Record which model call caused which, so a workflow made of several calls (a tool loop, an orchestrator and its sub-agents) is kept as a tree instead of a flat list.
Detect
Find the places where one model call leads to another:
grep -rnoE "tool_calls|tool_call_id|[\"']tool[\"']|StateGraph|AgentExecutor|create_react_agent|crewai|autogen|sub_?agent" --include='*.py' --include='*.ts' --include='*.tsx' --include='*.js' --include='*.jsx' --include='*.mjs' --include='*.cjs' --exclude-dir={node_modules,.git,.venv,venv,dist,build,.next} .The grep finds tool loops and agent frameworks. A chain it cannot see, such as a plan call whose answer is fed to the next call, is found by reading the code around each model call that the connect recipe found.
Nest a call only under the call that caused it: the next turn of a tool loop, or a sub-agent's call under the call that started it. The turns of a conversation are not nested: group those with the sessions recipe.
If a framework makes the calls itself and you cannot set a header on each one, do not force it. Group the calls with a session instead, and tell the person they are not nested.
Change
Every response carries a call_id. Send it in the X-Parent-Call header of the call it caused. Read it from the X-Call-Id response header: a streamed response has no other place for it.
Keep the model name the app already uses wherever a snippet shows YOUR_MODEL, and take model names from the Gateway model catalog. mock-gpt is only for the Verify step.
Python
parent = None
while True:
raw = client.chat.completions.with_raw_response.create(
model="YOUR_MODEL",
messages=messages,
tools=tools,
extra_headers={"X-Parent-Call": parent} if parent else None,
)
resp = raw.parse()
parent = raw.headers["x-call-id"] # the next call is caused by this one
message = resp.choices[0].message
if not message.tool_calls:
break
# run the tools, append their results to messages, then loopFor a sub-agent, pass the caller's call_id into the function that runs it and send it as X-Parent-Call on the sub-agent's first call.
TypeScript
let parent: string | null = null;
while (true) {
const { data: resp, response } = await client.chat.completions
.create(
{ model: "YOUR_MODEL", messages, tools },
parent ? { headers: { "X-Parent-Call": parent } } : undefined,
)
.withResponse();
parent = response.headers.get("x-call-id"); // the next call is caused by this one
const message = resp.choices[0].message;
if (!message.tool_calls?.length) break;
// run the tools, push their results onto messages, then loop
}curl
CALL_ID=$(curl -s -X POST "$FORGEBENCH_BASE_URL/v1/chat/completions" \
-H "Authorization: Bearer $FORGEBENCH_API_KEY" -H "Content-Type: application/json" \
-d '{"model":"YOUR_MODEL","messages":[{"role":"user","content":"plan"}]}' \
| python3 -c 'import sys, json; print(json.load(sys.stdin)["call_id"])')
curl -s -X POST "$FORGEBENCH_BASE_URL/v1/chat/completions" \
-H "Authorization: Bearer $FORGEBENCH_API_KEY" -H "Content-Type: application/json" \
-H "X-Parent-Call: $CALL_ID" \
-d '{"model":"YOUR_MODEL","messages":[{"role":"user","content":"next step"}]}'A registered workflow
When the steps are registered as a workflow (forgebench_workflows register), each step has its own agent and its own key. Call forgebench_integrate with action: workflow and the workflow's slug to get this code per step: each call sends its step's key and the previous call's call_id as X-Parent-Call. Then call verify with workflow set to the slug to check the wiring.
Env
FORGEBENCH_API_KEY and FORGEBENCH_BASE_URL, as in the connect recipe. Read them from the environment with no default value, add .env to .gitignore first, and name both in .env.example (no values).
Verify
Run the workflow once so that it makes at least two calls, then call forgebench_integrate verify with child: true. It checks that the newest call, the last one the workflow made, has a parent. With the curl commands above, the second call is the one checked.
A value the gateway cannot match is ignored without an error, so a call that was meant to be nested can quietly become a root. That is what this check catches.
What you get
Each call records its parent and its root, so the calls of one workflow can be read together and their cost summed under the root. A call the gateway blocks after the model answered, for a tool or a guardrail, is recorded as a child of that call without any change on your side.
Pitfalls
- The value is a
call_id, not atrace_id. The two are different ids:trace_idgroups calls,X-Parent-Callstructures them. - A malformed value, an unknown id, or an id from another workspace is ignored. The call proceeds as a root and nothing tells you, so check it with the verify tool.
- Lineage is correlation only. It never changes whether a call is allowed or what it costs, and a wrong value costs nothing except a wrong picture of the workflow.
- Send the header only on calls the previous call caused. Chaining every call to the one before it, across unrelated requests, builds one tree out of unrelated work.
- Read
X-Call-Idfrom the response headers on streamed calls: the stream has no body field for it. See the streaming recipe. - To put several calls under one trace, send the same
trace_id(up to 64 characters) in the request body (in Python, throughextra_body). That groups them and does not nest them.

