DOCSBooley user guide Tracks the main branch

Goal Mode

Goal Mode gives one Developer Agent session mandatory, evidence-backed completion conditions. Open a Developer Agent session and type /booley-goal with the work you want done. The skill helps you choose a clean linked worktree, Goalsets, Targets, and ad-hoc Goals, then calls goal_enter. Booley creates a Goal Branch from the worktree's current HEAD and records that base for comparisons.

Goalsets and entry

Goalsets are Project-owned Markdown files under .booley/goalsets/. booley init creates missing feature.md, bugfix.md, refactor.md, and verification.md without rewriting existing files. Customize their prose to express your project's rules. The Developer Agent translates them into concrete Goals; Booley validates the arguments, not the prose. Each Goal records its origin.

An optional default.md applies to every entry. Skipping it requires your explicit instruction and reason. Setup can create this file; initialization does not seed it. You can also add Goals for this particular change. Duplicate Goals merge to the stricter condition, and entry reports every merge.

Goalsets, including default.md, come from the Main Booley Directory. In the Sandbox that is BOOLEY_DIR, even when the directory lives outside the main checkout. The Worktree Booley Directory inside the Goal worktree is not used: a default.md that exists only there does not apply. work_dir selects the worktree, not the Goalsets.

A session started inside a worktree without BOOLEY_DIR can resolve that worktree's copy instead. Start the session with BOOLEY_DIR set to the Main Booley Directory; entry refuses a Booley Directory inside the Goal worktree.

A Goal has one name, CAPABILITY:target, for example SIM:sim_fifo. Review Goals use REVIEW:rtl_bugs. Details such as tests, thresholds and the review requirement follow the name in parentheses. Goal arguments use the uppercase capability as family. Every Goal is mandatory. For example, a Goalset excerpt can ask for:

[
  {"family": "LINT", "target": "lint_fifo"},
  {"family": "SIM", "target": "sim_fifo"},
  {"family": "REVIEW", "review": "rtl_bugs", "verdict": "clean"},
  {"family": "SYNTH", "target": "synth_fifo", "thresholds": {"area_increase_at_most": "10%"}}
]

Use the Goal catalog and booley cheat --criteria for the public Goal names and threshold parameters. A new Target can be named at entry but stays unmet until it exists. Spec reviews name a spec file relative to the worktree.

Threshold parameters

synth and fpga Goals name each Target directly and accept metric thresholds. Four flavours apply per metric: two absolute, two relative to the Goal base commit:

Flavour param suffix Baseline? Meaning
_max no metric must stay ≤ the given value
_min no metric must stay ≥ the given value
_increase_at_most yes metric may grow at most N% above baseline
_reduce_at_least yes metric must shrink at least N% below baseline

Percentage threshold values must include the % suffix (for example, "cell_count_reduce_at_least": "8%" inside thresholds).

For a relative threshold on a new Target, set baseline to an existing Target. The baseline Target defaults to the candidate name and must exist at the Goal base commit. Booley runs it on base code and the candidate on current code. Missing or mismatched baseline evidence fails the check.

Goal argument examples:

{"family": "SYNTH", "target": "asic_small", "baseline": "asic_base",
 "thresholds": {"cell_count_reduce_at_least": "8%"}}
{"family": "FPGA", "target": "fpga_top", "thresholds": {"lut_count_max": 5000}}

SYNTH:{target} (ASIC)

Metric _max _min _increase_at_most _reduce_at_least
area — — ✓ ✓
area_kge ✓ — — —
area_um2 ✓ — — —
cell_count ✓ — ✓ ✓
critical_path_ps ✓ — ✓ ✓
fmax_mhz — ✓ ✓ ✓
wire_count ✓ — ✓ ✓

Absolute area caps pick a unit (area_um2 / area_kge); the unit-agnostic area row carries the baseline-relative bounds only.

Mutually exclusive: area_um2_max ⊕ area_kge_max.

Mutually exclusive: critical_path_ps_max ⊕ fmax_mhz_min.

FPGA:{target} (FPGA)

