Skip to content
Public beta preview — you're seeing the sneak peek

Hosting & runtime

read as .md

You bring a declarative agent definition — a system prompt, a model, the MCP servers your agent uses, and an optional tool allowlist. Guuey runs it. There is no SDK to import, no polling loop to write, no event handler to register: configuration and a prompt are the whole integration.

Everything lives in one file, guuey.json, validated the same way by guuey dev, guuey deploy, and Guuey’s hosted platform — one contract everywhere.

This page is the how-to. For the machine underneath — the pod model, per-user pinning, pause and resume, and what happens at the edges — see How hosting works.

Each agent runs in its own long-running pod, in its own namespace, on a default-deny network. Every agent pod — no-code and code-mode alike — and every MCP server you host on Guuey runs under gVisor, a much stronger isolation boundary than an ordinary shared container. A no-code agent runs Guuey’s own runtime image, and the worker that runs your turns is additionally jailed inside the pod with a cleared, allowlisted environment. Long-running means your agent isn’t rebuilt on every request; it runs continuously from deploy to undeploy while the app has an active trial or plan (see Plans and the 7-day trial below). Nothing sleeps between conversations, so nothing has to wake up: the first message of the day is as fast as the last.

Hosted MCP servers work the other way by design — they scale to zero after about 30 minutes idle and wake on the next call.

The gVisor-isolated pod each hosted MCP server runs in matters most for compute workloads — code execution, binary tools like ffmpeg or pandoc, headless browser automation — the kind of thing most platforms can’t sandbox per tenant. The isolation model and data handling are covered in more depth on Security & isolation.

Billing is per app: every app you deploy carries its own plan, and your first app starts with a 7-day free trial from its first successful deploy — one always-on xs pod on the Free (trial) limits. One app per account (or workspace) runs on the trial at a time; the next app gets its own 7 days once the first takes a plan or is deleted. When the trial ends and the app has no plan, Guuey pauses it: the pod scales to zero and a redeploy is refused until a plan is chosen, but nothing is deleted, and choosing a plan resumes the app automatically. The plan table, the full trial walkthrough — the trial slot, warning emails, what the paused surface shows, the API and CLI fields — and metered usage are on Plans & billing.

Personal and workspace apps bill separately. A workspace — a team, managed in the console under Settings → Workspace → your workspace — has its own wallet and its own trial slot, and owns its apps and hosted MCP servers; CI for a workspace-owned app authenticates with a workspace API key.

In your personal workspace, a one-time $5 credit toward model usage on Guuey’s key is shared by your trial apps. An app in a team workspace gets no trial credit, so its model calls on Guuey’s key are refused until you choose a plan or connect your own model key (BYOK).

Agent pods come in five sizes — xs, sm, md, lg, xl — picked at deploy time: agent.deploy.size in guuey.json for a declarative app, which guuey agent apply reads, or guuey deploy --size for a code-mode app (default xs). An apply that names no size keeps the app’s current size (xs for its first build). Which sizes an app may use, and how many pods it may run, come from its plan:

Plan Sizes Max pods per app
Free / trial xs 1
Starter xs–sm 2
Pro xs–md 5
Scale xs–xl 10

Pod count is a live knob: guuey agent config --max-pods <n> scales a running app with no redeploy (the platform converges within about five minutes), and a code-mode app can also set it on guuey deploy --max-pods. Asking for more pods than the plan’s ceiling is refused with a 409 that names the ceiling; a higher per-app ceiling is a support request. Each user’s traffic stays pinned to one pod, so per-user session behavior is the same at any pod count.

The first always-on pod is included in the plan’s base fee; additional pods are metered by size and hours run — see Plans & billing. Runtime-image updates are automatic by default: --runtime-auto-update off (on guuey agent config, or on guuey deploy for a code-mode app) pins the app to the image captured at its last deploy.

Live responses stream over Server-Sent Events (SSE) directly from the pod running your agent — the client connects straight to the source, with no relay in between.

