Graine AI
Enterprise

Deployment

The three deployment models Graine offers — shared cloud, a dedicated instance, and your own compute with our control plane — what each gives you, what each requires, and how the platform is put together.

Graine is delivered three ways: as the multi-tenant cloud we operate, as a dedicated instance of the same platform reserved to you, or on your own infrastructure with our control plane still handling funding and metering.

All three run the identical set of services and data stores. The platform is defined as one self-contained stack, so choosing a model changes who operates it, where your data is processed, and what your concurrency ceiling is — not which features you get.

Deployment models

Shared cloudDedicated instanceYour compute, our control plane
Who operates itGraineGraineYou
Compute and data storesShared, isolated per organizationYours aloneYours
Where call data, recordings and transcripts livePlatform regionsThe region the instance is deployed inYour infrastructure
Region choiceFixed platform regionsChosen at deploymentYours
Concurrency ceilingYour organization limit, under a shared platform ceilingYour instance is the ceilingYours
Funding, finalization and meteringOursOursOurs — reached over the public internet
Network egress required from youNoneNoneTCP/443 to our control plane
Model, speech and telephony provider trafficOursOursLeaves your network directly
Air-gappedNot offeredNot offeredNot offered
How to get itSign upScoping conversationScoping conversation

Shared cloud

The multi-tenant platform at api.graine.ai. This is the shape that receives changes first, and everything described on this page runs there today.

What it gives you — the whole platform with no infrastructure to operate, no egress to arrange and no capacity to provision. Concurrency, retention and rate limits are per-organization settings that can be raised for your account within a minute, without a release.

Tenancy is enforced in the application rather than by deployment: your organization is resolved from the authenticating API key and nothing else, every primary read carries the organization as a query term, and an identifier belonging to another tenant answers 404 rather than 403 so existence cannot be probed. The mechanism is described in Security.

Fixed regions apply:

DataRegion
Compute, agents, call records, organization configurationIndia (Mumbai, ap-south-1)
API credential storeUnited States (N. Virginia, us-east-1)
Call recordings and audio objectsFrance (Paris) — third-party object storage, not the cloud provider above

There is no per-tenant region pinning on the shared cloud. If residency in a single jurisdiction is a requirement, one of the two models below is the answer.

Dedicated instance

The same platform — every service and all four data stores — deployed to infrastructure that serves only you, operated by us. Nothing about the software changes; what changes is that the compute, the databases, the object storage and the concurrency ceiling are yours alone, and the region is chosen at deployment rather than inherited.

What it gives you

  • Single-jurisdiction processing, including recordings and transcripts.
  • Capacity that is not shared. The platform-wide concurrency ceiling that sits above your organization limit on the shared cloud becomes your own.
  • A blast radius that contains only your traffic.
  • An upgrade cadence that can be agreed rather than assumed.

What it requires — a scoping conversation covering region, expected peak concurrency, provider accounts and network ingress. Commercial terms are per agreement.

Your compute, our control plane

You run the platform on your own instances or private cluster, omitting the local billing service.

What it gives you — call audio, transcripts, agent configuration and every provider credential stay inside your network and never transit ours. Model, speech and telephony traffic goes from your infrastructure to your providers directly. You choose the region, the hardware and the network posture.

This is not an air-gapped on-premises install

We avoid the unqualified word "on-premises", because it usually implies air-gapped, and this is not that. You run the compute — the calls, the models, the media, the transcripts. Funding checks, call finalization and metering still call our hosted API over the public internet.

Three call paths leave your network:

From your deploymentTo our control planePurpose
Telephony gatewayPOST /api/v1/credits/checkPre-call funding gate
Orchestration servicePOST /api/v1/calls/finalizeClose out a completed call
All servicesPOST /api/v1/ingest/metering, /api/v1/ingest/auditSigned usage and audit events

What it requires

  • Egress on TCP/443 to our API host, via NAT if the instances sit in private subnets.
  • Your egress address allowlisted on our side.
  • Generous HTTP timeouts — 30 seconds read, 10 seconds connect. Five seconds is not enough over TLS plus a WAF, and produces spurious "billing unavailable" failures.

