skip to content
$worker

fp

v0.2.6

Lodash-style value transforms (fp::get/pick/take/…) and fp::pipe — worker-side pipelines that move big values function→function without routing them through the model.

iiiverified
341 installs18 in 7d1 today
install
$iii worker add fp
binarylicense: Apache-2.0fpfunctionalpipelinetransformlodash
  • macOS: arm64 · x64
  • Linux: arm64 · armv7 · x64
  • Windows: arm64 · x64 · x86

exact versions are immutable; binary and bundle artifacts are digest-pinned.

agent-ready brief for v0.2.6
install + config + dependencies + readme + api reference, all in one place. fetch as agent-context.md for an llm to consume.
the same content rendered as discrete blocks below is exposed as a single markdown document at /workers/fp.md. paste it into an llm prompt or pipe it through curl from a worker.

install

install
$iii worker add fp@0.2.6

dependencies

no dependencies for v0.2.6

readme

README.md

fp

Lodash-style value transforms and fp::pipe — worker-side pipelines that move big values function→function over the iii bus without routing them through the model.

Install

iii worker add fp

The worker is a deploy: binary Rust worker with no configuration.

Why

An agent that fetches a document and re-types it into the next call's arguments burns its context window and stalls the provider stream mid-call. fp::pipe runs the whole move in one call — each step triggers a function and its result lands in the next step's payload — so the value flows worker→worker and the chat only ever sees per-step sizes and a preview.

While the worker is connected it also injects a usage section into the agent system prompt via the harness pre-generate hook (fp::inject-guidance), so the guidance is presence-gated: no fp worker, no prompt text. The binding is one-shot at startup and relies on the engine's recoverable triggers (iii #1962): bound before the harness is up, it parks as a pending intent and activates when the harness registers the trigger type.

Functions

function lodash request
fp::pipe { through: [{function, payload?, into?}], preview_chars? }
fp::get _.get { value, path } — JSON pointer; a miss names the available keys
fp::pick _.pick { value, paths } — subset an object by top-level keys
fp::omit _.omit { value, paths } — drop top-level keys
fp::take _.take { value, n } — first n array elements / string chars
fp::drop _.drop { value, n } — skip the first n
fp::map _.map { value, path } — pluck a pointer from each element; misses → null
fp::filter _.filter { value, matches } — keep elements matching a partial object
fp::split _.split { value, separator }
fp::join _.join { value, separator? } (default ,)
fp::uniq _.uniq { value } — dedupe, first occurrences win
fp::size _.size { value } — array length / string chars / object key count
fp::compact _.compact { value } — remove null elements (0/false/"" are kept)
fp::nth _.nth { value, n } — element at index; negative counts from the end
fp::getOr fp.getOr { value, path, default } — pointer value, or default on a miss
fp::flatten _.flatten { value } — unnest one level
fp::sortBy _.sortBy { value, path } — stable ascending sort by a plucked pointer ("" = the element itself)
fp::reverse _.reverse { value } — reversed copy (immutable, fp-style)
fp::sum _.sum / _.sumBy { value, path? } — total; path plucks the addend from each element. Empty array totals 0
fp::mean _.mean / _.meanBy { value, path? } — arithmetic mean; an empty array errors
fp::min _.min / _.minBy { value, path? } — smallest NUMBER (not the element holding it); an empty array errors
fp::max _.max / _.maxBy { value, path? } — largest NUMBER (not the element holding it); an empty array errors
fp::groupBy _.groupBy { value, path }{ key: [elements] } bucketed by a plucked key ("" = the element itself)
fp::countBy _.countBy { value, path }{ key: count } with the same key rules

The reductions are the worker's arithmetic: all-integer inputs fold to an integer (so the result compares cleanly in a fp::when guard), a non-numeric element errors instead of being skipped — a skipped row is a total that silently covers less than the caller counted — and min/max return the number rather than lodash's …By element so a guard can compare it directly.

groupBy/countBy add the per-key axis the reductions lack: countBy gives counts in one step, and groupBy's buckets each feed fp::sum for a per-key total. Group keys must be a string, number, or boolean — lodash coerces a null or object key to "null"/"[object Object]", silently merging distinct groups into one bucket, so those error here instead.

