Manifest Reference
sypho.manifest.json is the contract between your agent code and the platform. It
declares what the agent is called, which tools it may use, and what data it can touch —
sypho enforces all of it at run time, regardless of what your code tries to do.
Minimal example
{
"schema_version": "1.0",
"agent_name": "support-triage",
"description": "Triages incoming support tickets and drafts responses.",
"version": "0.1.0",
"entrypoints": {
"main": {
"tools": [
"mcp__browser__*",
"structured_data_*"
],
"deny_tools": [
"mcp__browser__browser_navigate_internal"
]
}
},
"structured_data_dependencies": {
"tickets": {
"required": true,
"default_permission": "read_write",
"keys": ["ticket:*"]
}
},
"secrets": {
"slack_webhook": {
"required": false,
"default_permission": "read"
}
},
"execution": {
"command": ["python", "agent.py"]
}
}
Top-level fields
| Field | Required | Description |
|---|---|---|
schema_version | yes | Currently always "1.0" |
agent_name | yes | Unique agent identifier |
description | yes | What the agent does — shown to anything that discovers it as a sub-agent |
version | yes | This manifest's version |
agent_config | no | Model, system prompt, iteration/token limits (only meaningful if your framework reads SYPHO_MODEL/SYPHO_SYSTEM_PROMPT/etc., or calls through the gateway's model endpoint) |
structured_data_dependencies | no | Namespaces this agent depends on — see below |
secrets | no | Secret aliases this agent depends on — see below |
entrypoints | yes | One or more named entrypoints — most agents declare just "main" |
execution | no | Declares the image's real ENTRYPOINT/CMD when it isn't the legacy SDK runtime |
gateway_version | no | Pin a specific gateway sidecar version instead of latest |
resumable | no | Declares the agent is deterministic given the gateway's responses, so a parked run can be replayed to resume it |
Entrypoints
Each entry under entrypoints describes one way to invoke the agent:
{
"tools": ["mcp__github__*", "structured_data_*"],
"deny_tools": ["mcp__github__delete_repo"],
"local_tools": [],
"allowed_sub_agents": ["ticket-summarizer"],
"skills": ["summarize-thread", "draft-reply@2"],
"data_bindings": {
"load": [
{ "source": "structured_data", "namespace": "tickets", "key_template": "ticket:{{input.ticket_id}}", "inject_as": "ticket", "required": true }
]
}
}
tools— authorization patterns, not schemas. Each entry is an exact tool name, a glob (mcp__github__*), or a/regex/. The real tool schema always comes from the live MCP upstream or the platform's own tool definitions — you're only declaring what this agent is allowed to call, not describing its shape.deny_tools— same pattern syntax; a deny match always beats an allow match.local_tools— bare names of platform-provided tools that need a sandboxed approval round-trip.allowed_sub_agents/deny_sub_agents— which other agents this one may delegate to viaspawn_sub_run, by exact name, glob, or regex.skills— skills from your application's skill library this entrypoint may use, as"name"(latest published version at run creation) or"name@version"to pin one. Served at runtime by the gateway'sread_skill/read_skill_file/run_skill_scripttools, whichtoolsmust also declare — see Skills for publishing and runtime details.data_bindings— optional automatic load/save of structured data keyed off the run's input, so your agent code doesn't have to call the platform tool itself for simple cases.
Structured data dependencies
{
"tickets": {
"required": true,
"default_permission": "read_write",
"keys": ["ticket:*"]
},
"playbooks": {
"required": false,
"default_permission": "read"
}
}
This is what actually grants data access — a run's effective scope is computed from
here, and can only be narrowed, never widened, by anything passed at run creation.
keys defaults to ["*"] (every key in the namespace) when omitted.
Secrets
{
"slack_webhook": {
"description": "Slack incoming webhook URL for this ticket queue",
"required": false,
"default_permission": "read"
},
"*_api_key": {
"default_permission": "read"
}
}
Keyed by secret alias (glob * allowed, e.g. "*_api_key"), never a raw secret
key — the run creator maps each alias to an actual secret when the run is created, so
the same manifest works against different secrets per environment or tenant.
Declaring an alias here doesn't gate reads — any alias the run creator provided stays
readable by your agent. It gates writes: the secret_save
platform tool can only update an alias that matches a read_write entry here, and only
when that alias was actually declared for the run.
Custom MCP servers
An entrypoint can also declare its own MCP server, sandboxed and run alongside the
agent, via custom_mcps — see MCP Servers for the full field reference and how
that compares to a managed/remote MCP server.
Execution contract
{
"execution": {
"command": ["python", "agent.py"]
}
}
The runner always just runs docker run <image> — this field doesn't change execution,
it lets you author (and validate) a manifest for an image whose real entrypoint isn't
the legacy SDK runtime. For a framework-agnostic agent, this is simply whatever command
your Dockerfile's own ENTRYPOINT/CMD runs.
Tool patterns, precisely
A tools or deny_tools entry can be:
| Form | Example | Matches |
|---|---|---|
| Exact name | structured_data_save | Only that tool |
| Glob | mcp__browser__* | Every tool from the browser MCP server |
| Regex | /^mcp__github__(get|list)_.*/ | Anything matching the pattern |
Patterns are evaluated at the gateway, where the real tool list exists — your manifest never has to enumerate every tool a server exposes.