Every deployed agent has an always-on API address (<app-id>.agents.us-east-1.guuey.com in production) — shown by guuey apps get and printed as Live at when a deploy or an apply --wait finishes, and pollable at /readyz. The Domains page covers that hostname, the slug host that serves the agent’s page, and custom domains.

Conversation history is persisted automatically by the platform. Users can close the tab, come back later, and pick up the thread. You never build or operate a chat store.

Guuey is framework-adaptive: declare a framework in guuey.json, and the platform loads the matching runner, drives one turn per request, and streams the framework’s native events.

You declare Runtime it drives
claude-agent-sdk @anthropic-ai/claude-agent-sdk
openai-agents-sdk @openai/agents
google-adk @google/adk

For a no-code agent, that declaration plus your system prompt is everything — start from guuey config init or the console. If you’d rather write framework-native code, deploy a code-mode project — a worker bundle or Dockerfile the CLI builds and ships. Scaffold a working code-mode project from a template:

Terminal window
npx @guuey/create-agentic-app my-agent

Agents declare their MCP servers in guuey.json in three flavors:

  • hosted — Guuey builds and runs the server for you: a workspace-owned server deployed with guuey mcp deploy (scaffold one with guuey mcp new), or an existing one reused by id.
  • colocated — the server runs alongside your agent inside its runtime, in a jail of its own spawned at pod boot, at no extra cost.
  • external — any URL you point at.

