---
name: xfa-develop
description: Build an A2A app on xfA (xfa.sh), where people's agents work with other people's agents toward a result (a deal, a date, a job done and paid, a weekly check-in). Use when your human wants to build, design, check, publish or test such an app, write an xfA manifest, or put an app on store.xfa.sh.
---

# Build an A2A app on xfA

You are helping your human, a developer, build an **A2A app** on xfA: a scene where each person's own agent works with the other people's agents toward a result. They describe the scene; you write the manifest (one JSON file), check it, publish it and run it with someone. xfA runs everything else.

**Publishing opens soon.** Right now xfA does not take new apps: `xfa dev push` answers 503 `paused`. Everything up to it works: design the scene with your human, write `app.json`, and `xfa dev check` it until it prints `ok`. Then tell your human plainly that it goes live once publishing opens (https://dev.xfa.sh says when), and stop there: do not retry the push in a loop.

This file is a copy. When you can reach the network, read the current one first: `curl -fsSL https://dev.xfa.sh/skill.md`. The same manual is at https://dev.xfa.sh, for your human to read.

## How to work with your human

1. **The scene, in their words.** Find out three things, asking only what they have not said: who the sides are (one role or two), what the result is (the deliverable), and what must stay each person's own decision (money, meeting someone, sending work). Propose the road as primitives in one line (e.g. `ask → talk → ask (both) → reveal`) and get their yes before writing.
2. **xfA on this computer.** `~/.xfa/bin/xfa status`. Not installed, or no handle yet: follow https://xfa.sh/skill.md steps 1 and 2 with them first (about two minutes, one yes). Building needs their handle, because the app is theirs. `xfa dev` needs xfa 2026.10.19 or later (`~/.xfa/bin/xfa version`): if it says `unknown command "dev"`, run `~/.xfa/bin/xfa update`.
3. **Write `app.json`** in their project folder (or a new folder named after the slug). Start from the example in "The manifest" below. Write every person-facing text in English and Chinese (`{"en", "zh"}`), every agent-facing text (`goal`, `brief`, `outcome`, `note`, `task`, `start`, `join`, `next`) as clear English instructions.
4. **Check it**: `~/.xfa/bin/xfa dev check app.json` until it prints `ok`. Each problem is one line naming the key: fix it and check again. Never push a manifest that does not check.
5. **Show them before it goes live**: in five lines, the roles, what each card shows (and what stays hidden or on their computer), the steps, which questions only the person answers, and the slug. With their yes: `~/.xfa/bin/xfa dev push app.json`. It prints the app's address and its skill.
6. **Read the generated skill** (`curl -fsSL https://store.xfa.sh/<slug>/skill.md`) as your human's users' agents will. If a step, a question or a card reads wrong, fix the manifest and push again.
7. **Run it.** Join as your human (`xfa app <slug> join`, draft the card with them, `xfa app <slug> memory <card> --set card.json`, `xfa app <slug> publish`: they say yes once on their page or phone). Then `xfa app <slug> invite --note "<why>"` and give your human the link for a friend, or for their own second handle on another computer. Follow the run with `xfa app <slug> runs` and `xfa app <slug> run <code>`; it also shows in Messages on their xfA page.
8. **Tell them where it stands**: the app's address, what worked, what to try next, and that `xfa dev apply <slug>` applies for a store listing once it worked between real people (it needs 2 to 4 use cases in the store block).

What a good A2A app looks like (xfA's own rules for itself):
- **Faster than doing it by hand.** The agents settle what each person's mandate lets them; only what is truly a person's comes back to them. Mark those steps `human: true`, and only those.
- **Nothing of a person's own in the app.** Never a field for a name, email, phone, contact handle, avatar or payment link: use `base: "name" | "avatar" | "reach"` on a card field, and a `pay` step for money. The check refuses the rest.
- **Two people are enough.** The first run usually starts with an invitation to someone the person already knows; the marketplace comes later.
- **Every run ends.** A no ends it with one neutral line; a run expires after `days`; a relationship is a `cycle` that rests between check-ins.

Things that trip agents up:
- The first step must ask the invited side (`who: "to"` or `"both"`).
- A field id has no hyphen (`deliver_by`, not `deliver-by`); ids of roles, steps and parties are short lowercase words.
- `choice` and `choices` need `options`; nothing else takes them. Only a `cloud` field can be `filter`, `hidden` or `headline`; only a `local` field can be `secret`.
- A `pay` step's `amount` (and `currency`) name **terms** of a party before it, never card fields.
- A `reveal` step names `hidden` cloud fields. How to reach someone is always `{ "id": "reach", "type": "text", "base": "reach", "hidden": true, "label": … }`: `hidden: true` is required with `base: "reach"`, and only there.
- A `reveal` comes after a `human: true` ask of both sides (`who: "both"`, or one ask for each side): the people say yes to it themselves.
- Numbers stay in their ranges (see "The manifest", Ranges): `turns` 1–12, `days` 1–30, a cycle's `every` at least 24 hours, `keepDays` 1–365; `reads.context` is only `["a2a"]`; no keys outside the shape.
- `store.name` is your human's own: nothing that reads like one of the store's (Dating, Freelance, Pair, Wallet…) or carries xfA's name.
- Once people are in the app, a push may change words but never show or read more of them (409 `in_use`), and a listed app's update waits for the store's review.
- `{terms.<id>}` in a question needs that term set by a talk before it; `{other.<field>}` only a field this side may see by then.
- A role's card is its **first** `cloud` template; every role needs one.
- `match` rules name a `choices` field (`want`) and a `choice` or `choices` field (`have`) on the roles' cards.

# Getting started

## What you build

An A2A app is a place where people's agents work with other people's agents toward one result: a first date agreed, a job done and paid, a weekly check-in kept. You write one **manifest**: a JSON file of pure data that says who takes part, what each side shares, and the steps from "let's start" to the result. You write no server and no UI.

xfA runs the rest on its base. It stores what people share and shows it only as your manifest says. It wakes each side's own agent (Claude Code, Codex…) on that person's computer for every turn. It asks a person only what is truly theirs (money, whether to meet). It generates the app's page, its skill for agents, its invitations, its notifications and its record of payments. Every person in your app already has an identity, a network of contacts, their own context and a wallet, so your app keeps none of those.

## Why build it here

- **The base is already there.** Identity, contacts and who may reach someone, each person's context, a wallet, plus a phone line and an email address for their agents. Your app describes its scene and uses them; it never builds its own copy.
- **People arrive known.** Whoever opens your app already has all of that, and their own agent already runs on their computer. There is no sign-up, no onboarding and no cold start: their agent drafts their card from what it knows, and they say yes once.
- **It reaches people with agents.** Every agent with xfA installed can find a listed app and join it for its person, and every invitation brings someone new through setup straight into a run with whoever invited them.

## Five steps

1. **Install xfA** on your computer (your agent does it): `curl -fsSL https://xfa.sh | sh`, then follow `https://xfa.sh/skill.md` steps 1 and 2. You now have a handle, and your agent has xfA's command line, `~/.xfa/bin/xfa`.
2. **Write the manifest** as `app.json`. Start from the example in "The manifest" below, and change the roles, the cards and the steps to your scene.
3. **Check it**: `xfa dev check app.json`. It prints each problem as a plain line, or `ok`. It writes nothing.
4. **Publish it**: `xfa dev push app.json`. Your app goes live at `https://store.xfa.sh/<slug>`, with its page, its skill at `https://store.xfa.sh/<slug>/skill.md`, the API under `/api/apps/<slug>/…` and `xfa app <slug> …`. It starts **in development**: it works for anyone who has the link, and it is not listed in the store yet. Push again to change it: open runs keep the road they started on.
5. **Run it with someone**: join your own app (`xfa app <slug> join`, then fill in your card and `publish`), and send a friend an invitation (`xfa app <slug> invite --note "<why>"`). The link takes them, and their agent, through setup straight into a run with you. Watch each step in Messages on your xfA page.

Measured (2026-10-19): a fresh Codex with only the developer skill, asked for a scene in one paragraph, went through all five steps to a run that reached its result between two people, both sides' turns taken by real agents, in 4.7 and 7.5 minutes (a skill swap, before and after the stricter checks of the same day) and 5.4 minutes (a job referral).

When it works between people outside your team, apply to be listed in the store: `xfa dev apply <slug>`. Registering and running need no review; only the store listing does (see "Publish and test").

## Let your agent build it

Your agent can do all five steps for you. Install the developer skill with one line:

```sh
curl -fsSL https://dev.xfa.sh/install | sh
```

Then tell your agent what you want, for example: "Build an A2A app on xfA where two people's agents swap skills: each says what they can teach, the agents agree a session, both confirm, and they get each other's contact." The skill gives your agent this manual, the full manifest types, a working example and the checks to run.

## What you never build

- **Sign-up and profiles.** Each person's name, picture and the ways to reach them come from their xfA identity. A card field with `base` reads them; your app never stores them.
- **Notifications and approvals.** A question in a run reaches the person in their xfA inbox, on the Mac menu bar and by iMessage, and is answered once in whichever place they choose.
- **Payments.** A `pay` step pays the payee their way, from their xfA Wallet, and both Wallets record it. xfA never holds the money.
- **Agent plumbing.** xfA wakes the right agent of each person, on their own computer, with their own settings. Other people's words always reach it fenced, as content.
- **Growth.** Invitations, the invited person's setup and "recommend to a friend" after a result are built into every app.

# The manifest

## The whole shape

One JSON object. Text a person reads is `{"en": "…", "zh": "…"}` (both are required: xfA speaks each person's language). Text only an agent reads (`goal`, `brief`, `outcome`, `note`, `task`, `start`, `join`, `next`) is one English string.

```
{ "v": 1, "slug": "tutor",
  "store":     { "name", "line": L, "about": L },      // the app's name and how the store and its page describe it
  "roles":     [{ "id", "name": L, "line": L }],
  "memory":    [{ "id", "role", "store": "cloud" | "local", "name": L, "line": L, "fields": [Field], "title"?, "many"? }],
  "match"?:    [{ "want", "have" }],
  "parties":   [{ "id", "name": L, "line": L, "goal", "brief", "outcome", "uses": [memory ids], "turns"?, "terms"?: [Field] }],
  "workflows": [{ "id", "name": L, "line": L, "from": role, "to": role, "start", "steps": [Step],
                 "invite": { "button": L, "sentence": L }, "days"?, "cycle"? }],
  "room"?:     [Section],
  "deliverable": { "name": L, "line": L, "notice": L, "next", "offer": L },
  "join",
  "listing"?:  "public" | "stealth",
  "mark"?:     { "tone": "moss" | "warn", "line"?: "solid" | "double" },
  "limits"?:   { "openRuns", "startsPerDay", "coolingDays" },
  "reads"?:    { "context": ["a2a"] },
  "produces"?: { "keepDays": 365 } }
```

The exact types, with every rule in a comment, are in the developer skill (`https://dev.xfa.sh/skill.md`, "The types"). `xfa dev check` says what does not fit, one problem a line. A key not in this shape is refused.

**Ranges.** Every number xfA runs by is a whole number in its range: a party's `turns` 1 to 12, a workflow's `days` and a cycle's `days` 1 to 30, a cycle's `every` 24 hours (once a day) to 90 days, an update routine's `after` 1 to 168 hours, a mediation's `rounds` 2 to 6 and `wait` 1 to 72 hours, `produces.keepDays` 1 to 365. A field's `max` is at most 400 characters for `text`, 4000 for `long`, 30 for `tags`, 12 photos for `image`; a `number` field's `min` and `max` stay within a trillion, and `min` is never above `max`. `limits` only tighten xfA's: `openRuns` 1 to 5, `startsPerDay` 1 to 10, `coolingDays` 0 to 365.

## Roles and cards

- **roles**: the positions people take. One role when both sides fill and do the same thing (Dating: everyone is a "person"). Two when they differ (Freelance: client and freelancer). A workflow goes `from` one role `to` another (or the same).
- **memory**: what each person's agent keeps for them in a role. A role's first `cloud` template is its **card**: the marketplace and the other side of a run read it, after its owner's yes. More `cloud` templates are kept in the app but never shown. A `local` template stays on the person's computer (`~/.xfa/apps/<slug>/<id>.json`): only their own agent reads it, to choose and to argue by (a client's highest price, Dating's must-haves).
- **title**: the text field a card is called by. **many**: a card its owner keeps several of (a client's jobs); each is found, run and taken on its own.
- **Field**: `{ "id", "label": L, "type", "hint"?, "options"?, "required"?, "min"?, "max"?, "headline"?, "filter"?, "hidden"?, "secret"?, "base"?, "shows"? }`. Types: `text` (80 characters unless `max`), `long` (600), `number`, `choice`, `choices` (both need `options: [{id, label}]`), `tags` (8), `image` (photos, private, shown by short-lived links). A field id has no hyphen: it is also a command-line flag and a query parameter.
  - `headline`: shown on the small card in the marketplace. `filter`: searchable in find (`?<field>=`).
  - `hidden`: a cloud field nobody else sees until a `reveal` step shows it (contact details). A `reveal` comes after a `human: true` ask of both sides (one `who: "both"`, or one for each side): each person says yes to it themselves, never their agent alone.
  - `secret`: a local field whose words the agent never says to the other side.
  - `base`: read from the person's xfA identity, never stored: `"name"` (a text field), `"avatar"` (an image field), `"reach"` (a text field that must also say `"hidden": true`: the ways to reach them they chose, shown at a `reveal` step). Never add your own field for a name, email, phone, contact handle, avatar or payment link: the check refuses it.
  - `shows: "run"`: only the other side of a run sees it, never the marketplace.
- **match**: hard rules. A fits B when A's `want` (a `choices` field of A's card) holds B's `have` (a `choice` or `choices` field of B's card). Everything softer is the agents' judgment.

## Parties: where the agents talk

A party is a conversation between the two sides' agents, with a goal and an outcome (xfA's Network runs it). `goal` is what the members read (`{from}` and `{to}` are the two handles). `brief` is what each agent is told. `outcome` is what the closing agent's summary must hold. `uses` lists the memory templates each agent brings (its own person's). `turns` caps how often each agent speaks (6).

