← Back to Plugins
Tools

Manifest

mnfst By mnfst ⭐ 3312 stars 👁 26 views ▲ 0 votes

Real-time cost observability for your OpenClaw agents.

Homepage GitHub

Install

npm install
cp

Configuration Example

# otel-collector-config.yaml
exporters:
  otlphttp/manifest:
    endpoint: http://localhost:3001
    headers:
      Authorization: 'Bearer mnfst_your-agent-api-key'

service:
  pipelines:
    traces:
      exporters: [otlphttp/manifest]
    metrics:
      exporters: [otlphttp/manifest]
    logs:
      exporters: [otlphttp/manifest]

README

<p align="center">
  <img src="home-gh.png" alt="Manifest" />
</p>

# Manifest

AI agent observability platform. Monitor costs, tokens, messages, and performance of your AI agents in real time.

## Tech Stack

| Layer     | Technology                                       |
| --------- | ------------------------------------------------ |
| Frontend  | SolidJS, uPlot, custom CSS tokens                |
| Backend   | NestJS 11, TypeORM, PostgreSQL 16                |
| Auth      | Better Auth (email/password + 3 OAuth providers) |
| Telemetry | OTLP HTTP (JSON + Protobuf)                      |
| Monorepo  | Turborepo + npm workspaces                       |

## Getting Started

### Prerequisites

- Node.js 22.x (LTS)
- npm 10.x
- Docker (for PostgreSQL)

### PostgreSQL

Start a local PostgreSQL 16 instance. The easiest way is Docker:

```bash
docker run -d --name postgres_db \
  -e POSTGRES_USER=myuser \
  -e POSTGRES_PASSWORD=mypassword \
  -e POSTGRES_DB=mydatabase \
  -p 5432:5432 \
  postgres:16
```

The `DATABASE_URL` in your `.env` must match these credentials:

```
DATABASE_URL=postgresql://myuser:mypassword@localhost:5432/mydatabase
```

> **Format:** `postgresql://<user>:<password>@<host>:<port>/<database>`

If you use a managed PostgreSQL instance (e.g. Railway, Supabase, Neon), set `DATABASE_URL` to the connection string provided by your provider.

### Install & Run

```bash
git clone <repo-url> && cd manifest
npm install
cp packages/backend/.env.example packages/backend/.env    # edit with your secrets
npm run dev
```

The frontend starts on `http://localhost:3000` and the backend on `http://localhost:3001`.

### Environment Variables

Copy `packages/backend/.env.example` to `packages/backend/.env` and fill in the required values.

#### Core

| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `BETTER_AUTH_SECRET` | Yes | — | Secret for session signing. Must be at least 32 characters. Generate with `openssl rand -hex 32`. |
| `DATABASE_URL` | Yes* | `postgresql://myuser:mypassword@localhost:5432/mydatabase` | PostgreSQL connection string. Format: `postgresql://user:password@host:port/database`. The default matches the Docker command above — override in production. |
| `PORT` | No | `3001` | Server port. |
| `BIND_ADDRESS` | No | `127.0.0.1` | Bind address. Use `0.0.0.0` for Docker/Railway. |
| `NODE_ENV` | No | `development` | Set `production` to disable CORS and serve frontend static files. |
| `CORS_ORIGIN` | No | `http://localhost:3000` | Allowed CORS origin (dev mode only). |
| `API_KEY` | No | — | Secret for programmatic API access via `X-API-Key` header. |
| `BETTER_AUTH_URL` | No | `http://localhost:{PORT}` | Base URL for Better Auth (set to your public URL in production). |
| `FRONTEND_PORT` | No | — | Extra trusted origin port for Better Auth (added to trusted origins list). |
| `THROTTLE_TTL` | No | `60000` | Rate limit window in milliseconds. |
| `THROTTLE_LIMIT` | No | `100` | Max requests per rate limit window. |
| `SEED_DATA` | No | — | Set `true` to seed demo data on startup. |

*The default `DATABASE_URL` matches the Docker command in the PostgreSQL section above. For production, you must set this to your actual PostgreSQL connection string.

#### Email (Mailgun)

Required for email verification and password reset to work. Without these, users can register but won't receive verification or reset emails.

| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `MAILGUN_API_KEY` | Yes* | — | Mailgun API key (starts with `key-`). Found in Mailgun dashboard under API Keys. |
| `MAILGUN_DOMAIN` | Yes* | — | Mailgun sending domain (e.g. `mg.yourdomain.com`). Must be verified in Mailgun. |
| `NOTIFICATION_FROM_EMAIL` | No | `[email protected]` | Sender email address for all outgoing emails. |

*If not set, the app runs normally but email verification and password reset emails are silently skipped.

#### OAuth Providers (all optional)

Each provider requires both `CLIENT_ID` and `CLIENT_SECRET` to be set. If either is missing, that provider is disabled.

| Variable | Description |
| --- | --- |
| `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` | Google OAuth |
| `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` | GitHub OAuth |
| `DISCORD_CLIENT_ID` / `DISCORD_CLIENT_SECRET` | Discord OAuth |