Transforms take their input at value and return { value }. They deviate from lodash where silence would thread garbage through a pipe: a type mismatch is an error (not a silent {}); a map path matching NO element errors naming what was available (pointers pluck stored fields — no computed properties like /length; fp::size counts); compact removes only null (lodash's full-falsey removal would eat legitimate 0/false/""); nth out of bounds and size on a non-collection error instead of returning undefined/0; sortBy requires every element to carry the key with one comparable type. Sporadic map misses still become null, like lodash — fp::compact drops them.

The pipe

fp::pipe { through: [
  { function: "scrapling::fetch",
    payload:  { url: "https://…", format: "markdown", main_content_only: true } },
  { function: "fp::get",  payload: { path: "/content" } },
  { function: "fp::take", payload: { n: 20000 } },
  { function: "state::set",   payload: { scope: "research", key: "article" } }
]}
  • Step N's result lands in step N+1's payload at into (a JSON pointer, default /value — which is exactly where the transforms and state::set read their input, so most pipes need no into at all).
  • A transform step threads the transformed value itself — the { value } wrapper in the function table is only its direct-call response shape, so there is never a {value} layer to unwrap between steps.
  • The FIRST step receives no threaded value: start with a producing function (a fetch, a state::get) or seed a leading transform via payload.value. An unseeded leading transform fails validation before anything runs.
  • fp::* transform steps run inline in this worker; every other step is one bus trigger with a 120 s budget. The whole pipe must also fit the caller's own dispatch timeout on fp::pipe.
  • 1–12 steps. The response is receipts only: { steps: [{function, chars}], value_preview } (preview_chars sizes the preview, default 400, capped at 8000 — the receipt must never become the bulk channel it replaces).
  • A failing step stops the pipe; the error carries the completed-step trail.

Boundaries

  • Steps run with THIS worker's authority, not the calling agent's per-step dispatch policy — the fp::pipe call itself is the policy/approval surface (an approver sees the full step list). For that reason the pipe is not agent-callable without approval by default; the pure transforms are. See iii-permissions.yaml.
  • shell::*/coder::* steps run under the harness-forwarded filesystem scope: the harness stamps the trusted fs_scope onto the fp::pipe call itself (harness/src/filesystem_scope.rs) and fp re-stamps it onto each scoped step as the last write before dispatch, overwriting anything authored or threaded at /fs_scope. Without a stamp (no session working directory, or a non-harness caller — cron, worker-to-worker) scoped steps are refused, fail-closed. A path-access rejection inside a pipe just fails the step; only a direct call offers the access-grant ladder.
  • Statically refused as steps: engine::register_trigger/engine::unregister_trigger (need the harness trusted-session stamp), nested pipes, and every class the agent policy hard-denies — session::*/approval::*, configuration::*/oauth::* (credentials), router::*/provider::* (model spend), harness::*/run::* (turn control), stream::* and bus internals, *::on-config-change — because steps run with worker authority and must not ride past those denies. state::* is the deliberate exception: persisting the threaded value is the pipe's purpose, and the pipe call itself is the approval surface.

api reference (json)

agent-api-reference.json
{
  "functions": [
    {
      "description": "Remove null elements from an array (lodash _.compact, null-only: 0, false and \"\" are KEPT so plucked data survives): { value }. Pairs with fp::map, whose sporadic misses become null. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::compact",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Shared request for the value-only ops (uniq/size/compact/flatten/reverse).",
        "properties": {
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "value"
        ],
        "title": "ValueRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Trigger-binding guard: compare a JSON pointer inside the fired event and answer the condition contract ({ decision: \"allow\" | \"skip\", reason }). Config is { path?, op, to?, negate? } with the fp::when ops (==, !=, >, >=, <, <=, exists, not_empty); a pointer that resolves to nothing SKIPS rather than erroring, so \"not there yet\" is an ordinary answer. Use it as a binding's `conditions` entry so a reaction fires only on events that matter — it is not a fan-in gate (that is state::barrier), it filters one event at a time.",
      "metadata": {},
      "name": "fp::condition",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ConditionConfig": {
            "description": "The caller-authored half, out of the binding's `conditions[].config`.",
            "properties": {
              "negate": {
                "default": false,
                "description": "Invert the verdict. The only way to say \"fire when this is ABSENT\", since a pointer miss fails the guard and there is no `not_exists` op. Note the footgun that comes with it: a path that never resolves then fires on EVERY event.",
                "type": "boolean"
              },
              "op": {
                "allOf": [
                  {
                    "$ref": "#/definitions/WhenOp"
                  }
                ],
                "description": "One of \"==\", \"!=\", \">\", \">=\", \"<\", \"<=\", \"exists\", \"not_empty\"."
              },
              "path": {
                "default": null,
                "description": "JSON pointer into the EVENT (default: the whole event). Almost always wanted — comparing a whole event object is rarely what you mean.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "to": {
                "default": null,
                "description": "Right-hand side for the comparison ops; rejected for exists/not_empty."
              }
            },
            "required": [
              "op"
            ],
            "type": "object"
          },
          "WhenOp": {
            "description": "The comparison a `fp::when` guard runs.",
            "enum": [
              "==",
              "!=",
              ">",
              ">=",
              "<",
              "<=",
              "exists",
              "not_empty"
            ],
            "type": "string"
          }
        },
        "description": "The condition envelope. `binding` and `context` are accepted and ignored so the same function also works as a direct call.",
        "properties": {
          "condition_config": {
            "anyOf": [
              {
                "$ref": "#/definitions/ConditionConfig"
              },
              {
                "type": "null"
              }
            ]
          },
          "event": true
        },
        "required": [
          "event"
        ],
        "title": "ConditionInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "The typed answer a binding's condition contract expects.",
        "oneOf": [
          {
            "properties": {
              "decision": {
                "enum": [
                  "allow"
                ],
                "type": "string"
              },
              "reason": {
                "type": "string"
              }
            },
            "required": [
              "decision",
              "reason"
            ],
            "type": "object"
          },
          {
            "properties": {
              "decision": {
                "enum": [
                  "skip"
                ],
                "type": "string"
              },
              "reason": {
                "type": "string"
              }
            },
            "required": [
              "decision",
              "reason"
            ],
            "type": "object"
          }
        ],
        "title": "Decision"
      }
    },
    {
      "description": "Count array elements per plucked key (lodash _.countBy): { value, path: \"/writer\" } -> { \"<key>\": <count> } (`\"\"` counts by the element itself). Same key rules as fp::groupBy. Pair with fp::groupBy + fp::sum for a per-key total. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::countBy",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Shared request for the keyed groupings (groupBy/countBy).",
        "properties": {
          "path": {
            "description": "JSON pointer plucked from each element as the bucket key (`\"\"` = the element itself).",
            "type": "string"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "path",
          "value"
        ],
        "title": "GroupByRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Skip the first n elements of an array or the first n characters of a string (lodash _.drop): { value, n }. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::drop",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "n": {
            "description": "How many array elements / string characters to skip.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "n",
          "value"
        ],
        "title": "DropRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Keep array elements whose properties equal a partial object (lodash _.filter with a matches iteratee): { value, matches: { status: \"active\" } }. matches is a partial OBJECT (never a bare string/number) and only object elements can match. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::filter",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "matches": {
            "additionalProperties": true,
            "description": "Partial object an element's top-level properties must equal.",
            "type": "object"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "matches",
          "value"
        ],
        "title": "FilterRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Flatten an array one level (lodash _.flatten): [1,[2],[[3]]] -> [1,2,[3]]. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::flatten",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Shared request for the value-only ops (uniq/size/compact/flatten/reverse).",
        "properties": {
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "value"
        ],
        "title": "ValueRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Extract the single value at a JSON pointer (lodash _.get): { value, path: \"/content\" }. A miss names the keys that were available. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::get",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "path": {
            "description": "JSON pointer selecting the value to return (e.g. `/content`).",
            "type": "string"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "path",
          "value"
        ],
        "title": "GetRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Extract the value at a JSON pointer, or `default` when the path misses (lodash fp.getOr): { value, path, default }. A stored null is a hit, not a miss. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::getOr",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "default": {
            "description": "Returned when the path matches nothing (a stored null is a hit)."
          },
          "path": {
            "description": "JSON pointer selecting the value to return (e.g. `/content`).",
            "type": "string"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "default",
          "path",
          "value"
        ],
        "title": "GetOrRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Bucket array elements by a plucked key (lodash _.groupBy): { value, path: \"/writer\" } -> { \"<key>\": [element, ...] } (`\"\"` groups by the element itself). Keys must be a string, number, or boolean; a null or container key errors rather than collapsing distinct groups into one bucket. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::groupBy",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Shared request for the keyed groupings (groupBy/countBy).",
        "properties": {
          "path": {
            "description": "JSON pointer plucked from each element as the bucket key (`\"\"` = the element itself).",
            "type": "string"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "path",
          "value"
        ],
        "title": "GroupByRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Internal pre_generate hook: appends fp::pipe usage guidance to the agent system prompt. Bound to harness::hook::pre-generate at worker startup; not called directly.",
      "metadata": {
        "internal": true
      },
      "name": "fp::inject-guidance",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "GenerateContext": {
            "properties": {
              "system_prompt": {
                "default": "",
                "description": "The system prompt assembled so far (base + any prior hook's mutation).",
                "type": "string"
              }
            },
            "type": "object"
          }
        },
        "description": "The slice of the `pre_generate` hook envelope we read (lenient: ignores every other field the harness sends). The harness nests the live generation context under `generate` (see harness `HookRunner::run_pre_generate`).",
        "properties": {
          "generate": {
            "$ref": "#/definitions/GenerateContext"
          }
        },
        "title": "PreGenerateEvent",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "PreGenerateMutations": {
            "description": "The harness applies `system_prompt` only when the key is present (`HookRunner::run_pre_generate` — `if m.system_prompt.is_some()`), so `None` serializes to an empty object: the safe no-op that preserves the harness's assembled prompt.",
            "properties": {
              "system_prompt": {
                "description": "Full replacement system prompt (base + appended guidance). The harness overwrites, it does not merge.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "type": "object"
          }
        },
        "description": "Hook envelope returned to the harness: the mutations to apply to the generation.",
        "properties": {
          "mutations": {
            "$ref": "#/definitions/PreGenerateMutations"
          }
        },
        "required": [
          "mutations"
        ],
        "title": "PreGenerateResponse",
        "type": "object"
      }
    },
    {
      "description": "Join array elements into one string (lodash _.join): { value, separator } (separator defaults to \",\"). Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::join",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "separator": {
            "default": ",",
            "description": "Separator between elements (default `\",\"`).",
            "type": "string"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "value"
        ],
        "title": "JoinRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Pluck the value at a JSON pointer from each array element (lodash _.map with a property iteratee): { value, path: \"/id\" }. Sporadic misses become null (fp::compact drops them), but a path matching NO element errors — pointers pluck stored fields, not computed properties like /length (fp::size counts). Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::map",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "path": {
            "description": "JSON pointer plucked from each element (e.g. `/id`). Sporadic misses become null; a path matching NO element is an error (stored fields only — no computed properties like `/length`).",
            "type": "string"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "path",
          "value"
        ],
        "title": "MapRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Largest number in an array (lodash _.max/_.maxBy): { value }, or { value, path: \"/amount\" }. Deviates from lodash _.maxBy: returns the NUMBER, not the element holding it, so a fp::when guard can compare it directly. An empty array errors. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::max",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Shared request for the numeric reductions (sum/mean/min/max).",
        "properties": {
          "path": {
            "default": null,
            "description": "Optional JSON pointer plucked from each element before reducing, so `[{amount: 3}, …]` reduces without a separate fp::map step.",
            "type": [
              "string",
              "null"
            ]
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "value"
        ],
        "title": "ReduceRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Arithmetic mean of an array of numbers (lodash _.mean/_.meanBy): { value }, or { value, path: \"/score\" }. Deviates from lodash: an EMPTY array errors instead of yielding NaN, which would thread garbage into the next step. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::mean",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Shared request for the numeric reductions (sum/mean/min/max).",
        "properties": {
          "path": {
            "default": null,
            "description": "Optional JSON pointer plucked from each element before reducing, so `[{amount: 3}, …]` reduces without a separate fp::map step.",
            "type": [
              "string",
              "null"
            ]
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "value"
        ],
        "title": "ReduceRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Smallest number in an array (lodash _.min/_.minBy): { value }, or { value, path: \"/amount\" }. Deviates from lodash _.minBy: returns the NUMBER, not the element holding it, so a fp::when guard can compare it directly. An empty array errors. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::min",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Shared request for the numeric reductions (sum/mean/min/max).",
        "properties": {
          "path": {
            "default": null,
            "description": "Optional JSON pointer plucked from each element before reducing, so `[{amount: 3}, …]` reduces without a separate fp::map step.",
            "type": [
              "string",
              "null"
            ]
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "value"
        ],
        "title": "ReduceRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Element at index n; negative n counts from the end (lodash _.nth): { value, n: -1 } is the last element. Out of bounds errors naming the length. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::nth",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "n": {
            "description": "Index to pluck; negative counts from the end (`-1` = last element).",
            "format": "int64",
            "type": "integer"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "n",
          "value"
        ],
        "title": "NthRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Drop top-level keys from an object (lodash _.omit): { value, paths: [\"a\",\"b\"] }. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::omit",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "paths": {
            "description": "Top-level keys to drop; missing keys are ignored.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "paths",
          "value"
        ],
        "title": "OmitRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Subset an object by top-level keys (lodash _.pick): { value, paths: [\"a\",\"b\"] } -> {a, b}; missing keys are omitted. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::pick",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "paths": {
            "description": "Top-level keys to keep; missing keys are silently omitted.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "paths",
          "value"
        ],
        "title": "PickRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Run a short worker-side pipeline in one call: each step triggers a function and its result lands in the next step's payload at `into` (default \"/value\") — large values flow worker→worker and never enter the chat. fp::* transform steps (get/pick/omit/take/drop/map/filter/split/join/uniq/size/compact/nth/getOr/flatten/sortBy/reverse/when, lodash semantics) run inline and thread the transformed value itself — the {value} wrapper they return when called directly never appears between steps. A failing fp::when guard STOPS the pipe (`short_circuited: true`), so a trailing write step runs only when its condition holds. The FIRST step receives no threaded value: start with a producing function (a fetch, a state::get) or seed a leading transform via payload.value. Example: fetch an article and persist it trimmed: through=[{function:'scrapling::fetch', payload:{url,format:'markdown'}}, {function:'fp::get', payload:{path:'/content'}}, {function:'fp::take', payload:{n:20000}}, {function:'state::set', payload:{scope,key}}]. Returns per-step sizes and a preview, not the value. shell::*/coder::* steps run under the session's harness-stamped filesystem scope; without one (no working directory, or a non-harness caller) they are refused — call them directly instead. Database write steps are refused only while `approval::gate` is registered.",
      "metadata": {},
      "name": "fp::pipe",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "PipeStep": {
            "additionalProperties": false,
            "properties": {
              "function": {
                "description": "Function id to trigger (e.g. `scrapling::fetch`, `fp::get`, `state::set`).",
                "type": "string"
              },
              "into": {
                "default": null,
                "description": "JSON pointer in THIS step's payload where the previous step's value lands (default `/value`). Object keys only (`~0`/`~1` escapes are decoded); indexing into arrays is unsupported.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "payload": {
                "default": null,
                "description": "Base payload for the call."
              }
            },
            "required": [
              "function"
            ],
            "type": "object"
          }
        },
        "properties": {
          "preview_chars": {
            "default": null,
            "description": "Preview length of the final threaded value in the receipt (capped at 8000).",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "through": {
            "description": "Steps run in order; step N+1 receives step N's threaded value.",
            "items": {
              "$ref": "#/definitions/PipeStep"
            },
            "type": "array"
          }
        },
        "required": [
          "through"
        ],
        "title": "PipeRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "StepReceipt": {
            "properties": {
              "chars": {
                "description": "Size of the value threaded OUT of this step (chars of its string or serialized form).",
                "format": "uint",
                "minimum": 0,
                "type": "integer"
              },
              "function": {
                "type": "string"
              },
              "note": {
                "description": "Set when the threaded value REPLACED a non-null literal already in this step's payload at the `into` pointer — almost always an omitted `into` on a write step clobbering the value it meant to write.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "chars",
              "function"
            ],
            "type": "object"
          }
        },
        "properties": {
          "short_circuited": {
            "description": "True when a `fp::when` guard failed and stopped the pipe: the receipts end at the guard and NO later step ran. Absent (false) on a full run.",
            "type": "boolean"
          },
          "steps": {
            "items": {
              "$ref": "#/definitions/StepReceipt"
            },
            "type": "array"
          },
          "value_preview": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "short_circuited",
          "steps"
        ],
        "title": "PipeResponse",
        "type": "object"
      }
    },
    {
      "description": "Reverse an array (lodash _.reverse, immutable like lodash/fp): { value }. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::reverse",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Shared request for the value-only ops (uniq/size/compact/flatten/reverse).",
        "properties": {
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "value"
        ],
        "title": "ValueRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Count a collection (lodash _.size): array length, string chars, or object key count: { value }. Non-collections error instead of returning 0. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::size",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Shared request for the value-only ops (uniq/size/compact/flatten/reverse).",
        "properties": {
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "value"
        ],
        "title": "ValueRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Sort array elements ascending by the value at a JSON pointer (lodash _.sortBy, stable): { value, path } — path \"\" sorts the elements themselves (primitives). Keys must all be numbers, strings, or booleans; an element missing the path errors. Pair with fp::reverse for descending. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::sortBy",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "path": {
            "description": "JSON pointer plucked from each element as the sort key (e.g. `/score`).",
            "type": "string"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "path",
          "value"
        ],
        "title": "SortByRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Split a string into an array of strings (lodash _.split): { value, separator: \"\\n\" }. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::split",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "separator": {
            "description": "Non-empty separator to split on (e.g. `\"\\n\"`).",
            "type": "string"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "separator",
          "value"
        ],
        "title": "SplitRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Total an array of numbers (lodash _.sum/_.sumBy): { value }, or { value, path: \"/amount\" } to pluck the addend from each element first. An empty array totals 0; a non-numeric element errors rather than being skipped. All-integer inputs total to an integer, so the result compares cleanly in a fp::when guard. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::sum",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Shared request for the numeric reductions (sum/mean/min/max).",
        "properties": {
          "path": {
            "default": null,
            "description": "Optional JSON pointer plucked from each element before reducing, so `[{amount: 3}, …]` reduces without a separate fp::map step.",
            "type": [
              "string",
              "null"
            ]
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "value"
        ],
        "title": "ReduceRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Keep the first n elements of an array or the first n characters of a string (lodash _.take): { value, n }. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::take",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "n": {
            "description": "How many array elements / string characters to keep.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "n",
          "value"
        ],
        "title": "TakeRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Deduplicate an array, keeping first occurrences (lodash _.uniq): { value }. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::uniq",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Shared request for the value-only ops (uniq/size/compact/flatten/reverse).",
        "properties": {
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "value"
        ],
        "title": "ValueRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Guard: test the value at a JSON pointer ({ value, path?, op, to? }; ops ==, !=, >, >=, <, <=, exists, not_empty; path defaults to the whole value; a pointer miss FAILS the guard, it never errors). Direct calls return { passed, value }. As a fp::pipe step, a passing guard threads the ORIGINAL value onward unchanged and a failing one STOPS the pipe (`short_circuited: true`), so a trailing write step (e.g. state::set) runs only when the condition holds.",
      "metadata": {},
      "name": "fp::when",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "WhenOp": {
            "description": "The comparison a `fp::when` guard runs.",
            "enum": [
              "==",
              "!=",
              ">",
              ">=",
              "<",
              "<=",
              "exists",
              "not_empty"
            ],
            "type": "string"
          }
        },
        "properties": {
          "op": {
            "allOf": [
              {
                "$ref": "#/definitions/WhenOp"
              }
            ],
            "description": "One of \"==\", \"!=\", \">\", \">=\", \"<\", \"<=\", \"exists\", \"not_empty\"."
          },
          "path": {
            "default": null,
            "description": "JSON pointer selecting what to test (default: the whole value).",
            "type": [
              "string",
              "null"
            ]
          },
          "to": {
            "default": null,
            "description": "Right-hand side for the comparison ops; meaningless (and rejected) for exists / not_empty."
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "op",
          "value"
        ],
        "title": "WhenRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "passed": {
            "type": "boolean"
          },
          "value": {
            "description": "The ORIGINAL input value, untouched — guards test, they never reshape."
          }
        },
        "required": [
          "passed",
          "value"
        ],
        "title": "WhenResponse",
        "type": "object"
      }
    }
  ],
  "triggers": []
}