Story 1566: Who governs the agent when nobody is watching

Why this exists (the catastrophe it prevents). The rails (Story-1562) gave every agent a leash. But a leash has to be set by someone — and in an Auto run there is no human in the room to set it. Auto is the extraction worker: a document lands, an event fires, the agent processes a batch overnight, and counsel reads the summary in the morning. Three doors swing open if a firm can't configure that headless run. First, no default: the agent has no Thinking level or review posture to use, so it either fails or falls back to something arbitrary. Second, no spend cap: counsel says "process everything" and wakes to a 500-file batch that silently ran every step at Extra-High and blew the firm's budget — or, worse, a tight budget quietly drops a privilege-detection step below its safety floor to fit, and a privileged document leaks. Third, no firm posture: an enterprise client wants "delegate routine work in trusted matters" expressed once, firm-wide, not re-picked per run. This Story builds the admin/firm configuration that governs all three — and wires it so that the budget governs cost, the floor governs safety, and safety always wins.

Built on ratified ADR-129, consumed by the merged rails. The thinking/authority/floor resolvers already exist broker-side (Story-1562). This Story is the producer of the config those resolvers consume: D3 floors (visible + immutable), D4 firm_policy authority profiles, D8 posture provenance (policy_version), and D11 Auto budget governance with a floor-protected minimum. Target repo = pramaan-functions (+ the admin UI, which is a sibling-repo follow-up — see the scope note at the end; ADR-031 forbids executable frontend changes in this repo).

The one analogy: a thermostat with a hard-wired frost guard

Think of the firm config as a building thermostat the office manager sets. The Auto defaults are the schedule it runs on when nobody's touching it — "overnight, run at this setting." The budget ceiling is a cap on the energy bill: spend no more than this. But the thermostat has a frost guard wired behind the panel — a minimum the heat will hold no matter how low you set the budget, because frozen pipes cost more than electricity. If your budget is so tight it can't even afford the frost guard, the thermostat doesn't quietly let the pipes freeze — it flashes a warning and waits for you. The authority profiles are named presets ("Eco", "Comfort") the manager defines once and selects. And the floor list is a label on the panel telling you the frost guard exists and at what temperature — readable, never a dial you can turn off.

🛰️ Satellite: what shipped

Two firm-scoped tables, one alembic migration (0122), a firm-config domain with a router under /app/agent-runtime, and two new pure rails the broker consumes. No model name, price, or token figure crosses any surface — the budget is in abstract cost units.

✈️ Airplane: how the budget governor decides (D11)

estimate = { item_count, total_cost_units, min_batch_cost_units }   <- min is floor-driven
ceiling  = firm.budget_ceiling_units    (None = unbounded)

resolve_budget(ceiling, estimate):
  ceiling is None                      -> WITHIN_BUDGET        (nothing to cap)
  min_batch_cost  >  ceiling           -> FLOOR_EXCEEDS_BUDGET (HELD + alert; floor NOT lowered)
  total_cost      <= ceiling           -> WITHIN_BUDGET        (run whole, silent)
  else (total > ceiling >= min)        -> CHUNK_AND_CHECKPOINT (ceil(total/ceiling) chunks + alert)

cost units are ABSTRACT: low=1, medium=2, high=4, extra_high=8   (no $/token/model)
a higher level costs strictly more, so a floor that RAISES a level RAISES its cost too

Four turns make this correct:

✈️ Airplane: how a firm config row becomes a governed run

admin PUTs /app/agent-runtime/policy   -> firm_agent_runtime_policies (1 row/firm, RLS)
                                          { auto_thinking_level, auto_authority_mode,
                                            active_authority_profile_id, budget_ceiling_units,
                                            budget_default_level, policy_version++ }

# later, a headless Auto run (broker-side, Story-1562 rails):
posture = resolve_auto_posture(                       <- this Story's rail
            firm_level   = policy.auto_thinking_level, <- config, never content
            firm_authority = policy.auto_authority_mode,
            firm_policy_profile_mode = profile.base_authority_mode,
            step = "privilege_detection")
          requested=low? -> effective = max(low, HIGH) = HIGH   (floor wins)
          firm_policy    -> authority resolves through the selected profile
          stamps { config_source: firm, policy_version, floor_applied } (D8)

🔬 Microscopic: the boundary lines that matter

Detail (titled by its reason)What & where
The floor cannot be configured away. The floor surface is read-only — there is a GET /floors and no write route — because floors are additive doctrine, not a firm setting. floors.py :: declared_floors(); router.py :: list_agent_floors (no POST/PUT/DELETE).
A profile can never name firm_policy itself. A profile declares a concrete posture (review_every_step / review_exceptions / authorized); a self-referential firm_policy profile is rejected by a DB CHECK and the DTO enum. entity.py CHECK _PROFILE_MODES_SQL; DTO ProfileAuthorityMode.
The non-delegable list holds under any profile. Even a profile resolving to authorized cannot auto-apply "submit to court" — the per-effect resolver short-circuits on the non-delegable list first, before the mode is consulted. authority.py :: resolve_effective_authority; proved in test_auto_posture.py.
The active profile is in-use; it can't be deleted. Deleting the firm's selected profile would dangle the policy's reference. Delete is rejected with a typed error (archive instead); a composite FK also blocks it at the DB. service.py :: AuthorityProfileService.delete_profile; FK fk_firm_agent_runtime_policies_active_profile to (firm_id, id).
The active profile must belong to the same firm. A firm-leading composite FK (firm_id, active_authority_profile_id) → profiles(firm_id, id) makes a cross-firm reference impossible — tenant isolation enforced by the schema, not just RLS. entity.py FirmAgentRuntimePolicy.__table_args__; migration 0122.
A new firm gets usable defaults, not an empty door. The zero-row first run synthesises medium / review_exceptions / unbounded / version 1 rather than 404-ing, so an Auto run for a never-configured firm has a posture (system-boundary doctrine). service.py :: FirmAgentRuntimePolicyService._view (row=None branch); integration test first-run case.
A no-op PUT does not bump the version. policy_version is stamped into provenance, so it must move only on a material change — re-saving identical settings leaves it untouched (D8). service.py :: _apply_updates returns changed; version bumps only when true.
Budget telemetry carries no firm or content. The governor logs {outcome, units, chunk_count, floor_protected, run_mode} — abstract units only, no firm_id, no document text (ADR-124). budget.py :: _emit_budget_telemetry.
Verify gate. ruff clean; new rail unit tests (test_budget.py, test_auto_posture.py) green; integration tests against a freshly provisioned EMPTY firm cover the zero-row first run, the version bump, the active-profile delete guard, and all three budget outcomes (within / chunk / floor-exceeds); RLS isolation test classifies both new tables as firm-config (support-readable, firm-isolated); alembic base→head + check shows zero drift; OpenAPI app-api.json regenerated and committed.

Scope note (ADR-031). The admin UI (semantic tokens, en+hi i18n) is a frontend change and cannot live in pramaan-functions, which is backend-only and whose lane is ⚡ functions. This Story ships the complete backend contract the admin UI binds to — the /app/agent-runtime routes and the synced openapi/app-api.json mirror — and the UI is a sibling-repo (pramaan-app) follow-up. Per repo doctrine, the cross-repo executable split is surfaced here rather than silently bundled.

One-sentence recall

When no human is watching, the firm config sets the agent's Auto defaults and a spend ceiling — but the safety floor is wired behind the panel: the budget can throttle the work into chunks, never below the floor, and if the floor alone costs more than the budget, the batch waits for counsel rather than running cheap.