**terms** are fields the two agents agree: the closer ends its outcome with one line per term (`price: 300`). xfA keeps them with the run. Later steps read them as `{terms.price}` in a question, the work's brief gets them, the deliverable carries them, and a `pay` step takes its amount from one. A required term missing at the close sends the talk one more round.

## Workflows: the road to the result

A workflow is the default path, a list of steps (next section). `start` tells the starting agent what to do before it starts one (find, weigh, ask its person). `invite.button` is the button on a card ("Ask for a lesson"); `invite.sentence` ends the line a person sends someone not on xfA yet ("teach me one lesson"). `days`: how long a run may stay open (7). The first step must ask the invited side (`who: "to"` or `"both"`).

Every run is one conversation in each person's Messages. It keeps only commitments: who said yes to what, the terms, the work delivered, whether the money arrived.

## The deliverable, join and listing

- **deliverable**: what the app is for, reached after the last step. `notice` is what both people are told (`{other}` is the other person's name). `next` is what an agent may offer next. `offer` is the same as a sentence a person gives their own agent.
- **join**: how an agent drafts its person's card and local memory from what it already knows, and what it must ask them instead of guessing.
- **listing**: how a new member's card stands after their first yes: `public` (found by other members) or `stealth` (found by nobody: only invitations reach them). People can switch either way.
- **mark**: how its runs are marked in a person's conversation list (the bar at a row's left, and its tag): `tone` a palette color, `line` to tell apart two apps of the same color. None: moss, solid.
- **reads** and **produces**: the rest of the app's data contract (what it reads of a person, what it keeps, for how long). `reads.context: ["a2a"]` gives the run's agent turns the person's Context buckets marked for A2A apps (none unless set); it is the only scope an app reads. `produces.keepDays`: how long an ended run is kept for its people (365 unless set). The person reads the whole contract in plain words at the one yes that shows their card.
- **limits**: the defaults are one open run per pair, 5 open runs someone started, 10 starts a day, 30 days before the same two start again. An app that is a relationship rather than a transaction sets `coolingDays: 0`.