Metric _max _min _increase_at_most _reduce_at_least
bram_count ✓ — ✓ ✓
critical_path_ps ✓ — ✓ ✓
dsp_count ✓ — ✓ ✓
ff_count ✓ — ✓ ✓
fmax_mhz — ✓ — —
lut_count ✓ — ✓ ✓

Mutually exclusive: critical_path_ps_max ⊕ fmax_mhz_min.

CYCLE_COUNT of one Test Run

CYCLE_COUNT:{target} Goals name the Target with target and one Test Run with test (test, test+variant, or either with @seed); put bounds in thresholds. All thresholds for that Test Run must pass. Relative forms compare the same Test Run of the same Target at the Goal base commit.

{"family": "CYCLE_COUNT", "target": "sim", "test": "smoke",
 "thresholds": {"cycle_count_max": 1000, "cycle_count_reduce_at_least": "8%"}}
Parameter Baseline? Unit Passing relation
cycle_count_max no cycles current ≤ threshold
cycle_count_min no cycles current ≥ threshold
cycle_count_increase_at_least yes percent signed change ≥ +N%
cycle_count_increase_at_most yes percent signed change ≤ +N%
cycle_count_reduce_at_least yes percent signed change ≤ -N%
cycle_count_reduce_at_most yes percent signed change ≥ -N%
cycle_count_increase_at_least_cycles yes cycles current - baseline ≥ N
cycle_count_increase_at_most_cycles yes cycles current - baseline ≤ N
cycle_count_reduce_at_least_cycles yes cycles baseline - current ≥ N
cycle_count_reduce_at_most_cycles yes cycles baseline - current ≤ N

A named [SIM_CYCLES] <test> <count> observation is gated evidence only when that exact test passes. Missing, malformed, duplicate, unnamed, failed, or inconclusive evidence fails closed. Without a CYCLE_COUNT:{target} Goal, existing Cycle Count records remain observational.

Relative comparisons report an observed Cycle Count change. When declared workload inputs differ, review reports disclose the changes and do not attribute the result to RTL alone.

For a SYNTH or FPGA Goal with a relative threshold (*_increase_at_most, *_reduce_at_least) whose evidence includes a baseline, the long booley goal status view (one Goal), the goal_status tool, the Dashboard's Goal details and the Review Package also show the baseline revision and, per thresholded metric, baseline → current with the signed change and percentage, for example baseline 0123456789ab → current: area 1,200 → 1,050 μm² (-150, -12.5%). Absolute Goals and Goals without baseline evidence show their usual metrics.

Goals that name Test Runs

A Test Run is one Test Variant simulated with one seed, written test[+variant][@seed] (reference). Simulation, Cycle Count and coverage Goals require exact Test Runs, and each can name them with a selector:

[
  {"family": "SIM", "target": "sim_fifo"},
  {"family": "SIM", "target": "sim_fifo", "test": "stress+soak"},
  {"family": "SIM", "target": "sim_fifo", "test": "stress+soak@42"},
  {"family": "SIM", "target": "sim_fifo", "test": "stress+soak@x20"},
  {"family": "CYCLE_COUNT", "target": "sim_fifo", "test": "burst@7",
   "thresholds": {"cycle_count_max": 1000}},
  {"family": "COVERAGE", "target": "sim_fifo", "tests": ["reset", "stress@x5"],
   "metrics": {"line": 90}}
]
family test / tests Requires
SIM without test none The default Test Variant of every Test of the Target that is not skipped, each at the Default Seed.
SIM with test test, test+variant, either with @seed, or either with @xN That Test Run. @xN requires N Test Runs of the Test Variant with different seeds, N from 1 to 1000.
CYCLE_COUNT test, test+variant, either with @seed One Test Run. @xN is refused: a threshold applies to the Cycle Count of one Test Run.
COVERAGE "all", or a list of selectors, @xN included Coverage floors over the merged coverage of the listed Test Runs.

A selector without a Test Variant means the Test's default Variant. A selector without a seed means the Target's Default Seed.

