Manifest explained
manifest.yaml is the declarative contract between your app and SotaAgents. It describes what the app is, what it contributes, where its backend lives, which native modules the platform may load, and which platform versions it supports. The server validates it again; the client manifest is never treated as authority. Unknown properties are rejected outright — run sota manifest schema to read the exact JSON Schema and sota manifest explain contributes.tools to explain one field.
The five root fields manifestSchemaVersion, appId, version, publisher, and contributes are required. Everything else is opt-in.
manifestSchemaVersion: 3
appId: my-first-project-demo
version: 1.0.0
displayName: "My First Project Demo"
description: "SotaAgent app."
publisher:
id: local-dev
displayName: "Local Dev"
contact: dev@local-dev.example
contributes:
skills:
- name: example
description: Example content-backed skill.
appendsTo: system
content: src/skills/example
timeoutMs: 3000
failure_mode: skip
tools:
- name: example
description: Example tool that echoes its input and verified tenant context.
route: POST /tools/example
inputSchema: src/schemas/tool-input.schema.json
outputSchema: src/schemas/tool-output.schema.json
timeoutMs: 15000
failure_mode: abort
ui:
- id: admin-screen
kind: nativeModule
surface: page
slot: admin.workspace.tab
sectionId: my-first-project-demo
label: Admin screen
route: /admin/*
module:
entry: dist/ui/app.js
export: AdminScreen
styles: dist/ui/app.css
service:
baseUrl: https://my-first-project-demo.example.com
health:
url: https://my-first-project-demo.example.com/health
intervalSeconds: 60
platform: ^1.2.0
locales:
default: en
files:
en: src/locales/en.json- Identity fields are stable. Treat
appIdas a permanent public identifier. - Version is immutable per artifact. Change bytes after a deploy only with a new version.
- Contributions are the complete surface the platform may expose; undeclared routes or modules are unavailable.
- Environment overlays are optional overrides named exactly
local,stg, andprod. The root service is the hosted default;localbelongs tosota dev. Global--originselects a SotaAgents deployment, while command-specific-eselects a saved environment profile or manifest overlay.
Each native UI surface and public runtime API is documented on its own page after this manifest reference.
service:
baseUrl: https://my-app.example.com
health:
url: https://my-app.example.com/health
intervalSeconds: 60
environments:
local:
service:
baseUrl: http://127.0.0.1:8787
health:
url: http://127.0.0.1:8787/health
stg:
service:
baseUrl: https://stg.my-app.example.com
prod:
service:
baseUrl: https://my-app.example.comenvironments.local.The base manifest describes the deployable app. A 127.0.0.1 URL in the root service block is rejected, and a deployable base URL still ending in .invalid means the real hosted backend was never configured.
Declaring a tool#
A tool is how the assistant reaches your backend. You declare it once under contributes.tools; the platform derives everything else — what the model is offered, how the call is routed, how it is authenticated, and how long it may take — from that declaration. The schema is closed, so an undeclared key fails validation.
| Field | Required | Rules |
|---|---|---|
name | Yes | The stable public identifier. Lowercase first letter, then letters, digits, and hyphens, optionally dot-segmented (for example documents.query); each segment is at most 64 characters. Keep it stable after release — prompts, renderers, and stored conversations refer to it. |
description | Yes | Non-empty. This is the text the model reads when deciding whether to call the tool, so state when to use it, what the backend does, what comes back, and any precondition. Do not merely restate the name. |
route | Yes | METHOD /path, where method is one of GET, POST, PUT, PATCH, DELETE and the path starts with /. The path is resolved against the service.baseUrl of the active environment, and may not escape that origin. Declare POST: the assistant's invocation path sends the arguments as a JSON body, and the scaffold and every system app use POST /tools/<name>. |
inputSchema | Yes | A path to a JSON Schema file inside the app, or an inline schema object. Prefer explicit required, additionalProperties: false, bounded strings and arrays, and enums for known modes. |
outputSchema | Yes | Same form as the input schema. Required even when the result is trivial — it documents the contract your backend must keep. |
timeoutMs | No | Positive integer, clamped to at most 30,000 ms when the artifact is built. Declare one. A tool that declares none gets no wall-clock budget on a hosted backend, and the Development tunnel bounds it at ten minutes. |
failure_mode | No | abort or skip; tools default to abort. Choose abort when a failure must be reported as a failure; choose skip only when the contribution is genuinely optional and the assistant can still answer honestly without it. Never hide a failed write or an authoritative query behind a graceful-looking response. |
searchHint | No | Extra keywords that help the tool be found. Non-empty. |
undoable | No | Boolean. Marks the action as reversible. |
configGate | No | An app-config key. The tool is only offered when that configuration key is present. |
The schema files the declaration points at are ordinary JSON Schema. The compiler resolves and inlines them into the artifact, so a referenced file must exist before build or deploy even if the route is never exercised locally.
// src/schemas/tool-input.schema.json
{
"type": "object",
"additionalProperties": false,
"properties": {
"message": {
"type": "string",
"minLength": 1,
"maxLength": 4000,
"description": "Message for the example tool to echo."
}
},
"required": ["message"]
}Declaring any tool makes service mandatory — the platform has to know where to send the call. Tool names must be unique within one manifest, and a UI contribution with surface: tool-view must list, in toolNames, names that actually exist in contributes.tools; the validator rejects a renderer bound to a tool you never declared. Two installed apps may not claim the same tool name for a renderer — the later app loses the contested names.
For what the platform then does with this declaration — the model-visible name, the signed request, the response contract — see Architecture philosophy.
