diff --git a/docs/blueprints/flows.md b/docs/blueprints/flows.md new file mode 100644 index 0000000..a2b563f --- /dev/null +++ b/docs/blueprints/flows.md @@ -0,0 +1,264 @@ +# MVP user flows + +- **Issue:** #22 · **Date:** 2026-09-26 +- **Sources:** [vision](../vision.md) (MVP scope), [ADR-0002](../adr/0002-agent-registry-on-soroban.md) (registry), [ADR-0003](../adr/0003-payment-rail-and-custody.md) (payments), [ADR-0004](../adr/0004-agent-manifest-and-deployment.md) (manifest and deploy), [domain model](../domain/model.md) + +What the user does, step by step, including what happens when something fails. The wireframes (#23), the API contract (#8), and the app screens (#25–#28) derive from these flows. Agent Studio details (drafts, test run, edit, my agents) are added to this file by #36. + +Screen IDs reuse the app's existing screens where they exist (`puls3_flutter/lib/src/screens/`). The full list is at the end. + +**Reading the tables:** *Error / edge path* says what the user sees and where they end up. "Stays on" means no navigation; the user can retry. + +## Coverage of the MVP scope + +| MVP "In" item ([vision](../vision.md#in)) | Flows | +|---|---| +| Create an agent | F4 | +| Discover agents | F2, F3 | +| Hire and pay an agent | F1, F5 | +| Get the result and rate it | F6, F7 | + +--- + +## F1. Connect wallet + +Needed before hiring (F5), deploying (F4) or rating (F7). The app asks for it at that moment; browsing never requires a wallet. + +```mermaid +flowchart TD + A["Any screen: Connect wallet"] --> B["S09 wallet picker"] + B --> C{"Wallet extension installed?"} + C -- No --> C1["Show install link, stay on S09"] + C -- Yes --> D["Wallet asks the user to approve"] + D --> E{"Approved?"} + E -- No --> E1["Connection cancelled, back to previous screen"] + E -- Yes --> F{"Wallet on testnet?"} + F -- No --> F1["Ask to switch the wallet to testnet"] + F -- Yes --> G["Address shown in the app bar"] +``` + +| # | Screen | User action | System response | Error / edge path | +|---|---|---|---|---| +| 1 | `S09-wallet-connect` | Taps **Connect wallet** from any screen | Opens the wallet picker | — | +| 2 | `S09-wallet-connect` | Picks a wallet | Asks the wallet to connect | Wallet not installed: show its install link, stay on S09 | +| 3 | `S09-wallet-connect` (wallet popup) | Approves | Reads the public address | User rejects: "Connection cancelled", back to the previous screen | +| 4 | `S09-wallet-connect` | — | Checks the wallet's network | Not testnet: "Switch your wallet to Testnet", stay on S09 | +| 5 | `S09-wallet-connect` closes | — | Shows the shortened address in the app bar and resumes the action that asked for the wallet (F4, F5, F6 or F7) | — | + +## F2. Discover agents + +```mermaid +flowchart TD + A["S01 landing: Explore agents"] --> B["S02 marketplace loads the catalog"] + B --> C{"Catalog loaded?"} + C -- No --> C1["Error banner with Retry, stay on S02"] + C -- Yes --> D["Grid of agent cards"] + D --> E["User searches or filters by skill"] + E --> F{"Any match?"} + F -- No --> F1["Empty state: clear filters"] + F -- Yes --> G["Tap a card: go to F3"] +``` + +| # | Screen | User action | System response | Error / edge path | +|---|---|---|---|---| +| 1 | `S01-landing` | Taps **Explore agents** | Navigates to `/market` | — | +| 2 | `S02-marketplace` | — | Loads the catalog: agents registered on-chain, with name, skills, price and rating | Load fails (network, backend down): error banner with **Retry**, stay on S02 | +| 3 | `S02-marketplace` | Types in search | Filters by name and description as they type | — | +| 4 | `S02-marketplace` | Taps skill chips | Filters to agents with every selected skill | No match: empty state with **Clear filters** | +| 5 | `S02-marketplace` | Taps an agent card | Opens F3 | — | + +## F3. View agent detail + +```mermaid +flowchart TD + A["S02 card tapped"] --> B["S03 agent detail loads"] + B --> C{"Agent exists?"} + C -- No --> C1["Agent not found, link back to S02"] + C -- Yes --> D["Price, skills, reputation, on-chain identity"] + D --> E{"On-chain data reachable?"} + E -- No --> E1["Show cached data with a warning, disable Hire"] + E -- Yes --> F["Hire button enabled: go to F5"] + D --> G["View on explorer link"] +``` + +| # | Screen | User action | System response | Error / edge path | +|---|---|---|---|---| +| 1 | `S03-agent-detail` | Opens `/agent/:id` | Loads the agent: name, description, skills, price in USDC, rating and number of paid hires | Unknown id: "Agent not found" with a link back to S02 | +| 2 | `S03-agent-detail` | — | Shows the on-chain identity: agent id, owner and payment wallet, and a **View on explorer** link to the Identity Registry | Chain data unreachable: show the last known data with a warning; **Hire** is disabled until it loads | +| 3 | `S03-agent-detail` | Taps **View on explorer** | Opens stellar.expert in a new tab | — | +| 4 | `S03-agent-detail` | Taps **Hire** | Opens F5 | Agent paused (`active: false`, ADR-0004): **Hire** hidden, "Not accepting hires" | + +## F4. Create (register) an agent + +The Studio form fields map 1:1 to the manifest (ADR-0004): name, description, skills, model, system prompt, input (type and maximum characters), output (type and maximum characters), and price. Deploy steps follow ADR-0004 and ADR-0003. Test runs and drafts are detailed in #36. + +**Registration URI.** The server gives every Studio draft its own registration URI (the URL of the agent's public registration file, ADR-0004), built from a server-generated draft id. Before the builder signs `register_full`, the server calls `agent_id_by_uri` on the Identity Registry: + +- **Not registered:** continue with `register_full`. +- **Registered by this builder:** an earlier attempt already landed (for example, the app lost the confirmation). The deploy **resumes** with that `agent_id` at step 7; nothing is registered twice. +- **Registered by another address:** the server issues a **new draft id and URI**, rebuilds the transaction, and the builder signs it. + +Retrying with an unchanged URI therefore never repeats a failing `register_full`. + +```mermaid +flowchart TD + A["S07 studio: fill in the agent"] --> B{"Form valid?"} + B -- No --> B1["Inline errors, stay on S07"] + B -- Yes --> C["Deploy: S08 deploy sheet"] + C --> D{"Wallet connected?"} + D -- No --> D1["F1 connect wallet, then resume"] + D -- Yes --> E["Server stores the version and creates the agent wallet"] + E --> P{"Registration URI already registered?"} + P -- "By this builder" --> H + P -- "By another address" --> P1["Server issues a new draft URI"] + P1 --> F + P -- No --> F["Builder signs register_full"] + F --> G{"Signed and confirmed?"} + G -- "Rejected" --> G1["Deploy paused at this step, Retry"] + G -- "Tx failed" --> G2["Error with reason, Retry"] + G -- Yes --> H["Builder signs the wallet authorization for set_agent_wallet"] + H --> I{"Confirmed?"} + I -- No --> I1["Deploy paused at this step, Retry"] + I -- Yes --> J["Agent active: link to S03 and to the explorer"] +``` + +| # | Screen | User action | System response | Error / edge path | +|---|---|---|---|---| +| 1 | `S07-studio` | Fills in name, description, skills, model, system prompt, input (type, max characters), output (type, max characters) and price | Validates as they type: skills (I6, I9, I10), name (I7), description (I8), price as whole USDC stroops (I2) and greater than zero (I11), input ≤ 8,000 and output ≤ 16,000 characters (ADR-0004) | Invalid field: inline error; **Deploy** disabled | +| 2 | `S07-studio` | Taps **Test run** (optional) | Runs the draft once, no payment, no on-chain write (#36) | Daily test quota used: "Quota resets at …" | +| 3 | `S07-studio` | Taps **Deploy** | Opens `S08-deploy-sheet` with the steps listed | No wallet: F1 first, then resumes here | +| 4 | `S08-deploy-sheet` | — | Server stores the manifest version, its salted hash, and creates the agent's wallet (testnet custody, ADR-0003) | Server error: step marked failed, **Retry** | +| 5 | `S08-deploy-sheet` | — | Server checks the draft's registration URI with `agent_id_by_uri` (see **Registration URI** above) | Already registered by this builder: resume at step 7 with that `agent_id`. Registered by another address: new draft URI, then step 6 | +| 6 | `S08-deploy-sheet` (wallet popup) | Signs `register_full` | Sends it and waits for confirmation; reads the new `agent_id` from the `Registered` event | Rejects: step paused, **Retry**. Tx failed: reason shown, **Retry**. `UriAlreadyRegistered` (another deploy took the URI between step 5 and now): back to step 5, which resumes or issues a new URI | +| 7 | `S08-deploy-sheet` (wallet popup) | Signs the authorization entry for `set_agent_wallet` (`signAuthEntry`) | Server submits it with the agent account as source and waits for confirmation | Rejects or fails: step paused, **Retry** from this step (the agent stays registered) | +| 8 | `S08-deploy-sheet` | — | Publishes the registration file and activates the agent. Shows **View agent** (S03) and **View on explorer** | — | + +## F5. Hire and pay an agent + +Rail and checks follow ADR-0003: a USDC transfer to the agent's muxed address, verified by the server before the agent runs. + +```mermaid +flowchart TD + A["S03: Hire"] --> B["S04 hire sheet: task input and price"] + B --> C{"Wallet connected?"} + C -- No --> C1["F1 connect wallet, then resume"] + C -- Yes --> D{"Enough USDC and a USDC trustline?"} + D -- No --> D1["Show balance and how to get testnet USDC"] + D -- Yes --> E["Confirm: server creates the hire"] + E --> F["Wallet asks to sign the USDC transfer"] + F --> G{"Signed?"} + G -- No --> G1["Hire not paid, back to review"] + G -- Yes --> H["Server verifies the payment on-chain"] + H --> I{"All checks pass?"} + I -- "Not yet visible" --> I1["Keep checking, show Verifying"] + I -- No --> I2["Payment rejected with reason, hire stays unpaid"] + I -- Yes --> J["Agent runs: go to F6"] +``` + +| # | Screen | User action | System response | Error / edge path | +|---|---|---|---|---| +| 1 | `S04-hire-sheet` | Types the task input | Shows the price in USDC and the character limit | Input over the agent's limit: inline error, **Confirm** disabled | +| 2 | `S04-hire-sheet` | — | Checks the wallet is connected and holds enough USDC | No wallet: F1. Not enough USDC or no trustline: shows the balance and how to get testnet USDC, **Confirm** disabled | +| 3 | `S04-hire-sheet` | Taps **Confirm and pay** | Server creates the hire (hire id, price and manifest version fixed) and returns the payment: USDC contract, the agent's muxed address, amount | Server error: "Could not create the hire", **Retry** | +| 4 | `S04-hire-sheet` (wallet popup) | Signs the USDC transfer | Submits it; shows **Paying…** | Rejects: "Payment cancelled", hire stays unpaid, back to review. Tx fails (fees, balance changed): reason shown, **Retry** | +| 5 | `S04-hire-sheet` | — | Server verifies: success, sent by the USDC contract, to the agent's wallet, exact amount, this hire's id, hash not used before. Shows **Verifying payment…** | Not visible yet: keeps checking. A check fails: "Payment does not match this hire", shows the tx link, hire stays unpaid | +| 6 | `S04-hire-sheet` | — | Payment confirmed: shows the tx hash with an explorer link, and the hire moves to running. In the background, the server (the configured feedback authorizer) submits `authorize_feedback` for this hire (see F7) | Authorization tx fails: the server retries it; F7 step 2 checks it again before rating | + +## F6. Track a hire and see its result + +States follow the hire lifecycle (#10): Requested → Paid → InProgress → Delivered → Rated. + +```mermaid +flowchart TD + A["S04: payment confirmed"] --> B["S06 hire detail: InProgress"] + B --> C{"Agent finished?"} + C -- "Failed or timed out" --> C1["Hire marked failed, reason shown"] + C -- Yes --> D["Delivered: result shown"] + D --> E["Copy result, or Rate: go to F7"] + F["S05 my hires"] --> B +``` + +| # | Screen | User action | System response | Error / edge path | +|---|---|---|---|---| +| 1 | `S04-hire-sheet` | Taps **View hire** | Opens `S06-hire-detail` (`/hires/:id`) | — | +| 2 | `S06-hire-detail` | — | Shows status (Paid, InProgress), the agent, the price paid and the payment tx link | — | +| 3 | `S06-hire-detail` | Waits | Updates the status while the agent runs | Agent fails or times out: status **Failed** with the reason. Refunds are out of the MVP ([vision](../vision.md#out)): the screen says so and keeps the payment link | +| 4 | `S06-hire-detail` | — | Status **Delivered**: shows the result | Result larger than the screen: scrollable, with **Copy** | +| 5 | `S05-my-hires` | Opens `/hires` later | Lists the user's hires with status, newest first | Wallet not connected: F1 (hires are tied to the paying address) | + +## F7. Rate an agent (P1) + +Feedback rules come from the domain (I16, I17) and ADR-0002. The Reputation Registry accepts `give_feedback` only for a hire that the **configured feedback authorizer** (our server) registered first with `authorize_feedback(hire_id, agent_id, client_address)`, after verifying the payment in F5. `hire_id` is a 32-byte id the server derives from the hire record and its payment transaction. `give_feedback` must be signed by that same `client_address` (the address that paid), and it consumes the authorization, so each hire is rated once. + +```mermaid +flowchart TD + A["S06: Rate"] --> B["S10 rate sheet: score 1 to 5, optional comment"] + B --> C{"Hire delivered and not rated yet?"} + C -- No --> C1["Rate button hidden or already rated message"] + C -- Yes --> K{"Connected wallet is the address that paid?"} + K -- No --> K1["Switch to the paying wallet"] + K -- Yes --> Z{"Authorizer registered this hire with authorize_feedback?"} + Z -- No --> Z1["Server submits authorize_feedback and waits for confirmation"] + Z1 --> D + Z -- Yes --> D["Wallet asks to sign the feedback"] + D --> E{"Signed and accepted?"} + E -- "Rejected" --> E1["Rating not sent, stay on S10"] + E -- "Not authorized or already rated" --> E2["Reason shown, back to S06"] + E -- Yes --> F["Rating saved, shown on S03"] +``` + +| # | Screen | User action | System response | Error / edge path | +|---|---|---|---|---| +| 1 | `S06-hire-detail` | Taps **Rate** | Opens `S10-rate-sheet` | Hire not delivered, or already rated: **Rate** hidden, or "You already rated this hire" | +| 2 | `S10-rate-sheet` | — | Checks that the connected wallet is the address that paid, and that the server's `authorize_feedback` for this hire is confirmed on-chain. If it is missing (the background call from F5 failed), the server, as the configured authorizer, submits `authorize_feedback(hire_id, agent_id, client_address)` now and waits | Another wallet connected: "Rate with the wallet that paid". Authorization cannot be recorded: "Rating unavailable right now", **Retry**; **Send** stays disabled until it is confirmed | +| 3 | `S10-rate-sheet` | Picks a score and writes an optional comment | Validates: score 1–5, comment ≤ 500 characters | Comment too long: inline error, **Send** disabled | +| 4 | `S10-rate-sheet` (wallet popup) | Signs the feedback | Submits `give_feedback` with this `hire_id`; the contract checks and consumes the authorization in the same call | Rejects: "Rating not sent", stay on S10 | +| 5 | `S10-rate-sheet` | — | Waits for confirmation | `HireAuthorizationConsumed`: "You already rated this hire", back to S06. `HireNotAuthorized` (should not happen after step 2): back to step 2 | +| 6 | `S06-hire-detail` | — | Status **Rated**; the new score counts in the agent's rating on S03 | — | + +--- + +## Demo paths + +### Grant video, under 15 seconds (#32) + +The shortest path that shows find → pay → result. Uses the demo shell's mock wallet, so no signing popups. + +| Time | Screen | Action | +|---|---|---| +| 0–2 s | `S01-landing` | Tap **Explore agents** | +| 2–5 s | `S02-marketplace` | Tap one skill chip; the grid narrows | +| 5–7 s | `S03-agent-detail` | Tap a card, show price and rating, tap **Hire** | +| 7–11 s | `S04-hire-sheet` | Type a short task, **Confirm and pay**, payment confirmed with tx hash | +| 11–15 s | `S06-hire-detail` | Result appears | + +### Serverpod video, under 2 minutes (#4) + +Covers the five demo criteria of the [vision](../vision.md#7-demo-success-criteria), with real testnet transactions. + +| Time | Screen | Action | Vision criterion | +|---|---|---|---| +| 0:00–0:10 | `S01-landing` | One-line pitch | — | +| 0:10–0:40 | `S07-studio` → `S08-deploy-sheet` | Fill in a prompt-only agent, **Deploy**, sign twice, open the explorer on the new agent | 1 | +| 0:40–0:55 | `S02-marketplace` | The new agent appears in the catalog, served by Serverpod | 2 | +| 0:55–1:25 | `S03-agent-detail` → `S04-hire-sheet` | **Hire**, sign the USDC transfer, **Verifying payment…**, open the tx on the explorer | 3 | +| 1:25–1:40 | `S06-hire-detail` | Status moves to **Delivered**, result shown | 4 | +| 1:40–1:55 | `S10-rate-sheet` → `S03-agent-detail` | Rate 5, the rating shows on the agent | 5 | +| 1:55–2:00 | — | Closing line | — | + +--- + +## Screen IDs + +| ID | Route / kind | Exists in the app today | Used in | +|---|---|---|---| +| `S01-landing` | `/` | Yes (`landing_screen.dart`) | F2, demos | +| `S02-marketplace` | `/market` | Yes (`market_screen.dart`) | F2, F3, demos | +| `S03-agent-detail` | `/agent/:id` | Yes (`agent_detail_screen.dart`) | F3, F4, F5, F7, demos | +| `S04-hire-sheet` | Bottom sheet over S03 | Yes (`hire_sheet.dart`) | F5, F6, demos | +| `S05-my-hires` | `/hires` | No, new | F6 | +| `S06-hire-detail` | `/hires/:id` | No, new | F6, F7, demos | +| `S07-studio` | `/studio` | Yes (`studio_screen.dart`) | F4, demo | +| `S08-deploy-sheet` | Bottom sheet over S07 | Yes (`deploy_sheet.dart`) | F4, demo | +| `S09-wallet-connect` | Dialog over any screen | No, new (the app has a mock `WalletPort`) | F1, F4, F5, F6 | +| `S10-rate-sheet` | Bottom sheet over S06 | No, new | F7, demo |