Freezing. At entry Booley resolves every such Goal to exact Test Runs and stores them in the Goal Record. From then on the Goal requires those Test Runs. Editing a Test's default Variant or the Target's default_seed in tests.toml afterwards does not change what the Goal requires. For @xN Booley draws N distinct seeds at random at entry, so each Goal Mode samples new seeds, and reruns inside one Goal Mode use the same ones. Goals of one Target that name the same Test Variant with the same N share one set of seeds.

Goals that resolve to the same Test Runs merge at entry, like other duplicate Goals. Of two @xN Goals on the same Test Variant, the larger N remains. A Goal with a pinned seed and an @xN Goal stay separate Goals. A Goal that names a seed on an unseeded Target is refused at entry. So is a Goal that names a Test its Target skips in tests.toml, and so is a Goal change that would freeze one: remove the Test from skip or drop the Goal.

Targets and Tests that do not exist yet. A Goal can name a Target, Test or Test Variant that the work will add. That part of the Goal is pending and the Goal is unmet. Booley freezes it when it first exists, at the next Flow run or status read, with the defaults tests.toml declares at that moment. A Goal over a whole Target also freezes each Test that is added to the Target later. The Review Package lists every such Test Run under the heading Test Runs resolved after entry.

Evidence. A Test Run counts for a Goal only when its name and its declared arguments and environment equal one of the Goal's frozen Test Runs. Each frozen Test Run is passed, failed or missing, computed from applicable evidence history at the same Goal revision and dependent file contents. Observations retain their own Test Run rows; older stored outcome copies are ignored. Evidence without Test Run rows supplies no outcome:

  • It becomes passed when it passes at the current contents of the files the Goal depends on.
  • It becomes failed when it fails at those contents, and a later pass at the same contents does not change that. A flaky seed therefore stays failed until the code changes.
  • It is missing until it has run. An error or inconclusive Test Run leaves the outcome as it was.

A diagnostic MCP run changes none of these outcomes, even when it runs a frozen Test Run. Its report names the Goals whose frozen Test Runs ran and the ones that failed; use a Goal-bound Developer Agent MCP call with work_dir and without diagnostic to record them. CLI runs never record Goal evidence.

A simulation Goal is met when every frozen Test Run is passed. They do not have to pass in one invocation: 20 seeds can pass over several runs and Simulation Campaigns, including after an interruption, and a run of a few named Test Runs adds their outcomes without disturbing the others. A Cycle Count Goal takes its measurement from a run of its frozen Test Run, and that Test Run must be passed.

A coverage Goal is decided by its coverage floors alone. It takes its measurement from one --coverage run that ran exactly its frozen Test Runs, because coverage is not merged across runs. Whether those Test Runs passed does not matter to it: when the floors are met the Goal is met, even if a Test Run failed in that run or in any other. goal_status shows the outcomes of the measuring run under the Goal, with a rerun command for each failure. Add a simulation Goal for the same Test Runs when they must also pass. A run that does not measure the Goal, such as a plain simulation, leaves a measurement at the same file contents as it was. A failing Test Run that ends before the simulator writes its coverage data makes collection incomplete, so that run measures nothing either. With no earlier measurement at those contents, the Goal stays unmet as not measured.

If a frozen Test Run's Test or Test Variant is removed, or its declared arguments or environment change, the Goal stays unmet until you approve a retarget. If its Test is skipped in tests.toml, the Goal stays unmet until the Test leaves skip or you approve a Goal change that drops it; a retarget of a Goal that names the skipped Test is refused, while a retarget of a Goal on the whole Target freezes its suite again without that Test. See A Goal that names Test Runs stays unmet for each reason goal_status can show.

Seeing the frozen Test Runs. goal_status, booley goal status and the Dashboard list each Goal's frozen Test Runs with their outcomes, the parts still pending, the Test Runs resolved after entry, and a rerun command for every failed Test Run. The Review Package computes the same outcomes from its frozen evidence and lists each frozen Test Run with its rerun command. It retains the contributing observations, so later runs do not change a saved package.

Working with evidence

The Developer Agent passes the absolute worktree root as work_dir to every tool that accepts it.

Built-in MCP worktree routing:

Accepts work_dir Does not accept work_dir
sim, lint, synth, fpga booley_status
reviewer, mutation_tester, coverage_analyst booley_poll, booley_cancel
goal_enter, goal_status, goal_propose_change, goal_finish booley_sleep, coverage_evidence
booley_targets, booley_report bwave

booley_sleep appears only when diagnostic tools are enabled, and coverage_evidence only in the Coverage Analyst's nested server.

While any Goal Record occupies the Project, Booley Flows, Specialists, Goal tools, and booley_report refuse a call without work_dir, even a diagnostic call outside that worktree; booley_targets may omit it. Project-defined endpoints must declare work_dir to run.

Only Booley Flows and Specialists meet Goals. Shell output or the Developer Agent saying "done" does not. goal_status shows each Goal's current evidence and freshness; code changes can require another run. For a bugfix, reproduce the reported bug with a failing test before editing the design.

Protected Inputs decide how evidence is produced: booley.toml, FUSESOC_IGNORE, and the Project's hooks, .managed, generators, and mcp_tools directories. Editing one blocks Finish until reverted and discards affected evidence. Never weaken tests or Targets to make a Goal pass. The Review Package flags .core, tests.toml, and .sdc/.xdc edits.

Simulation baseline reports

Entering Goal Mode starts a background Job for each SIM Goal's frozen Test Runs at the Goal Branch's base contents. Adding a SIM Goal through an approved Goal Change schedules its baseline too. Entry returns while the Job runs; status shows the baseline as pending. Booley reuses a result for the same Target, resolved Test Run and dependent file contents across sessions and worktrees. Protected execution inputs are part of the cache identity too.

goal_status, booley goal status, the Dashboard's Goal details and the Review Package show a summary for each SIM Goal. Every Test Run other than pass -> pass is named, with its Variant and seed:

Transition Meaning
pass -> pass Passed at baseline and passes at current contents.
fail -> pass Failed at baseline and passes now.
pass -> fail Passed at baseline and fails now: a regression.
fail -> fail Failed at baseline and still fails.
new (seen failing) -> pass Absent at baseline; a matching functional failure was recorded earlier in this session at different dependent contents.
new (never seen failing) -> pass Absent at baseline and no qualifying earlier failure was recorded.
new -> fail Absent at baseline and fails now.

Baseline and current evidence must name the same resolved Test Run, including Variant, seed and declared arguments. An existing Test with incompatible inputs is not comparable, with a reason. A new Test never runs against the baseline design using a newly authored testbench. A stale or missing current result is shown as unavailable rather than a transition. The Review Package calls attention to regressions and new Test Runs that passed without being seen failing.

These reports do not change any Goal's met, unmet or stale state and never block Finish. A build error, timeout, interrupted Job, unavailable dependency or ungraded result is unavailable, with its reason, rather than a failing Test. Only a recorded functional Test failure counts as seen failing; a timeout or infrastructure error does not. Earlier evidence without that classification cannot prove a functional failure. Finish freezes the report as it is, so its Review Package may still show a pending baseline.

Booley verifies versioned baseline dependencies against the base commit and uses captured entry configuration for an unversioned Booley Directory. Dependencies outside those pinned or captured inputs make the report unavailable. The Project must ignore Booley's baseline scratch directory, as Project Initialization configures it; otherwise Booley reports an unavailable baseline without leaving scratch files that would affect entry or Finish. The advice to reproduce a bug before editing remains unconditional.

Synthesis baseline Jobs

Entering Goal Mode, or approving a Goal Change that adds one, starts a background synthesis Job for each baseline-relative SYNTH Goal. It synthesises the Goal's baseline Target at the Goal Branch's base contents, with the synthesis recipe frozen at that entry or change. Absolute SYNTH Goals get none. Booley reuses a result for the same Target identity, frozen recipe, dependent file contents and Protected Inputs across sessions and worktrees. Candidate runs still synthesise the baseline themselves.