#### Plugin (optional)

| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `PLUGIN_OTLP_ENDPOINT` | No | — | Custom OTLP endpoint URL shown in the plugin setup UI. |

## Authentication

Manifest uses [Better Auth](https://www.better-auth.com/) for user authentication. Two auth methods are supported:

### Session-Based (UI)

Users sign in via the web UI using email/password or one of three OAuth providers (Google, GitHub, Discord). Sessions are cookie-based and managed by Better Auth at `/api/auth/*`.

### API Key (Programmatic)

For CLI/script access, set the `API_KEY` env var and pass it via `X-API-Key` header. This bypasses session auth and is useful for automation.

### OTLP Ingest Keys

Each agent gets a unique ingest key (`mnfst_*` format) for sending telemetry. Pass it via `Authorization: Bearer <key>` to OTLP endpoints. Keys are created automatically when onboarding a new agent.

## Multi-Tenancy

Each authenticated user is mapped to a **tenant**. Agents belong to tenants, and all data is filtered by tenant ownership:

```
User (Better Auth) ──→ Tenant ──→ Agent ──→ AgentApiKey (mnfst_*)
                                    │
                                    └──→ agent_messages (telemetry data)
```

- Users only see their own agents and telemetry data
- Creating an agent via the UI auto-creates the tenant, agent, and OTLP ingest key
- The `user.id` from Better Auth is used as the tenant `name`

## Connecting OpenTelemetry

Manifest accepts standard OTLP HTTP signals (traces, metrics, logs). Any OpenTelemetry SDK exporter can send data directly to the platform.

### Endpoints

| Signal  | Endpoint                | Auth                                    |
| ------- | ----------------------- | --------------------------------------- |
| Traces  | `POST /otlp/v1/traces`  | `Authorization: Bearer <agent-api-key>` |
| Metrics | `POST /otlp/v1/metrics` | `Authorization: Bearer <agent-api-key>` |
| Logs    | `POST /otlp/v1/logs`    | `Authorization: Bearer <agent-api-key>` |

Both `application/json` and `application/x-protobuf` content types are supported.

### Node.js / TypeScript Example

```bash
npm install @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-http @opentelemetry/exporter-metrics-otlp-http @opentelemetry/exporter-logs-otlp-http
```

```typescript
import { NodeSDK } from '@opentelemetry/sdk-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'

const headers = { Authorization: 'Bearer mnfst_your-agent-api-key' }
const baseUrl = 'http://localhost:3001'

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter({
    url: `${baseUrl}/otlp/v1/traces`,
    headers,
  }),
  metricReader: new PeriodicExportingMetricReader({
    exporter: new OTLPMetricExporter({
      url: `${baseUrl}/otlp/v1/metrics`,
      headers,
    }),
  }),
  logRecordProcessor: new OTLPLogExporter({
    url: `${baseUrl}/otlp/v1/logs`,
    headers,
  }),
  serviceName: 'my-agent',
  resource: { 'agent.name': 'my-agent' },
})

sdk.start()
```

### Python Example

```bash
pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-http
```

```python
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

provider = TracerProvider()
exporter = OTLPSpanExporter(
    endpoint="http://localhost:3001/otlp/v1/traces",
    headers={"Authorization": "Bearer mnfst_your-agent-api-key"},
)
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)

tracer = trace.get_tracer("my-agent")
with tracer.start_as_current_span("process-request"):
    pass  # your agent logic
```

### Using the OpenTelemetry Collector

If you run the OTel Collector, add Manifest as an OTLP HTTP exporter:

```yaml
# otel-collector-config.yaml
exporters:
  otlphttp/manifest:
    endpoint: http://localhost:3001
    headers:
      Authorization: 'Bearer mnfst_your-agent-api-key'

service:
  pipelines:
    traces:
      exporters: [otlphttp/manifest]
    metrics:
      exporters: [otlphttp/manifest]
    logs:
      exporters: [otlphttp/manifest]
```

### Semantic Conventions

Manifest classifies OTLP trace spans using these attributes:

| Attribute                    | Maps to               | Purpose                           |
| ---------------------------- | --------------------- | --------------------------------- |
| `agent.name`                 | Agent name            | Groups data per agent             |
| `gen_ai.system`              | LLM call record       | Identifies spans as LLM API calls |
| `gen_ai.request.model`       | Model name            | Tracks which model was used       |
| `gen_ai.usage.input_tokens`  | Input tokens          | Token usage tracking              |
| `gen_ai.usage.output_tokens` | Output tokens         | Token usage tracking              |
| `tool.name`                  | Tool execution record | Identifies spans as tool calls    |
| `session.key`                | Session key           | Groups messages by session        |

## API Reference

### Agent Management

| Method | Route                       | Auth            | Purpose                            |
| ------ | --------------------------- | --------------- | ---------------------------------- |
| GET    | `/api/v1/agents`          

... (truncated)
tools

Comments

Sign in to leave a comment

Loading comments...