nacl-goal

SkillAI & models

Safety-first wrapper around Anthropic's /goal command. Resolves a high-level NaCl alias into a deterministic GOAL_PROOF completion condition that the transcript-only evaluator can actually verify. Two UX modes coexist: preview-by-default for the 2.10.0 aliases (wave / fix / validate / reopened-drain); autonomy-by-default for the 2.10.1 `intake` orchestrator alias (free-text/image goal → classified atoms → one PR → CI → staging) and the 2.18.0 `conduct` multi-cluster orchestrator (heterogeneous goal → clusters → one PR per cluster, wave-ordered). Use when: running a long NaCl loop autonomously, or the user says "/nacl-goal".

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the nacl-goal skill

What this skill tells your AI

The instructions your AI receives, as published by itsalt/nacl in nacl-goal/SKILL.md and read by ahel’s review.

Contract

Inputs this skill consumes:

  • <alias> — required positional. One of the named aliases from nacl-goal/aliases.md (wave:<N>, fix:<BUG-NNN>, validate:<MOD-ID>, reopened-drain, intake, conduct, custom), or the special invocations resume and abort <run_id>.
  • --start — optional flag. Without it the preview-mode aliases (wave, fix, validate, reopened-drain, custom) run in preview/dry-run mode only. The intake alias has the inverse default: autonomy ON, with --plan-only as the opt-out (see §intake alias UX below).
  • --tier=<S|M|L|XL> — optional override for custom alias (mandatory for custom).
  • --check-script=<path> — path to executable check script for custom alias (mandatory for custom).
  • --description="<one line>" — optional label recorded in the run file.
  • intake-only opt-out flags: --plan-only, --strict, --target=<staging|dev-only>, --new-run. See §intake alias UX.

Outputs this skill produces:

  • Without --start: a preview block containing the full resolution: alias, tier, soft budget, check_script path, GOAL_PROOF template, human gates, permissions denylist, and (for Tier L/XL) estimated dollar cost from nacl-goal/pricing.json. The exact --start command to copy-paste.
  • With --start (2.10.0, Tier S/M): a warning that autonomous execution is 2.10.1 functionality, then issues /goal with the composed condition. Does NOT produce a .tl/goal-runs/ file in 2.10.0.
  • With --start (2.10.0, Tier L/XL): structured refusal REFUSE_TIER_NOT_YET_ENABLED.
  • Refusal block (any tier, any phase) when a Tier-C gate is detected statically.

Downstream consumers of this output:

  • Human user (preview, refusal, run summary)
  • .tl/goal-runs/ — run files written on --start (enforced from 2.10.1)

Two-phase invocation (Architecture §2)

/goal starts a turn immediately on invocation. The preview/confirm UX lives outside /goal, in this wrapper:

/nacl-goal <alias>            # preview only — no /goal issued, no turn consumed
/nacl-goal <alias> --start    # issues /goal with composed GOAL_PROOF condition

Preview output must include all of:

  1. Resolved alias name and canonical form
  2. Tier and full soft budget (turns, hours, observed token target) from the tier table below
  3. check_script path and how it is invoked each turn
  4. Completion condition verbatim (including the GOAL_PROOF instruction block)
  5. Human gates that would block this alias (or "none detected")
  6. Permissions denylist that will be enforced
  7. For Tier L/XL: estimated dollar cost at current model pricing from nacl-goal/pricing.json
  8. The exact --start command to copy-paste

--start behavior in 2.10.0

  • Tier S / Tier M: Issues /goal with the composed GOAL_PROOF condition, but emits this warning before doing so:

    WARNING (2.10.0): Autonomous execution via /nacl-goal --start is 2.10.1 functionality.
    In 2.10.0, /goal is issued but .tl/goal-runs/ write, concurrent-execution lock,
    crash/resume, and runtime gate detector are NOT active. Run interactively and monitor.
    
  • Tier L / Tier XL: Refuses with REFUSE_TIER_NOT_YET_ENABLED:

    REFUSE_TIER_NOT_YET_ENABLED
    Tier L and XL autonomous execution is not enabled in 2.10.0.
    Use /nacl-goal <alias> (preview) to inspect the plan.
    Autonomous Tier L/XL arrives in 2.10.1.
    