At most one baseline Job per Flow runs at a time, SIM included; others wait, and the wait does not count against a Job's time budget. goal_status and booley goal status print baseline: pending, baseline: complete or baseline: unavailable (<reason>) for each such Goal. An interrupted, timed-out, infrastructure-failed or recipe-mismatched Job is unavailable and stores no result. Like simulation baselines, it never changes a Goal's met, unmet or stale state.

Changes and human decisions

When a Goal needs to be added, relaxed, retargeted, or changed through a coverage waiver, the Developer Agent uses goal_propose_change. You see the exact proposal and approve or reject it with a reason. The client form is used when available; chat fallback records your quoted instruction against the saved proposal ID. Silence cannot approve a change. Approved changes appear in the Change Log. Coverage Waiver Candidates need your decision; the Analyst cannot approve them.

Changing which Test Runs a Goal requires is a Goal change like any other:

Change Kind Frozen Test Runs
Add a Goal that names a Test Run add Frozen when the change is applied.
Pin a seed, name another Test Variant, or raise N retarget Raising N keeps the frozen seeds and draws only the difference.
Lower N relax The Goal keeps its first N frozen seeds.
Retarget a Goal without changing how it is written retarget Frozen again from the Target as it is now. Use this when a frozen Test Run is gone or its declaration changed.

A change that would freeze a Test Run of a Test the Target skips in tests.toml is refused.

After any of these changes the Goal's Test Run outcomes start again.

Goal Mode clients must expose MCP structuredContent to the Developer Agent. Entry and status return the internal goal_key change addresses there, separately from human Goal names. A client that forwards only text can read status but cannot address an existing Goal for a change.

Finish and review

Finish requires all Goals met with fresh evidence at a clean, committed HEAD and a Session Summary. The Developer Agent calls goal_finish with the record ID, a stable operation ID, and that summary. The Review Package contains the diff against the base, final Goals and evidence, the Change Log, open review findings, Target changes, constraint edits, and the Session Summary.

A clean review requires no open findings. A done review is advisory: findings remain in the package and do not require a second approval to Finish. Read the package before deciding what to merge. Completion keeps the Goal Branch and worktree; merge, publication, and cleanup need separate instructions.

Local Goal Records live under .booley/goals/<goal-id>/. Outside Stealth, Finish commits the Session Summary under .booley/goals/history/<goal-id>.md in the Goal worktree when that path is Git-trackable and owned by the RTL repository. Stealth, excluded, or Paired Checkout summaries stay local.

Booley deletes a finished, abandoned, or failed Goal Record, with its Session Summary and Review Package, once both its worktree and its Goal Branch are gone. A worktree is gone when it is absent from Git’s worktree registry: after git worktree remove, or after git worktree prune for a worktree directory you deleted by hand. Booley starts a background cleanup after every Goal entry, Finish, and abandonment; the command returns without waiting for Git checks or deletion. If the worker cannot start or is interrupted, the next lifecycle command retries cleanup. A record is kept whenever Git cannot confirm that the worktree or branch is gone, the record is unreadable, or another command is using it. Committed summaries under goals/history/ are never deleted. Copy anything you want to keep before you remove both the worktree and the branch.

Recovery, unattended work, and parallel sessions

The same Goal session can work with your guidance or unattended. If intent is missing or a Goal cannot be met, the Developer Agent explains what decision it needs. After a crash or reconnection, ask it to call goal_status(rules=true) in the same worktree before continuing. Saved proposal IDs and Finish operation IDs allow recovery without duplicating a decision or completion. A Finish result of revalidation_required needs fresh evidence and a new summary and operation ID.

Abandonment is available only on your explicit instruction, through goal_finish(abandon=true) with your quoted words. Quiet presence or a Developer Agent being stuck never abandons a Goal automatically. The branch and worktree remain.

For parallel work, use one linked worktree and Goal Branch per session. Start another Developer Agent in another container terminal and invoke /booley-goal there. Jobs share the Project's admission caps. booley dashboard shows sessions, Goals, and Jobs inside the Sandbox; it opens on VS Code folder attachment by default. Opt out with [sandbox].dashboard=false. Each session shows what its agent client last reported: working, waiting for tool call, or waiting for user input. Click a report, artifact, or evidence path in a detail view to open it in the editor. Press s for the session list, j for running Jobs, r for recent results, or a for all Jobs. From a session detail, Job shortcuts show that session's Jobs; press u to include all sessions and standalone Jobs. Esc returns to the previous view, and ? shows help.

