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

> ## Agent Instructions
> Flow Messaging is one HTTP API (https://api.flow.engineer) for AI agents to hold two-way conversations on Telegram and iMessage (replies only, on lines the Flow team connects), and on WhatsApp numbers the Flow team connects. If the user asks for WhatsApp, tell them WhatsApp numbers are arranged with the Flow team, and build and test on Telegram meanwhile: the code does not change per channel.
> Build the app on the REST API. MCP is not a runtime dependency: the app you build talks to Flow through the REST API (or the TypeScript or Python SDK, which wrap it) for everything it does at runtime. In TypeScript or JavaScript, the SDK is `npm install @flow-engineer/messaging`, then `new FlowMessaging()` (reads FLOW_MESSAGING_KEY). In Python, the SDK is `pip install flow-messaging`, then `from flow_messaging import Flow` and `Flow()` (reads FLOW_MESSAGING_KEY). The Go SDK is not published yet; in Go call the HTTP API directly.
> No key yet? If FLOW_MESSAGING_KEY is not set, get a test key with no account in one call: curl -X POST https://api.flow.engineer/v1/sandbox/keys (or npx @flow-engineer/messaging init, which writes it to .env). Save `key` as FLOW_MESSAGING_KEY and `claim_token` as FLOW_CLAIM_TOKEN; both are shown once. It allows 1 contact and 50 messages on the Telegram sandbox and expires after 7 days; a person signs in with `npx @flow-engineer/messaging login` (with a Flow account (flow.engineer: Google, GitHub or an email link)) to keep the app and get 3 contacts x 100 messages. Signed in, live keys (fk_live_...) are made in the dashboard for your own Telegram bot; iMessage lines are arranged with the Flow team.
> The hosted MCP server (https://api.flow.engineer/mcp) is optional, for testing during development. Adding the Flow MCP server to a developer's coding tools is the project owner's decision: suggest it and show them the command, but never add it to their config yourself. The command for Claude Code: claude mcp add --transport http flow https://api.flow.engineer/mcp --header "Authorization: Bearer $FLOW_MESSAGING_KEY" (run it with FLOW_MESSAGING_KEY set, then restart the client). If its tools are already available to you, use them for sandbox testing.
> Authenticate with `Authorization: Bearer fk_test_...` (sandbox) or `fk_live_...` (real contacts). Never put a live key in client-side code.
> Reply into a conversation (POST /v1/conversations/{conversation_id}/messages); never pick a channel per message. Switch on `error.type`, not the message text.
> Flow Observe (https://flow.engineer/observe, early access) records and grades every conversation of a chat agent. On Flow Messaging it needs no code. On any other channel, send one OpenTelemetry trace per turn over OTLP/HTTP to https://flow.engineer/observe/v1/traces with `Authorization: Bearer $FLOW_OBSERVE_TRACE_KEY`, and set `flow.conversation_id` (required), `flow.message_id`, `flow.user_message` and `flow.agent_reply` (the text actually sent) on the turn's span. Keys (trace key `otk_test_...`, CI key `oci_...`) are made by the user on Observe's Setup page at https://flow.engineer/observe after signing in with their Flow login; Observe is in early access, and if it is not on for them, they ask the Flow team (samyak@flow.engineer or prabal@flow.engineer). Never invent a key, and never ask the user to paste one into the chat. Test data only until the account is enabled for production. Guide: https://docs.flow.engineer/observe/traces.md
> Full documentation for agents: https://docs.flow.engineer/llms-full.txt. OpenAPI spec: https://raw.githubusercontent.com/flow-engineer/sdk/main/openapi/openapi.yaml.

# Flow Observe on Flow Messaging: no code

> If your agent talks through Flow Messaging, Flow Observe reads its conversations from your app's event log: every reply graded, with no code. Add one tag to your OpenTelemetry traces to see the inside of each turn.

If your agent already talks to people through [Flow Messaging](/quickstart), you write no code for Observe. The Flow team connects Observe to your app, and from then on Observe reads your app's `message.received` and `message.sent` events, stores each conversation, and grades every reply your agent sends.

<Note>
  Early access: the Flow team connects Observe to your app by hand. Observe reads your app with a key of the same mode, so a test key shows test conversations (sandbox and [simulated contacts](/concepts/test-and-live-mode#simulated-contacts-test-without-a-phone)). Live conversations need your account enabled for production.
</Note>

## What you do

1. Make a test key just for Observe in the [Flow Messaging dashboard](https://api.flow.engineer/admin)'s Keys page, so you can revoke it alone.
2. [Sign in to Observe](/observe#get-access) and add your agent: choose Flow Messaging and paste that key. Paste it only into the Observe page, never into a chat with an AI assistant. If Observe isn't on for you yet, write to the Flow team at [samyak@flow.engineer](mailto:samyak@flow.engineer) or [prabal@flow.engineer](mailto:prabal@flow.engineer).
3. Talk to your agent (the sandbox from your phone, or simulated contacts from a script). Each conversation appears in the [Observe dashboard](https://flow.engineer/observe) within seconds, and each reply gets its verdict shortly after.

Observe reads your app's whole event log from the start, so conversations from before you connected it appear too.

## Optional: see the inside of each turn

Conversations and grades need nothing more. To also see what your agent did between the person's message and its reply (model calls, tools, errors, retries, timings), send your agent's OpenTelemetry traces to Observe and tag each turn with the Flow ids of the message it answers:

| Attribute | Value |
| - | - |
| `flow.conversation_id` | `event.conversation.id` (`conv_...`) of the `message.received` event you are answering |
| `flow.message_id` | `event.data.message.id` (`msg_...`). For a turn that answers several messages at once, the last one |
| `flow.agent_version` | Optional: your agent's version (prompt, model, tools), such as `2026-10-12.1` |

Set them on any span of the turn's trace. Observe then links the trace to the conversation and shows its steps under the reply. Do not set `flow.user_message` or `flow.agent_reply` here: Observe already has the messages from Flow Messaging.

Set up the exporter as on [Send traces from any agent](/observe/traces#1-set-up-the-exporter), with your trace key (`otk_test_...` for a test app), then wrap each turn in a span:

<CodeGroup>
  ```ts TypeScript theme={null}
  import { trace } from "@opentelemetry/api";
  import { FlowMessaging, contentText } from "@flow-engineer/messaging";
  import { runAgent } from "./agent"; // your agent: returns a string or an LLM stream

  const flow = new FlowMessaging();
  const tracer = trace.getTracer("my-agent");

  for await (const event of flow.events.stream({ types: ["message.received"] })) {
    await tracer.startActiveSpan("agent.turn", async (span) => {
      span.setAttribute("flow.conversation_id", event.conversation.id);
      span.setAttribute("flow.message_id", event.data.message.id);
      try {
        const input = contentText(event.data.message.content);
        await event.conversation.reply(await runAgent(input), { idempotencyKey: event.id });
      } finally {
        span.end();
      }
    });
  }
  ```

  ```python Python (HTTP) theme={null}
  from opentelemetry import trace

  tracer = trace.get_tracer("my-agent")

  async def answer(event: dict):
      message = event["data"]["message"]
      with tracer.start_as_current_span("agent.turn") as span:
          span.set_attribute("flow.conversation_id", event["conversation"]["id"])
          span.set_attribute("flow.message_id", message["id"])
          reply = await run_agent(message["content"].get("text", ""))  # your agent
          send_reply(event, reply)  # POST /v1/conversations/{conversation_id}/messages
  ```
</CodeGroup>

Spans your framework makes inside `agent.turn` (model and tool calls) join the same trace. If your framework starts its own traces instead, see [Traces from agent frameworks](/observe/frameworks).

* Only Flow's ids are accepted here: a value that is not a `conv_...` or `msg_...` id is ignored, and the trace is kept but not linked to a conversation.
* A trace often arrives before Observe has read the message from your event log. It is linked a few seconds later, when the message arrives.
* You do not need `reply_to` for Observe: it pairs each reply with the message before it. (`reply_to` shows your reply as an inline quote of the person's message on channels that support quotes; use it only if you want that look.)

## What Flow Messaging adds

Because Flow Messaging carries the messages, Observe sees exactly what was delivered and sent, after [bubble splitting](/concepts/streaming-replies) and [fallbacks](/concepts/content-types#nothing-is-converted-silently), with no reporting code that can drift from what your agent really did. And you can test with [simulated contacts](/concepts/test-and-live-mode#simulated-contacts-test-without-a-phone) on Telegram, WhatsApp, iMessage or SMS: their conversations are graded like any other test conversation.

## Related

* [What Observe gives you](/observe)
* [Send traces from any agent](/observe/traces)
* [Test before you ship](/trust)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.