Skip to content

Poppy Company gateway

Status: Current | Last reviewed: 2026-10-20

Use this gateway to expose a reviewed, published Chat Agent through Poppy 0.2. Read this page when publishing an endpoint, testing a new agent version or operating the gateway.

The gateway is a separate FastAPI process on port 3036, implemented in apps/conversation-api/poppy. It reuses conversation-api's gRPC client and the published TrueServe runtime. Its protocol ledger lives in trueserve_poppy, outside the core schema.

Initial Company profile

This release provides guest sessions and native text/data conversations. It does not advertise Direct/Device Sign-In, customer scopes, Operations, MCP, OpenAPI, browser continuity or a staffed human queue. A Studio user identity never becomes a Company customer identity.

Only an explicitly published text_to_text / chat_agents version is eligible. The publisher must review it for unauthenticated guest use: prompts, knowledge, tools and returned data must suit that access. Use a tool-free sandbox first. Authentication-sensitive and transactional agents need the account and business-authorization work in the roadmap.

Each exposure gets an independently routed hostname and issuer. Requests cannot choose another internal agent ID. The initial client admission list contains only the configured Ari origin. Client metadata fetching rejects local, link-local and other non-public addresses, including DNS rebinding.

Provision and publish

Load the city environment and initialize the two separate databases:

bash .devcontainer/scripts/with-env.sh node .devcontainer/scripts/init-poppy-company.mjs --configure-studio

The provisioner writes apps/conversation-api/.env.poppy with mode 0600 and applies poppy/schema.sql to its application and test databases. The --configure-studio option also writes the registry connection and domain suffix to the root and ai-manager private environment files. It preserves unrelated settings.

Start the gateway with .devcontainer/Procfile.poppy alongside an existing city, or use the main Procfile when starting the whole city. Render/install the city nginx template for poppy-*.<PUBLIC_FQDN> and verify public TLS.

Publication uses the existing management API with agents:publish. Send:

POST /api/v1/management/poppy/exposures
Authorization: Bearer <scoped Studio credential>
Content-Type: application/json

{
  "agentId": "4992",
  "expectedVersion": 2,
  "name": "Ari Test Company",
  "guestReviewed": true
}

Use your agent's actual published version. The API rejects a stale version, another tenant's agent and an unsupported agent type. Its response gives the stable exposure ID and discovery URL. This is an API/operator publication flow; a Studio publication settings screen is not yet implemented.

The operator CLI is an alternative. From apps/conversation-api, with the city and .env.poppy settings loaded, run:

.venv/bin/python -m poppy.provision \
  --tenant-id <tenant-uuid> --agent-id <agent-id> --version <published-version> \
  --name "Test Company" --guest-reviewed

Keep the connection strings and signing secret in private files. Do not place credentials in command arguments.

Updates and rollback

An exposure pins the exact reviewed agent version. Publishing a new Studio version alone does not update the endpoint. Review the new version and repeat the publication API with its expectedVersion; the hostname stays the same. New conversations use that version, while existing conversations and their direct children keep their original runtime version.

To compare updates, export a protocol run, publish the reviewed update, choose Rediscover in Ari and run again. The nonstandard x-trueserve.agent_version discovery annotation records the version in the snapshot. Use the CLI's explicit --version to return an exposure to an older published version after reviewing it. Core agent versions remain unchanged.

Disable an endpoint with:

DELETE /api/v1/management/poppy/exposures/<exposure-uuid>
Authorization: Bearer <scoped Studio credential>

Disabling blocks new requests and workers that have not yet sent their message. It preserves the ledger. Work already accepted by a runtime may finish; disabling an endpoint is not a business-operation cancellation mechanism.

Wire and runtime behavior

Path Behavior
/.well-known/poppy.json Discovery for the enabled, reviewed hostname
/.well-known/oauth-authorization-server/oauth Issuer metadata and admitted grant type
/oauth/token private_key_jwt, JWT bearer session assertion, ES256 DPoP, nonce and replay checks
/oauth/revoke Authenticated no-op for unknown account grants; this profile issues none
/poppy/conversations Accept a message and durably queue its runtime delivery
/poppy/conversations/<id>/messages Append an owner message; original ID retries return original acceptance
/poppy/conversations/<id>/events Cursor pagination and bounded JSON long polling
/poppy/conversations/<id>/handoff Honest unavailable response; responder remains the Company agent
/poppy/conversations/<id>/close Idempotent close and detached runtime cleanup

Guest session identity is (exposure, client_id, opaque user subject), with a fixed DPoP key per session. A fresh session for the same principal can resume its conversation; another user or Company cannot. Tokens live for five minutes and sessions for seven days in this profile.

Message acceptance, immutable user event, retry fingerprint and pending job commit together. IDs are deduplicated across the principal's conversations. Open direct children accept only sender=human; their parents reject sends. Closing a parent also closes its direct child.

The worker uses a stable UUID runtime conversation and a pinned agent:vN, with transcript resumption after a transport reconnect. It stores a Redis cursor, reads final Company transcripts, and persists protocol events. Started jobs interrupted by a restart are not resent automatically. The ledger records uncertainty so a transport failure cannot silently duplicate an action.

Configuration and verification

Variable Purpose
POPPY_DATABASE_URL Separate protocol registry/ledger; also used by Studio's publication API
POPPY_NONCE_KEY Random server-only nonce secret, at least 32 characters
POPPY_DOMAIN_SUFFIX Operator-configured city domain, used only to generate hostnames
POPPY_ALLOWED_CLIENT_ORIGINS Exact public Personal Agent origins admitted by the gateway
POPPY_TEST_DATABASE_URL Test connection; test runner forces trueserve_poppy_test
Existing runtime env Core database, Redis and orchestrator connection settings

The gateway takes an exclusive database lease and runs four independent conversation jobs at a time. An owner can have ten open conversations per exposure. Protocol bodies are capped at 64 KiB; long polling is capped at 30 seconds. This is a single-process pilot, not a horizontal worker deployment.

From apps/conversation-api:

.venv/bin/python test_poppy.py

The tests exercise actual FastAPI routes, signatures and the separate PostgreSQL ledger. They use a controlled runtime fixture. The hosted Ari check additionally exercises the real model and gRPC runtime.

See also