# Dependent actions and recovery

> Compose primitive actions on confirmed settlement, inspect captured inputs, handle each failed leg and retain uncertain submissions through recovery.

[Canonical documentation](https://flowscript.ftw.trade/docs/action-recovery)

Put a dependent action inside its predecessor's `.onFill`, and give each
forward and recovery action its own failure handlers. A group of actions is
not an atomic transaction. A later failure does not undo an earlier fill.

This guide describes an external application embedding Flowscript with
`requestRun`. The standalone CLI emits unsent intents and does not execute
these requests. ftw.trade's protocol followers have their own execution and
reconciliation paths; compiling this syntax does not install a correlated
settlement executor in that product.

ftw.trade's native protocol and spot paper books execute these continuations
at the modeled fill or refusal. Its multi-book paper runner supports the
native onchain protocol actions and market spot actions (`paper.buy`,
`buyPct`, `sell`, `sellPct`). Other book and service actions with outcome
handlers are refused at strategy load because that runner does not report
their settlement. A standalone host or paper broker has its own declared
consumers and support boundary.

Protocol account/source reads in a same-bar paper continuation still reflect
the bar's recorded observation. Use the current `fill` and `outcome.args`
for immediate continuation inputs; do not assume an earlier action refreshed
an account source. A spot outcome chain that fires during
warm-up is refused: replay the actual entry with `startBar: 0` instead of
replacing an earlier position with a synthetic fill. Live native protocol
outcome chains are refused until the hosted follower can deliver correlated
chain-settlement evidence. External hosts can use `requestRun` today.

## Choose the actual primitive actions

On ftw.trade, `get_module_help { id: "aerodrome" }` lists every advertised
native read/action and imported helper. Use `member` to inspect one method's
argument names, units, required alternatives, scope and docstring. The same
information drives editor hover and signature help. There is no runtime
`aerodrome.help()` method. Read the protocol skill for custody, supported
markets, pending states and which observation permits the next step.

In another host, inspect that application's capability manifests and
registered consumers. A manifest declares an interface; it does not submit
transactions or authorize spending. Public Rust functions are not
automatically Flow methods. Importing a helper library does not register
additional native actions.

Keep user-composable primitives available when publishing a convenience
wrapper: the individual swap, approval, supply, withdrawal, stake and claim
operations, with their distinct units and settlement boundaries. A wrapper's
internal transactions may partially succeed. Preserve each submitted
transaction/request identity and confirmed result if a later internal step
fails; a thrown exception alone does not establish that nothing happened.

The hosted follower persists structured interruption receipts and checks
known pending hashes. It stops for reconciliation after a partial or
uncertain batch; it has no automatic rollback or resume command. This is
not a durable pre-submission journal: a process crash between a send and
checkpoint persistence still requires transaction/account reconciliation.
Its interruption hold covers that run's books and market changes. It is not
a shared account lease across independent runs using the same wallet.

## Sequence on settlement

These statements request two operations; the second has no success
dependency on the first:

```text
await venue.first({ amount: 100 });
await venue.second({ amount: 100 });
```

`await` controls strategy pausing at the execution coordinator. It is not an
imperative promise binding between adjacent statements. Nest the dependent
operation instead:

```text
await venue.first({ amount: 100 }, timeout: "30s")
  .onFill {
    await venue.second({ amount: fill.amountOut }, timeout: "30s")
      .onFill { audit.note({ message: "completed" }); }
      .onRevert {
        await venue.release({ amount: outcome.args.amount }, timeout: "30s")
          .onFill { audit.note({ message: "recovered" }); }
          .onRevert { audit.note({ message: "recovery failed: " + outcome.reason }); }
          .onAbort { audit.note({ message: "recovery refused: " + outcome.reason }); }
          .onTimeout { audit.note({ message: "recovery still pending" }); };
      }
      .onAbort { audit.note({ message: "second refused: " + outcome.reason }); }
      .onTimeout { audit.note({ message: "second still pending" }); };
  }
  .onRevert { audit.note({ message: "first failed: " + outcome.reason }); }
  .onAbort { audit.note({ message: "first refused: " + outcome.reason }); }
  .onTimeout { audit.note({ message: "first still pending" }); };
```

The names above are illustrative host capabilities. The complete
[runnable example](https://github.com/johnny-emp/flywheel/tree/main/flowscript/examples/action-recovery)
declares its own host and covers three forward legs with recovery for both
reverts and aborts. It uses invented outcomes in an isolated scenario, not
recorded or live protocol evidence.

The host must supply the authoritative output amount before advancing a
branch that spends `fill.amountOut`. It must also check token identity,
custody, fees, outstanding debt, liquidity and current authorization. A
number alone does not establish that two protocols can share that asset.
Size an unwind from confirmed positions and the operation's contract; do not
withdraw an entire shared account merely because one strategy leg failed.
For example, an Aave supply's moved underlying units need not equal the
immediately withdrawable aToken balance because scaled balances round.
Read the protocol's available balance and sizing contract; `all: true`
is appropriate only when withdrawing that whole position is the intended
policy. A reported amount is not a universal spendable claim across actions.

## What a handler can inspect

| Read | Meaning |
| --- | --- |
| `fill.amountOut`, `fill.price` | Authoritative evidence supplied for this action's fill. Available in `.onFill`; these describe the operation being handled, not an enclosing action. |
| `outcome.reason` | The host's reason for this action's reported event. |
| `outcome.args.amount` | The evaluated original `amount` of this action, captured when its decision fired. It is not recalculated from current inputs and is not the output of the failed action. |
| Ordinary declared reads | The observations at the watermark handling the event. They may differ from the observations that triggered the action. |

`outcome.args` has this action's captured evaluated inputs. A capability's
manifest selects these with `evaluated`; contract parameters and built-in
evaluated fields also appear in `Decision.values`. Inspect the compiled
action's `typed_args` for the exact names and `program.outcomeArgsSites` for
the sites whose handlers consume them. Static unevaluated arguments are not
available, and unknown fields are compile errors. A nested action's handler has its own
outcome scope; an enclosing `fill`, task `result` or `progress` is not an
implicit capture. Explicitly pass a value into the child's declared argument
when it needs to remain available for the child's recovery. Exact argument
types still apply; a `Number` does not become an exact token-unit integer.

With `requestRun`, the coordinator takes these arguments from the original
request's saved `Decision.values`. `record` refuses venue-supplied `args`:
venue evidence cannot replace what the strategy originally requested. The
journal retains those captured values across restart.

A raw `Run.report` host must pass the matching original evaluated values in
`Outcome.args` when a matching handler reads them, and must establish the
action/invocation identity itself. Missing or incorrectly typed captured
arguments are refused. The site-only raw reporting API is not a durable
request coordinator.

## Distinguish failure from an unresolved operation

| Observation | Permitted next step |
| --- | --- |
| Confirmed `fill` | Advance its `.onFill` branch using reconciled outputs and current guards. |
| Confirmed terminal `revert` | Run `.onRevert`. Determine which earlier legs still hold assets before choosing compensation. |
| Pre-submission `abort` | Run `.onAbort`; no transaction for that primitive was submitted. Earlier filled legs still exist. |
| Wait timeout | Run `.onTimeout` for the wait ending. Keep the request and its resources reserved; do not compensate or replace an unresolved order. |
| Lost acknowledgement, keeper silence or uncertain send | Reconcile the original saved submission identity. A missing answer is not a revert or abort. |
| Partial effects inside a multi-transaction wrapper | Preserve every known transaction/result and reconcile the remaining state. Do not collapse the wrapper into an all-or-nothing failure. |

`requestRun.record` accepts terminal `fill`, `revert` and `abort`. Its bounded
wait emits `wait_timeout` internally. The lower-level language also has
`.onStuck`, but `requestRun` does not accept a stuck observation as a terminal
result. A stuck or timed-out request can still settle later. An upstream
cancel is a separate operation whose acknowledgement is not proof of a
refund; GMX's skill describes its keeper-settled example.

Compensation is an explicitly requested operation that can fail, consume gas,
return a different amount or wait on settlement. It is not rollback. Choose
the policy yourself: unwind confirmed inventory, keep it and notify an
operator, or stop until a reviewed recovery is authorized. Flowscript does
not automatically retry failed legs or choose replacement trades.

## Own submission and recovery in the host

Use `requestRun` for the entire action set participating in a chain. Its
default `concurrency: "serial"` admits one unresolved managed action in that
run, including after a wait timeout. Sibling action statements still do not
become success-dependent. Use separate success handlers for dependencies.

For each `prepared` entry, `submitRequest` exposes three application hooks:
build the exact envelope, atomically save its journal and submission identity,
and send it through the authorized executor. It saves uncertainty before IO
and never retries a failed save or send. On restart restore the whole
coordinator snapshot, reconcile uncertain/submitted entries, and deliver
terminal evidence by invocation ID. An acknowledgement is not a fill.

Persist incoming evidence, its cursor and coordinator progress consistently.
Use single-writer fencing, grants, account/position exclusion and an exact
submission identity at the host boundary. A run's serial policy does not
exclude a different run from touching the same position. Actions outside
the managed action list are returned in `own`; the application must consume
them, including audit/notification decisions and their delivery failures.

Flow libraries currently contain pure helpers and contract interfaces, not
effectful functions containing actions and recovery handlers. Users can write
the explicit strategy chain, import pure calculations, or package a custom
JavaScript/TypeScript host coordinator that consumes the public primitive
actions. The [extension guide](https://flowscript.ftw.trade/docs/extending) describes that boundary.

## Test every exit from the chain

The example's npm lifecycle tests exercise success, terminal revert and
pre-submission abort at every forward leg, failed compensation, snapshot
restoration with captured arguments, duplicate old outcomes, and timeout
followed by late fill or revert. Add a failure after every internal
transaction of a real wrapper, including a failed persistence step or lost
acknowledgement. Verify that confirmed partial effects remain visible and
uncertain submissions cannot be sent again.

Use `.t.flow` for input-driven strategy decisions, invariants and explicitly
configured paper behavior. Its runner currently cannot inject external
keeper settlement or restore `requestRun` journals. Test those through the
embedding API with explicit isolated evidence, then qualify the actual
protocol path with its contract/fork tests and authorized real-chain
acceptance. Synthetic lifecycle tests establish orchestration behavior;
they do not establish a protocol's live execution or economics.