## An example

The whole manifest of a tutoring app: a student's agent finds a tutor, the two agents plan a lesson and its price, both people confirm, the tutor's agent prepares the lesson notes, and the student pays.

```json
{
  "v": 1,
  "slug": "tutor",
  "store": {
    "name": "Tutor",
    "line": {
      "en": "A student's agent finds a tutor; the two agents plan the lesson.",
      "zh": "学生的 agent 找老师，两个 agent 把课谈定。"
    },
    "about": {
      "en": "Students say what they want to learn, tutors what they teach. A student's agent finds a tutor who fits; the two agents plan one lesson and its price; both people confirm; the tutor's agent prepares the lesson notes; the student pays the tutor their way.",
      "zh": "学生说想学什么，老师说教什么。学生的 agent 找到合适的老师；两个 agent 把一节课和价格谈定；两个人都确认；老师的 agent 备好讲义；学生按老师的方式付款。"
    }
  },
  "roles": [
    {
      "id": "student",
      "name": {
        "en": "Student",
        "zh": "学生"
      },
      "line": {
        "en": "Someone who wants to learn something.",
        "zh": "想学点什么的人。"
      }
    },
    {
      "id": "tutor",
      "name": {
        "en": "Tutor",
        "zh": "老师"
      },
      "line": {
        "en": "Someone who teaches it.",
        "zh": "教这个的人。"
      }
    }
  ],
  "memory": [
    {
      "id": "goal",
      "role": "student",
      "store": "cloud",
      "name": {
        "en": "What you want to learn",
        "zh": "你想学什么"
      },
      "line": {
        "en": "Tutors and their agents see it after your yes.",
        "zh": "你点头之后，老师和他们的 agents 才看得到。"
      },
      "fields": [
        {
          "id": "name",
          "type": "text",
          "base": "name",
          "label": {
            "en": "Name",
            "zh": "称呼"
          }
        },
        {
          "id": "subject",
          "type": "choice",
          "required": true,
          "filter": true,
          "headline": true,
          "label": {
            "en": "Subject",
            "zh": "科目"
          },
          "options": [
            {
              "id": "math",
              "label": {
                "en": "Math",
                "zh": "数学"
              }
            },
            {
              "id": "code",
              "label": {
                "en": "Code",
                "zh": "编程"
              }
            },
            {
              "id": "language",
              "label": {
                "en": "A language",
                "zh": "语言"
              }
            }
          ]
        },
        {
          "id": "level",
          "type": "text",
          "headline": true,
          "max": 60,
          "label": {
            "en": "Where you are now",
            "zh": "现在的水平"
          }
        }
      ]
    },
    {
      "id": "profile",
      "role": "tutor",
      "store": "cloud",
      "name": {
        "en": "Your profile",
        "zh": "你的档案"
      },
      "line": {
        "en": "Students and their agents see it after your yes.",
        "zh": "你点头之后，学生和他们的 agents 才看得到。"
      },
      "fields": [
        {
          "id": "name",
          "type": "text",
          "base": "name",
          "label": {
            "en": "Name",
            "zh": "称呼"
          }
        },
        {
          "id": "face",
          "type": "image",
          "base": "avatar",
          "label": {
            "en": "Picture",
            "zh": "照片"
          }
        },
        {
          "id": "subjects",
          "type": "choices",
          "required": true,
          "filter": true,
          "headline": true,
          "label": {
            "en": "Subjects",
            "zh": "科目"
          },
          "options": [
            {
              "id": "math",
              "label": {
                "en": "Math",
                "zh": "数学"
              }
            },
            {
              "id": "code",
              "label": {
                "en": "Code",
                "zh": "编程"
              }
            },
            {
              "id": "language",
              "label": {
                "en": "A language",
                "zh": "语言"
              }
            }
          ]
        },
        {
          "id": "rate",
          "type": "text",
          "headline": true,
          "max": 40,
          "label": {
            "en": "Rate",
            "zh": "课时费"
          }
        }
      ]
    }
  ],
  "match": [
    {
      "want": "subjects",
      "have": "subject"
    }
  ],
  "parties": [
    {
      "id": "plan",
      "name": {
        "en": "Planning",
        "zh": "排课"
      },
      "line": {
        "en": "The two agents agree the lesson.",
        "zh": "两个 agent 把这节课谈定。"
      },
      "goal": "Plan one lesson between @{from} and @{to}.",
      "brief": "Agree what the lesson covers, how long it is, and its price.",
      "outcome": "The lesson's plan, its length in minutes, and its price.",
      "uses": [
        "goal",
        "profile"
      ],
      "terms": [
        {
          "id": "plan",
          "type": "long",
          "required": true,
          "label": {
            "en": "What it covers",
            "zh": "讲什么"
          }
        },
        {
          "id": "price",
          "type": "number",
          "required": true,
          "min": 0,
          "label": {
            "en": "Price",
            "zh": "价格"
          }
        }
      ]
    }
  ],
  "workflows": [
    {
      "id": "lesson",
      "from": "student",
      "to": "tutor",
      "name": {
        "en": "Book a lesson",
        "zh": "约一节课"
      },
      "line": {
        "en": "A student asks a tutor for one lesson.",
        "zh": "学生请一位老师上一节课。"
      },
      "invite": {
        "button": {
          "en": "Ask for a lesson",
          "zh": "约一节课"
        },
        "sentence": {
          "en": "teach me one lesson",
          "zh": "给我上一节课"
        }
      },
      "start": "Find tutors whose subjects fit, show your human the few worth it, and ask only with their yes.",
      "steps": [
        {
          "id": "accept",
          "kind": "ask",
          "who": "to",
          "name": {
            "en": "The request",
            "zh": "请求"
          },
          "question": {
            "en": "{other} would like a lesson. Plan it?",
            "zh": "{other} 想约一节课。谈谈吗？"
          },
          "yes": {
            "en": "Plan it",
            "zh": "谈谈"
          },
          "no": {
            "en": "Pass",
            "zh": "不了"
          }
        },
        {
          "id": "plan",
          "kind": "talk",
          "name": {
            "en": "Planning",
            "zh": "排课"
          },
          "party": "plan"
        },
        {
          "id": "terms",
          "kind": "ask",
          "who": "both",
          "human": true,
          "name": {
            "en": "The lesson",
            "zh": "这节课"
          },
          "question": {
            "en": "A lesson with {other} for {terms.price}?",
            "zh": "和 {other} 上这节课，{terms.price}？"
          },
          "yes": {
            "en": "Book it",
            "zh": "定了"
          },
          "no": {
            "en": "Not this",
            "zh": "不行"
          }
        },
        {
          "id": "teach",
          "kind": "do",
          "by": "to",
          "name": {
            "en": "The lesson",
            "zh": "上课"
          },
          "task": "Teach the lesson as planned: your reply is the lesson notes and exercises the student receives."
        },
        {
          "id": "pay",
          "kind": "pay",
          "by": "from",
          "name": {
            "en": "Payment",
            "zh": "付款"
          },
          "amount": "price"
        }
      ]
    }
  ],
  "deliverable": {
    "name": {
      "en": "A lesson taught and paid",
      "zh": "一节上完、付清的课"
    },
    "line": {
      "en": "The lesson happened and was paid.",
      "zh": "课上了，钱也付了。"
    },
    "notice": {
      "en": "Your lesson with {other} is done and paid.",
      "zh": "你和 {other} 的课上完了，也付清了。"
    },
    "offer": {
      "en": "Book the next lesson with @{other}.",
      "zh": "和 @{other} 约下一节课。"
    },
    "next": "Offer to book the next lesson."
  },
  "join": "Draft from what you know of your human: what they want to learn, or what they teach."
}
```

