Mango

Quickstart

Start Mango locally and complete a Session in your preferred language.

Edit on GitHub

Run a text-only Session with the built-in offline model. You will create an Environment and an Agent, send a message, and read its persisted reply. No model-provider account or credentials are required.

Requirements

  • Git, Docker with Compose, and make.
  • curl for the readiness check; jq if you choose the HTTP example.
  • One SDK runtime if needed: Node.js 22+, Python 3.11+, or Go 1.24+.

Get the code

git clone https://github.com/yanpgwang/mango.git
cd mango

Run the following commands from this directory.

Run the server

export MANGO_API_KEY=sk-mango-local-development
MANGO_MODEL_BASE_URL= MANGO_MODEL_API_KEY= MANGO_MODEL_ID= \
  docker compose -f deployments/local/compose.yaml up -d --build
make local-health

Wait for every service to report healthy. The API listens on http://localhost:8080; Temporal's workflow explorer is at http://localhost:8233.

curl -i http://localhost:8080/readyz

Expect HTTP/1.1 200 OK. The stack initializes the schema with a one-shot migrate container, then starts Mango's API and orchestration worker, PostgreSQL, Temporal, NATS, and SeaweedFS. The model variables above explicitly select offline mode. Later, use Model configuration to connect a real endpoint.

Local development

The example key and Compose configuration are for local use. Docker shares the host kernel; this stack is not a hardened boundary for untrusted tenants. Use Deployment to review its operating limits.

Run your first Session

Choose a language below. Each command runs a complete application that creates its own resources and removes them when finished. Keep MANGO_API_KEY set in this terminal; in a new terminal, export the same value again.

Install the SDK from this checkout

These examples use the current resource-based SDKs, which are not yet published. The commands below install them from source. For use in your own application, see SDK installation.

npm --prefix sdk/typescript ci
npm --prefix sdk/typescript run build
node --experimental-strip-types sdk/typescript/examples/quickstart.ts

The program prints the offline response and persisted history, then finishes with:

Quickstart completed

The offline model exercises the Session lifecycle; it does not generate open-ended answers or choose tools. A successful run confirms that your local stack can accept input, complete a turn, and retrieve its recorded response.

Understand the example

The following snippets come from the complete programs above. They share the same client and resource variables; run the complete file to execute them rather than pasting each excerpt as a separate program. Go excerpts belong inside a function returning error.

Configure the client

MANGO_BASE_URL defaults to http://localhost:8080. Do not append /v1. The Workspace key authenticates to Mango, not to the model provider.

import { Mango } from 'mango-sdk';

const client = new Mango({
  baseURL: process.env.MANGO_BASE_URL ?? 'http://localhost:8080',
  apiKey: process.env.MANGO_API_KEY!,
});

Create an environment

An Environment groups Sessions by their execution configuration. Omitting config selects self_hosted. This example has no shell or file tools, so it completes without a separate Environment worker.

const environment = await client.environments.create({ name: 'Quickstart' });

Create an agent

The offline stack uses offline-fake. Agents are versioned; each Session keeps the resolved definition captured at creation.

const agent = await client.agents.create({ name: 'Assistant', model: 'offline-fake', system: 'Be concise.' });

Create a session

Creating the Session without initial events does not start a model turn.

const session = await client.sessions.create({ agent: agent.id, environment_id: environment.id, title: 'First session' });

Send a message and observe the turn

Sending an event admits durable work; the response contains accepted input, not the eventual agent reply. The SDK variants subscribe before sending, then wait for session.status_idle with stop_reason.type = end_turn. The HTTP-only variant polls persisted history for this fresh Session's first turn.

// Subscribe before sending: the stream does not replay earlier events.
const stream = await client.sessions.events.stream(session.id, {}, { signal: AbortSignal.timeout(60_000) });
let completed = false;
try {
  await client.sessions.events.send(session.id, { events: [{ type: 'user.message', content: [{ type: 'text', text: 'Hello, Mango!' }] }] });
  for await (const event of stream) {
    if (event.type === 'agent.message') console.log(event.content);
    if (event.type === 'session.status_idle') {
      if (event.stop_reason.type !== 'end_turn') throw new Error('The turn requires attention');
      completed = true;
      break;
    }
  }
  if (!completed) throw new Error('Stream ended before completion; reconcile persisted history');
} finally {
  await stream.close();
}

The examples fail if the stream ends early or the turn needs attention. They do not blindly retry a message after an ambiguous network failure. For an existing or reconnected Session, open a stream and reconcile history before deciding whether to send again. Preview deltas are ephemeral, not durable output.

Read persisted history

SDK iterators follow pagination. With raw HTTP, follow next_page until it is null; the first-turn example is small enough for one page.

const history = [];
for await (const event of client.sessions.events.listItems(session.id, { order: 'asc', limit: 100 })) history.push(event);
console.log(`Persisted events: ${history.length}`);

Clean up

The example deletes its Session and Environment and archives its Agent. Stop the local services when you are done:

make local-down

This keeps the PostgreSQL and SeaweedFS volumes for your next run. Use make local-down VOLUMES=1 only when you intend to delete the stack's stored data.

Next steps

On this page