Tier table — v0 calibration defaults (Architecture §13)

All three columns are soft. /goal cannot hard-enforce them. A true hard cap requires an external runner or Stop-hook script (future work, 2.10.2+). Do not run XL unattended overnight in 2.10.0 or 2.10.1.

Tierturns_softwall_clock_softobserved_token_target
S1502 h3,000,000
M5006 h8,000,000
L1,20016 h20,000,000
XL3,00036 h50,000,000

Turn and wall-clock are surfaced through GOAL_PROOF every turn and trigger GOAL_BUDGET_EXHAUSTED via the in-condition instruction. To be calibrated in 2.10.2 from aggregated .tl/goal-runs/.


GOAL_PROOF protocol (Architecture §1)

Every alias generates a /goal condition that instructs the primary session to run the alias check script at the end of every turn and print a block of this exact shape immediately after the raw command output:

GOAL_PROOF
alias: <alias>
tier: <S|M|L|XL>
check_command: <exact shell command run this turn>
result: GOAL_OK | GOAL_NOT_OK | GOAL_BLOCKED | GOAL_BUDGET_EXHAUSTED
evidence:
  - <key>: <value>
  - <key>: <value>
turns_so_far: <int>
observed_tokens: <int>
elapsed: <duration>
END_GOAL_PROOF

The evaluator (Haiku 4.5 by default) is transcript-only — it cannot run tools, read files, or execute commands. GOAL_PROOF surfaces machine-checkable state into the transcript so the evaluator's only job is: "did the last block have result == GOAL_OK AND does .tl/goal-runs/<run_id>.md exist."

This block is a wire format. Field renames and delimiter changes are major version bumps. No narrative is permitted between the command output and the GOAL_PROOF block. See docs/guides/goal-proof-protocol.md for full schema, semantics, and examples.


Alias resolution and check scripts (Architecture §3)

Aliases and their binding contracts are defined in nacl-goal/aliases.md. Do not duplicate alias definitions here — reference that file.

Check scripts shipped in 2.10.0 (stubs; truth-source wiring in progress):

nacl-goal/checks/wave.sh             <N>
nacl-goal/checks/fix.sh              <BUG-NNN>
nacl-goal/checks/validate.sh         <MOD-ID>
nacl-goal/checks/reopened-drain.sh

Check scripts shipped in 2.10.1 (intake ships in PR1; the others remain deferred):

nacl-goal/checks/intake.sh           --run-id <goal-run-id>     # ✅ PR1
nacl-goal/checks/stubs-cleanup.sh    <MOD-ID>                   # deferred
nacl-goal/checks/migrate-canary.sh                              # deferred
nacl-goal/checks/feature.sh          <FR-NNN>                   # deferred
nacl-goal/checks/probe-stop-signals.sh   (invoked each turn)    # deferred

Check script shipped in 2.18.0 (conduct multi-cluster orchestrator):

nacl-goal/checks/conduct.sh          --run-id <goal-run-id>     # ✅ scans clusters/*/

Every check script:

  • Takes its positional args per the contract in nacl-goal/aliases.md
  • Reads its truth source directly (graph via Cypher, registry file, YouGile API, test runner)
  • Prints stable, grep-friendly output followed immediately by a GOAL_PROOF block
  • Always exits 0 — the evaluator cannot see exit codes; GOAL_PROOF carries the actual status

Structured refusal flow (Architecture §5)

Tier-C refusals fire at preview time wherever statically possible (by alias identity). The runtime gate detector catches dynamic crossings (2.10.1).

Every refusal must:

  1. Name the specific gate by its REFUSE_* code from nacl-goal/refusal-catalog.md
  2. Cross-reference nacl-tl-core/references/gate-fire-catalog.md
  3. Offer a split-mode suggestion (interactive skill then wrapper)
  4. Print copy-paste commands for the interactive path

User-facing rendering follows the rendering rule in nacl-goal/refusal-catalog.md: lead with the plain-language reason + copy-paste fallback; the gate code is a trailing tag, not the headline; and step numbers / Tier-C never appear in user-facing text. (Items 1–2 above are satisfied by the trailing tag and the internal cross-reference — they are not the headline.)

Refusal codes (full catalog in nacl-goal/refusal-catalog.md):

