> ## Documentation Index
> Fetch the complete documentation index at: https://developers.telnyx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Bring Your Own Framework

Any turn taking runs on the `Agent` class, whether a framework or a loop the user writes. Glue code joins the two. The class keeps the conversation, and the turn taking produces each reply.

## Write the Glue

```ts theme={null}
import { Agent, type Env } from "@telnyx/edge-runtime";

export class Conversation extends Agent<Env> {
  async receive(text: string): Promise<void> {
    await this.messages.add("user", text);
    await this.queue("process");
  }

  async process(): Promise<void> {
    const history = await this.messages.toOpenAI();
    const reply = await takeTurn(history);
    await this.messages.add("assistant", reply);
  }
}
```

* `receive` records the message in the [log](/docs/agent-sdk/interface/messages) and returns, inside the 30-second budget of an inbound call.
* [`queue`](/docs/agent-sdk/interface/queue) runs `process` as a task, which has minutes, retries on failure and survives restarts.
* `process` reads the history in the shape the turn taking expects and records the reply, so the next turn sees it.

`takeTurn` is the slot. The log reads the history out in three shapes for it, `toOpenAI()`, `toAnthropic()` and `toLangChain()`.

Under the entry `deploy` generates, a text or an email reaches the class's `receiveVia(text, from, key, via)`, so a class wired to channels implements that method, and `receive` is the RPC door. A client reaches it once the method is marked with [`@rpc()`](/docs/agent-sdk/interface/rpc) and [`authorize`](/docs/agent-sdk/interface/authorize) grants the client the `rpc` claim.

## Write the Loop by Hand

The smallest turn taking is one model call. The Telnyx inference endpoint is reached through the `TELNYX` binding on [`env`](/docs/agent-sdk/interface/env) with the account's credential, so the loop holds no key.

```ts theme={null}
import { Agent, type Env } from "@telnyx/edge-runtime";
import type Telnyx from "telnyx";

type ChatResponse = { choices: Array<{ message: { content: string } }> };

export class Conversation extends Agent<Env & { TELNYX: Telnyx }> {
  // receive() as above

  async process(): Promise<void> {
    const history = await this.messages.toOpenAI();
    const res = (await this.env.TELNYX.ai.openai.chat.createCompletion({
      model: "zai-org/GLM-5.3-Flash",
      messages: [{ role: "system", content: "Answer in one or two sentences." }, ...history] as never,
    })) as ChatResponse;
    await this.messages.add("assistant", res.choices[0].message.content);
  }
}
```

`Env` types no bindings, so the class adds `TELNYX`, and the history and the response are cast, as `managed/model.ts` does.

A loop with tools calls the model again after each tool result. It records the call and the result with [`append()`](/docs/agent-sdk/interface/messages#append), and `toOpenAI()` returns them to the model on the next read.

## Plug In a Framework

A framework takes the same place. LangGraph reads the `toLangChain()` history as it is.

```bash theme={null}
npm install @langchain/openai @langchain/langgraph @langchain/core zod
```

```ts theme={null}
import { Agent, type Env } from "@telnyx/edge-runtime";
import { ChatOpenAI } from "@langchain/openai";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { tool } from "@langchain/core/tools";
import { z } from "zod";

const lookupOrder = tool(
  async ({ orderId }) => JSON.stringify({ orderId, status: "shipped" }),
  {
    name: "lookup_order",
    description: "Look up an order by its id.",
    schema: z.object({ orderId: z.string() }),
  },
);

const graph = createReactAgent({
  llm: new ChatOpenAI({
    model: "zai-org/GLM-5.3-Flash",
    apiKey: process.env.TELNYX_API_KEY,
    configuration: { baseURL: "https://api.telnyx.com/v2/ai/openai" },
  }),
  tools: [lookupOrder],
});

export class Conversation extends Agent<Env> {
  // receive() as above

  async process(): Promise<void> {
    const history = await this.messages.toLangChain();
    const out = await graph.invoke({
      messages: [{ role: "system", content: "Answer in one or two sentences." }, ...history],
    });
    await this.messages.add("assistant", String(out.messages.at(-1)?.content ?? ""));
  }
}
```

* A framework makes its own HTTP calls, so it authenticates like any client. Its key reaches the class as an environment variable.
* Only the final reply reaches the log. The tool calls inside the run stay with the framework, and `append()` records them when a later turn needs them.

[The Agent Harness SDK](/docs/agent-sdk/harness-sdk) is Telnyx's own turn taking, and it takes the same place. [`createHarness()`](/docs/agent-sdk/harness-sdk/create-harness) builds it inside the class.