A hosted server answers your Guuey agents only. The URL guuey mcp deploy prints (Hosted MCP live at https://mcp.…/proxy/<id>/) sits behind Guuey’s MCP gateway, which accepts a call only with a short-lived token Guuey mints for one of your apps, and only for an app granted that server. ChatGPT, Claude.ai and other outside MCP clients can’t get that token, and the gateway offers them no OAuth sign-in (no protected-resource metadata, authorization server or client registration for hosted servers). To serve one MCP server both to your Guuey agent and to outside clients as a connector, host it yourself with its own OAuth, and connect your agent to it as an external server with "credential": "oauth" (below). Guuey’s own Platform MCP is the one server here that outside clients can sign in to.

An external server can carry its own credentials four ways: static headers (secrets referenced as ${env.NAME}), "federate": true (Guuey mints a per-user token the server verifies against Guuey’s issuer), "credential": "caller" (Guuey forwards the signed-in user’s own token from your identity provider; below), or "credential": "oauth" — the server’s own OAuth authorization server signs each end user in, and Guuey brokers the tokens (see Connecting third-party accounts for what the user sees):

"mcpServers": {
"linear": { "kind": "external", "url": "https://mcp.linear.app/mcp", "credential": "oauth" }
}

In an app that signs users in with your own identity provider (userAuthMode: "byo"), "credential": "caller" sends the signed-in user’s own token to your server. On every call, Guuey forwards the bearer your provider issued, the one Guuey verified for this conversation, as the server’s Authorization: Bearer. Your server sees the user’s real token, verifies it against your own provider as usual, and acts as that user. The token is valid for its own lifetime and never longer.

"mcpServers": {
"crm": { "kind": "external", "url": "https://mcp.example.com/mcp", "credential": "caller" }
}
  • Only a token your provider issued is forwarded. Guuey never forwards a Guuey account sign-in, and a guest has nothing to forward. With nothing to forward, that server is left out of the turn: its tools aren’t available to the agent for that turn.
  • The url must be an https:// URL.
  • Your own Authorization header is replaced. On a caller server, an authorization header you declare in headers is dropped; your other static headers are still sent.
  • It can’t be combined with "federate": true.
  • Whoever runs that server can act as the user. The forwarded value is the user’s real token for its whole lifetime, so the server can use it anywhere that token is accepted: against this app through Guuey’s API, against any other app of yours that trusts the same issuer and audience, and against anything else that accepts tokens from your provider. Point caller only at servers you control, and treat the forwarded token as a secret with the user’s lifetime.

With "federate": true, each call to that server carries Authorization: Bearer <token>, a short-lived JWT that Guuey mints for the user in the conversation. Its claims:

Claim Value
iss https://id.guuey.com
aud the server URL exactly as you declared it: mcpResourceUrl if the entry sets one, otherwise url
sub the user’s Guuey id: byo_… for a user signed in with your own identity provider, g_… for a guest
issuerAuthMode anonymous for a guest, native-pool for a user signed in with a Guuey account, byo for a user signed in with your own identity provider
app_id your app’s id
original_iss your app’s issuer URL as configured; only for a user signed in with your own identity provider (see below)
original_sub your provider’s own sub, exactly as its token carried it; only for a user signed in with your own identity provider (see below)
exp 32 minutes after the token was minted

The conversation’s thread id is never sent to an external server. Declare a federated server’s url (or its mcpResourceUrl) with a trailing /, as in https://mcp.example.com/mcp/: Guuey mints a token only for a URL that ends in /, and aud is that string verbatim.

For a user signed in with your own identity provider, sub is not your provider’s user id. It is byo_ followed by the first 30 characters of the lowercase hex SHA-256 of your app’s issuer URL, a newline, and your provider’s sub: "byo_" + sha256(issuerUrl + "\n" + sub).slice(0, 30). The issuer URL is the value configured on the app, exactly as stored, so changing it, even only its trailing slash, changes every user’s derived id. To map a token back to your own user, compute the same hash on your side.

You don’t have to: for a user signed in with your own identity provider, a token minted for a federate: true server your app declares also carries original_iss, your app’s issuer URL as configured, and original_sub, your provider’s own sub exactly as its token carried it. Match your user on the pair (original_iss, original_sub), never on original_sub alone, since two providers can issue the same sub. Neither claim is sent for a guest or a user signed in with a Guuey account, and original_sub is left out when your provider’s sub is longer than 255 characters, the limit OpenID Connect sets on an ASCII sub (Guuey counts UTF-16 code units, so a non-ASCII sub can be left out sooner).

To verify a token, check its RS256 signature against Guuey’s public keys at https://id.guuey.com/.well-known/jwks.json (the issuer plus /.well-known/jwks.json; pick the key by the token’s kid), then check iss, aud and exp. There is no OpenID discovery document at the issuer yet, so point your verifier at the keys URL directly.

A Guuey-hosted official server lives at a different host per environment. Name the host as a platform placeholder — the whole host, nothing else — and every environment resolves its own when the deployment is written; the committed manifest stays environment-agnostic, and guuey pull writes the placeholder back, not the resolved host:

"mcpServers": {
"admin": { "kind": "external", "url": "https://${platform.adminMcpDomain}/mcp", "credential": "oauth" }
}

The facts you can name: adminMcpDomain, platformMcpDomain, mcpDomain, apiDomain. A placeholder anywhere but the host (a port, a label around it, a path, a query) is refused at validation, and ${env.NAME} (your app’s own secrets, used in headers) never carries a platform fact.

A colocated server is declared by source path: the deploy builds the directory and runs it as an HTTP child inside the agent pod — it rides the agent pod for free. devPort is where guuey dev serves it locally:

"mcpServers": {
"notes": { "kind": "colocated", "source": "./mcps/notes", "devPort": 6782 }
}

The platform is MCP-server-agnostic: anything that speaks MCP works. The ggui server at mcp.ggui.ai gives an agent Generative UI, so it can push real interactive interfaces to users instead of text-only replies. Agents created in the console or in Studio start with it off. The Generative UI for this agent switch in the console’s Tools tab turns it on, or, in Studio, the Generative UI switch under Superpowers. A new project from @guuey/create-agentic-app 0.34.0 and later, and a guuey.json pulled or ejected from a console agent, carry "ggui": false in agent.mcpServers, so delete that entry and redeploy. An agent whose definition has no ggui entry keeps it on. Generative UI, the protocol, and its SDKs are documented at docs.ggui.ai.

A view your own MCP server serves under the MCP Apps convention renders live in the chat, during the turn that produced it, when your agent runs on the Claude Agent SDK and the server is an external server reached over HTTP. After a page reload, the card reads “This view expired” instead. Fetching the live view again on reload is a later improvement.

An MCP server can mark a tool as meant for its rendered view alone, under the MCP Apps convention: a tool whose _meta.ui.visibility doesn’t include "model". Such a tool is not offered to the model, and neither is one whose visibility is present but can’t be read. A tool without _meta.ui.visibility is offered as usual, since the default is the model and the app. Who applies the rule depends on the framework:

  • OpenAI and Google ADK: Guuey’s host drops these tools from every server’s listing, before your tool gates. That covers no-code agents and code-mode Google ADK agents, whose agent the platform runs. A code-mode app picks this up on its next deploy (see runtime updates). A code-mode OpenAI agent runs its own worker, so the rule applies when that worker passes modelVisibleToolFilter from @guuey/worker as its MCP servers’ toolFilter. The OpenAI template does this from @guuey/create-agentic-app 0.29.0, and modelMayCallTool is the same rule for your own filtering.
  • Claude: Guuey doesn’t filter these tools here; the Claude Agent SDK withholds them itself. Guuey’s hosted runtime runs SDK 0.3.282 today, and Guuey has measured there that a Claude agent is offered none of its servers’ app-only tools. A code-mode project follows the SDK version it pins, and 0.3.282 is the only version Guuey has measured, so check yours. New scaffolds ask for ^0.3.282.

On some turns Guuey takes a tool out of the model’s catalog for that turn only. Today that tool is ggui’s ggui_consume, on a session opened by a client that sends each card interaction as the next turn (Guuey’s chat widget does). Guuey reads the card’s interactions for the model, so a turn that renders a card ends when the card is drawn instead of waiting on ggui_consume. Your worker gets the list as withheldTools on each invoke: each entry names an MCP server by its key and a tool on that server. @guuey/worker exports the helpers named below from its release after 0.31.1.

  • No-code agents and code-mode Google ADK agents: Guuey’s host applies the list. A code-mode app picks this up on its next deploy.
  • Code-mode OpenAI: your worker builds its own MCP servers, so it applies the list itself. Pass modelVisibleToolFilterFor(name, invoke.withheldTools) from @guuey/worker as each server’s toolFilter, in place of modelVisibleToolFilter. It applies the visibility rule above as well. The OpenAI template uses it from @guuey/create-agentic-app’s release after 0.31.1. In a project scaffolded earlier, update @guuey/worker and change the toolFilter line in src/worker.ts (and its import). Until then the model is still offered ggui_consume, and a call to it holds the turn open.
  • Code-mode Claude: your worker builds its own MCP servers too, so it applies the list itself: add mcp__<server>__<tool> for each entry to the query’s disallowedTools. withheldToolNamesFor(server, invoke.withheldTools) from @guuey/worker gives the names withheld on one server.

On Guuey, your agent’s outbound connections follow rules that don’t apply when you run it on your own machine:

  1. Public addresses only. Your agent can reach hosts on the public internet. It can’t reach private or internal addresses: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10 and 169.254.0.0/16. That rules out a database or API inside a VPC, behind a VPN or on an office network, and any public hostname that resolves to one of those addresses. localhost and 127.0.0.1 never reach your machine.
  2. TCP only. Connections use TCP, on any port. UDP isn’t available, except for the name lookups Guuey makes for your agent.
  3. IPv4 only. A host must have an IPv4 address (an A record). A host reachable only over IPv6 can’t be reached.

A connection these rules don’t allow is dropped, so it usually fails as a timeout rather than a clear error. To use a private database or service, give it a public endpoint protected by authentication (TLS plus a credential or token).

guuey dev --serve runs your agent locally under the same host harness used on Guuey pods, so your local loop matches production, except for the network: guuey dev doesn’t apply the hosted network rules. When it works, ship it:

Terminal window
npm install -g @guuey/cli
guuey login
guuey deploy