{% extends "base.html" %} {% block title %}Configuration{% endblock %} {% block body %}
Dispatch control and the effective config values most often tuned. Edit the underlying YAML files to change most values: the scheduler re-reads garden.yaml (and its env / local overlays) each tick when the file changes, so an edit takes effect within one tick with no restart. A few keys are read once at startup and need a restart — they are named below. The pause switch and max_parallel can also be overridden live from here.
Version {{ tool_build.get('installed_version', 'unknown') }} · installed source {{ tool_build.get('active', '')[:12] or 'unknown' }}. {% if tool_build.get('sha') %}Available source update: {{ tool_build.sha[:12] }} (not installed; not a published release).{% else %}No newer source update has been recorded by this scheduler.{% endif %}
Each save is validated and replaces garden.yaml atomically. Open forms carry a revision token, so a stale tab cannot overwrite a newer edit. Add, edit, or remove collection rows below; mapping values accept YAML scalars or nested values.
{% if saved_pending %}Saved changes are waiting to be adopted. The values below are on disk; “effective now” still reflects the scheduler’s accepted configuration. Restart-only fields need a process restart, and a safety-sensitive reload may be held while a run is active.
{% endif %}{{ row.field.key }}{{ row.field.help }}
Saving this field changes garden.yaml, but {{ row.effective_source }} currently masks that value.
Locked by project policy. {{ row.reason }}{% if row.policy_source %} · {{ row.policy_source }}{% endif %}
{% elif selected_product and not row.editable %}This setting is global-only. Switch to Global garden to edit it.
{% else %} {% endif %}garden.yaml changed while a run was in flight. Held since {{ config_hold.since }}: {{ config_hold['keys']|join(', ') }} disagrees with the config run(s) {{ config_hold.runs|join(', ') }} were dispatched under. It will apply on its own once every run above is reaped, or confirm it now if the change is yours.
Dispatch is paused{% if pause_ctrl.by %} (by {{ pause_ctrl.by }}{% if pause_ctrl.at %} at {{ pause_ctrl.at[11:16] }}Z{% endif %}){% endif %}. {% if pause_ctrl.reason %}Reason: {{ pause_ctrl.reason }}.{% endif %} Reaping, polling and reviews continue.
{% else %}Dispatch is running. Pause it to prevent new work and revise runs from being dispatched on the next tick without restarting the server.
{% endif %}Maintenance is {{ "quiesced" if maintenance.quiesced else "requested" }}. Unlike dispatch pause, it freezes collection and every scheduler mutation at a transaction boundary.
{% if maintenance.live %}Reinstall blockers:{% for run in maintenance.live %}
{{ run.task }}/{{ run.run }} — {{ run.blocker }}{% endfor %}
Finished but uncollected (safe to reinstall): {{ maintenance.finished_uncollected|join(', ') }}.
{% endif %}{{ "Reinstall ready" if maintenance.ready else "Reinstall not ready" }}
{% else %}Use this before reinstalling or restarting the scheduler. It lets active processes finish and save results, but does not collect them until you explicitly resume. Ordinary dispatch pause still lets collection, checks, reviews and merges continue.
{% endif %}Host admission: {{ resource_status.active }}/{{ resource_status.limit }} · heavy execution: {{ resource_status.heavy_running }}/{{ resource_status.heavy_limit }} authoritative (requested {{ resource_status.requested_heavy_limit }}) {% if resource_status.heavy_waiting %}({{ resource_status.heavy_waiting }} waiting: heavy-test budget full){% endif %} · cgroup isolation: {{ resource_status.isolation }}. Available memory: {{ resource_status.memory_available_mb if resource_status.memory_available_mb is not none else "unknown" }} MiB {% if resource_status.cgroup_available_mb is not none %}(effective {{ resource_status.cgroup_boundary }} headroom {{ resource_status.cgroup_available_mb }} MiB){% endif %}; temporary storage: {{ resource_status.temp_free_mb if resource_status.temp_free_mb is not none else "unknown" }} MiB. Disk reserve: {{ resource_status.disk_reserve_bytes }} bytes; admitted allocation: {{ resource_status.disk_required_bytes }} bytes.
{% for volume in resource_status.storage_volumes %}{{ volume.label }}: {{ volume.free_bytes if volume.free_bytes is not none else "unavailable" }}{% if volume.free_bytes is not none %} free bytes{% endif %}{% if volume.error %} — {{ volume.error }}{% endif %}.
{% endfor %} {% if resource_status.capacity_full %}At local execution capacity: {{ resource_status.active }}/{{ resource_status.limit }} busy. Eligible work waits for a slot and dispatches automatically when one opens.
{% endif %} {% if resource_status.pressured %}Resource pressure blocks {{ resource_status.disk_operation }}: {{ resource_status.pressure_reasons|join('; ') }}.{% if resource_status.capacity_full %} Also at local execution capacity ({{ resource_status.active }}/{{ resource_status.limit }} busy).{% endif %} New local launches wait for pressure to clear; active work can drain.
{% endif %} {% if resource_status.heavy_conflict %}Heavy capacity conflict: {{ resource_status.heavy_conflict }}. Existing authoritative capacity remains enforced.
{% endif %} {% if resource_status.reclaim %}{{ resource_status.reclaim }}. Admission still requires a fresh ordinary headroom check.
{% endif %} {% if resource_status.cgroup_events %}Memory events: {% for name, value in resource_status.cgroup_events %}{{ name }}={{ value }}{% if not loop.last %}, {% endif %}{% endfor %}.
{% endif %} {% if resource_status.isolation != "enforced" %}Local descendants do not have a separately enforced CPU/memory budget ({{ resource_status.isolation }}). Configure a delegated, finite resources.execution_cgroup before relying on service isolation.
garden.yaml: {{ max_parallel_file }} {% if max_parallel_override is not none %} · live override: {{ max_parallel_override }} (in effect){% else %} · no live override{% endif %} {% if max_parallel_source == "profile" %} · effective now: {{ max_parallel_value }} (from the operating profile {{ operating_profile_active }}, below){% endif %}
Worker occupancy: {{ workers_running }}/{{ max_parallel }} — the same worker-mode count used by dispatch and shown in the rail and garden status.
Takes effect on the next tick; running workers are never stopped by lowering it. Clear the field to drop the override.
Worker slots: {{ worker_slot_modes }}. Reviews use the separate reviews cap; checks and edit runs are short machine-bound runs and do not consume worker slots, but they still count toward the shared local execution limit shown in the rail.
garden.yaml: {{ operating_profile_file or "(none)" }} {% if operating_profile_override %} · live override: {{ operating_profile_override }} (in effect){% else %} · no live override{% endif %} {% if profile_source %} · active stop set by {{ profile_source }}{% endif %}
Sets workers, reviews, the model tier map, the review and retro tiers and the observation feed together, within one tick, no restart. A more specific live override (e.g. max_parallel above) still wins for that one knob.
| stop | workers | reviews | review | retro | feed |
|---|---|---|---|---|---|
| {{ row.name }}{% if row.active %} (active){% endif %} | {{ row.workers if row.workers is not none else "—" }} | {{ row.reviews if row.reviews is not none else "—" }} | {{ row.review_difficulty or "—" }} | {{ row.retro_difficulty or "—" }} | {{ row.observe or "—" }} |
{% for row in operating_profile_rows %}{{ row.name }}: {{ row.meaning }}
{% endfor %}
garden.yaml: {{ observe_profile_file or "(none — base observe fields)" }} {% if observe_profile_override %} · live override: {{ observe_profile_override }} (in effect){% else %} · no live override{% endif %} {% if observe_profile_source == "profile" %} · effective now: {{ observe_profile_effective }} (from the operating profile {{ operating_profile_active }}, above){% endif %}
A running garden observe --follow picks up the switch on its next pass, no restart.
defaults only (no garden.yaml found)
{% endif %}Set review.max_rounds in garden.yaml to a positive integer for a hard total-round cap, or null for unlimited automated rounds. Unlimited still requires current-head review evidence, checks, security/persona evidence, merge gates and all ordinary resource admission.
review.friction_after is a separate positive round threshold (or null to disable it): it records one retrospective-visible loop-friction entry without creating an owner stop. review_parallel remains the concurrent-review slot limit, not a round limit.
Edit these in garden.yaml; the change is picked up on the next tick, no restart.
These keys are read when the scheduler is built (or when the watch/serve loop first sets its interval), so editing them takes effect only after restarting garden watch / garden serve.