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

# Messages

> Send and receive chat messages, stream AI responses, and search conversation history.

# Messages API

<Note>
  Messages live in Convex today and are read directly by the web app. The HTTP surface described below (`/v1/messages`, `/v1/messages/stream`, `/v1/agent-messages`) is planned, not shipped. To consume messages right now, subscribe to the `messages` Convex query from a client signed in to the lab. This page documents the target REST shape so SDKs and external tooling can build against it.
</Note>

Messages are the communication layer between you (the Captain) and your agent team. Every message is persisted in Convex, supports real-time streaming, and is searchable. Messages are scoped to a lab.

## Message Roles

| Role        | Description                                                        |
| ----------- | ------------------------------------------------------------------ |
| `user`      | Message from the Captain or a collaborator                         |
| `assistant` | Response from an AI agent                                          |
| `system`    | System-generated message (experiment alerts, status changes, etc.) |

## Send a Message

Send a message to the orchestrator or a specific agent. The response streams back in real time.

<ParamField body="labId" type="string" required>
  Lab ID for the conversation.
</ParamField>

<ParamField body="content" type="string" required>
  Message text. Supports markdown.
</ParamField>

<ParamField body="agentId" type="string">
  Target a specific agent. If omitted, the message goes to the orchestrator.
</ParamField>

<ParamField body="context" type="object">
  Contextual metadata (e.g., which experiment, paper, or view is currently open).

  <Expandable title="Context fields">
    <ParamField body="experimentId" type="string">Active experiment ID.</ParamField>
    <ParamField body="paperId" type="string">Active paper ID.</ParamField>
    <ParamField body="view" type="string">Current UI view (e.g., `captain`, `experiments`, `papers`).</ParamField>
  </Expandable>
</ParamField>

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://www.hubify.com/api/v1/messages \
    -H "Authorization: Bearer $HUBIFY_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "labId": "j57a8k9m2n3p4q5r",
      "content": "What is the current status of the Planck+BAO MCMC chains?",
      "context": {"view": "experiments"}
    }'
  ```

  ```typescript TypeScript SDK theme={null}
  const message = await hubify.messages.send({
    labId: "j57a8k9m2n3p4q5r",
    content: "What is the current status of the Planck+BAO MCMC chains?",
    context: { view: "experiments" },
  });
  ```
</CodeGroup>

<ResponseField name="data" type="object">
  <Expandable title="Message object">
    <ResponseField name="_id" type="string" required>Convex document ID.</ResponseField>
    <ResponseField name="labId" type="string" required>Lab ID.</ResponseField>
    <ResponseField name="userId" type="string">Sender user ID (for `user` role).</ResponseField>
    <ResponseField name="agentId" type="string">Sender agent ID (for `assistant` role).</ResponseField>
    <ResponseField name="role" type="string" required>Message role: `user`, `assistant`, or `system`.</ResponseField>
    <ResponseField name="content" type="string" required>Message content (markdown).</ResponseField>
    <ResponseField name="context" type="string">JSON-encoded context metadata.</ResponseField>
    <ResponseField name="streaming" type="boolean">True while the assistant is still generating.</ResponseField>
    <ResponseField name="model" type="string">Model that generated the response (e.g., `claude-opus-4-8`).</ResponseField>
    <ResponseField name="tokenCount" type="number">Total tokens used.</ResponseField>
    <ResponseField name="costUsd" type="number">Estimated cost in USD.</ResponseField>
    <ResponseField name="createdAt" type="number" required>Timestamp (ms).</ResponseField>
  </Expandable>
</ResponseField>

## Streaming Responses

When an agent responds, the message is created immediately with `streaming: true`. The `content` field updates in real time as tokens are generated. When the response is complete, `streaming` flips to `false`.

### Subscribe to Streaming (Real-Time)

```typescript theme={null}
import { useQuery } from "convex/react";
import { api } from "../convex/_generated/api";

function ChatPanel({ labId }: { labId: string }) {
  // This query re-renders on every content update (token-level streaming)
  const messages = useQuery(api.messages.list, { labId });

  return (
    <div>
      {messages?.map((msg) => (
        <div key={msg._id} className={msg.role}>
          {msg.content}
          {msg.streaming && <span className="cursor-blink" />}
        </div>
      ))}
    </div>
  );
}
```

### HTTP Streaming (Server-Sent Events)

For non-Convex clients, the API supports SSE streaming:

```bash theme={null}
curl -N https://www.hubify.com/api/v1/messages/stream \
  -H "Authorization: Bearer $HUBIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "labId": "j57a8k9m2n3p4q5r",
    "content": "Summarize the latest experiment results"
  }'