Sessions have short, persistent Project handles such as S17. Jobs include their kind: J-SIM25, J-SYN34, J-LINT45, or J-FPGA33; simulation baseline Jobs use SIM, synthesis baseline Jobs use SYN, and other endpoints use J-JOB<n>. One Job counter covers every kind, so numbers may skip within a kind. Handles survive Dashboard restarts and session pruning. The 1–9 keys remain navigation shortcuts, independent of handles. Full session keys and Job run IDs are secondary text in details; MCP polling continues to use the full run ID.

Sessions receive handles when registered; Jobs receive them when created. Historical records without allocations keep S? or J-SIM? (and the equivalent for other kinds). Handle-storage failures show a diagnostic. The Dashboard itself only reads handles and never allocates them.

For scripted inspection, booley goal status shows this worktree's Goal Mode, or every active Goal Mode in the Project when this worktree hosts none. In a worktree without an active Goal Mode, booley goal status and goal_status also show its most recently ended Goal Record: Goal id, state, end time, the failure reason of a failed record, and the Session Summary and Review Package paths when those files exist.

Goal catalog

Every supported Goal has the same name in status, proposals, the Dashboard and Review Package. {target} is its bare name when unique, or its qualified selector otherwise.

Goal Requirement Evidence producer
LINT:{target} The Target's linter passes with no unwaived findings lint
SIM:{target} RTL simulation passes all tests sim
ELAB:{target} RTL/TB compiles and elaborates cleanly (no simulation) sim (mode="elab-only")
SYNTH:{target} ASIC synthesis completes within area/timing budgets synth
FPGA:{target} FPGA implementation completes within resource/timing budgets fpga
CYCLE_COUNT:{target} A named test passes and its observed Cycle Count meets every declared threshold sim
COVERAGE:{target} Native Coverage Campaign policy for one Simulation Target sim (coverage=true)
MUTATION:{target} Mutation testing achieves minimum kill rate mutation_tester
REVIEW:rtl_spec RTL review: spec compliance (RTL matches the Goal/spec, no more, no less) reviewer (category="rtl", focus="spec")
REVIEW:rtl_bugs RTL review: bug patterns, synthesis hazards, and ifdef/config consistency (the RTL as hardware, not against the spec) reviewer (category="rtl", focus="bugs")
REVIEW:rtl_protocol RTL review: bus/protocol compliance and clock-domain crossings (CDC) reviewer (category="rtl", focus="protocol")
REVIEW:rtl_security RTL review: hardware attack resistance to fault injection, simple power/timing analysis, and secret exposure (post-sim) reviewer (category="rtl", focus="security")
REVIEW:rtl_optimization RTL review: unused/dead code and missed power/performance/area wins, strict improvements only (post-sim) reviewer (category="rtl", focus="optimization")
REVIEW:rtl_code_style RTL review: comments, naming, readability, and assertion coverage (post-sim) reviewer (category="rtl", focus="code_style")
REVIEW:tb_quality Source-scoped TB review: false-pass detection, coverage gaps, and TB code quality reviewer (category="tb", focus="quality")

{target} is the bare Target name when unique, or its qualified selector otherwise. Details (thresholds, tests, baseline, review verdict and spec) follow the name in parentheses. Review Goals require clean or done; the name is identical for both requirements. Each Review Goal also names exactly one scope: target (that Target's RTL or TB files plus dependencies) or paths (files relative to the Goal worktree). Examples: {"family":"REVIEW","review":"rtl_bugs","verdict":"clean","target":"top"}, {"family":"REVIEW","review":"tb_quality","verdict":"done","paths":["tb/top_tb.sv"]}.

Goal argument family uses the uppercase capability before the colon. Internal evidence keys and Project-defined evidence bindings are not Goal names.

Source: docs/user/GOAL-MODE.md on GitHub