The consequence to plan for. With the funding gate enabled and configured to fail closed — the setting we document — loss of egress to our control plane stops you placing calls. That is intended: it prevents unmetered calling. Confirm it is acceptable to your network team before choosing this model, and verify the path from the host:

curl -sS -m 30 -I https://api.graine.ai/health

A fully local billing path exists in the codebase. It is not the documented configuration for this model, and we will not represent it as supported until it has been validated in a customer deployment.

What runs in a deployment

Every model above runs the same components. A single outbound call touches four of them.

ComponentResponsibilityProtocol surface
Orchestration serviceCampaigns, batches, retries, follow-ups, the credit ledger, the concurrency governor, the audit log, webhook delivery, and the public /v2 APIHTTP
Agent serviceAgent lifecycle — agent documents, versions, prompts, A/B resolutionHTTP
Telephony gatewayResolves the agent, runs the pre-call funding gate, dials via the carrierHTTP
Conversation serviceThe live media and event plane; holds one WebSocket for the duration of a callWebSocket + HTTP
Authentication serviceSessions and API credentialsHTTP
Billing serviceUsage metering, debits, audit ingestHTTP
Knowledge retrieval serviceRetrieval over an agent's knowledge baseHTTP

Four data stores sit behind them:

StoreHolds
MongoDBAgents, call records, organization configuration, the credit ledger, the audit log
RedisConcurrency counters, rate-limit windows, short-TTL caches
KafkaInter-service events
PostgreSQLBilling records, and the scheduler's job store — so a retry or follow-up ladder survives a redeploy rather than living in process memory

The call path

Your request  ──▶  orchestration service  ──▶  telephony gateway  ──▶  carrier  ──▶  callee
                     │            │
                     │            └──▶  agent service   (resolve which agent to use)
                     │            └──▶  funding gate    (refuse the dial if unfunded)

                     └──▶  conversation service  ◀══ WebSocket ══▶  media for the live call

Two properties of that path matter for capacity planning:

  • The orchestration service is stateless per request. Its state lives in MongoDB and Redis, so it scales by adding replicas.
  • The conversation service is not. It holds one WebSocket per live call for the whole call and tracks those sockets in process. Its replicas therefore need sticky sessions, and a replica's ceiling is a connection count, not a request rate.

Bringing the stack up

The complete platform — four data stores, every service, three orchestration workers, and the observability stack — is defined as one composable set and brought up together:

docker compose \
  -f docker-compose.yml \
  -f docker-compose.observability.yml \
  -f docker-compose.billing.yml \
  up -d

Requires a Linux host with Docker and the Compose plugin, outbound HTTPS to your model, speech and telephony providers, and TLS termination in front of the host for the API and the WebSocket.

Trade-off — on one host, every component shares a failure domain. That is the right choice where operational simplicity outweighs redundancy, and it is the reason a dedicated instance is scoped with its own capacity target rather than assumed to be highly available by default.

Scaling within a deployment

Adding conversation capacity

The conversation service is the component whose ceiling is a connection count, so it is the one that benefits from its own tier. A committed topology runs four conversation-service replicas behind their own load balancer, each capped at 2,500 connections and limited to 1.5 vCPU and 2 GB, on a host separate from the API tier.

Requires

  • A second host, reachable from the API tier over a private network.
  • The API tier's private addresses configured for the agent service, Redis and Kafka — the committed file ships placeholder addresses that must be replaced before it will start.
  • WebSocket-aware load balancing with sticky sessions.

Scaling the stateless services

The orchestration service, agent service and telephony gateway hold no per-request state, so they scale by replica count behind a load balancer. Adding replicas on a single host raises throughput; it does not add availability, because the host remains one failure domain.

Ask for the current tier files

Several earlier tier and replica configurations exist in the repository — separate API, WebSocket and telephony tiers. They predate the current orchestration service and still start the previous-generation orchestrator, so they run but they do not carry the current concurrency governor, credit ledger or /v2 API. If you are planning a split-tier deployment, ask us for the current tier files rather than building on the committed ones.

Capacity

Capacity is governed, not merely observed. Before every dial, the orchestration service takes a slot through a four-level governor:

