Files
openprose__prose/packages/std/controls/fallback-chain.md
irl-dan 63c8ce4b23 Migrate std and co into packages/ monorepo layout
Move the standard library (previously `github.com/openprose/std`) and
the company-as-prose library (previously local dir
`/Users/sl/code/openprose/company-as-prose`) into this repository
under `packages/std/` and `packages/co/` respectively.

Motivation: concentrate attention on one repository so stars,
discussions, and issues aggregate. `openprose/std` was a genuinely
useful asset nobody could find because ~50 stars on its own repo
told a much worse story than 1,106 stars on `openprose/prose`
would have. Same logic applies to co.

Layout choice: `packages/{std,co}/` rather than `std/` + `co/` at
the repo root. Root siblings accumulate over time, and a flat
top-level made future libraries (hub, kit, dev, etc.) compete with
spec infrastructure. `packages/` is bounded — new libraries land
there without cluttering root. Matches pnpm/Deno/monorepo tooling
conventions.

Changes:

- New: `packages/std/` (copied from openprose/std, `.git` excluded)
- New: `packages/co/` (copied from company-as-prose, README updated
  for new name and location)
- `packages/std/README.md`: updated shorthand expansion reference
- `packages/co/README.md`: renamed "company-as-prose" to "co" in
  self-references, added std-vs-co split heuristic, updated
  `prose run` examples to use `co/` shorthand
- `skills/open-prose/deps.md`:
  - `std/` shorthand now expands to
    `github.com/openprose/prose/packages/std/`
  - `co/` shorthand added, expands to
    `github.com/openprose/prose/packages/co/`
  - Both shorthands resolve into the same clone of
    `openprose/prose` under `.deps/github.com/openprose/prose/`;
    `packages/std/` and `packages/co/` are sibling subdirectories
  - `prose.lock` format row updated to `host/owner/repo sha`
    (matches the explicit-host resolution that landed in #35)
  - `.deps/` directory structure example redrawn to show the
    `{host}/{owner}/{repo}/packages/{std,co}/...` shape
  - Error message and Interaction tables refreshed accordingly
  - Resolution Algorithm step 1 now documents both shorthands
- `skills/open-prose/forme.md`: Step 2 resolution list updated to
  include co/ shorthand and the host-keyed `.deps/` layout
- `skills/open-prose/help.md`: Q&A "How do dependencies work?"
  rewritten to name both shorthands and the new explicit-host model
- `skills/open-prose/state/filesystem.md`: `.deps/` directory
  tree re-drawn to include the `{host}/` layer with std/co as
  sibling packages inside the clone
- `skills/open-prose/guidance/patterns.md`,
  `skills/open-prose/guidance/antipatterns.md`: examples now use
  the `std/` shorthand or explicit `github.com/...` rather than
  the deprecated bare `openprose/std/` form
- `skills/open-prose/examples/registry-import/index.md`: sample
  service reference switched to `std/evals/inspector`
- `README.md`: Stdlib badge replaced with internal `packages/std/`
  link plus new `packages/co/` link; "Standard Library" section
  rewritten as "Libraries" with both packages described

Out of scope for this PR (deliberately):
- Tooling changes to `prose install` and Forme's `.deps/` writer
  to adopt the `{host}/{owner}/{repo}/` layout in practice — this
  PR is spec-only. Existing tooling continues to work against
  whatever layout it currently implements; the spec now describes
  the target.
- Retiring the `openprose/std` GitHub repo. Delete that repo
  separately after this merges.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 14:14:31 -07:00

4.0 KiB

name, kind
name kind
fallback-chain composite

Fallback Chain

Try delegate A. If it fails, try B. If B fails, try C. Ordered list of fallbacks with decreasing preference.

Metadata

  • version: 0.1.0
  • role: coordinator

Runtime

  • state.reads: &controlState
  • state.writes: &controlState

Slots

  • chain

Shape

  • self: try each delegate in order, return first success, stop on success
  • delegates:
    • fallback_1..fallback_N: attempt the task
  • prohibited: trying the next fallback when the current one succeeded

Requires

  • &controlState exists at __controlState with: chain: string[] -- ordered list of component names (first = most preferred) task_brief: string -- the task (same brief goes to each attempt) failure_criteria: string -- (optional) declarative description of what constitutes failure. Default: only thrown errors count as failure.

Ensures

  • Delegates are tried sequentially in chain order
  • First successful result is returned immediately — no further delegates are tried
  • Each delegate receives the ORIGINAL brief — no failure context from prior attempts
  • If all delegates fail: return null with full failure history
  • &controlState.result contains the winning result (or null)
  • &controlState.winner contains the winning delegate's name (or null)
  • &controlState.attempts contains the number of delegates tried
  • &controlState.failure_history contains reasons for each failed attempt

Delegation

const { chain, task_brief, failure_criteria } = __controlState;
const failures = [];

for (let i = 0; i < chain.length; i++) {
  try {
    const result = await rlm(task_brief, null, { use: chain[i] });

    // Evaluate failure_criteria if provided
    if (failure_criteria) {
      const evalBrief = `Does this result satisfy the failure criteria?\n\nCriteria: "${failure_criteria}"\n\nResult:\n${String(result).slice(0, 2000)}\n\nRespond with JSON: { "is_failure": true/false, "reason": "..." }`;
      const evalResult = await rlm(evalBrief, null, {});
      try {
        const jsonMatch = String(evalResult).match(/\{[\s\S]*"is_failure"[\s\S]*\}/);
        const evaluation = JSON.parse(jsonMatch[0]);
        if (evaluation.is_failure) {
          failures.push({ delegate: chain[i], reason: evaluation.reason });
          continue;
        }
      } catch {
        // Unparseable evaluation — treat as success (fail open)
      }
    }

    __controlState.result = result;
    __controlState.winner = chain[i];
    __controlState.attempts = i + 1;
    __controlState.failure_history = failures;
    return(result);
  } catch (e) {
    failures.push({ delegate: chain[i], reason: e.message });
  }
}

// All delegates failed
__controlState.result = null;
__controlState.winner = null;
__controlState.attempts = chain.length;
__controlState.failure_history = failures;
return(null);

Notes

Each delegate receives the original brief with no modification. Unlike retry-with-learning, fallback-chain does not pass failure context between attempts — each delegate gets a clean start. The assumption is that different delegates have different capabilities, not that the same delegate needs better instructions.

The chain order expresses preference. Put the best, most capable, or cheapest delegate first. Later delegates are fallbacks for when preferred options fail.

Different from retry-with-learning: retry retries the SAME component with enriched failure context. Fallback-chain tries DIFFERENT components with the original brief. A search service that returned no results should be retried with broader terms (retry-with-learning). A search service that is down should be replaced with an alternative (fallback-chain).

Different from race: race tries all candidates simultaneously. Fallback-chain tries them sequentially, only advancing on failure. Use fallback-chain when later candidates are expensive and should only run if cheaper ones fail. Use race when all candidates are worth trying in parallel.