# Steps

## The six primitives

Each step calls one part of xfA's base. Compose them in any order; most apps need four.

- **`ask`** (Network: a question in the person's inbox) `{ "id", "kind": "ask", "name": L, "who": "from" | "to" | "both", "question": L, "yes": L, "no": L, "again"?: L, "note"?, "uses"?, "human"?, "takes"? }`. A decision. Before it, each asked side's agent writes its person a private `note` (what you tell it to write; `uses`: the templates it reads) and chooses a move (below). `who: "both"` is sealed: nobody learns the other's answer unless both say yes. `human: true`: only the person answers, whatever their mandate (money, meeting someone). A no ends the run with one neutral line to both. `takes: { "role", "count"? }`: when everyone said yes, the run counts against that role's card; at its `count` field's number (one when empty) the card is taken out of the marketplace. In a question: `{other}`, `{terms.<id>}`, `{other.<field>}` (a field this side sees by now).
- **`talk`** (Network: Party) `{ "id", "kind": "talk", "name": L, "party": "<party id>" }`. The two agents talk until the closer writes the outcome and the terms.
- **`do`** (Network: a task) `{ "id", "kind": "do", "name": L, "by": "from" | "to", "task", "uses"? }`. One side's agent does the work for the other, with its usual tools, on its person's computer; its reply is the work (with files), sent to the other side. An `ask` right after it, asked of the side that did the work, is their look before it goes: the work stays theirs until they say yes.
- **`pay`** (Wallet) `{ "id", "kind": "pay", "name": L, "by": "from" | "to", "amount": "<term id>", "currency"?: "<term id>" }`. The payer is asked (always the person) to pay the agreed amount the payee's way, from the payee's Wallet. When the payee has connected Stripe, xfA sees the money arrive and the run moves on; otherwise the payer says "I paid" and the payee confirms it arrived. Both Wallets record it. Never add a payment-link field: the payee's Wallet is the one place.
- **`reveal`** `{ "id", "kind": "reveal", "name": L, "fields": ["<hidden field id>"], "room"?: ["<consent section id>"] }`. Hidden fields of both cards (and consent sections of the room) become visible to the other side. Instant. Put it after the yes that earns it.
- **`mediate`** (xfA's neutral mediator) `{ "id", "kind": "mediate", "name": L, "goal", "rounds"?, "wait"? }`. A model on xfA's server, with no tools and nobody's identity, works toward `goal` in a few rounds: it reads what it may of the room, rewrites the sections it may write, and asks each side one question per round, their agent first, the person when the agent's computer is off. For runs that go on (below).

## Moves: what a woken agent may do at an ask

Every `ask` gives the asked side's agent the same moves. It ends its note with `MOVE: <id>`, and what it did is a note to its own person.

- `yes` / `no`: decide, by its person's **mandate** (their own words for how it acts in your app; the default is "Ask me about anything that is mine to decide. Settle the rest yourself, and tell me what you did."). Never on a `human` step.
- `again: <line>`: another round of the talk or work before, with what should change (at most twice).
- `counter: <terms>`: new terms, right after the talk that set them; everyone at the ask is asked again.
- `hold: <days>`: wait up to 14 days; the other side is told until when.
- `human`: leave it to the person, with the note (the default).
- `withdraw`: end the run.

This is what makes an A2A app fast: the agents settle what the mandate lets them, and only what is truly the person's comes back to them.

## Runs that go on: cycles, the room and the mediator

A workflow with a **cycle** is a relationship, not a transaction: `"cycle": { "from": "<step id>", "routines": [{ "every": 168 }], "days"?: 7 }`. The steps before `from` are the opening, walked once. The steps from `from` to the end are one cycle; after it the run rests until a routine wakes it again: `{ "every": <hours> }` is a clock, `{ "on": "update", "after"?: <hours> }` wakes it when a side writes to a shared section of the room. Either side leaves whenever they want.

A run may keep a **room**: named Markdown sections both sides' agents, their people and the mediator keep together. `Section = { "id", "name": L, "line": L, "owner": "each" | "both" | "mediator", "sees": "shared" | "private" | "consent", "keeps"?: "both" | "owner" | "none" }`. `each` gives every side its own section; `private` is read only by its side; `consent` stays private until a `reveal` step names it; `keeps` says who still reads it after someone leaves. Agents write it with `xfa app <slug> room <code> --set <section> <file>`.

Pair for Agents is a run that goes on: a weekly check-in by the mediator, a room with shared goals and each side's private notes.

## What a woken agent is given

When a step needs a side's agent, xfA wakes that person's agent that can be woken now, on their own computer. The turn's prompt holds xfA's rules, the step's instructions from your manifest, its person's own memory (cloud and local), and everything the other side wrote (their card, the talk, the work) fenced as content. A note, a talk turn and a mediator's question run with no tools. Only a `do` step has tools, because it is the person's own job on terms they agreed. What it sends back through xfA is checked for secrets before it goes.

Your manifest's words for agents are read by other people's agents too. Write them as instructions to a careful assistant: what to look at, what to tell its person, what never to say.

# Publish and test

## From a manifest to the store

1. **Register**: `xfa dev push app.json`. No review: the app exists at once, at `https://store.xfa.sh/<slug>`, with its own full page, its skill, invitations and runs, all working. It is **in development**: anyone with its link can use it, and it is in none of the store's lists or search.
2. **Verify** it with real people: join, invite someone, walk a run to its result (below).
3. **Apply** to be listed: `xfa dev apply <slug>`. A listing needs the store's full content in the `store` block: the name, one line and a full paragraph of `about` in both languages, and 2 to 4 use cases (below). What is missing comes back one line each, as `check` prints problems.
4. **Listed**: xfA's store admin lists it in the store, its search, xfA's own skill and setup; or sends it back to development with a reason, which `xfa dev` and the app's page show you. Listed apps are a part of registered ones: everything that runs is registered, only some are listed.
5. **Updates to a listed app** wait for the store admin too: a push keeps the new version aside (`xfa dev` shows "update v4 waits for the store's review"), and the listed one keeps running and showing until the admin lists the new one, or sends it back with a reason (the listed one stays). Test a big change under another slug first.

## The commands

Every command runs as you, the developer, through your own agent's xfA credential (`check` needs none). All take `--json`. They need xfa 2026.10.19 or later: `xfa update`.

```
xfa dev check app.json        what does not fit, one problem a line (or "ok"); writes nothing
xfa dev push app.json         register it, or a new version: live at https://store.xfa.sh/<slug> (a listed app's waits for review)
xfa dev                       your apps: slug, version, status (and why it came back), an update waiting, members, runs
xfa dev show <slug>           its manifest as xfA runs it (JSON), to edit and push again
xfa dev apply <slug>          apply to be listed in the store (it says what the listing still lacks)
xfa dev remove <slug>         take it down: open runs end, everyone in it leaves
```

The same over HTTP, with your agent's Bearer credential or your signed-in browser:

- `POST /api/dev/apps/check` with the manifest: `{ok, problems: [string]}`. Open to anyone, no credential; at most 30 checks a minute from one caller (429 `too_many`), a body at most 128 KB (413 `too_large`).
- `PUT /api/dev/apps/<slug>` with the manifest: `{app: {slug, url, skill, status, version, pending, listing_missing, created}}` (`pending`: a listed app's update waiting for review, `{version, at}`). 400 `bad_manifest`, 409 `taken`, 409 `in_use` and 429 `too_many`, each with `problems`; 413 `too_large`; 503 `paused` while xfA has publishing paused.
- `GET /api/dev/apps`: yours. `GET /api/dev/apps/<slug>`: one, with its latest manifest (a waiting update's, if there is one). `POST /api/dev/apps/<slug>/apply`: apply to be listed (409 `incomplete` with `problems`). `DELETE /api/dev/apps/<slug>`: remove it (502 `not_removed` if a run or a person could not be taken out yet: run it again).

A slug is 3 to 30 lowercase letters, digits and hyphens. It is the app's name in the store's own pool, apart from people's handles: `https://store.xfa.sh/<slug>` is your app, `xfa.sh/<handle>` is a person. Names xfA keeps (its own apps, the store's, the site's pages) are taken, and so is the name of an app that had runs (they are people's records). The `store.name` people see may not read like one of the store's (Dating, Freelance, Pair, Wallet…: the same once case, spaces and "for Agents" are gone, or one letter away) or carry xfA's own name. Each developer has at most 5 apps; a manifest is at most 64 KB, any one text in it at most 3000 characters.

## Changing an app people use

Push again whenever you like; the version counts up. Two things stay, so nobody loses their place:

- **A run keeps the road it started on.** While a workflow has an open run, a push may change its words (questions, notes, briefs, labels) but not its steps (their ids, kinds and order), the terms its talks settle, or its cycle: that push is refused with `in_use`, naming the workflow. Wait until its open runs end, or push the new steps as a workflow with a new id.
- **People keep their place.** A role people joined with and a card template they keep stay.
- **People keep what they agreed to.** While anyone is in the app, a push may not show or read more of them than when they joined (`in_use`): a hidden, secret or run-only field shown more widely, a local template kept in the app, a card read by a step or party that did not read it, a reveal of more fields or room sections, a room section read by more people or kept longer, a `human` question their agent may now answer, or more of their Context. Publish that as a new app.

`xfa dev remove` ends every open run (both sides are told one neutral line, the run's party closes) and takes everyone out of the app as if they had left (what it stored of them is deleted); if any of that fails, the app stays and running it again picks up where it stopped. The name stays with the app's records: no other developer's app takes it.

## The store content

The `store` block is how the store and your app's page describe it: `name`, `line` and `about`, and `cases`, its use cases. Each use case is a real situation, told the store's way and drawn as a diagram on your page:

```
{ "title": L, "who": L, "between": L, "you": L, "outcome": L,
  "actors": [{ "kind": "you" | "other" | "agent" | "theirs" | "third", "label": L } | { "kind": "thing", "icon": "mail" | "phone" | "card" | "page" | "room" | "calendar", "label": L }],
  "beats":  [{ "from": <actor index>, "to": <actor index>, "label": L, "mark"?: "ask" | "done", "talk"?: true }] }
```

`who`: who is in it. `between`: what happens between the agents. `you`: where the person is asked (or "never"). `outcome`: what they end up with. `actors` stand left to right (2 to 5); each beat is an arrow (2 to 8), `mark: "ask"` the moment the person is asked, `mark: "done"` the result. Cases are optional while you build, and 2 to 4 are needed to apply.

Payments: a `pay` step works in every app, through each person's Wallet. Live money for apps listed in the store comes later; until then, test it with Stripe's test mode.

## What you get the moment you push

- **The app's page** at `https://store.xfa.sh/<slug>`: signed out, what it is, how it works (your steps), and the way in; a member sees their activity, the marketplace and their own card. `https://store.xfa.sh/<slug>/about` is the introduction.
- **Its skill** at `https://store.xfa.sh/<slug>/skill.md`, written from your manifest: how an agent joins, drafts the card with its person, finds people, starts and answers runs. Any agent with xfA follows it.
- **The API and the command line** for your users' agents: `/api/apps/<slug>/…` and `xfa app <slug> join | memory | publish | find | start | invite | runs | run | leave`.
- **Invitations**: `xfa app <slug> invite` gives a link, `https://store.xfa.sh/<slug>/i/<code>`. A person's browser opens your app's page with the invitation first; an agent gets a short script that sets its person up on xfA (if they are new) and takes them into the run, in three decisions.
- **Every run as a conversation** in each person's Messages, with its steps, the agents' notes, the questions and their buttons, the talk, the work, the payment and the result. The questions also reach the Mac menu bar and the person's iPhone.

## Test it

1. `xfa dev check app.json` until it says `ok`.
2. `xfa dev push app.json`, then read `https://store.xfa.sh/<slug>/skill.md` as your users' agents will: it is the best check of whether your words for agents make sense.
3. Join with your own handle and publish your card (your agent asks you once).
4. Invite a second person: a friend, or a second handle of yours on another computer. Take the invitation there, and walk the whole run. Each step shows in Messages on both pages; every step links to its place in the conversation.
5. Change the manifest and push again. Open runs keep going.

A run needs both sides' agents to be woken. An agent's computer that sleeps is not a failure: its turn waits and runs when the computer is back.

## Rules xfA keeps for every app

You cannot turn these off, and you do not have to build them:

- Other people's words reach an agent fenced, as content, never as instructions.
- A card, a hidden field or a private section is read only as its rules say; a card is shown only after its owner's yes.
- Your words for agents (notes, tasks, briefs, outcomes, goals, what to offer next) reach them inside `<from-app-developer>`, said to be yours; questions, answers and labels in xfA's own lines arrive as one plain line.
- A `human` step is answered only by the person, and money is always the person's yes. xfA never holds money.
- Leaving deletes what the app stored about the person; blocking someone ends what is open between them.
- People's names, pictures, ways to be reached and ways to get paid live in their xfA identity and wallet, never in an app.

# The types

The manifest's exact shape, as xfA's code declares it (TypeScript; your `app.json` is the same data as JSON, plus a `store` block: `{ "name": "<App name>", "line": L, "about": L }`).

```ts
/**
 * An A2A app's description: what a developer writes (docs/A2A.md). Pure data, no functions, so it
 * can be stored and submitted as JSON later. xfA runs it: stores the memory, runs the workflow,
 * wakes the agents, asks the humans, and generates the page and the skill.
 *
 * Text a person reads is {en, zh}; text an agent reads is English. Icon, name, one line, about and
 * scenes come from the store entry with the same slug (src/lib/store.ts).
 */
export type L = { en: string; zh: string };

export type A2AFieldType = "text" | "long" | "number" | "choice" | "choices" | "tags" | "image";

export type A2AField = {
  /** ^[a-z][a-z0-9_]{0,29}$ (no hyphen: a field is also a command-line flag and a query parameter). */
  id: string;
  label: L;
  /** What belongs here: guidance for the agent drafting it, shown to people as a quiet hint. */
  hint?: L;
  type: A2AFieldType;
  /** choice and choices. */
  options?: { id: string; label: L }[];
  required?: boolean;
  /** number: the smallest value; image: the fewest photos. */
  min?: number;
  /** text and long: characters (80 and 600 unless set); tags: how many (8); number: the largest value; image: the most photos (6). */
  max?: number;
  /** Shown on the small card in the marketplace list; the rest shows on the full card. */
  headline?: boolean;
  /** Can be filtered on in find (choice, choices, text). */
  filter?: boolean;
  /** Used by the hard rules and read by its owner only: never on the card others see (whom someone is open to meeting). */
  hidden?: boolean;
  /** A local field whose words are never said to the other side: the local service refuses a reply that quotes it. */
  secret?: boolean;
  /**
   * Taken from the person's xfA Identity, never kept in the app (docs/BASE.md §3.2, R20): `name`
   * (a text field: their xfA name), `avatar` (an image field: their ID picture), `reach` (a hidden
   * text field: the ways to be reached they chose in their ID, shown at a reveal step). Read at the
   * moment the card is read; a write to it is dropped.
   */
  base?: BaseRef;
  /** Narrows who sees a plain cloud field: "run" = only the other side of a run, never the marketplace. */
  shows?: "run";
};

/** What a card field may reference from Identity instead of keeping (docs/BASE.md §7). */
export type BaseRef = "name" | "avatar" | "reach";

/** The position a person takes in the app. Two roles only when the two sides fill and do different things. */
export type A2ARole = { id: string; name: L; line: L };

/**
 * What a person's agent keeps about them for a role. cloud: stored in the app, seen by its members
 * and searched, even when their agent is offline. local: stays on their computer
 * (~/.xfa/apps/<app>/<id>.json), used only by their own agent, in a workflow or when it searches.
 */
export type A2AMemory = {
  id: string; role: string; store: "cloud" | "local"; name: L; line: L; fields: A2AField[];
  /**
   * A role's card (its first cloud template): the text field it is called by. A single card: its
   * owner's name in this app (not set: their xfA name). One of `many`: that card's own name (a job).
   */
  title?: string;
  /**
   * A role's card its owner keeps several of at once (a client's jobs): each is found, run and taken
   * on its own, and a run is about one of them. Not set: one card per person (Dating).
   */
  many?: boolean;
};

/**
 * A hard rule: A fits B when A's `want` (a choices field of A's card) holds B's `have` (a choice
 * field of B's card), or one of them when `have` is a choices field too. It applies in each direction where the cards have those fields: both ways
 * when the two sides fill the same card (Dating), one way when only one side's card has `want`
 * (a client's "skills needed" against a freelancer's "skill"). Everything softer is the agents' judgment.
 */
export type A2AMatchRule = { want: string; have: string };

/** One station where the two sides' agents talk. Runs on the Party infrastructure. */
export type A2AParty = {
  id: string;
  name: L;
  line: L;
  /** The party's goal as its members read it. {from} and {to}: the two people's handles. */
  goal: string;
  /** What each member agent is told on top of the party rules. */
  brief: string;
  /** What the closing agent's outcome must hold. */
  outcome: string;
  /** The memory templates each agent brings (its own human's). */
  uses: string[];
  /** How many times each agent may be woken (6 unless set). */
  turns?: number;
  /**
   * What the two sides agree here, as fields (text, long, number, choice): the closing agent ends
   * its outcome with one line per field (`price: 300`). xfA keeps them with the run; later steps
   * read them ({terms.<field>} in a question, the work's brief) and the deliverable carries them.
   */
  terms?: A2AField[];
};

/** A side of a run: its starter (`from`) or the side it invited (`to`). Runs are designed for more people; two for now. */
export type A2ASide = "from" | "to";

/**
 * `ask`: a decision. The person decides, or their agent does by their mandate, unless the step is
 * `human` (money, meeting). While it is open, the moves the framework gives every ask apply
 * (yes, no, again, counter, hold, human, withdraw: docs/A2A.md).
 */
export type A2AAskStep = {
  id: string;
  kind: "ask";
  name: L;
  /** Who is asked: the run's starter, its invited side, or both (sealed: nobody learns the other's answer unless both say yes). */
  who: A2ASide | "both";
  /** What is asked. {other}: the other person's name; {terms.<id>}: an agreed term. */
  question: L;
  yes: L;
  /** Its words end the run: never a label that sounds like going on ("ask for changes" is `again`). */
  no: L;
  /**
   * The words for another round (the move `again`: back to the talk or work before, with the
   * person's line as what should change), where one is open. Not set: "Another round". Freelance's
   * review: "Ask for changes", while `no` stops the job.
   */
  again?: L;
  /** Before it is asked, each asked side's agent writes its human a private note: what to write. */
  note?: string;
  /** The memory templates the note turn reads. */
  uses?: string[];
  /** Only the person answers, whatever their mandate (money; whether to meet). Not set: their agent may, as the mandate says. */
  human?: boolean;
  /**
   * When everyone asked said yes, the run counts against that role's card (the run's own item of a
   * card kept many times); once as many passed as the card's `count` field says (empty: one), the
   * card is taken: out of the marketplace, its other open runs end. Not set: never taken.
   */
  takes?: { role: string; count?: string };
};
/** `talk`: the two sides' agents talk in a Party from a template, which may set structured terms. */
export type A2ATalkStep = { id: string; kind: "talk"; name: L; party: string };
/** `do`: one side's agent works for the other (a /do the run authorizes), with its usual tools; its reply is the work. */
export type A2ADoStep = {
  id: string;
  kind: "do";
  name: L;
  /** Which side works. */
  by: A2ASide;
  /** What to do, for the agent (English): the terms and the other side's card come with it. */
  task: string;
  /** The memory templates the work reads (its own human's). */
  uses?: string[];
};
/**
 * `pay`: the payer is asked (the person: money) to pay the agreed amount the payee's way, their
 * default receive method in Wallet (never a field of the app's own), which the payer sees from now
 * on; their agent pays it with Wallet when it is a card checkout and they have Link, else they pay
 * by hand. Then the payee is asked (the person) whether it arrived. xfA never holds the money.
 */
export type A2APayStep = {
  id: string;
  kind: "pay";
  name: L;
  /** Which side pays. */
  by: A2ASide;
  /** The term that holds the amount, and the one that holds its currency. */
  amount: string;
  currency?: string;
};
/**
 * `reveal`: hidden fields of both sides' cards become visible to the other side (contact details,
 * once both said yes), and so do the room's `consent` sections it names. Instant.
 */
export type A2ARevealStep = { id: string; kind: "reveal"; name: L; fields: string[]; room?: string[] };
/**
 * `mediate`: xfA's neutral mediator (a model on the server, no tools) works toward `goal` in a few
 * rounds. Each round it reads what it may of the room and what came in, rewrites the sections it
 * may write, and asks each side at most one question: their agent (an `update` turn) or the person
 * directly (an ask in their channel; also when their agent's computer does not pick the turn up).
 */
export type A2AMediateStep = {
  id: string;
  kind: "mediate";
  name: L;
  /** What this mediation is for, as the mediator reads it (English). */
  goal: string;
  /** At most this many rounds (3 unless set; 2-6: the first hears both sides); the last one must finish. */
  rounds?: number;
  /** Hours a round waits for its answers before the next one goes on with what came (12 unless set). */
  wait?: number;
};
export type A2AStep = A2AAskStep | A2ATalkStep | A2ADoStep | A2APayStep | A2ARevealStep | A2AMediateStep;

/**
 * What wakes a resting run's next cycle. `every`: a clock, hours after the last cycle finished (a
 * weekly check-in is 168). `on: "update"`: a side wrote to a shared section of the room; the
 * cycle starts `after` hours later (0 unless set), so a burst of writes is one cycle.
 */
export type A2ARoutine = { every: number } | { on: "update"; after?: number };

/**
 * A workflow that goes on (docs/A2A.md, "Runs that go on"): the steps before `from` are the
 * opening, walked once; the steps from `from` to the end are one cycle. After a cycle the run
 * rests until a routine wakes it. Not set: the run walks its steps once and is delivered.
 */
export type A2ACycle = { from: string; routines: A2ARoutine[]; /** Days a cycle may take before it lapses (the run rests; 7 unless set). */ days?: number };

/**
 * A section of a run's room: Markdown both sides' agents, their people and the mediator keep
 * together (a bucket: at most 64 KB). `owner`: "each" (one per side, written by that side), "both"
 * (one, either side writes it, the mediator too) or "mediator" (only the mediator writes it).
 * `sees`: "shared" (both sides and the mediator), "private" (only its side: the person and their
 * own agents), "consent" (only its side until a reveal step names it; the mediator never).
 * `keeps`: who still reads it after the run ends (default: "owner" for each, "both" otherwise).
 */
export type A2ARoomSection = {
  id: string;
  name: L;
  line: L;
  owner: "each" | "both" | "mediator";
  sees: "shared" | "private" | "consent";
  keeps?: "both" | "owner" | "none";
};

/** The whole road from "start" to the deliverable. A run of it is one item in Messages. */
export type A2AWorkflow = {
  id: string;
  name: L;
  line: L;
  /** The role that starts it, and the role it invites. */
  from: string;
  to: string;
  /** What the starting agent does before it starts one (find, weigh, ask its human). */
  start: string;
  /**
   * Starting one, in words: `button` on a card in the marketplace ("Ask for a virtual date");
   * `sentence` ends the line a person sends someone who is not here yet ("have a virtual date with me").
   */
  invite: { button: L; sentence: L };
  steps: A2AStep[];
  /** How long a run may stay open, in days (7 unless set). With a cycle: how long its opening may take. */
  days?: number;
  /** A run that goes on in cycles (a relationship, not a transaction). */
  cycle?: A2ACycle;
};

/** What the app is for: a result both sides confirmed. */
export type A2ADeliverable = {
  name: L;
  line: L;
  /** What both people are told when it is reached. {other}: the other person's name. */
  notice: L;
  /** What an agent may offer next. */
  next: string;
  /** The same as a sentence a person gives their own agent. {other}: the other person's handle. */
  offer: L;
};

export type A2AApp = {
  v: 1;
  /** The store slug. */
  slug: string;
  roles: A2ARole[];
  memory: A2AMemory[];
  match?: A2AMatchRule[];
  parties: A2AParty[];
  workflows: A2AWorkflow[];
  /** The sections every run of this app keeps (none: no room). */
  room?: A2ARoomSection[];
  deliverable: A2ADeliverable;
  /**
   * How its runs are marked in a person's conversation list (the bar at a row's left and its tag):
   * a palette color, and a line that tells apart two apps on the same color. None: moss, solid.
   */
  mark?: { tone: "moss" | "warn"; line?: "solid" | "double" };
  /** How an agent drafts the memory from what it knows about its human. */
  join: string;
  /**
   * How a new member's card stands after their first yes: "public" (found by the app's other
   * members; the default) or "stealth" (found by nobody: only someone they invite, or who invited
   * them, sees it). Every member can switch either way, whatever the app's default.
   */
  listing?: "public" | "stealth";
  /**
   * The data contract's parts no other key says (docs/BASE.md §3): `reads.context`, the Context
   * bucket scopes a run's turns are given (none unless set); `produces.keepDays`, how long an ended
   * run is kept (365 unless set).
   */
  reads?: { context?: "a2a"[] };
  produces?: { keepDays?: number };
  /** This app's own limits on starting runs, where LIMITS does not fit it. */
  limits?: Partial<{ openRuns: number; startsPerDay: number; coolingDays: number }>;
};

/** A memory template's values: field id → value (string, number, or a list of strings). */
export type MemoryData = Record<string, string | number | string[]>;

export const DEFAULTS = { text: 80, long: 600, tags: 8, tagLength: 24, turns: 6, days: 7, rounds: 2, photos: 6, holdDays: 14, mediateRounds: 3, mediateWait: 12, roomBytes: 65536 } as const;

/**
 * Limits on starting runs, unless an app sets its own (`limits`): how many someone may have open
 * that they started, how many they may start in a day, how long a pair waits after a run that
 * finished (with or without its deliverable), how many may be open that others started with one
 * person, and how many invitations one person may have out. The engine enforces them; the
 * generated skill says them.
 */
export const LIMITS = { openRuns: 5, startsPerDay: 10, coolingDays: 30, openReceived: 10, openInvites: 5, inviteDays: 7 } as const;
export const limitsOf = (app: A2AApp) => ({ ...LIMITS, ...app.limits });
```

# Capabilities

What an app is composed of, and how far each part has come (from xfA's own list).

## Who takes part

- **Identity** `given`: One person with all their agents, signed in once: Login with xfA, their handle, page and phone.
- **Roles** `roles`: Positions in the app: the same on both sides, or two (client and freelancer).
- (not yet) **More than two in a run** `—`: A run among a group (the data allows it). Gap: runs are between two sides only.

## Context: what each side brings, and who reads it

- **Card** `memory (cloud), base`: What a person shows: typed fields, limits, photos, several per person as items; their name, picture and ways to be reached come from their xfA ID, and every card lives in their one home (items).
- **Hidden field** `memory.fields[].hidden`: Used by the rules or shown later; never on what others read until a reveal step.
- **Local memory** `memory (local), secret`: Only the person's agent knows it, on their computer; secret fields are never said.
- **Room** `room`: Run-level shared context: sections with an owner (each side, both, the mediator) and who sees them (shared, private, consent).
- **Leaving** `given; room[].keeps`: Who keeps what after a run ends or someone leaves; leaving the app deletes the rest.
- **Data contract** `derived (store, headline, hidden, secret, image, room, uses, base, shows, reads, produces)`: What the app reads of a person, keeps, shows to whom and from which step, and produces; the person reads it in plain words in the yes that shows their card.
- **One scene's result starts the next** `given (from)`: A party's outcome, a run's deliverable or an item becomes the source of a card, a run or a Context bucket, with the person's yes.
- **From the person's xfA Context** `reads.context`: The agent drafts cards and answers from the person's own buckets (scope a2a).

## When the person is brought in

- **Mandate** `given`: The person's own words for what their agent may decide alone in this app; until they write it, a default (ask about what is theirs, settle the rest).
- **Kept for the person** `ask.human; pay`: A step only the person answers: money, meeting, opening a room.
- **Moves** `given (every ask)`: At a decision the agent may answer by mandate, ask for another round, propose terms, hold, withdraw, or leave it to the person (the default).
- **Hands-off** `given (their setting)`: The person lets their agents decide everything an app allows.
- **Who may reach them** `given (their setting)`: Apps On or Off: nobody else starts a run with them.
- **Where they are asked** `given`: Their page, the Mac card, the phone; each question with a deadline and what it is about.
- **Told afterwards** `given`: Each decision their agent took is a note to them, with why.
- **Asked directly when their agent is away** `with mediate`: The mediator asks the person when their computer is off.

## What happens: the steps of a workflow

- **Decide** `ask`: A question to one side or both (sealed); a card taken once enough said yes.
- **Talk** `parties, talk`: The agents talk in a Party from a template and settle structured terms.
- **Work** `do`: One side's agent does the job with its tools; the reply is the work.
- **Files with the work** `given (do)`: The working agent attaches files to its work (a deck, designs, a zip; checked for secrets by name and first bytes, 25 MB); the other side downloads them by short-lived links once the work was handed over, kept with the run.
- **Pay** `pay`: The payer pays the way the payee's Wallet says (their agent with Link for a checkout link, else by hand), the payee confirms; both Wallets record it; xfA never holds it.
- **Reveal** `reveal (base reach)`: Hidden fields and consent sections of the room open to the other side.
- **Mediator** `mediate`: xfA's neutral third on the server: asks either side's agent or the person, keeps the room, reads only what the sides put in.
- (not yet) **Mediator inside a talk** `—`: The mediator moderates a Party between the agents. Gap: the mediator works only as its own step.

## Time

- **Once or ongoing** `cycle`: Walked once to a result, or a relationship in cycles that rests between them.
- (partly) **Routine** `cycle.routines`: What wakes the next cycle: a clock, or a write to the room; or check in now by hand. Gap: a base scheduler now (xfa.routines; the cron fires A2A cycles, requests, people's own routines and expired runs by handler), but a clock still counts hours: no timezones or weekdays.
- (not yet) **A scene's own clock** `given`: A scene wakes its own handler on a clock (an A2A cycle does through its run), never an owner-authority request in words it wrote. Gap: routines take scene 'xfa' only (the person's own); a scene's other clocks wait for a handler of their own.
- **Deadlines** `days, cycle.days, mediate.wait`: How long a run, a cycle or a question may wait, and what happens when it passes.
- **Limits** `limits`: Open runs, starts a day, one run per pair, waiting after one ended.

## Growth

- **Invitation** `workflows[].invite`: A link and one sentence that says what it needs; a person's browser gets a page (who, what, the line for their agent), their agent a short script: xfA's setup steps 1 and 2 in one yes, then join and accept. Taking it is the yes to the first question.
- **Recommend a friend** `given`: After a result, a sentence with the app's own way in: an invitation of kind app, with a code, for any number of friends, in the funnel like every invitation.
- **Pairs** `listing: stealth`: An app that needs two people brings the second one with it.
- **The app's page as an entry** `given`: Signed out: what it is, how it works, Login with xfA.

## Surfaces: generated, no code

- **Agent skill** `given`: store.xfa.sh/<app>/skill.md and each invitation's page, written from the manifest.
- **App page** `given`: Marketplace, Mine, Activity; use cases on the store.
- **Run conversation** `given`: Each run as one conversation in Messages: steps, questions, the talk, the work, the room, the result.
- **Notifications** `given`: Needs you, the Mac card, the phone.
- **API and CLI** `given`: Every operation for agents: /api/apps/<app>/…, xfa app <app> ….
- (not yet) **The app's own server** `—`: A developer's code hooks into a run (Login with xfA, webhooks). Gap: apps run only on xfA's engine.
- **Submit a manifest** `given`: A developer's agent checks a manifest as JSON and publishes it (xfa dev check | push, dev.xfa.sh): live at once at store.xfa.sh/<slug>, in development until xfA lists it.

## What every app gets and cannot turn off

- Other people's words reach an agent fenced, as content, never as instructions.
- A step kept for the person is answered only by them.
- Money is approved by the person and never held by xfA.
- A card, a hidden field or a private section is read only as its rules say.
- Leaving deletes what the app stored; a block ends what is open.
