feat(runtime): let agents record roadblocks in a run-local friction log - #1162
Conversation
aviggiano
left a comment
There was a problem hiding this comment.
Thanks for this — the opt-in flag, the static preamble fragment, and the real-Frog integration tests are well done. Before this merges I think it needs a rework, mainly because of cost and a few wrapper holes found while reviewing:
Cost when disabled. [email protected] adds 32 third-party packages to @ultrafuzz/runtime, and every run copies and seals the runtime's transitive closure (trusted-CLI closure: ~343 → 374 packages / ~15.7k → 16.8k files, roughly +20 MB per run, ~10 MB of it @octokit/openapi-types; the same packages also land in the execution snapshot). So friction_log_enabled = false is not a no-op, and it makes the #921 snapshot cost worse.
Bugs found:
- After a plain
ultrafuzz resume(no--refresh-controller),workflowControlChildEnvironmentblanks any env value under the project root, soULTRAFUZZ_FRICTION_LOG/ULTRAFUZZ_FRICTION_LOG_DIRbecome""while the prompt still tells agents to use them. - A trailing value-taking flag (e.g.
... log --label) swallows the wrapper's pinned--cwd, and Frog then writes.agents/into the task worktree — the same contamination the first end-to-end run hit. - The
log/listallowlist misses incur's global--update(runspnpm add --global frog@latest) and--mcp(exposes apublishtool). - The wrapper lives in an agent-writable
addDir; if it becomes a symlink the next resume/replay/fork throwsArtifactPathError.
Suggested shape (my recommendation): keep the flag and the static fragment, drop the runtime dependency, and have agents write Frog-format files directly (<run>/friction/.agents/friction-log/<UTC-ts>-<slug>/friction.md with the front matter), deriving the directory from task.runRoot rather than inherited env vars. The operator can run npx frog over the directory after review. That keeps the value of #172 without adding a dependency, a shell allowlist, or git-discovery fencing.
Leaving the final call to the maintainer since #172 is a product decision.
…ion log Adds an opt-in `run.friction_log_enabled` setting (default false) for #172. When enabled, a run initializes a Frog friction log at `<run>/friction` with the exact-pinned [email protected] dependency of @ultrafuzz/runtime, and every agent receives: - ULTRAFUZZ_FRICTION_LOG: absolute path of a run-owned wrapper around the pinned Frog CLI. It allows only `log` and `list`, fixes `--cwd` to the run log, rejects --publish/--target/--token/--cwd/--open, and strips GITHUB_TOKEN, GH_TOKEN and GITHUB_API_URL, so entries stay local and pending until an operator reviews them. - ULTRAFUZZ_FRICTION_LOG_DIR: absolute path of the log, also added to the agent's writable directories so sandboxed agents can write to it. The log sits outside target worktrees and per-attempt artifact directories, which retries wipe. Start, native resume, and replay/fork all prepare it. Prompt caching: the agent instructions are one static preamble fragment with no paths or run identifiers, appended directly after the shared untrusted-content boundary. Disabled runs render byte-identical prompts; enabled runs add the same bytes to every task's shared prefix, ahead of the timeout-dependent runtime context. `frictionLogEnabled` is optional in the resolved-config v4 schema so configs sealed by earlier builds still validate on resume. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Frog anchors its log at `git rev-parse --show-toplevel`. Run roots live inside the target repository, so a live run initialized `.agents/` and `.github/` in the target's root, which also tripped the campaign source check. Set GIT_CEILING_DIRECTORIES to the log's parent and clear GIT_DIR and GIT_WORK_TREE for `frog init` and in the agent wrapper, so Frog uses the run-owned directory and never reads the target's remote or commit. Co-Authored-By: Claude Opus 5.5 <[email protected]>
… check The startRun integration test pinned the single-line `addDir` form. The friction log adds the run log directory when it is enabled, so match the new array while still rejecting the raw task.dependencyArtifactDirs form. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…g dependency Review of #1162 found that the pinned [email protected] runtime dependency added 32 packages (~20 MB) to every run's sealed trusted-CLI closure even with the friction log disabled, and that the generated wrapper had four holes: - a plain resume blanked ULTRAFUZZ_FRICTION_LOG(_DIR) because workflowControlChildEnvironment clears values under the project root; - a trailing value-taking flag swallowed the pinned --cwd, so Frog wrote .agents/ into the task worktree; - the log/list allowlist missed incur's global --update and --mcp; - the wrapper sat in an agent-writable addDir, so replacing it with a symlink made the next resume/replay/fork throw. Drop the dependency and the wrapper. Agents now write Frog-format entries directly to <run>/friction/.agents/friction-log/<UTC stamp>-<slug>/friction.md. Each local task derives that directory from its own run root, receives it as an addDir, and creates it best-effort during preparation, so no environment variable, lifecycle hook, or run-root check is involved and continuation resolves the same directory. Cloud tasks get no friction log. The prompt fragment's only variable is the directory, which is shared by every task in a run. Operators review entries with Frog after the run, stopping Git discovery at the run directory. Co-Authored-By: Claude Opus 5.5 <[email protected]>
54f1706 to
3020a9d
Compare
|
@aviggiano thanks, all four bugs reproduced from the code as you described. I went with your suggested design. It's in 3020a9d, rebased on current Cost when disabled: the Bugs:
Shape: agents write One operator detail: run roots sit inside the target repo and Frog anchors at the Git root, so review needs Verified with a clean smoke run of this branch on a small Foundry target (Claude via subscription):
The entries were real findings about Ultrafuzz itself:
I'll open issues for both unless you already track them. Other checks:
The PR description is updated with the details. |
A friction log directory that cannot be created is reported on stderr and does not fail task preparation. Say exactly that instead of claiming the task itself can never fail, since the directory is still handed to the agent as an addDir. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
Small correction to my note on point 4: a directory that can't be created is reported on stderr and doesn't fail task preparation. The path is still passed to the agent as an |
|
|
||
| If Ultrafuzz tooling, the sandbox, or these instructions block or slow this task in a way you cannot fix within it, record it in the run friction log at `{{friction_log_directory}}` and keep working. | ||
| First read the `title:` line of every `*/friction.md` already there; if the same problem is recorded, add nothing. | ||
| Otherwise create `{{friction_log_directory}}/<UTC time as YYYYMMDDHHMMSS>-<first three title words, lowercase, hyphenated>/friction.md` that starts with this front matter, with any `'` inside a value doubled: |
There was a problem hiding this comment.
If two local agents record a problem in the same second and their titles start with the same three words, both use the same friction.md path. The existing-title check is not atomic, so both can pass it before either writes. One entry may overwrite the other or leave an incomplete file, losing a useful roadblock report.
Prompt To Fix With AI
This is a comment left during a code review.
Path: .ultrafuzz/prompts/_templates/agent-preamble/friction-log.mdx
Line: 5
Comment:
**Friction entries can collide**
If two local agents record a problem in the same second and their titles start with the same three words, both use the same `friction.md` path. The existing-title check is not atomic, so both can pass it before either writes. One entry may overwrite the other or leave an incomplete file, losing a useful roadblock report.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Ultrafuzz no longer uses Frog, so the docs, changelog, prompt catalog, code comments and test name describe the friction entries on their own terms and drop the Frog review command. Co-Authored-By: Claude Opus 5.5 <[email protected]>
| it, and creates it during preparation. Ultrafuzz adds no dependency for it, so a | ||
| disabled run installs, seals, and renders nothing extra. | ||
| Cloud tasks do not receive the friction log. |
There was a problem hiding this comment.
Operator listing guidance is missing
The docs ask operators to review friction entries before publishing, but remove the only documented way to list them. Runs sit inside the target Git repository, so a plain frog list --cwd <run>/friction looks at the repository root and misses the entries. Please restore the Git-ceiling command or explain how to list and read the Markdown files directly; otherwise operators may overlook roadblocks.
Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/reference/configuration.md
Line: 188-190
Comment:
**Operator listing guidance is missing**
The docs ask operators to review friction entries before publishing, but remove the only documented way to list them. Runs sit inside the target Git repository, so a plain `frog list --cwd <run>/friction` looks at the repository root and misses the entries. Please restore the Git-ceiling command or explain how to list and read the Markdown files directly; otherwise operators may overlook roadblocks.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.The .agents/friction-log nesting only mirrored Frog's layout, which Ultrafuzz no longer uses. Entries now live at <run>/friction/<YYYYMMDDHHMMSS>-<slug>/friction.md. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Preparation now calls ensureFrictionLogDirectory, which reads the workflow's frictionLog constant. The lifecycle harness evaluates the template's functions without that constant, so every preparation test threw a ReferenceError. Supply null, the value a disabled run bakes in. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
@mrthankyou the idea to remove frog was actually claude's not mine I noticed frog is quite active and maintained, so I wouldn't mind having an external dependency rather than growing ultrafuzz's codebase sorry for not having said that earlier what do you think? I'm fine with both options tbh, with a slight preference for using a lib (reducing LOC is always good) |
# Conflicts: # CHANGELOG.md
|
Let me look at frog again. I'm fine with either approach as long as frog can gracefully handle errors along the way. Reducing LoC is a valid reason as well. |
# Conflicts: # CHANGELOG.md
Replace the hand-written friction entries with Frog ([email protected], now an exact-pinned dependency of @ultrafuzz/runtime), as discussed on #1162. Agents run <run>/friction-bin/ultrafuzz-friction-log, a generated wrapper around the pinned Frog CLI. It addresses the four problems found in the earlier Frog version: - No environment variables: the prompt carries the command's absolute path, and each task resolves the command and entry directory from its own run root, so resume, replay and fork all resolve the same paths. - The wrapper passes --cwd before every agent argument and checks each option and its value, so a trailing value-taking flag cannot consume it. - Only `log` and `list` with their local options are accepted; publishing, --update, --mcp, --cwd, --target and every other global are refused. - The wrapper lives outside the agent's only write root (<run>/friction), is replaced atomically during task preparation, and a failure to prepare it is reported on stderr rather than failing the task. The wrapper stops Git discovery at the run root and removes GitHub credentials, so entries land in <run>/friction/.agents/friction-log and never in the target repository. The prompt tells agents to continue when a command fails and never to edit entries by hand, since Frog refuses every later entry while one entry is malformed. This also fixes two bugs in the native version once main removed per-node cloud execution: frictionLogDirectory read task.execution.mode, which no longer exists, and the addDir spread split the directory into characters. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
@aviggiano went with Frog, as you preferred: 9e0e453 replaces the hand-written entries with
It also fixes Greptile's two P1s, which came from main removing per-node cloud execution. Two things worth knowing:
A smoke run of this commit succeeded 24/24, and an agent recorded a real entry through Frog. Details are in the updated description. |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
| [ -z "$value" ] || refuse "the last option is missing its value" | ||
| run_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd -P) | ||
| unset ${[...PUBLISHING_ENVIRONMENT_VARIABLES, ...GIT_DISCOVERY_ENVIRONMENT_VARIABLES].join(" ")} | ||
| GIT_CEILING_DIRECTORIES=$run_root |
There was a problem hiding this comment.
If the target project path contains a colon, the wrapper puts the raw run path in GIT_CEILING_DIRECTORIES, which Git treats as a colon-separated list. Git can then discover the enclosing target repository instead of stopping at the run root. A friction entry may land in the target repository rather than the run-local log, bypassing the operator’s review location.
How this was verified: The wrapper exports the unescaped run path as Git’s ceiling value before invoking Frog, which uses Git discovery to locate its entry root.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/runtime/src/friction-log.ts
Line: 74
Comment:
**Git ceiling fails on colons**
If the target project path contains a colon, the wrapper puts the raw run path in `GIT_CEILING_DIRECTORIES`, which Git treats as a colon-separated list. Git can then discover the enclosing target repository instead of stopping at the run root. A friction entry may land in the target repository rather than the run-local log, bypassing the operator’s review location.
**How this was verified:** The wrapper exports the unescaped run path as Git’s ceiling value before invoking Frog, which uses Git discovery to locate its entry root.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.|
Thanks @mrthankyou, this is careful work. The rework fixes all four bugs from the earlier review and both of Greptile's cloud-removal P1s. A disabled run renders byte-identical prompts, with no directory or addDir, and nothing in verification, reports or bundles reads A follow-up PR before v0.1.3 hardens the Frog wrapper. Nothing is needed from you.
Thanks also for the smoke-run findings: the macOS |
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
@aviggiano I opened issues for both smoke-run findings:
|
Refs #172
Summary
Adds an opt-in
run.friction_log_enabledsetting (defaultfalse). It lets agents running under Ultrafuzz record roadblocks they hit with Ultrafuzz, tooling, or their instructions. Entries are recorded with Frog and stay local to the run until an operator reviews them.Now uses Frog. Following @aviggiano's preference for a maintained library over more Ultrafuzz code, this PR goes back to Frog (
[email protected], exact-pinned in@ultrafuzz/runtime) and replaces the hand-written entry format of the previous revision. The four problems found in the first Frog version are fixed; see below.How it works
<run>/friction-bin/ultrafuzz-friction-log, a generated wrapper around the pinned Frog CLI. The prompt tells them to run… listfirst, then… log '<title>' --severity … --body '…'with Frog's five sections.logandlistwith their local options (--body/-b,--severity/-s,--label,--force,--format), and checks each option's value. Everything else is refused with exit 2:publish,sync,--publish,--update,--mcp,--llms,--cwd,--target/-t,--token, and--.--cwd <run>/frictionbefore every agent argument and setsGIT_CEILING_DIRECTORIES=<run>. Entries therefore land in<run>/friction/.agents/friction-log/<id>/friction.md, never in the target repository, and are never validated against the target's issue forms. It also unsetsGITHUB_TOKEN,GH_TOKEN,GITHUB_API_URL,GIT_DIRandGIT_WORK_TREE.runRoot, so start, resume, replay and fork all resolve the same paths.<run>/friction(0700) and installs the wrapper (0700) by atomic rename, rewriting it only when its bytes change. A wrapper path that isn't a regular file is left alone, not followed. Any preparation failure goes to stderr and never fails the task.<run>/frictionas anaddDir, never the command's directory.friction.mdmakes Frog refuse every laterlogandlistwithMALFORMED_ENTRY, even with--force.GIT_CEILING_DIRECTORIES=<run> npx frog list --cwd <run>/friction, or read the Markdown files directly. Review entries before publishing anything, because they can describe private targets.Fixes to the first Frog version (@aviggiano's review)
ULTRAFUZZ_FRICTION_LOG*. There are no environment variables now. Paths come fromtask.runRoot, and the prompt carries the absolute command path. PATH was not an option either, because continuation drops every target-local PATH entry except the Forge guard'ssafe-bin.--cwd.--cwdnow comes before all agent arguments,--cwditself is refused, and a trailing option without its value is refused.--update/--mcp. Refused, along with every other global except--format.This revision also fixes Greptile's two P1s, both introduced when main removed per-node cloud execution.
frictionLogDirectoryreadtask.execution.mode, which no longer exists, and theaddDirspread split the directory path into single characters. Greptile's P2 about colliding entries is also resolved: Frog reserves each entry directory with an atomicmkdir, so concurrent entries get distinct ids and none is overwritten.Cost
@ultrafuzz/runtime, and they are installed and sealed even when the friction log is disabled. This is the trade-off for fewer lines of code in Ultrafuzz.postgres, which satisfies Smithers' optionalpostgrespeer, so pnpm re-resolves the@smthrs/*packages under a new peer suffix. Patch hashes are unchanged andnode scripts/smithers-patches.mjs --checkpasses. The engine still runs withSMITHERS_BACKEND=sqlite. This does move the engine's install directory, which main's CHANGELOG warns breaks running campaigns in the same checkout.nulland prompts are byte-identical tomain.Testing
End to end. I ran a smoke run of
9e0e4532onultrafuzz_simple_test. Setup:ultrafuzz init --forcefrom this build,friction_log_enabled = true,--audit-profile smoke, Claude via subscription.done (succeeded), 24/24 nodes, 0 failed, 21m 41s; the report isCOMPLETEand verified. There were 0 failed run events and nofriction log unavailablewarnings in any engine or agent log.--add-dir <run>/friction. The wrapper was installed at<run>/friction-bin/ultrafuzz-friction-log(0700).smoke-contextranlist, which returned an empty log.smoke-contextranlog, which wrote20260930170102-forge-wrapper-aborts.final-reportranlist, which showed that entry aspending.safe-bin/forgeulimit -vfailure..agents/appeared in the target root or in any task worktree.logandlist, and both worked.--publishwas refused with exit 2. The documented operator command,GIT_CEILING_DIRECTORIES=<run> frog list --cwd <run>/friction, listed the entries. I removed my test entry afterwards.Automated
friction-log.test.ts(new) runs the generated wrapper against the real pinned Frog:-and a body value starting with-;DUPLICATE_FRICTIONcomes back as a clean exit-1 error;generated-workflow-verifier:addDir.pinned source proof counts hidden unreachable commits, which assumes Git's default branch ismasterand passes withinit.defaultBranch=master.runtime.test.ts:compileSmithersWorkflow applies group execution defaultschecks the disablednulland the sealed{ instructions, entriesPath, commandPath, wrapper }. ThestartRun … submits through Smithers CLItest checks the newaddDir.workspace-preparation-lifecyclepasses, as does the prompt fragment test.format:check,lint,lint:strict,typecheck,knip,docs:check,size,security:dependency-advisories,smithers-patches.mjs --checkandpnpm install --frozen-lockfileare clean.validate:releasewere not run locally (macOS, Runtime suite is 2-63x slower per test on macOS than Linux; a full local run does not finish in 7 hours #1060); CI covers them.Not in this PR
included_roots, which is better as a follow-up.logcould skip unreadable entries during its duplicate check instead of refusing to write.🤖 Generated with Claude Code
The PR is not yet safe to merge because a run path containing a colon can defeat the wrapper’s Git discovery boundary.
Fix with agent prompt
Summary
Adds an opt-in, run-local Frog friction log for agents.
log/listwrapper and grants agents write access only to the entry directory.Reviews (9) · Last reviewed commit: "Merge remote-tracking branch 'origin/mai..."