Skip to content

Runner Config & Acceptance Gates

The tasks/.runner-config.yml file in each project declares the acceptance gates that every task must pass before it can be marked complete. It also carries the honesty contract that governs agent behaviour.

Location and override

The default configuration lives at defaults/runner-config.yml in the craft-easy-agentic repository. It is synced into tasks/.runner-config.yml in each project via project sync.

Projects can customize their gate configuration without being overwritten by future syncs — project sync skips existing runner-config.yml files unless you pass --force.


Gate categories

Gates are grouped into eleven categories. Each gate has a required flag (skip fails the gate) and a manual_review flag (the agent must assert completion; no automated check).

1. build

Gate What it checks
api_imports All Python imports resolve cleanly (python -c "import app")
api_lint ruff check . && ruff format --check . passes
api_typecheck mypy --strict passes
frontend_build pnpm -w build (or npm run build) succeeds

2. test

Gate What it checks
api_unit pytest tests/unit — fast, no I/O
api_integration pytest tests/integration — real MongoDB
frontend_test Jest / Vitest suite per app

3. verify

Gate What it checks
verify_changes Static analysis: phantom imports, broken route references, missing i18n keys, Pydantic field truncation
verify_spec_coverage Every acceptance criterion in the spec has a file:line proof (section 21 of the spec)

4. smoke

Mount-level component render tests with mocks. One gate per app surface:

app_smoke, admin_smoke, sys_smoke, web_smoke, www_smoke

5. e2e

Full-browser or device flows:

web_e2e, www_e2e, sys_e2e (Playwright), app_maestro, admin_maestro (Maestro mobile)

6. tenants

Per-tenant fixture tests and isolation verification:

sverige_fixture, france_fixture, deutschland_fixture, cross_tenant_isolation

7. security

Gate What it checks
pip_audit pip-audit — no known Python vulnerabilities
npm_audit npm audit --audit-level=high — no high/critical JS vulnerabilities
secrets_scan trufflehog or gitleaks — no credentials in committed code
capability_check Manual: every new endpoint has require_capability(...) called

8. performance

Both are manual review gates:

Gate Criterion
api_p95 API endpoints respond in ≤ 300ms at p95 (local dev, non-cold-start)
bundle_size_delta Frontend bundle size increase ≤ +50KB gzipped

9. accessibility

All manual review:

contrast_check (≥ 4.5:1 text, ≥ 3:1 large text), keyboard_nav (all interactive elements reachable), screen_reader (NVDA / VoiceOver tested)

10. documentation

All manual review:

api_endpoint_spec (OpenAPI / docs/ updated), view_spec (UI spec sections filled), feature_inventory (docs/product/features/ updated), summary_changelog (CHANGELOG.md entry added)

11. evidence

The evidence gates are the "honesty gates" — they require the agent to explicitly assert completion:

Gate Description
adversarial_review Section 19 of the spec filled; no unhandled HIGH-risk items
vad_testade Section 20 of the spec: honest summary of what was actually run
surface_matrix Section 13 of the spec: all touched surfaces marked
spec_bullet_signoff Section 21 of the spec: all ACs mapped to file:line
create_consume_pairs Section 14 of the spec: every create flow has a consume flow
test_scenarios Section 15 of the spec: filled BEFORE implementation

Honesty contract

The runner-config embeds a behavioural contract that Claude agents must follow:

Marking tasks complete

  • Never mark [x] in BACKLOG.md if any required gate is red.
  • Never silence failing tests with @pytest.mark.skip, xfail, // eslint-disable, or similar.
  • Never bypass type checks without documenting the reason in OWNER_TODO.md.
  • If a manual_review gate cannot be verified: document in OWNER_TODO.md and leave [ ] in BACKLOG.md.

Test quality

  • Delete or rewrite broken tests. Do not skip them.
  • Write new tests when the spec's "Required Tests" section demands them.
  • Tests must use real fixtures, not mocked logic, for integration gates.

Scope discipline

  • Undiscovered work → OWNER_TODO.md. Never expand scope without owner approval.
  • If a dependency is missing, document it and leave the task [ ] — do not implement a workaround.

Code quality

  • Grep for every import, function call, and route reference before using it. Do not assume anything exists.
  • git grep or find first; cat second; never assume.
  • Match existing code style exactly — no "this is cleaner" refactors in scope.

Commit discipline

{TASK-ID}: {short description}

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

One commit per task. No mid-task "WIP" commits in BACKLOG-driven runs.


Project-specific customization

Projects can override specific gate settings:

# tasks/.runner-config.yml (project override)
gates:
  e2e:
    app_maestro:
      required: false    # this project has no mobile app
  tenants:
    deutschland_fixture:
      required: false    # germany tenant not enabled for this project

Any gate not mentioned in the project override inherits the default from defaults/runner-config.yml.


Concurrency (runner.default_parallel)

Default parallel-slots per project is 3 — sized for safe multi-project use on an 8 GiB MemoryMax (each claude --print subprocess holds roughly 1 GiB resident, so the practical cap is around 6 concurrent tasks across all projects after dashboard + bash + system overhead).

Override per project by adding the runner section to tasks/.runner-config.yml:

runner:
  default_parallel: 5     # 1–20; out-of-range falls back to default + WARN

Resolution order, highest priority first:

  1. ?parallel=N query string on runner/start
  2. CLI/UI override on the Start button
  3. runner.default_parallel in tasks/.runner-config.yml
  4. Legacy defaults.parallel in the same file
  5. Hard default 3

RunnerStateSnapshot.effective_parallel exposes the value the last start_runner actually used (null when this dashboard session hasn't started a runner yet).


Smart concurrency (memory-aware throttle)

The dashboard watches the cgroup memory.peak / memory.max ratio and projects total RSS for the requested parallel value. Behaviour:

Peak ratio UI Backend
< 70 % No banner Start always allowed
70–85 % Yellow banner + tooltip on Start Start allowed (warn-only)
> 85 % Red banner + Start button disabled 409 memory_pressure on Start

Constants live in services/concurrency.rs:

PER_TASK_RSS_BYTES   = 1.0 GiB
SYSTEM_HEADROOM_BYTES = 1.5 GiB
PEAK_BLOCK_RATIO     = 0.85
PEAK_WARN_RATIO      = 0.70

The 409 body carries current_runners, current_active_tasks, would_be_total, memory_peak_pct, memory_max_bytes, and a Swedish suggestion string the UI renders directly. The frontend disables the Start button proactively at 85 % so the user doesn't have to click and get refused.

Force-start (power-user opt-in)

Settings → "Tillåt force-start vid memory pressure" enables a secondary ⚠ Start button. Clicking opens a confirmation modal with the actual cgroup numbers; confirming sends force=true to the backend, which logs a WARN and bypasses the throttle. Use only when other runners are about to finish — the kernel cgroup OOM-killer is not patient.

CLI / scripts can pass ?force=true to POST /api/projects/{slug}/runner/start to achieve the same bypass.