```

Response stream:

```
data: {"type": "token", "content": "The"}
data: {"type": "token", "content": " latest"}
data: {"type": "token", "content": " MCMC"}
...
data: {"type": "done", "messageId": "p12e3f4g5h6i7j8k", "tokenCount": 847}
```

## List Messages

Retrieve conversation history for a lab.

<ParamField query="labId" type="string" required>
  Lab ID.
</ParamField>

<ParamField query="limit" type="number" default="50">
  Number of messages to return (most recent first).
</ParamField>

<ParamField query="cursor" type="string">
  Pagination cursor for older messages.
</ParamField>

<ParamField query="role" type="string">
  Filter by role: `user`, `assistant`, `system`.
</ParamField>

<ParamField query="agentId" type="string">
  Filter to messages from a specific agent.
</ParamField>

<CodeGroup>
  ```bash curl theme={null}
  curl "https://www.hubify.com/api/v1/messages?labId=j57a8k9m2n3p4q5r&limit=20" \
    -H "Authorization: Bearer $HUBIFY_TOKEN"
  ```

  ```typescript TypeScript SDK theme={null}
  const messages = await hubify.messages.list({
    labId: "j57a8k9m2n3p4q5r",
    limit: 20,
  });
  ```
</CodeGroup>

## Search Messages

Full-text search across conversation history.

<ParamField query="labId" type="string" required>
  Lab ID to search within.
</ParamField>

<ParamField query="query" type="string" required>
  Search query. Matches against message content.
</ParamField>

<ParamField query="limit" type="number" default="20">
  Maximum results.
</ParamField>

<CodeGroup>
  ```bash curl theme={null}
  curl "https://www.hubify.com/api/v1/messages/search?labId=j57a8k9m2n3p4q5r&query=convergence+diagnostics" \
    -H "Authorization: Bearer $HUBIFY_TOKEN"
  ```

  ```typescript TypeScript SDK theme={null}
  const results = await hubify.messages.search({
    labId: "j57a8k9m2n3p4q5r",
    query: "convergence diagnostics",
  });
  ```
</CodeGroup>

## Delete a Message

Delete a single message. Only the Captain can delete messages.

<CodeGroup>
  ```bash curl theme={null}
  curl -X DELETE https://www.hubify.com/api/v1/messages/p12e3f4g5h6i7j8k \
    -H "Authorization: Bearer $HUBIFY_TOKEN"
  ```

  ```typescript TypeScript SDK theme={null}
  await hubify.messages.delete({ messageId: "p12e3f4g5h6i7j8k" });
  ```
</CodeGroup>

## Agent-to-Agent Messages

Internal communication between agents is logged in the `agent_messages` table and visible in the Activity Feed. These are separate from Captain-facing chat messages.

### List Agent Messages

<ParamField query="labId" type="string" required>
  Lab ID.
</ParamField>

<ParamField query="channel" type="string">
  Filter by channel: `delegation`, `status`, `escalation`, `broadcast`.
</ParamField>

<CodeGroup>
  ```bash curl theme={null}
  curl "https://www.hubify.com/api/v1/agent-messages?labId=j57a8k9m2n3p4q5r&channel=escalation" \
    -H "Authorization: Bearer $HUBIFY_TOKEN"
  ```

  ```typescript TypeScript SDK theme={null}
  const agentMessages = await hubify.agentMessages.list({
    labId: "j57a8k9m2n3p4q5r",
    channel: "escalation",
  });
  ```
</CodeGroup>

<ResponseField name="data" type="object[]">
  <Expandable title="Agent message object">
    <ResponseField name="_id" type="string" required>Convex document ID.</ResponseField>
    <ResponseField name="labId" type="string" required>Lab ID.</ResponseField>
    <ResponseField name="fromAgentId" type="string" required>Sending agent ID.</ResponseField>
    <ResponseField name="toAgentId" type="string">Receiving agent ID. Null for broadcasts.</ResponseField>
    <ResponseField name="channel" type="string" required>Communication channel.</ResponseField>
    <ResponseField name="content" type="string" required>Message content.</ResponseField>
    <ResponseField name="metadata" type="string">JSON-encoded context.</ResponseField>
    <ResponseField name="createdAt" type="number" required>Timestamp (ms).</ResponseField>
  </Expandable>
</ResponseField>
