Cache optimization and convergence
Optimize workflow caches and follow convergence from preparation through verification and promotion. Learn how to interpret blockers, proof runs, partial promotion, and review-gated outcomes.
Build and repair cached flows through the Cofactor CLI. The fast loop is cache-only run → inspect the first failure → repair that step → commit → run again.
Availability: Cache-only runs and basic cache authoring require access for your organization. Force replacement, trace authoring, and verified import currently require Cofactor assistance.
01Commands and options mean different things
| Command or option | Meaning | When to use it |
|---|---|---|
workflow create, workflow patch, workflow node | Create or edit the flow definition: its steps, inputs, and expected outcomes. | Design or change what the test does. |
workflow cache commit | Validate and write one node's cached playbook and replay actions. | Supply or repair how an existing step replays. |
workflow run --cache-only | Run the flow using cached actions for cache-managed steps, wait for completion, and exit non-zero on failure. | Test the candidate and find the first missing or failing cached interaction. |
cache commit --dry-run | Validate the proposed content and source evidence without installing it. | Check a playbook before writing it. This does not execute the application. |
cache commit --force | Replace the incumbent cache even when the normal replacement policy would retain it. | Install a deliberate replacement. Requires --expected-cache-id and --yes; content validation still applies. |
cache commit --verified | Import the exact content you attest has already passed replay proof. | Install proven content using --force, --yes, and two distinct --verified-from run IDs. It does not execute proof runs. |
cache commit --unverified-from-trace | Write an unverified, zero-gap candidate from eligible trace or cache-only boundary evidence, even when the source step did not succeed. | Build forward from retained evidence; prove the candidate with subsequent cache-only runs. Requires --yes. |
Verification is a run-and-inspect process. The cache option is named --verified; there is no --verify option. Two fresh cache-only passes after the last edit provide proof, and you must inspect their replay and assertion results. A successful cache write or dry-run is not replay proof.
--force here belongs to cache commit. workflow promote --force is a separate option for including a test in suite runs; it does not replace its cache.
02Read the current CLI guidance
The CLI includes the authoring rules and command reference:
cofactor workflow guide cofactor workflow --help cofactor workflow cache cofactor workflow guide playbook cofactor workflow guide assertions cofactor docs search caching cofactor docs get reference/cache-authoring
Use cofactor docs search and cofactor docs get for related references as you work. When adding options to docs search, use equals syntax for their values, such as --env=prod or --limit=8; the current CLI can otherwise include separate option values in the search query. Check the installed command help for supported inputs and options.
Select the intended organization, application, credential, and application environment. --env prod selects the Cofactor API; --environment selects the application environment for a run. Cache-only runs perform real actions, including writes and deletions.
03Create or update the flow directly
Create a new flow with cofactor workflow create. Use cofactor workflow patch for flow properties and cofactor workflow node commands for individual steps. Read an existing definition with cofactor workflow get before changing it.
A new flow can be built and proved in place. When repairing an existing flow that others use, a fork can keep definition and cache edits separate until they are ready to promote. It does not isolate application data.
Make the flow repeatable after success or failure. Use focused steps, dynamic test data, and assertions that check the intended outcomes. Inspect real application behavior and run evidence before authoring cached interactions.
04Write the cache itself
Create or inspect the flow first, then author a playbook JSON file for one existing node. The file describes ordered replay actions, their readable steps, source evidence, and any gaps. It is cache content, separate from the flow definition.
Playwright is the executable cache
For code-based browser replay, put self-contained Playwright statements in a planned action with toolName: "browser_run_code", actionType: "run_code", and a code string. Cofactor supplies the authenticated page; the snippet performs the interaction. The surrounding playbook describes what that code does.
Use the real application's semantic locators. A run_code action embeds its locators in code, so it does not need a separate selectors object. Keep each step focused on one intent; interactions coupled through a transient menu can stay together in one snippet. Separate stable intermediate outcomes and their checks.
Avoid .first(), .last(), and .nth() in cached locators: strict durability admission rejects positional selection without attested evidence, including positional calls used to loop over a matching set. Give targets semantic identities; when observing a whole matched set, enumerate it without choosing an element by position and preserve the identities needed for comparison.
Strict admission treats raw page.mouse calls as coordinate-based actions, including page.mouse.wheel. For a journey testing scroll behavior, target the observed scroll container and perform real scrolling with a locator-bound scrollBy call in the interaction code. Inspect the resulting scroll position and rendered content; do not simulate a pinned state by changing styles, storage, or application data. A journey specifically testing mouse-wheel handling still needs that actual input and must report an admission blocker if it cannot replay.
This example clicks Save through Playwright, then checks a configured success assertion. Use the source run and assertion ID from your actual flow, and adapt the locator and expected message to observed behavior:
{
"sourceRunId": "<source-run-uuid>",
"playbookSteps": [
{ "order": 1, "action": "click", "target": "Save button" },
{
"order": 2,
"action": "verify",
"target": "Saved message appears",
"assertionId": "<configured-assertion-id>"
}
],
"plannedActions": [
{
"stepOrder": 1,
"toolName": "browser_run_code",
"actionType": "run_code",
"args": {},
"code": "await page.getByRole('button', { name: 'Save', exact: true }).click();"
},
{
"stepOrder": 2,
"toolName": "browser_assert",
"actionType": "assert",
"args": {},
"assertion": {
"type": "text_present",
"condition": "Saved",
"inlineAssertionId": "<configured-assertion-id>"
}
}
],
"gaps": []
}
A normal commit requires this node to have succeeded in the source run of this workflow. Preserve all configured assertions and dynamic inputs; the example is complete only for a node with this interaction and this single configured assertion.
Payload fields
| Field | Shape and purpose |
|---|---|
sourceRunId | UUID of the run supplying evidence for this workflow. |
playbookSteps | Non-empty array of readable steps, with contiguous order values starting at 1. Each step has an action and target; verification steps bind the configured assertionId. Values in readable step params are strings, including numeric-looking values such as viewport dimensions. |
plannedActions | Array of executable actions. Each has stepOrder, toolName, actionType, and an args object. stepOrder links it to the readable step. |
plannedActions[].code | For run_code, a non-empty self-contained Playwright snippet, at most 16,000 characters. |
plannedActions[].assertion | For assert, a specification with type and non-empty condition; bind inlineAssertionId to the same configured assertion. Preserve its severity and any required expected value. |
gaps | Array of { "stepOrder": number, "reason": string } for steps that cannot replay yet. Use [] for a fully cached candidate. |
carriedActions | Optional array of { "stepOrder": number, "action": object } carrying complete actions copied from an existing proven cache. Preserve the whole action, including dynamic bindings. |
variableValues | Optional object mapping variable names to their recorded string values, used when reconstructing dynamic bindings from evidence. |
Each readable step must have exactly one planned or carried action, or be declared a gap. Do not give the same step both kinds of action, or both an action and a gap. The playbook, planned-action, carried-action, and gap arrays are each limited to 200 entries. The server generates the stored action/compiled-entry mapping from this payload.
Use cofactor workflow guide playbook and cofactor workflow guide assertions for readable steps and assertion semantics. Other supported actions can use structured arguments and selectors, but the Playwright run_code shape above is the code-based authoring path.
Executable assertion shape
A cached browser_assert action has actionType: "assert", args: {}, and an assertion object. For Playwright checks, use type: "playwright" and put self-contained Playwright statements in condition. The authenticated page is supplied. Return a boolean, or return a primitive value and supply expected for an exact comparison. Playwright assertion code is limited to 8,000 characters. A non-empty string or object is not a passing verdict.
For example, if the spec requires a Saved message, this assertion object checks the current status text. Use the actual observed locator and configured ID:
{
"type": "playwright",
"condition": "return await page.getByRole('status').innerText() === 'Saved';",
"inlineAssertionId": "<configured-assertion-id>",
"severity": "fail"
}
Choose the predicate to prove the specified behavior. A pinning check, for example, compares live observations before and after scrolling rather than treating a control attribute as proof that a column stayed fixed. Keep measurement capture and verification bound to the current run and the same named targets.
Playwright assertion code has a restricted read-only API: locator.evaluate() and page.evaluate() are not allowed in that code. For DOM reads that need an in-page expression, use type: "evaluate" instead. Interaction and measurement-capture run_code actions have a different contract from assertion code.
type: "evaluate" evaluates an expression in the page context. Return a boolean, or supply a primitive expected value. For both executable types, expected values can be strings, numbers, booleans, or null; object results are not supported. Assertions run as checks and may be polled, so keep their code observational and put UI interactions in the action steps.
Capture current-run measurements
When a later step needs a baseline, declare the output name in the action node's configJson.declaredOutputs array. A readable capture step uses params.saveAs with that name. Its run_code action uses args.saveAs with the same name and must return a non-empty string. Use JSON.stringify to capture several measurements together; do not return the object itself. Each captured string is limited to 2,048 characters and validated as extracted data, so keep it compact and capture only the measurements needed by the check. Use separate declared outputs for distinct measurements when necessary; do not capture whole DOM or HTML blobs or discard required checks to fit the limit.
For example, this planned action captures a named header's current horizontal position:
{
"stepOrder": 1,
"toolName": "browser_run_code",
"actionType": "run_code",
"args": { "saveAs": "column_baseline" },
"code": "const box = await page.getByRole('columnheader', { name: 'Name', exact: true }).boundingBox(); if (!box) throw new Error('Name header is not visible'); return JSON.stringify({ x: box.x });"
}
The flow node declares "declaredOutputs": ["column_baseline"]; its matching readable step has "action": "capture" and "params": {"saveAs": "column_baseline"}. Adapt the header's name to the observed UI. The output is a measurement, not a success verdict.
Playwright snippets and executable assertions can read captured strings through vars.column_baseline; parse JSON before comparing measurements. A Playwright assertion must also read from the live page; a predicate comparing only captured variables is rejected. Compare the live target with the current phase capture as well as checking the before/after measurements. Capture the baseline in the current run, perform the scrolling interaction in a separate step, and compare the named header's live position with that baseline. Also verify that scrolling actually occurred and a suitable unpinned comparator moved. Keep each phase's measurements distinct, and verify it before the next state transition.
Validators before replay
Cache writes have server-side validators. cache commit --dry-run checks the candidate content and source evidence without installing it. The actual force or verified-import commit also checks its mode-specific write guards:
- Payload shape: field types, supported actions, code length, selectors, assertion specifications, and valid option combinations.
- Step mapping: contiguous step orders, one action per step, no unknown or conflicting action/gap references.
- Test fidelity: required macros and variable references remain present. Supplied assertion IDs are checked for unknown IDs, duplicate claims, and mismatched bindings.
- Replay admission: actions must be executable under the cache contract. Force replacement and verified import require a fully replayable candidate; trace authoring requires zero gaps and no agent-dependent actions.
- Write guards: source-run provenance, organization scope, and the expected incumbent cache ID. Verified imports require two distinct proof-run IDs; audit their provenance and exact-content equivalence before importing.
Authors must preserve every configured assertion and audit proof runs for complete, exactly-once assertion results. A successful commit or dry-run does not establish complete assertion coverage.
Validation failures return details to repair before committing. --force does not bypass these validators. They check the authored contract; they do not prove that Playwright locators resolve on the live page or that the application behaves correctly. Cache-only runs provide that execution proof.
Validate, write, and inspect the result:
cofactor workflow cache commit <workflowId> --node <nodeId> --playbook node-playbook.json --dry-run cofactor workflow cache commit <workflowId> --node <nodeId> --playbook node-playbook.json cofactor workflow cache status <workflowId> --json
Read whether the commit installed your candidate or retained an incumbent. In cache status, use the node's effectivePlaybooks[].activeCache for the active content; nodes[] can show a newer unusable row.
For a deliberate replacement, read the current incumbent immediately before writing:
cofactor workflow cache commit <workflowId> --node <nodeId> --playbook node-playbook.json \ --force --expected-cache-id <currentCacheId-or-none> --yes
Use none only when no incumbent exists for the candidate's target hash. If the cache ID changed, inspect the new content before deciding to replace it. Force preserves validation, assertion requirements, and the expected-ID check.
For eligible forward authoring, use the evidence run as sourceRunId and write an unverified candidate:
cofactor workflow cache commit <workflowId> --node <nodeId> --playbook node-playbook.json \ --unverified-from-trace --yes
This requires a completed trace-bearing run of the same workflow or its exact eligible missing/invalid cache-only boundary. It accepts only zero-gap agentless content. It cannot be combined with --dry-run, --force, --verified, or --expected-cache-id.
05Run, inspect, repair, repeat
Run the flow to establish its behavior and capture evidence. Inspect the result with cofactor workflow runs get, cofactor workflow debug overview, or cofactor workflow debug materialize.
Use cofactor workflow cache status to inspect the cache and cofactor workflow cache commit to validate and install a step's playbook. Follow the CLI's action and assertion contracts: preserve macros, captured variables, and every configured check. Verify semantic locators against the observed UI.
Once a candidate is installed, prove and repair it:
cofactor workflow run <workflowId> --cache-only --environment <environmentName> cofactor workflow debug overview <failedRunId> cofactor workflow debug materialize <failedRunId> --out ./cache-evidence
Continue with cache-only runs while proving the candidate: a normal run may send an unverified playbook through an agent and rewrite it. A missing cache or replay failure stops the run so you can focus on the first failing step. Inspect its evidence, repair the playbook, recommit, and rerun. Validate locators against retained evidence before another run when possible.
- Missing or unusable cache: author or repair that step's playbook.
- Replay failure: check the locator, timing, inputs, and starting state.
- Failed assertion: compare the application with the spec and preserve the check.
- Missing prerequisite: correct the data, credential, or environment.
Check the commit result to confirm the candidate was installed. For a replacement, use the freshly read expected cache ID and the CLI's force options. For forward authoring from an incomplete run, follow the CLI's trace-authoring requirements.
06Verify repeatability
After the last edit, require two consecutive fresh cache-only passes on the unchanged flow. Inspect the run evidence to confirm:
- Cache-managed steps replay successfully.
- Every configured assertion runs once and passes.
- Dynamic values belong to the current run.
- Cleanup leaves the flow ready to run again.
Authentication and explicitly agent-only steps may still use an agent. A cache hit means replay was used; it does not establish that its assertions passed. Nonfatal assertions can fail while execution continues to a later missing-cache boundary. Inspect every assertion verdict, repair the first failed check, and check replay evidence alongside the overall status. Report application behavior that contradicts the spec with its expected outcome, observed outcome, and run evidence.
If you used a fork, follow the CLI's mapping and verified-import guidance to promote the exact proven content, then run and check the original. Leave final human approval to the person responsible for the test.
07Import already verified content
Use --verified only after two distinct successful cache-only runs proved the unchanged content you are importing. Both proof runs must belong to the same workflow: the target or its direct fork in your organization. Check that provenance before importing; you attest that the imported content is exactly what those runs proved.
cofactor workflow cache commit <workflowId> --node <nodeId> --playbook proven-node-playbook.json \ --force --expected-cache-id <currentCacheId-or-none> --yes \ --verified --verified-from <proofRunId1> --verified-from <proofRunId2>
The file's sourceRunId must still reference a qualifying run of the target workflow. Preserve the full proven actions and dynamic bindings; when importing from a fork, map node and assertion IDs using the CLI's fork guidance. After import, run the target cache-only and inspect its assertions and replay results.
Run the plain --dry-run validation separately: it cannot be combined with --force or --verified.
08Related
- Caching — how caching works and when Cofactor handles it.
- Author repeatable cached flows — idempotency and a copyable agent prompt.
- Assertions — checks that replay must preserve.
- CLI authentication — organization access.