Global (platform ceiling)
  └── Organization
        └── Agent
              └── Batch

The first three levels are atomic counters in Redis, incremented by a single Lua check-and-increment so two workers cannot both take the last slot. Each slot carries a two-hour safety expiry so a lost release cannot strand capacity permanently, and a reconciler corrects drift against the calls actually in flight. Slot release is idempotent per execution and retried on failure — a double release would under-count live lines and admit dials the carrier would then fail.

Defaults, all overridable per organization:

LevelDefaultWhere it is set
Per agent100, seeded on organization creationorganization_configs.concurrency_limits.per_agent
Per organization100, seeded on organization creationorganization_configs.concurrency_limits.total_system
Batch10, seeded on organization creationorganization_configs.concurrency_limits.batch_size
Platform ceiling1,000platform_configs.global_limit

Those three values are written into the organization's configuration document when the organization is created. An organization whose document is missing that block — a hand-provisioned tenant, for instance — falls back to a conservative deployment-wide default instead, so read the live values from GET /v2/user/me rather than assuming them.

Both the per-organization limits and the platform ceiling are read from MongoDB and cached for 60 seconds. Raising capacity for a large campaign takes effect within a minute across every worker — no redeploy and no restart.

Two behaviours to know:

  • Enforcement can be disabled per organization. Counters still increment for observability, but nothing blocks.
  • If Redis becomes unreachable, the governor falls back to per-process in-memory counters. Calling stays alive, but limits are no longer enforced globally until Redis returns. This is monitored.

Your current position is readable at any time:

curl -sS https://api.graine.ai/v2/user/me \
  -H "Authorization: Bearer gat_YOUR_API_KEY"
{
  "organization_id": "organization-live-example",
  "plan": "trial",
  "concurrency": {
    "max": 100,
    "current": 12,
    "agent_max": 100,
    "global_max": 1000,
    "enforced": true
  },
  "rate_limits": {
    "default_per_minute": 1000,
    "calls_per_minute": 500,
    "executions_per_minute": 500
  }
}

WebSocket capacity

The conversation service accepts up to 6,000 concurrent WebSocket connections per worker process by default, and runs four workers per instance. Past the cap it closes new sockets with code 1013 ("Service overloaded") rather than degrading every call already in progress — a refusal the caller can see and the metrics record.

The dedicated conversation tier deliberately sets a lower 2,500 per replica across four replicas, trading per-instance density for blast-radius containment.

Those enforced caps are the numbers we stand behind. For a validated figure at your volume, ask for a load test against your configuration and we will run one.

Health and readiness

Every service exposes a liveness endpoint. The orchestration service is the only one with a true readiness probe — one that checks its dependencies rather than merely answering.

ComponentLivenessReadinessMetrics
Orchestration serviceGET /healthGET /readyGET /metrics
Agent serviceGET /health
Conversation serviceGET /healthGET /metrics
Telephony gatewayGET /healthyes
Billing serviceGET /healthGET /metrics
Knowledge retrieval serviceGET /health

The readiness probe pings MongoDB and Redis and returns a per-dependency breakdown:

curl -sS https://api.graine.ai/ready
{
  "status": "ready",
  "checks": { "mongodb": true, "redis": true }
}

When a dependency is unreachable the same endpoint returns 503:

{
  "status": "not_ready",
  "checks": { "mongodb": true, "redis": false }
}

Point your orchestrator's readiness check at /ready on the orchestration service and at /health elsewhere. Do not use /health as a readiness gate on the orchestration service — it answers 200 while the databases are still connecting.

Not offered today

Stated plainly, so it does not surface late in an evaluation:

  • Air-gapped installation. Every model requires egress to our control plane for funding and metering.
  • A managed container-service topology. A container-service definition exists in the infrastructure tree but does not yet synthesize deployably, and it provisions a single availability zone. Raise it during scoping if it is a requirement for your account.
  • Offline licensing, signed release artifacts for self-hosted deployment, or a documented self-hosted upgrade and rollback procedure.
  • Multi-region active-active. A design exists; no infrastructure implements it.

On this page