REFUSE_HUMAN_GATE_BA_SA_HANDOFF
REFUSE_HUMAN_GATE_SA_PHASE_CONFIRMATION
REFUSE_HOTFIX_JUDGMENT
REFUSE_POST_CANARY_RETROSPECTIVE
REFUSE_PRODUCTION_MUTATION
REFUSE_UNTIERED_CUSTOM_GOAL
REFUSE_UNTRUSTED_WORKSPACE
REFUSE_HOOKS_DISABLED
REFUSE_CONCURRENT_GOAL_LOCKED
REFUSE_DANGEROUSLY_SKIP_PERMISSIONS
REFUSE_TIER_NOT_YET_ENABLED

Refusal codes are part of the wire format. Renaming or removing a code is a major version bump for /nacl-goal.


Permissions denylist (Architecture §6)

/nacl-goal runs only in default permissions with explicit approvals, OR in auto mode with the NaCl allowlist active.

Full text in docs/guides/goal-permissions.md. Brief summary:

Never allowed under any alias:

  • --dangerously-skip-permissions (triggers REFUSE_DANGEROUSLY_SKIP_PERMISSIONS)
  • Any mode that disables hooks (triggers REFUSE_HOOKS_DISABLED)
  • Any workspace where workspace trust is not granted (REFUSE_UNTRUSTED_WORKSPACE)
  • git push to any remote
  • git merge into main, master, or release/*
  • Any release-publishing action (npm publish, gh release create, etc.)
  • Production DB migrations
  • rm -rf outside the current workspace
  • Editing .env*, secrets, credentials, .ssh/, ~/.aws/, ~/.config/gh
  • Changing CI/CD configuration or credentials
  • Calling third-party paid APIs with side effects beyond test budget

Per-alias allowlist (positive grants):

  • Local test execution
  • Graph reads and writes scoped to current project
  • Branch commits
  • gh pr create (but never gh pr merge)
  • YouGile column moves within the project board

Custom alias (Architecture §12)

/nacl-goal custom \
  --tier=<S|M|L|XL>            # mandatory
  --check-script=<path>         # mandatory; must exist, be executable,
                                # and produce GOAL_PROOF-compatible output
  --description="<one line>"    # recorded in run file
  --start                       # must be a separate invocation

Custom without --check-script returns REFUSE_UNTIERED_CUSTOM_GOAL. Custom without --tier returns REFUSE_UNTIERED_CUSTOM_GOAL. Custom may not target paths matching the Tier-C catalog in nacl-goal/gate-fire-detector.md.


intake alias (2.10.1 — autonomous goal orchestrator)

intake is the FIRST alias with default_mode: autonomous. Where the four 2.10.0 aliases (wave, fix, validate, reopened-drain) require an explicit --start to issue /goal, intake issues /goal by default and provides opt-outs for previewing or strict mode.

This is intentional UX: /nacl-goal intake "<goal>" should be the short, normal invocation. The user shouldn't need to remember internal flags or gate names to drive a goal autonomously to a staging stand. See [[feedback-autonomy-default-ux]] for the design rationale.

intake UX

/nacl-goal intake "<goal>"

Default behavior:
  • autonomous execution is ON
  • standard safe-exception envelope is ON (see nacl-goal/envelope.md)
  • target = staging if config.yaml → deploy.staging.url exists,
            otherwise PLAN_BLOCKED_STAGING_REQUIRED_BUT_MISSING
  • branch_mode = current when invoked from a non-production branch:
            atoms run ON the branch you are standing on, commits stay local,
            ONE push at DELIVER (push_cadence = deferred). The preview prints
            a one-line notice: "Running on your branch <name>; one push at
            deliver; do not commit to this branch while the run is active."
            From main/master/release/* the production refusal still fires —
            create a working branch first.
  • atoms BUG / TASK / FEATURE_SMALL run on that single branch, one PR
  • atoms FEATURE_HEAVY → PLAN_BLOCKED with planning artifacts (no silent split)
  • uncommitted changes (another agent's WIP) do NOT refuse the run in
    branch_mode=current — see Flow step 3 "Smart WIP" for the
    file-overlap protocol

Opt-outs (each disables a slice of the default):
  --plan-only        write planning artifacts only; no /goal, no branch, no PR,
                     no exception YAML, no source-code changes
  --strict           disable default safe-exception envelope; pre-flight refuses
                     if plan predicts a gate would need envelope auto-authorization
                     (PLAN_BLOCKED_STRICT_REQUIRES_INTERACTIVE_FLOW)
  --branch=current   run on the currently checked-out branch (default when on a
                     non-production branch)
  --branch=new       pre-2.14 behavior: create feature/goal-<short-hash>; requires
                     a clean worktree (PLAN_BLOCKED_DIRTY_WORKTREE applies)
  --push=deferred    atoms commit locally; single push at DELIVER (default when
                     branch_mode=current)
  --push=per-atom    push after every atom; PR opens on first push (default when
                     branch_mode=new — pre-2.14 behavior)
  --push=none        no push at all; run ends with local commits; ONLY valid with
                     --target=dev-only (with staging it is a usage error rejected
                     at argument parsing, before step 0 — no artifacts written);
                     deliver later with /nacl-tl-deliver
  --target=staging   require staging (default)
  --target=dev-only  local verify + PR only; final message MUST NOT claim staging
                     delivery; dev_verified is asserted via local /nacl-tl-verify
  --new-run          force fresh run-id even if goal_fingerprint matches an existing
                     run; does NOT close or reuse prior PR in 2.10.1
  --budget=<profile> optional budget override (default Tier M: 200 turns / 3h / 4M tokens)

Backward-compat invariant: `--branch=new` reproduces the pre-2.14 flow
byte-for-byte (new goal branch, per-atom pushes, dirty-worktree refusal).
The default changed ONLY for invocations from an existing feature branch.

intake Flow (14 steps)

The Claude session running /nacl-goal intake executes the following flow. For per-file schemas see nacl-goal/plan-lock-schema.md. For artifact locations and idempotence see nacl-goal/run-artifacts.md. For the exception envelope see nacl-goal/envelope.md. For gate prediction see nacl-goal/gate-prediction.md. For retry semantics see nacl-goal/retry-policy.md. For regression diff see nacl-goal/regression-schema.md.

0. PRIVACY / IGNORE PRECHECK
   verify .tl/goal-runs/ AND .tl/exceptions/goal-runs/ are gitignored
     (use `git check-ignore` from project_root)
   if either is NOT ignored:
     → PLAN_BLOCKED_GOAL_ARTIFACTS_NOT_GITIGNORED
   The 2.10.1 wrapper does NOT auto-patch .gitignore. The user must do it.
   Writing PII (user email, free-text goal, image refs) into a non-ignored
   directory is irreversible if the user pushes by accident.

1. INIT_RUN
   compute goal_fingerprint (see run-artifacts.md §Goal fingerprint)
   acquire flock on .tl/goal-runs/index.lock (timeout 30s; else
   PLAN_BLOCKED_INDEX_LOCK_BUSY)
   consult index.json per the re-invocation rules in run-artifacts.md
     (RESUME for transient interruptions; refuse for non-resumable terminal
      states unless --new-run)
   run_id = goal-intake-<utc-iso>-<short-hash>
   mkdir .tl/goal-runs/<run_id>/{atoms/, planning/}
   write request.json, budget.json
   append index.json entry (state: "init", resumable: true)
   atomic rename; release flock

2. RESOLVE_TARGET
   --target=staging or default + deploy.staging.url present → deploy_target = staging
   --target=dev-only                                        → deploy_target = dev-only (WARN)
   else                                                     → PLAN_BLOCKED_STAGING_REQUIRED_BUT_MISSING

3. PRECHECKS  (Tier-C; /goal not yet issued)
   on main/master/release/*    → PLAN_BLOCKED_UNSAFE_PRODUCTION_MUTATION
     (fires regardless of branch_mode; create a working branch first)
   resolve branch_mode / push_cadence:
     --branch absent  → branch_mode = current  (we are on a non-production branch)
     --branch=new     → branch_mode = new
     push_cadence = --push if given, else deferred (current) / per-atom (new)
   Smart WIP (branch_mode=current):
     preexisting_dirty_files[] = paths from `git status --porcelain`
       (including untracked); recorded in plan.lock.json at step 5
     non-empty does NOT refuse — uncommitted files are presumed to be
       another agent's in-flight work in the shared worktree. They are
       never staged, never committed, never reverted by the goal run.
     overlap resolution happens at step 5 (needs classified atoms);
       hard runtime backstop at step 9 (commit-time collision gate)
   branch_mode=new: dirty worktree → PLAN_BLOCKED_DIRTY_WORKTREE (pre-2.14 rule)
   resolve baseline_command chain (config.yaml → package.json → pyproject → defaults)
     missing → PLAN_BLOCKED_BASELINE_COMMAND_MISSING
   capture regression-baseline.json per regression-schema.md
     run the baseline in an ISOLATED throwaway worktree pinned to the
       current HEAD sha (`git worktree add --detach <tmp> HEAD`), so other
       agents' uncommitted WIP never contaminates the baseline; provision
       deps per regression-schema.md §Worktree isolation; if provisioning
       fails, fall back to in-tree run with worktree_isolated: false
       (disclosed in GOAL_PROOF)
     PLAN_BLOCKED_BASELINE_RED only fires if
       (exit_code != 0 AND collected_count == 0)
       OR (collected_count > 0 AND passed_count == 0)
     i.e. zero-tests-collected runner error, or all-tests-failing.
   missing gh auth / CI perms  → PLAN_BLOCKED_GH_AUTH_OR_CI_PERMISSION_MISSING

4. CLASSIFY  (/nacl-tl-intake --autonomous --yes --emit-state .tl/goal-runs/<run_id>/intake.json)
   atoms[] with: id, type, linked_uc, evidence, confidence, risk_level,
   depends_on, hard_refuse_triggers, trigger_evidence, spec_gap, residual_note,
   diagnosis, skill_path.
   Intake now SELF-DIAGNOSES before this policy applies (Step 2a.5 PROBE):
   for every atom the graph alone did not resolve it verifies the competing
   hypotheses against the actual code/DB (bounded read-only probes) and
   derives a rubric score (nacl-tl-core/references/intake-scoring.md;
   thresholds from the project's config.yaml -> intake.*, frozen into
   diagnosis.threshold_used). "The graph didn't resolve it" alone never
   reaches the user anymore.
   --autonomous question policy (2.14+, probe-scored; see nacl-tl-intake
   Step 2b case table):
     HIGH L0/L1, HIGH spec-gap-no-hard-refuse → auto-route (as before)
     HIGH + CODE (probe score >= high_confidence [0.9]) → auto-route
       (as HIGH+GRAPH; "verified against the code")
     HIGH L2/L3 launch-sanity (Template B)    → auto-confirmed; informational line
     MEDIUM with probe leaning (route_threshold [0.7] <= score <
       high_confidence [0.9]) (Template D)    → auto-route on the leading
       hypothesis; alternative + blocking_fact tracked as residual_note
       (reason medium_confidence_alternative);
       pre-authorized via envelope.md gate `medium-confidence-routing`;
       NEVER when the atom carries a hard_refuse_trigger
     Sub-threshold (probe ran, score < route_threshold) (Template E)
                                              → ONE consolidated batch question,
       asked HERE (pre-/goal interaction is allowed): list every unresolved
       atom WITH its diagnosis (what was checked, per-hypothesis results,
       leaning, blocking fact); the user answers once and the run proceeds
       fully autonomously. Non-interactive session or declined →
       PLAN_BLOCKED_AMBIGUOUS_CLASSIFICATION (as before)
     hard_refuse (Template C)                 → unchanged: PLAN_BLOCKED_* below —
       the user's "critical questions" always survive autonomy; a probe
       never clears a hard_refuse_trigger
   Refuse mapping (per plan-lock-schema.md §hard_refuse_triggers):
     billing | destructive | l2_l3 | product_decision → _FEATURE_REQUIRES_PRODUCT_DECISION
       (or _FEATURE_REQUIRES_HUMAN_PRODUCT_DECISION if FEATURE)
     schema_migration | public_api_contract           → _FEATURE_REQUIRES_SCHEMA_MIGRATION
     auth_or_security | permissions                   → _FEATURE_REQUIRES_AUTH_OR_SECURITY_CHANGE
     hotfix_or_release_routing                        → REFUSE (interactive)
   FEATURE_HEAVY without trigger → write planning/feature-plan.md +
     planning/open-decisions.md → PLAN_BLOCKED_FEATURE_REQUIRES_HUMAN_PRODUCT_DECISION
   ambiguous AFTER the probe AND the consolidated batch → PLAN_BLOCKED_AMBIGUOUS_CLASSIFICATION
     (MEDIUM-confidence atoms no longer reach this refusal — they auto-route;
      sub-threshold atoms reach it only with their diagnosis attached)
   PLAN_BLOCKED_PLAN_SPLIT_REQUIRED fires when EITHER:
     (a) atoms touch >1 top-level module AND no dependency path connects the
         atom groups AND total atoms >= 3; OR
     (b) atoms require incompatible release targets; OR
     (c) atoms require both normal feature-branch and hotfix/release routing; OR
     (d) atoms imply mutually exclusive hard-refuse policies.

5. LOCK PLAN
   ATOM ID INVARIANT: atom.id is assigned here once and is immutable for the run.
     Form: atom-<short_sha256(type + linked_uc + normalized_title)[:12]>
     Resume reads plan.lock.json and atoms/*.state.json — never re-classifies.
   topological sort atoms by depends_on; cycle → PLAN_BLOCKED_ATOM_DEPENDENCY_CYCLE
   tie-break: BUG before FEATURE_SMALL, then by id lexicographically
   branch = feature/goal-<short-hash>            (branch_mode=new)
   branch = $(git rev-parse --abbrev-ref HEAD)   (branch_mode=current)
   branch_mode=current bookkeeping (recorded in plan.lock.json):
     branch_base_sha        = git merge-base <branch> <base_branch>
     prior_unpushed_commits = git rev-list --count <base_branch>..HEAD
     preexisting_dirty_files[] (snapshot from step 3)
   WIP-overlap check (branch_mode=current, preexisting_dirty_files non-empty):
     predict each atom's touch zone from the graph (linked UC → Module →
       workspace directories + api-contracts paths) — coarse, directory-level
     predicted zone ∩ preexisting_dirty_files == ∅ →
       print one notice line ("N uncommitted files left untouched — presumed
       another agent's work") and proceed
     overlap → ONE consolidated plain-language question (pre-/goal, allowed):
       per overlapping atom: continue anyway / commit those files into the
       branch first / exclude the atom from this run
       non-interactive session or declined → PLAN_BLOCKED_DIRTY_WORKTREE
       (see refusal-catalog.md — the catalog entry covers both modes)
   write plan.lock.json (incl. branch_mode, push_cadence), authorization.json
   write atoms/<atom_id>.state.json with state="pending" for each atom
   render initial PR body (per pr-body-template.md) to pr-body.md — WIP, atom table.
     per-atom cadence: /nacl-tl-ship reads this file when it opens the PR on
       the first push. deferred cadence: /nacl-tl-deliver reads it at the
       single push. none: no PR in this run.
   index.json state: "planned", resumable: true (flock-protected, atomic rename)
   --plan-only: EXIT here

6. --STRICT PRE-FLIGHT (only when --strict)
   for each atom: predict which gates would fire via gate-prediction.md
     (uncertain prediction → block conservatively)
   if any predicted gate ∈ envelope.md §Auto-enabled gates:
     → PLAN_BLOCKED_STRICT_REQUIRES_INTERACTIVE_FLOW

7. MATERIALIZE EXCEPTION ENVELOPE (skipped if --strict)
   for each auto-enabled gate that the plan would hit:
     write .tl/exceptions/goal-runs/<run_id>/EXC-goal-<gate>.yaml
       owner: <git user.email>
       reason: pre-authorized; goal fingerprint + sanitized_preview only
         (full goal stays in gitignored request.json)
       expires: issued_at + 3h
   YAML sanitization rules per envelope.md §sanitized_preview (YAML-injection defense).

8. ISSUE /goal
   composed condition = success_condition (per aliases.md §intake) + budget envelope
     + run_id binding
   index.json state: "running", resumable: true
   open progress.jsonl (wrapper-level events only; inner-skill summaries flow
     through budget.json → inner_skill_runs[])

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
27
Forks
4
Last commit
Sep 2026
Advanced
Item type
skill
Key
nacl-goal
Source
github.com/itsalt/nacl