This document is non-normative. It provides an accessible overview of AFPS concepts. The normative specification is spec.md.
Agent Flow Packaging Standard (AFPS) is an open specification for declaring portable AI workflow packages. Think of it as a packaging standard: it defines how to describe, version, and distribute the building blocks of AI workflows.
AFPS answers a specific question: what is this AI workflow package, and what does it need? It does not define how agents call tools, how agents talk to each other, or how a runtime executes prompts. Those concerns belong to other specifications.
The key distinction is between goals and capabilities:
Agent Skills (Anthropic / AAIF) and MCP Tools define capabilities. AFPS defines the goal layer that composes those capabilities into a complete, portable workflow:
┌───────────────────────────────┐
Goal │ AFPS Flow │ The user's intent, packaged.
│ prompt.md + manifest.json │ "What should the agent accomplish?"
├───────────────────────────────┤
Capability │ AFPS Skills / Tools │ Reusable abilities the agent
│ MCP Tools │ can draw on to reach the goal.
├───────────────────────────────┤
Connection │ AFPS Providers │ Authenticated access to
│ (OAuth2, API key, ...) │ external services.
├───────────────────────────────┤
Transport │ MCP (tool invocation) │ Runtime protocols — out of
│ A2A (agent-to-agent) │ AFPS's scope.
└───────────────────────────────┘
A flow’s prompt.md replaces what a human would type to give an agent its objective. The flow manifest declares which skills, tools, and providers the agent needs to fulfill that objective. AFPS packages everything together into a versioned, distributable .afps artifact (a standard ZIP file).
MCP standardizes how an agent invokes tools at runtime. A2A standardizes how agents discover and communicate with each other. Agent Skills standardize reusable capability descriptions. AFPS standardizes the goal and its dependencies — the package that gets published, installed, and composed before any of that happens. The four are complementary.
AFPS is transport-agnostic: it does not prescribe how packages are fetched, transferred, or cached.
AFPS defines exactly four package types. Each one serves a distinct role.
A flow is a complete AI workflow — the primary unit of execution. It represents the user’s intent: a manifest describing what the workflow needs, paired with a prompt.md file containing the objective sent to a language model.
Think of it like a docker-compose.yml for AI agents — it declares the goal, the dependencies, the inputs, the outputs, the configuration, and execution hints, all in one portable artifact. Where a skill says “I know how to rewrite text professionally”, a flow says “process these emails and create a summary” — and lists the skills, tools, and providers needed to do it.
Minimal example (manifest.json):
{
"name": "@acme/customer-intake",
"version": "1.0.0",
"type": "flow",
"schemaVersion": "1.0",
"displayName": "Customer Intake",
"author": "Acme Corp",
"requires": {
"providers": { "@acme/gmail": "1.0.0" }
}
}
The companion prompt.md contains the actual instructions:
# Customer Intake
You are a support assistant. Read the latest emails and produce a summary.
See spec.md, Section 3.2 for the full field reference.
A skill is a declarative capability — a reusable instruction set that a flow can reference. AFPS skill packages are a superset of the Agent Skills format: a valid Agent Skill directory becomes a valid AFPS skill package when you add a manifest.json. The SKILL.md format, all frontmatter fields, and optional directories (scripts/, references/, assets/) are preserved unchanged. AFPS adds identity, versioning, and dependency resolution on top.
rewrite-tone/
├── manifest.json # AFPS addition: identity + versioning
├── SKILL.md # Agent Skills format (unchanged)
├── scripts/ # Optional: executable code
├── references/ # Optional: additional documentation
└── assets/ # Optional: templates, resources
manifest.json — AFPS identity layer:
{
"name": "@acme/rewrite-tone",
"version": "1.0.0",
"type": "skill"
}
SKILL.md — Agent Skills format, unchanged:
---
name: rewrite-tone
description: Rewrite a draft into a concise and professional tone.
---
# Rewrite Tone
## When to Use
- Improve the tone of a customer-facing draft
- Shorten verbose text without changing meaning
The SKILL.md frontmatter supports the fields defined by Agent Skills: name, description, license, compatibility, metadata, and allowed-tools. AFPS does not modify or extend this vocabulary.
See spec.md, Section 3.3 for details.
A tool is a callable capability — executable code that an agent can invoke during flow execution. It consists of a manifest declaring the tool interface and an implementation source file. Where a skill provides instructions (declarative), a tool provides code (executable).
@acme/fetch-json.afps
├── manifest.json # Tool interface + metadata
└── tool.ts # Implementation source file
manifest.json:
{
"name": "@acme/fetch-json",
"version": "1.0.0",
"type": "tool",
"displayName": "Fetch JSON",
"entrypoint": "tool.ts",
"tool": {
"name": "fetch_json",
"description": "Fetch JSON from a URL and return the parsed response.",
"inputSchema": {
"type": "object",
"properties": {
"url": { "type": "string", "description": "HTTP or HTTPS URL" }
},
"required": ["url"]
}
}
}
The tool object describes the interface — what the tool is called, what it does, and what parameters it accepts. This metadata is available to consumers without executing the source code.
The entrypoint field points to the implementation source file. AFPS does not prescribe the programming language, module format, or execution strategy — those are implementation concerns.
Because tools contain executable code, they are the highest-risk package type. See spec.md, Section 3.4 and Section 8.2 for security considerations.
A provider is a service connector — it describes how to authenticate with an external service. It is manifest-only; there is no companion file. Providers declare an authentication mode (oauth2, oauth1, api_key, basic, or custom) and the metadata needed to establish a connection.
Example — an API key provider (manifest.json):
{
"name": "@acme/openai",
"version": "1.0.0",
"type": "provider",
"displayName": "OpenAI",
"definition": {
"authMode": "api_key",
"credentialSchema": {
"type": "object",
"properties": {
"apiKey": { "type": "string", "description": "API key" }
},
"required": ["apiKey"]
},
"credentialHeaderName": "Authorization",
"credentialHeaderPrefix": "Bearer",
"authorizedUris": ["https://api.openai.com/*"]
}
}
See spec.md, Section 3.5 and Section 7 for all auth modes.
Every AFPS package has a scoped name of the form @scope/name. Both segments are lowercase, alphanumeric, and may contain hyphens — but must start and end with a letter or digit. No underscores, no uppercase.
@acme/customer-intake valid
@my-org/gmail valid
@Acme/Flow invalid (uppercase)
@acme/my_flow invalid (underscore)
acme/flow invalid (missing @)
Scopes let registries enforce ownership: only authorized publishers can release packages within a scope. See spec.md, Section 2.2.
AFPS uses semantic versioning for package identity. Every package declares an exact version (e.g. 1.2.0), and dependencies can use semver ranges (e.g. ^1.0.0, ~2.1).
See spec.md, Section 2.3.
AFPS packages are distributed as ZIP files. Every archive must contain manifest.json at the root. Depending on the package type, additional files are required:
| Type | Required companion files |
|---|---|
| flow | prompt.md (non-empty) |
| skill | SKILL.md |
| tool | Source file referenced by entrypoint |
| provider | None beyond manifest.json |
Package archives should use the .afps file extension (e.g., customer-intake-1.0.0.afps). The file is a standard ZIP — any ZIP tool can open it — but the .afps extension makes packages immediately recognizable and enables OS-level file association with AFPS-aware tooling.
Consumers must sanitize ZIP entries to prevent path traversal attacks. See spec.md, Section 2.5.
A flow composes skills, tools, and providers as dependencies. The following diagram shows a typical flow and the packages it depends on:
┌──────────────────────────────────┐
│ @acme/customer-intake │
│ type: flow │
│ │
│ prompt.md = the objective │
│ input/output = structured data │
│ config = user settings │
└──────┬───────────────────────────┘
│ requires (runtime)
│ registryDependencies (install-time)
┌──────────────┼──────────────┐
▼ ▼ ▼
┌──────────────┐ ┌────────────┐ ┌────────────┐
│ @acme/gmail │ │ @acme/ │ │ @acme/ │
│ provider │ │ rewrite- │ │ fetch-json │
│ │ │ tone │ │ │
│ OAuth2 creds │ │ skill │ │ tool │
│ scopes │ │ SKILL.md │ │ source │
└──────────────┘ └────────────┘ └────────────┘
connection capability capability
"access Gmail" "rewrite text "fetch JSON
professionally" from a URL"
The flow says what to accomplish. The dependencies provide how — reusable capabilities and service connections the agent draws on at runtime.
AFPS distinguishes two kinds of dependency declarations, because they serve different purposes at different times:
requires — runtime composition (flows only). This tells a flow runner which packages must be loaded when the flow executes. Values are typically exact versions or *.
registryDependencies — install-time resolution (all package types). This tells a registry which packages to resolve and auto-install when a package is published or imported. Values are semver ranges.
A typical flow lists the same packages in both fields:
{
"requires": {
"providers": { "@acme/gmail": "1.0.0" }
},
"registryDependencies": {
"providers": { "@acme/gmail": "^1.0.0" }
}
}
The requires field says “this flow needs Gmail at runtime.” The registryDependencies field says “when installing this flow, resolve Gmail from the registry using this version range.”
They are not redundant. A skill or tool has no requires but can still declare registryDependencies. A flow might require a package that is already pre-installed in the runtime environment.
See spec.md, Section 4.1.
AFPS defines a simplified schema system for three distinct sections in a flow manifest. Although they share the same format, they serve different purposes:
input — per-execution data, supplied each time the flow runs (e.g., a search query, a file to process). Consumers should prompt for these values at each execution.output — per-execution result, produced at the end of each run (e.g., a summary, a report). Consumers may use this to validate the language model’s response.config — per-deployment settings, configured once and reused across executions (e.g., preferred language, notification threshold). Consumers should persist these values.The schema format is intentionally smaller than JSON Schema. Every schema must be an object with type: "object" and a properties map:
{
"input": {
"schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query",
"placeholder": "label:inbox newer_than:7d"
},
"max_results": {
"type": "number",
"default": 20
}
},
"required": ["query"]
}
},
"config": {
"schema": {
"type": "object",
"properties": {
"language": {
"type": "string",
"description": "Output language",
"default": "fr",
"enum": ["fr", "en", "es"]
}
}
}
}
}
Supported property types are string, number, boolean, array, object, and file. The file type supports additional hints like accept, maxSize, multiple, and maxFiles.
There is no nesting, no $ref, no oneOf/anyOf — the schema is flat by design, optimized for form generation and validation rather than complex data modeling.
See spec.md, Section 5.
Provider packages declare one of five authentication modes:
| Mode | Use case | Requires |
|---|---|---|
oauth2 |
Standard OAuth 2.0 services | authorizationUrl, tokenUrl |
oauth1 |
Legacy OAuth 1.0a services | requestTokenUrl, accessTokenUrl |
api_key |
API key-based services | credentialSchema |
basic |
HTTP Basic authentication | credentialSchema |
custom |
Non-standard authentication schemes | credentialSchema |
OAuth2 providers can additionally declare PKCE support, custom scopes, token endpoint configuration, and authorized URI patterns.
See spec.md, Section 7.
AFPS manifests are extensible. Unknown fields are preserved by consumers rather than rejected. When adding custom fields to a manifest or nested object, producers should use the x- prefix to avoid collisions with future specification fields:
{
"name": "@acme/my-flow",
"type": "flow",
"x-internal-team": "platform",
"x-cost-center": "eng-42"
}
This follows the same convention used by OpenAPI and other extensible specifications.
To set clear expectations, here is what AFPS intentionally does not cover:
Not a tool-calling protocol. AFPS does not define how an agent invokes tools at runtime. That is the domain of protocols like MCP.
Not an agent-to-agent transport. AFPS does not define how agents discover or communicate with each other. That is the domain of protocols like A2A.
Not a prompt language. AFPS requires a prompt.md file in flow packages, but it does not define prompt templating, variable interpolation, or execution semantics.
Not a runtime API. AFPS does not specify how a flow runner loads packages, manages state, or handles scheduling. Those are implementation concerns.
Not a registry protocol. AFPS defines what packages look like and how dependencies are declared, but it does not define the HTTP API for publishing, searching, or downloading packages.
Not a full schema language. The AFPS schema system is deliberately constrained. It is not JSON Schema. There is no nesting, no composition keywords, and no $ref support.
AFPS is a packaging standard. It defines the artifact — the ZIP file, the manifest, the companion files, the dependency declarations — and leaves execution, transport, and discovery to other layers.