Apps
Turn agent work into scheduled, publishable automations that keep running after the conversation ends.
What an app is
An app is recurring agent work: a schedule or trigger, an instruction, and published output.
A conversation with an agent ends; an app keeps going. An app is a small automation that lives inside a thread: a trigger (a cron schedule, an external webhook, an uploaded file, or a manual run), an instruction telling the agent what to do on each run, and optionally a set of static files the agent publishes to a public URL.
The defining property is that the agent builds the app itself. You describe what you want in chat — "every morning, collect the AI news I care about and publish a digest page" — and the agent writes the manifest, registers the app, and publishes the first version. From then on, the platform wakes the agent on schedule and the app updates itself.
trigger fires (cron / webhook / upload / run now)
|
v
thread wakes ── agent receives an app.run turn
| with the app's instruction
v
agent does the work
├─ fetches data, runs code in /workspace
├─ publishes updated assets ──> https://app.outgate.ai/...
└─ reports the run (ok / failed + summary)Every run is a visible turn in the thread, so the full history of what the app did — and why a run failed — reads like any other conversation.
Creating your first app
Describe the automation in chat; the agent does the rest.
You do not fill in forms to create an app. Open a thread and describe the automation:
"Build me an app called news-digest that runs every day at 07:00 UTC,
pulls the top stories from these three RSS feeds, and publishes a
clean HTML digest page. Name the thread accordingly."The agent writes a manifest
A small TOML file in the workspace declares the app: name, instruction, triggers, and which files to publish.
The agent registers it
Registration validates the manifest, arms the schedule, and returns the app’s public URL. Registration is idempotent by app name, so iterating on the manifest updates the same app.
The agent publishes the first version
Assets are uploaded and the page is live at its app.outgate.ai address before the first scheduled run ever fires.
The Apps tab in chat shows each app’s card: its address, schedule, recent runs, and controls to run, pause, resume, or delete it. Clicking a run jumps to the exact place in the transcript where that run happened.
The app manifest
One TOML file declares everything the platform needs to run the app.
version = 1
name = "news-digest"
description = "Morning digest of AI news"
instruction = """
Fetch the three feeds listed in kv, pick the 10 most relevant
stories, rewrite the summaries, and publish the digest page.
"""
[[triggers]]
type = "cron"
cron = "0 7 * * *" # 5-field cron, UTC
[[triggers]]
type = "webhook"
name = "refresh-now" # external systems can fire this
mode = "run" # or "ingest": store data, no agent run
docs = "POST the source id to refresh"
schema = '{"type":"object","required":["source"]}'
[[triggers]]
type = "upload"
name = "inbox" # external systems can send files here
mode = "ingest" # or "run": wake the agent with the file
dir = "incoming" # defaults to uploads/<name>
max_bytes = 10000000 # optional, capped at 50 MB
[assets]
base_dir = "apps/news"
main = "index.html"
files = ["index.html", "style.css", "digest.json"]
[kv]
feed_1 = "https://example.com/ai.rss"
feed_2 = "https://example.org/ml.rss"
[secrets]
keys = ["NEWSAPI_KEY"]
[email]
purpose = "Send me the digest summary each morning"
subject_prefix = "News: "
notify_on_failure = true| Field | Rules |
|---|---|
| name | Lowercase slug, up to 63 characters. Registration is idempotent by name. |
| instruction | Required, 1–4000 characters. The agent receives it verbatim on every run. |
| triggers | 1 to 3 triggers; any mix of cron, webhook, and upload. Cron is 5-field UTC with a 15-minute minimum interval. |
| triggers[].mode | Webhook and upload: "run" (each fire or file starts an agent run) or "ingest" (store it in the workspace without a run). Webhooks default to run; uploads default to run as well. |
| triggers[].schema | Webhook only. Optional JSON Schema (as a string, up to 8 KB) the endpoint enforces: non-matching payloads are rejected with 400 before reaching the app. |
| triggers[].docs / example | Caller-facing description and sample payload, shown next to the trigger on the app card. Uploads take docs but not example (the payload is a file). |
| triggers[].dir | Where data lands in the workspace. Ingest webhooks default to ingest/<name>; uploads default to uploads/<name> and use it in both modes. |
| triggers[].max_bytes | Upload only. Optional per-file size limit, clamped to the 50 MB platform maximum. |
| assets | base_dir (inside the workspace), main entry file, and 1–50 files to publish. |
| kv | Optional plain-text configuration: up to 20 keys, 1 KiB per value. Not for secrets. |
| secrets | Optional list of UPPER_SNAKE secret names the app uses (up to 10). Values are provided separately — never in the manifest. |
| Optional. Declares that the app may email you, with a stated purpose (required) and optional failure notifications. |
The manifest is reviewable
Because the app is a file in the workspace, you can always ask the agent to show it, explain it, or change it — the manifest is the single source of truth for what the app is allowed to do.
Triggers: cron, webhook, upload, manual
Four ways a run starts — and one run at a time, always.
Cron triggers fire on a UTC schedule with a minimum interval of 15 minutes. The platform wakes the thread if it is asleep, so schedules keep working regardless of whether anyone has the chat open.
Manual runs come from the Run button on the app card in chat, or POST /api/v1/agents/threads/{threadId}/apps/{appId}/run through the API.
Webhook triggers let external systems start a run. Declaring a webhook trigger in the manifest lets the agent mint a fire-only endpoint with its own token:
curl -X POST https://api.outgate.ai/api/v1/hooks/apps/{appId} \
-H "X-Outgate-Hook-Token: oghk_..." \
-H "Content-Type: application/json" \
-d '{ "source": "github", "ref": "refs/heads/main" }'- The oghk_ token is shown once at mint time and can be revoked and re-minted at any point.
- The JSON payload (up to 32 KB) is handed to the agent clearly framed as untrusted external input — the agent treats it as data, not as instructions.
- Fires are accepted with 202 and a runId; the run itself is asynchronous.
- Rate limits: 5 fires per minute per hook, plus an hourly cap by plan (20 / 100 / 500 for free / plus / pro).
- If the trigger declares a schema, the endpoint validates every payload against it and rejects mismatches with 400 schema_mismatch — the agent can trust the shape of what arrives.
One run at a time
Whatever the trigger, an app never runs concurrently with itself. A trigger that arrives mid-run is rejected with run_in_flight (409) — retry after the current run finishes.
A webhook can also run in ingest mode (mode = "ingest" in the manifest). An ingest fire does not start the agent at all: the platform accepts the payload immediately with 202 and an ingestId, wakes the sandbox in the background if it is asleep, and appends the event as one JSON line to an hourly data file in the workspace:
/workspace/<dir>/YYYY/MM/DD/HH.jsonl (UTC, dir defaults to ingest/<name>)
{"ts":"2026-07-11T14:03:00Z","hook":"github-push","payload":{...}}- The agent reads the accumulated files on its next run — ingest turns a webhook into a durable event feed the app processes in batches.
- Delivery is at-least-once: a rare duplicate line is possible, so treat entries as idempotent by content or timestamp.
- Ingest hooks get much higher limits: 60 fires per minute, with hourly caps of 200 / 1,000 / 5,000 by plan. A saturated buffer returns 429 ingest_backlog.
- Ingest requires a persistent workspace — registration rejects ingest triggers on ephemeral threads.
- Re-registering the manifest switches a hook between run and ingest (and updates schema/docs); existing tokens follow the current manifest.
Upload triggers are the same idea for files rather than JSON. Where a webhook carries its payload in the request body (32 KB of JSON), an upload trigger accepts a real file — up to 50 MB — through a two-step handshake, and the same mode switch decides whether the agent wakes or the file just lands.
Mint the endpoint
The agent declares an upload trigger and mints its endpoint, exactly like a webhook. The token is oup_ and is shown once.
Ask for a presigned upload
The publisher POSTs to that endpoint and receives a short-lived presigned upload — a url plus a set of form fields — along with the uploadId and the size limit.
Send the file
The publisher POSTs the file to that url with the returned fields. There is no completion callback: the file arriving in storage is the signal.
The app reacts
The platform notices the arrival, and the trigger mode decides what happens next.
# step 1 — ask for a presigned upload
curl -X POST "https://api.outgate.ai/api/v1/uploads/apps/{appId}?api_key=oup_..." \
-H "Content-Type: application/json" \
-d '{ "filename": "report.pdf" }'
# → { "uploadId": "upl_...", "maxBytes": 52428800, "url": "...", "fields": { ... } }
# step 2 — send the file (submit every returned field, the file last)
curl -F key=<fields.key> -F policy=<fields.Policy> ... \
-F file=@report.pdf "<url>"- mode = "run" (the default) wakes the agent once the file is in place, with the local path in view and the file's contents framed as untrusted input.
- mode = "ingest" places the file silently and starts no run — the agent picks it up on its next run, the same batching pattern as ingest webhooks.
- Either way the file lands at /workspace/<dir>/YYYY/MM/DD/<uploadId>_<filename>, where dir defaults to uploads/<name>.
- The 50 MB ceiling is enforced by the storage layer itself: an oversized upload is refused outright rather than accepted and discarded. A trigger can set a lower maxBytes.
- Each upload wakes the app exactly once, even though arrival notifications can be delivered more than once.
- The presigned upload is single-use, expires within the hour, and is scoped to one file — a leaked link cannot be replayed or aimed elsewhere.
- Like ingest, uploads need a persistent workspace and are rejected on ephemeral threads.
Secrets
Declared in the manifest, stored encrypted, released to the agent only during a run.
Apps that call external services need credentials, and credentials do not belong in manifests, chat messages, or kv. The secrets flow keeps them out of all three.
Declare
The manifest lists the secret names the app uses — names only, like NEWSAPI_KEY.
Provide
Set values from the Secrets section on the app card in chat, or PUT them through the API. Values are write-only: once set, no interface ever displays them again.
Use
During a run, the agent fetches a declared secret on demand. The fetch only works while that run is actually executing — outside a run the platform refuses.
Secrets are encrypted at rest, never appear in the event stream or run history, and are deleted with the app. Listing shows only names and whether a value has been set.
Email from apps
Apps can email you — only you — with declared purpose and daily caps.
An app with an [email] block in its manifest may send email as part of its runs — a morning digest, a weekly report, an alert. The recipient is always the thread owner’s account address; apps cannot email arbitrary addresses.
- The manifest must state the purpose of the emails; it is shown on the app card so it is always clear why an app mails you.
- Daily caps per app by plan: 10 (free), 50 (plus), 200 (pro).
- With notify_on_failure enabled, you get one email per failed run — and a distinct notice if the app is disabled after repeated failures.
Publishing and hosting
Published assets are served from an isolated CDN origin with a stable address.
When the agent publishes, the files listed in the manifest are uploaded from the workspace and served at a stable address on app.outgate.ai. The URL identifies your organization, user, thread, and app, and the main file is the default document for the app directory.
- Up to 50 files per app, 10 MiB per file, 50 MiB total.
- Published pages are served from a separate origin with a strict sandbox policy, so app content is fully isolated from Outgate itself.
- Assets are served with open CORS, so other pages and tools can fetch app output (for example a published JSON feed) directly.
- Updates propagate within about a minute; publishing the same app again simply replaces its files.
Publishing is optional. An app that only sends email or reacts to webhooks does not need assets at all.
Runs, failures, and history
Every run is accountable: visible in the transcript, reported, and bounded in time.
A run starts with an app.run turn in the thread carrying the trigger type, a run ID, and the instruction. The agent works like it does on any other turn, then reports the outcome — success or failure, a short summary, and optionally the published URL.
- Runs have a hard 15-minute budget; a run that exceeds it is recorded as a timeout failure and the schedule moves on.
- Three consecutive failures put the app into an error state and pause the schedule, so a broken app cannot burn runs indefinitely. Fix the cause in chat and resume it.
- The app card lists recent runs; clicking one scrolls the transcript to that run, so debugging a bad run is reading what the agent actually did.
- Run outcomes are also emitted as app.run and app.run_result events, which webhook subscriptions can receive.
Managing apps
Pause, resume, run, and delete — from the app card or the API.
The app card in chat is the day-to-day surface: open the published page, copy its address, trigger a run, pause or resume the schedule, manage secrets and webhooks, and delete the app. The same operations exist on the API for automation.
Apps live inside their thread. Deleting the thread removes its apps; a healthy, active app keeps its thread alive by extending the thread’s lifetime on registration, updates, and successful runs — an app that runs well does not silently expire out from under you.
| Action | Chat | API |
|---|---|---|
| List apps | Apps tab | GET .../threads/{id}/apps |
| Inspect + run history | App card | GET .../apps/{appId} |
| Run now | Run button | POST .../apps/{appId}/run |
| Pause / resume | Card menu | PATCH .../apps/{appId} { "status": "paused" | "active" } |
| Manage secrets | Secrets section | PUT / DELETE .../apps/{appId}/secrets/{name} |
| Unpublish | Unpublish button | POST .../apps/{appId}/unpublish |
| Delete | Card menu | DELETE .../apps/{appId} |
Unpublish takes the published page down without touching the app: the assets are deleted from app.outgate.ai (cached copies can linger for up to five minutes while the CDN cache expires), the card flips to unpublished, and the next publish brings the page back. Deleting an app — or its thread — removes the published assets the same way.
What the agent can do
The app tools available to agents — useful when writing prompts.
Agents manage apps through built-in tools. You never call these yourself, but knowing they exist makes prompting precise — "register it, publish it, and mint a webhook called deploy-done" maps one-to-one onto what the agent can actually do.
| Tool | Purpose |
|---|---|
| og_app_register | Validate the manifest and create or update the app. |
| og_app_list | List the thread’s apps with status and next run time. |
| og_app_publish | Upload the manifest’s asset files to the app’s address. |
| og_app_report | Report a run’s outcome (required at the end of every run). |
| og_app_unregister | Remove the app and cancel its schedule. |
| og_app_secret_set / list / delete | Manage secret values (write-only; reading is run-gated). |
| og_app_email | Send an email to the thread owner, within the daily cap. |
| og_app_webhook_create / list / revoke | Mint and manage fire-only webhook endpoints. |
| og_app_upload_create / list / revoke | Mint and manage file-upload endpoints (presigned, up to 50 MB). |
Limits
The guardrails that keep apps predictable.
| Limit | Value |
|---|---|
| Apps per thread | 10 |
| Triggers per app | 3 (cron and webhook combined) |
| Cron minimum interval | 15 minutes (UTC schedule) |
| Run timeout | 15 minutes |
| Consecutive failures before auto-pause | 3 |
| Published assets | 50 files, 10 MiB per file, 50 MiB total |
| kv entries | 20 keys, 1 KiB per value |
| Declared secrets | 10 per app |
| Webhook payload | 32 KB JSON |
| Webhook fires (run mode) | 5 per minute per hook; 20 / 100 / 500 per hour by plan |
| Webhook fires (ingest mode) | 60 per minute per hook; 200 / 1,000 / 5,000 per hour by plan |
| Payload schema / docs per trigger | 8 KB JSON Schema; 2,000-character docs |
| Emails per app per day | 10 (free), 50 (plus), 200 (pro) |