From c001b0ae7f753cd3b7a2650b2dd5f07172863829 Mon Sep 17 00:00:00 2001 From: Mike Clay Date: Thu, 6 Aug 2026 13:00:31 +0100 Subject: [PATCH 1/5] Teach a worker to fetch cheaply, and a dispatch to say what it is doing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fetch guidance: resource ids come from the delivery's own maps and refs, so an unresolvable one is a defect to report rather than a spelling to search for; a fetch hands over the whole composed body, with the measured range from one real run; a repeat under the same agent_id comes back as a marker, so it costs the round trip rather than the body; and a shared block inside one activity response may be a marker whose bytes an earlier entry of that response carries. The batch standing is read from the batch: block leading each get_activity response, which the three places that name it now say. A dispatch announces itself before it spawns — what is running, the gate the user's answer is next needed at, and how long a comparable dispatch took — and no minute of a run is silent, including the waits the orchestrator imposes between dispatches. --- meta/techniques/workflow-engine/TECHNIQUE.md | 12 ++++++++++-- .../workflow-engine/activity-worker.md | 6 +++--- .../workflow-engine/dispatch-activity.md | 17 ++++++++++++----- .../workflow-engine/finalize-activity.md | 4 ++-- meta/workflow.yaml | 2 +- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/meta/techniques/workflow-engine/TECHNIQUE.md b/meta/techniques/workflow-engine/TECHNIQUE.md index 1a425d75f..dc4f8fd8e 100644 --- a/meta/techniques/workflow-engine/TECHNIQUE.md +++ b/meta/techniques/workflow-engine/TECHNIQUE.md @@ -1,6 +1,6 @@ --- metadata: - version: 6.10.0 + version: 6.11.0 --- ## Capability @@ -25,6 +25,14 @@ Client walks dispatch workers via [dispatch-activity](./dispatch-activity.md), e Resource refs returned in operation bodies (e.g. `planning-readme`) are lightweight pointers. When `get_activity` includes a sibling `resources` map, reuse those bodies (or unchanged markers). Otherwise load via `get_resource { session_index, resource_id }`. +The ids come from the delivery: the `resources` map keys, `resource_refs`, and the refs in the operation bodies this response carried, each already qualified. Pass one of those verbatim. A ref that will not resolve is a definition defect to report, not a spelling to search for — an id guessed under another workflow prefix or another slug spelling costs a round trip and returns an error, and the id that would have worked was in the response already. + +### fetch-costs-what-it-delivers + +A fetch hands over the whole composed body, so ask for what a step needs and reuse what a response already carried. Measured on one real run, lazy technique fetches ran 5,242 to 15,126 characters apiece and lazy resource fetches 1,426 to 14,980. + +Two things make a second ask cheap rather than free. A repeat under the same `agent_id` comes back as an unchanged marker, because the ledger records that this context received those bytes — so a repeat costs the round trip, not the body, and the marker is the expected answer rather than an error. And within one `get_activity` response, a shared contract or rules block may arrive as a marker whose bytes an earlier `step_techniques` entry of that same response carries in full; read it from there. Where content has genuinely left this context, [force-full-after-summarization](#force-full-after-summarization) is how to get it back. + ### resource-section-or-whole Choose bare vs `#section` `resource_id` by how much of the resource this agent context will need. Prefer a `#section` anchor when the current step needs a single slice of a large resource. When the same agent context will need two or more sections from the same resource in the current activity (or in the immediate next steps of that activity), call `get_resource` once with the bare resource id and reuse that content — do not issue repeated section fetches for the same file. Bare and `#section` ids are distinct delivery keys: loading sections does not populate the whole-resource key, and loading the whole file does not collapse a later section fetch under a different key. In the eager `resources` map the file takes precedence — a bundled whole resource carries its own sections, so a technique citing both ways receives the file alone and its sections are read out of that body rather than fetched again. Unchanged-references and `full: true` follow [force-full-after-summarization](#force-full-after-summarization). @@ -47,4 +55,4 @@ Before executing any step, confirm the activity `id` returned by the `get_activi ### progressive-step-technique-load -A step's bound technique loads as that step is reached; the whole activity is never pre-fetched. `get_technique { session_index, step_id }` serves steps not already inlined, and where `get_activity` carries `step_techniques` or a sibling `resources` map, those response notes govern — begin-beat, reuse map, lazy remainder — rather than bundling policy re-derived in prose. +A step's bound technique loads as that step is reached; the whole activity is never pre-fetched. `get_technique { session_index, step_id }` serves steps not already inlined, and where `get_activity` carries `step_techniques` or a sibling `resources` map, those response notes govern — begin-beat, reuse map, lazy remainder — rather than bundling policy re-derived in prose. An inlined step is read from the bundle; re-fetching it pays the round trip for content the response already delivered ([fetch-costs-what-it-delivers](#fetch-costs-what-it-delivers)). diff --git a/meta/techniques/workflow-engine/activity-worker.md b/meta/techniques/workflow-engine/activity-worker.md index 25cfad881..249d26557 100644 --- a/meta/techniques/workflow-engine/activity-worker.md +++ b/meta/techniques/workflow-engine/activity-worker.md @@ -1,6 +1,6 @@ --- metadata: - version: 1.5.0 + version: 1.6.0 --- ## Capability @@ -35,7 +35,7 @@ Worker agent identity for this dispatch. - Confirm the activity `id` on the `get_activity` response whose operations bundle delivered this technique equals `{activity_id}` per [verify-dispatched-activity](./TECHNIQUE.md#verify-dispatched-activity) - Follow the operations bundle and delivery notes on that same response (`step_techniques_note`, `resources_note`, reference-mode notes) -- Read `may_continue` from `_meta.batch` on that response — this context's standing against its bound ([batch-ends-where-the-server-says](#batch-ends-where-the-server-says)) +- Read `may_continue` from the `batch:` block leading that response — this context's standing against its bound ([batch-ends-where-the-server-says](#batch-ends-where-the-server-says)) ### 2. Load resources @@ -81,4 +81,4 @@ The last thing this context emits is the envelope this activity owes — the `ch ### batch-ends-where-the-server-says -`_meta.batch` on each `get_activity` reports how many activities this context has taken, what it has been delivered, and whether it may take another. On `may_continue: false`, finish the current activity and report it — do not ask for a further one. If you do ask, the server refuses with the payload undelivered: report that activity as needing its own dispatch and stop. +Each `get_activity` opens with a `batch:` block reporting how many activities this context has taken, what it has been delivered, its two limits, and whether it may take another; `_meta.batch` carries the same reading. On `may_continue: false`, finish the current activity and report it — do not ask for a further one. If you do ask, the server refuses with the payload undelivered: report that activity as needing its own dispatch and stop. diff --git a/meta/techniques/workflow-engine/dispatch-activity.md b/meta/techniques/workflow-engine/dispatch-activity.md index 07bd61b3a..a0f60e447 100644 --- a/meta/techniques/workflow-engine/dispatch-activity.md +++ b/meta/techniques/workflow-engine/dispatch-activity.md @@ -1,6 +1,6 @@ --- metadata: - version: 1.17.0 + version: 1.18.0 --- ## Capability @@ -51,12 +51,13 @@ Opaque HMAC-signed trace token from the `next_activity` response `_meta.trace_to - **`step_manifest`:** a dispatch whose activity ran steps carries one manifest entry per completed step — the server validates step completion against it, and reports a gap when it is absent. A first dispatch has no prior worker context to attribute it to, so `agent_id` is omitted here; a continuation names one ([continue-batch](./continue-batch.md)). - **Trace accumulate (required):** when `_meta.trace_token` is present, append it to `trace_tokens[]`. Tokens stay opaque — no routine per-activity `get_trace`. Live `_meta.validation` self-correct remains; do not resolve tokens mid-run (close-out resolve is [resolve-trace-at-close-out](#resolve-trace-at-close-out)). 3. Mint `{worker_agent_id}` for this dispatch per [delivery-keys-on-agent-context](#delivery-keys-on-agent-context), then apply [compose-prompt](./compose-prompt.md) with `{agent_technique}`, `holds_prior_deliveries: false` (a minted identity holds nothing), and `{state}` as substitutions (include `session_index`, `workflow_id`, `activity_id`, and `{worker_agent_id}` as `agent_id`). -4. Apply [harness-compat](../harness-compat/TECHNIQUE.md)::[spawn-agent](../harness-compat/spawn-agent.md) with the composed prompt; await the worker's envelope and return it unchanged as `{worker_result}`. +4. Tell the user what is about to run and what it will cost them in waiting: the activity's name, the next gate their answer is needed at (the first checkpoint of that activity, or that the activity runs to completion without one), and the elapsed figure of the last comparable dispatch where the session record has one. A dispatch is minutes of silence otherwise ([say-what-a-dispatch-is-doing](#say-what-a-dispatch-is-doing)). +5. Apply [harness-compat](../harness-compat/TECHNIQUE.md)::[spawn-agent](../harness-compat/spawn-agent.md) with the composed prompt; await the worker's envelope and return it unchanged as `{worker_result}`. > When the harness reports the worker ended without returning an envelope, dispatch a fresh worker for the same `{activity_id}`, which mints its own identity. > When the harness still reports the worker live and what came back is not an accepted result ([reject-partial-worker-result](#reject-partial-worker-result)), apply [harness-compat](../harness-compat/TECHNIQUE.md)::[continue-agent](../harness-compat/continue-agent.md) under `{worker_agent_id}` with explicit instructions to finish what the result left undone and return the envelope. -5. Account for this activity, and for any replacement worker dispatched for the same `{activity_id}`, per [account-every-activity](#account-every-activity). -6. Reconcile any critical routing or path variable an orchestrator decision depends on: compare the session record against the just-completed worker's `activity_complete` envelope, and against planning-folder evidence when the two still leave it uncertain ([distrust-then-reconcile](#distrust-then-reconcile)). -7. On `activity_complete`, read `{worker_result.next_activity_id}` (and optionally `{worker_result.evaluated_condition}`) as the authoritative next-activity routing — the worker evaluated transitions via [finalize-activity](./finalize-activity.md). +6. Account for this activity, and for any replacement worker dispatched for the same `{activity_id}`, per [account-every-activity](#account-every-activity). +7. Reconcile any critical routing or path variable an orchestrator decision depends on: compare the session record against the just-completed worker's `activity_complete` envelope, and against planning-folder evidence when the two still leave it uncertain ([distrust-then-reconcile](#distrust-then-reconcile)). +8. On `activity_complete`, read `{worker_result.next_activity_id}` (and optionally `{worker_result.evaluated_condition}`) as the authoritative next-activity routing — the worker evaluated transitions via [finalize-activity](./finalize-activity.md). > On a **blocked** signal from the worker or the harness, apply [sync-progress-status](./sync-progress-status.md) for the blocked moment in [Progress Status call sites](../../resources/planning-readme.md#progress-status-call-sites) for `{activity_id}` before surfacing or retrying. > When the path **skips / cancels** an activity without running it, apply [sync-progress-status](./sync-progress-status.md) for the path-skip / cancel moment in [Progress Status call sites](../../resources/planning-readme.md#progress-status-call-sites) for that activity's rows. @@ -74,6 +75,12 @@ Where the session record and a just-completed worker's `activity_complete` envel Client finalize/retrospective paths that consume execution history MUST resolve accumulated `trace_tokens[]` once via `get_trace { session_index, trace_tokens }` (optionally `inspect_session` for fetch/fidelity context). This operation owns the accumulate half of the contract; the client's close-out path owns the resolve. Skip resolve when `trace_tokens` is empty. +### say-what-a-dispatch-is-doing + +No minute of a run is silent to the user. A dispatch takes minutes during which nothing they can read is produced, and a gate arrives whenever the worker reaches one, so the wait is legible only if it was quoted before it was spent — a cost paid unannounced reads as a stall, and a gate that arrives unannounced arrives to someone who has stopped watching. + +Two consequences beyond the announcement in phase 4. Any wait the orchestrator itself imposes between dispatches — a commit cycle, a reconciliation, a retry — is named as it happens. And where a dispatch is one of a run of activities under one worker, which of the run it is belongs in the announcement, so the standing the server reports to the worker is legible to the person too. + ### no-get-activity-from-orchestrator Workflow orchestrators NEVER call `get_activity`. diff --git a/meta/techniques/workflow-engine/finalize-activity.md b/meta/techniques/workflow-engine/finalize-activity.md index f730f060a..af5f2256f 100644 --- a/meta/techniques/workflow-engine/finalize-activity.md +++ b/meta/techniques/workflow-engine/finalize-activity.md @@ -1,6 +1,6 @@ --- metadata: - version: 1.4.0 + version: 1.5.0 --- ## Capability @@ -23,7 +23,7 @@ Array of artifact entries (`id`, `name`, `path`). ### batch_may_continue -Whether this worker's context may take another activity, read from `_meta.batch.may_continue` on the `get_activity` response for this activity ([batch-ends-where-the-server-says](./activity-worker.md#batch-ends-where-the-server-says)). The envelope is the only place this answer appears again, so it is read here and carried there unchanged. +Whether this worker's context may take another activity, read from `may_continue` in the `batch:` block of the `get_activity` response for this activity ([batch-ends-where-the-server-says](./activity-worker.md#batch-ends-where-the-server-says)). The envelope is the only place this answer appears again, so it is read here and carried there unchanged. ## Outputs diff --git a/meta/workflow.yaml b/meta/workflow.yaml index 30431f623..7c8aa30f2 100644 --- a/meta/workflow.yaml +++ b/meta/workflow.yaml @@ -1,6 +1,6 @@ $schema: ../../schemas/workflow.schema.json id: meta -version: 5.20.0 +version: 5.21.0 title: Meta Workflow description: Top-level lifecycle workflow that orchestrates client workflow sessions. Excluded from list_workflows — bootstrap navigates here directly. author: m2ux From e99acb67893c76249be290b68c34d98a89b0e380 Mon Sep 17 00:00:00 2001 From: Mike Clay Date: Thu, 6 Aug 2026 13:30:40 +0100 Subject: [PATCH 2/5] Hold the dispatch signal in one home, and correct what an opt-in is needed for The announcement is a standing duty across the whole dispatch loop, not a work outcome at one position in it, so it lives once under Rules and instructs the orchestrator directly. That also removes its citation of a protocol phase by ordinal, which would have addressed the wrong work after any renumber. An opt-in is what get_activity needs to collapse its bundle. A repeat technique or resource fetch to a named context collapses without one, so the rule that describes the ledger says which call each ground governs, and the fetch rule points at the one that says how to avoid the second ask at all. --- meta/techniques/workflow-engine/TECHNIQUE.md | 4 ++-- .../techniques/workflow-engine/dispatch-activity.md | 13 ++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/meta/techniques/workflow-engine/TECHNIQUE.md b/meta/techniques/workflow-engine/TECHNIQUE.md index dc4f8fd8e..0c50cbf6b 100644 --- a/meta/techniques/workflow-engine/TECHNIQUE.md +++ b/meta/techniques/workflow-engine/TECHNIQUE.md @@ -31,7 +31,7 @@ The ids come from the delivery: the `resources` map keys, `resource_refs`, and t A fetch hands over the whole composed body, so ask for what a step needs and reuse what a response already carried. Measured on one real run, lazy technique fetches ran 5,242 to 15,126 characters apiece and lazy resource fetches 1,426 to 14,980. -Two things make a second ask cheap rather than free. A repeat under the same `agent_id` comes back as an unchanged marker, because the ledger records that this context received those bytes — so a repeat costs the round trip, not the body, and the marker is the expected answer rather than an error. And within one `get_activity` response, a shared contract or rules block may arrive as a marker whose bytes an earlier `step_techniques` entry of that same response carries in full; read it from there. Where content has genuinely left this context, [force-full-after-summarization](#force-full-after-summarization) is how to get it back. +Two things make a second ask cheap rather than free, and neither licenses one — how to avoid the second ask is [resource-section-or-whole](#resource-section-or-whole). A repeat under the same `agent_id` comes back as an unchanged marker, because the ledger records that this context received those bytes, so a repeat costs the round trip rather than the body and the marker is the expected answer rather than an error. And within one `get_activity` response, a shared contract or rules block may arrive as a marker whose bytes an earlier `step_techniques` entry of that same response carries in full; read it from there. Where content has genuinely left this context, [force-full-after-summarization](#force-full-after-summarization) is how to get it back. ### resource-section-or-whole @@ -43,7 +43,7 @@ Variables mutate from two sources only: checkpoint option effects (`setVariable` ### agent-id-scopes-delivery -The delivery ledger is keyed on agent context, not on the session. `agent_id` on `get_activity`, `get_technique` and `get_resource` names that context — the worker agent identity bound into the stub that dispatches or continues the agent — and each context reads and writes its own ledger. A first dispatch under a new `agent_id` holds no prior deliveries, so it takes full delivery; the same `agent_id` calling again is that context resumed, where `bundle: "reference"` collapses what it already received to unchanged markers. A solo walk is the one case carrying no `agent_id`, the session's own agent being its only context. +The delivery ledger is keyed on agent context, not on the session. `agent_id` on `get_activity`, `get_technique` and `get_resource` names that context — the worker agent identity bound into the stub that dispatches or continues the agent — and each context reads and writes its own ledger. A first dispatch under a new `agent_id` holds no prior deliveries, so it takes full delivery; the same `agent_id` calling again is that context resumed, and what it already received arrives as unchanged markers — on `get_activity` when the call carries `bundle: "reference"`, and on a repeat `get_technique` or `get_resource` whether or not it does ([fetch-costs-what-it-delivers](#fetch-costs-what-it-delivers)). A solo walk is the one case carrying no `agent_id`, the session's own agent being its only context — and the one case where nothing collapses on a repeat, since sibling contexts would share that identity. ### force-full-after-summarization diff --git a/meta/techniques/workflow-engine/dispatch-activity.md b/meta/techniques/workflow-engine/dispatch-activity.md index a0f60e447..a5aca44de 100644 --- a/meta/techniques/workflow-engine/dispatch-activity.md +++ b/meta/techniques/workflow-engine/dispatch-activity.md @@ -51,13 +51,12 @@ Opaque HMAC-signed trace token from the `next_activity` response `_meta.trace_to - **`step_manifest`:** a dispatch whose activity ran steps carries one manifest entry per completed step — the server validates step completion against it, and reports a gap when it is absent. A first dispatch has no prior worker context to attribute it to, so `agent_id` is omitted here; a continuation names one ([continue-batch](./continue-batch.md)). - **Trace accumulate (required):** when `_meta.trace_token` is present, append it to `trace_tokens[]`. Tokens stay opaque — no routine per-activity `get_trace`. Live `_meta.validation` self-correct remains; do not resolve tokens mid-run (close-out resolve is [resolve-trace-at-close-out](#resolve-trace-at-close-out)). 3. Mint `{worker_agent_id}` for this dispatch per [delivery-keys-on-agent-context](#delivery-keys-on-agent-context), then apply [compose-prompt](./compose-prompt.md) with `{agent_technique}`, `holds_prior_deliveries: false` (a minted identity holds nothing), and `{state}` as substitutions (include `session_index`, `workflow_id`, `activity_id`, and `{worker_agent_id}` as `agent_id`). -4. Tell the user what is about to run and what it will cost them in waiting: the activity's name, the next gate their answer is needed at (the first checkpoint of that activity, or that the activity runs to completion without one), and the elapsed figure of the last comparable dispatch where the session record has one. A dispatch is minutes of silence otherwise ([say-what-a-dispatch-is-doing](#say-what-a-dispatch-is-doing)). -5. Apply [harness-compat](../harness-compat/TECHNIQUE.md)::[spawn-agent](../harness-compat/spawn-agent.md) with the composed prompt; await the worker's envelope and return it unchanged as `{worker_result}`. +4. Apply [harness-compat](../harness-compat/TECHNIQUE.md)::[spawn-agent](../harness-compat/spawn-agent.md) with the composed prompt; await the worker's envelope and return it unchanged as `{worker_result}`. > When the harness reports the worker ended without returning an envelope, dispatch a fresh worker for the same `{activity_id}`, which mints its own identity. > When the harness still reports the worker live and what came back is not an accepted result ([reject-partial-worker-result](#reject-partial-worker-result)), apply [harness-compat](../harness-compat/TECHNIQUE.md)::[continue-agent](../harness-compat/continue-agent.md) under `{worker_agent_id}` with explicit instructions to finish what the result left undone and return the envelope. -6. Account for this activity, and for any replacement worker dispatched for the same `{activity_id}`, per [account-every-activity](#account-every-activity). -7. Reconcile any critical routing or path variable an orchestrator decision depends on: compare the session record against the just-completed worker's `activity_complete` envelope, and against planning-folder evidence when the two still leave it uncertain ([distrust-then-reconcile](#distrust-then-reconcile)). -8. On `activity_complete`, read `{worker_result.next_activity_id}` (and optionally `{worker_result.evaluated_condition}`) as the authoritative next-activity routing — the worker evaluated transitions via [finalize-activity](./finalize-activity.md). +5. Account for this activity, and for any replacement worker dispatched for the same `{activity_id}`, per [account-every-activity](#account-every-activity). +6. Reconcile any critical routing or path variable an orchestrator decision depends on: compare the session record against the just-completed worker's `activity_complete` envelope, and against planning-folder evidence when the two still leave it uncertain ([distrust-then-reconcile](#distrust-then-reconcile)). +7. On `activity_complete`, read `{worker_result.next_activity_id}` (and optionally `{worker_result.evaluated_condition}`) as the authoritative next-activity routing — the worker evaluated transitions via [finalize-activity](./finalize-activity.md). > On a **blocked** signal from the worker or the harness, apply [sync-progress-status](./sync-progress-status.md) for the blocked moment in [Progress Status call sites](../../resources/planning-readme.md#progress-status-call-sites) for `{activity_id}` before surfacing or retrying. > When the path **skips / cancels** an activity without running it, apply [sync-progress-status](./sync-progress-status.md) for the path-skip / cancel moment in [Progress Status call sites](../../resources/planning-readme.md#progress-status-call-sites) for that activity's rows. @@ -77,9 +76,9 @@ Client finalize/retrospective paths that consume execution history MUST resolve ### say-what-a-dispatch-is-doing -No minute of a run is silent to the user. A dispatch takes minutes during which nothing they can read is produced, and a gate arrives whenever the worker reaches one, so the wait is legible only if it was quoted before it was spent — a cost paid unannounced reads as a stall, and a gate that arrives unannounced arrives to someone who has stopped watching. +Leave the user no silent minute. Before spawning, tell them what is about to run, which gate their answer is next needed at — the first checkpoint of that activity, or that it runs to completion without one — and how long a comparable dispatch took where the session record carries a figure. Where this dispatch is one of a run of activities under one worker, say which of the run it is. Name any wait you impose between dispatches as you impose it: a commit cycle, a reconciliation, a retry. -Two consequences beyond the announcement in phase 4. Any wait the orchestrator itself imposes between dispatches — a commit cycle, a reconciliation, a retry — is named as it happens. And where a dispatch is one of a run of activities under one worker, which of the run it is belongs in the announcement, so the standing the server reports to the worker is legible to the person too. +A dispatch produces nothing the user can read while it runs, and a gate arrives whenever the worker reaches one. So a cost not quoted before it is spent reads as a stall, and a gate nobody was told to expect arrives to someone who has stopped watching. ### no-get-activity-from-orchestrator From 1a9fcbc188f547b7229301b87b80c6a211f23436 Mon Sep 17 00:00:00 2001 From: Mike Clay Date: Thu, 6 Aug 2026 18:13:14 +0100 Subject: [PATCH 3/5] Point the fetch rule at the homes that own each answer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rule restated two contracts that already have homes: when a marker comes back, which the ledger-scoping rule owns, and what a marker inside one response stands for, which that response's own notes own — the stance its sibling progressive-step-technique-load already states, that the response notes govern rather than policy re-derived in prose. What is left is what only this rule holds: what a fetch costs, measured, and the instruction to ask for what a step needs. --- meta/techniques/workflow-engine/TECHNIQUE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta/techniques/workflow-engine/TECHNIQUE.md b/meta/techniques/workflow-engine/TECHNIQUE.md index 0c50cbf6b..142615d38 100644 --- a/meta/techniques/workflow-engine/TECHNIQUE.md +++ b/meta/techniques/workflow-engine/TECHNIQUE.md @@ -31,7 +31,7 @@ The ids come from the delivery: the `resources` map keys, `resource_refs`, and t A fetch hands over the whole composed body, so ask for what a step needs and reuse what a response already carried. Measured on one real run, lazy technique fetches ran 5,242 to 15,126 characters apiece and lazy resource fetches 1,426 to 14,980. -Two things make a second ask cheap rather than free, and neither licenses one — how to avoid the second ask is [resource-section-or-whole](#resource-section-or-whole). A repeat under the same `agent_id` comes back as an unchanged marker, because the ledger records that this context received those bytes, so a repeat costs the round trip rather than the body and the marker is the expected answer rather than an error. And within one `get_activity` response, a shared contract or rules block may arrive as a marker whose bytes an earlier `step_techniques` entry of that same response carries in full; read it from there. Where content has genuinely left this context, [force-full-after-summarization](#force-full-after-summarization) is how to get it back. +A second ask is cheap rather than free, and cheap is not a licence: how to avoid it is [resource-section-or-whole](#resource-section-or-whole), when a marker comes back instead of a body is [agent-id-scopes-delivery](#agent-id-scopes-delivery), and a marker inside one response is explained by that response's own notes, which govern. Read a marker as the expected answer rather than an error, and where content has genuinely left this context, [force-full-after-summarization](#force-full-after-summarization) is how to get it back. ### resource-section-or-whole From e5bfd37745cff957cc2192bccd14a84df3acf8ed Mon Sep 17 00:00:00 2001 From: Mike Clay Date: Thu, 6 Aug 2026 18:24:22 +0100 Subject: [PATCH 4/5] Keep the measured range out of a rule every worker receives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rule is on a container, so it reaches every operation inside it and every worker through the activity bundle. A range from one historical run is not something a reader can act on differently, and it dates — which makes it exactly the fan-out the batch benchmark's warn-only line counts: characters delivered with no observable behaviour behind them. What a fetch costs is stated in the terms a reader acts on. The measured range lives in the epic's planning record, which is its home. --- meta/techniques/workflow-engine/TECHNIQUE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta/techniques/workflow-engine/TECHNIQUE.md b/meta/techniques/workflow-engine/TECHNIQUE.md index 142615d38..933b70db2 100644 --- a/meta/techniques/workflow-engine/TECHNIQUE.md +++ b/meta/techniques/workflow-engine/TECHNIQUE.md @@ -29,7 +29,7 @@ The ids come from the delivery: the `resources` map keys, `resource_refs`, and t ### fetch-costs-what-it-delivers -A fetch hands over the whole composed body, so ask for what a step needs and reuse what a response already carried. Measured on one real run, lazy technique fetches ran 5,242 to 15,126 characters apiece and lazy resource fetches 1,426 to 14,980. +A fetch hands over the whole composed body — thousands of characters, whatever fraction of it a step reads — so ask for what the step needs and reuse what a response already carried. A second ask is cheap rather than free, and cheap is not a licence: how to avoid it is [resource-section-or-whole](#resource-section-or-whole), when a marker comes back instead of a body is [agent-id-scopes-delivery](#agent-id-scopes-delivery), and a marker inside one response is explained by that response's own notes, which govern. Read a marker as the expected answer rather than an error, and where content has genuinely left this context, [force-full-after-summarization](#force-full-after-summarization) is how to get it back. From ade836e7d578730e236ae0a28fbd117548818a6f Mon Sep 17 00:00:00 2001 From: Mike Clay Date: Fri, 7 Aug 2026 06:04:17 +0100 Subject: [PATCH 5/5] State the ledger exception as the identity, not as an omitted argument MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two things were wrong once a repeat stopped collapsing on the session's own identity. The exception is that identity, reached by passing it as much as by omitting agent_id and falling back to it — not the omission. And a solo walk does collapse a repeat: it declares reference delivery, which is the ground the exception leaves open, so saying nothing collapses for it was the reverse of the truth. --- meta/techniques/workflow-engine/TECHNIQUE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta/techniques/workflow-engine/TECHNIQUE.md b/meta/techniques/workflow-engine/TECHNIQUE.md index 933b70db2..1a473a92e 100644 --- a/meta/techniques/workflow-engine/TECHNIQUE.md +++ b/meta/techniques/workflow-engine/TECHNIQUE.md @@ -43,7 +43,7 @@ Variables mutate from two sources only: checkpoint option effects (`setVariable` ### agent-id-scopes-delivery -The delivery ledger is keyed on agent context, not on the session. `agent_id` on `get_activity`, `get_technique` and `get_resource` names that context — the worker agent identity bound into the stub that dispatches or continues the agent — and each context reads and writes its own ledger. A first dispatch under a new `agent_id` holds no prior deliveries, so it takes full delivery; the same `agent_id` calling again is that context resumed, and what it already received arrives as unchanged markers — on `get_activity` when the call carries `bundle: "reference"`, and on a repeat `get_technique` or `get_resource` whether or not it does ([fetch-costs-what-it-delivers](#fetch-costs-what-it-delivers)). A solo walk is the one case carrying no `agent_id`, the session's own agent being its only context — and the one case where nothing collapses on a repeat, since sibling contexts would share that identity. +The delivery ledger is keyed on agent context, not on the session. `agent_id` on `get_activity`, `get_technique` and `get_resource` names that context — the worker agent identity bound into the stub that dispatches or continues the agent — and each context reads and writes its own ledger. A first dispatch under a new `agent_id` holds no prior deliveries, so it takes full delivery; the same `agent_id` calling again is that context resumed, and what it already received arrives as unchanged markers — on `get_activity` when the call carries `bundle: "reference"`, and on a repeat `get_technique` or `get_resource` whether or not it does ([fetch-costs-what-it-delivers](#fetch-costs-what-it-delivers)). The session's own identity is the exception, whether a call passes it or omits `agent_id` and falls back to it: several contexts can hold it at once, so under it a name is no evidence of one context and a repeat collapses only where the call declares reference delivery — which is what a solo walk, the one context that legitimately owns that identity, does. ### force-full-after-summarization