Files

9.3 KiB

Row 122 design — agent settings and organization writes (#1616)

Design first, for Sage to post before implementation. Rocko, GPT-6.1-Sol. Brief: docs/plans/2026-10-14_prd-1.2-build.md, pinned 240d25518, section Agent limits as settings, with the organization-file write path. Requirements: REQ-ROLE-3, REQ-LIMIT-1, REQ-LAUNCH-1, REQ-WEB-1. No live organization/configuration/secrets were read. No product code or migration has run. Decision144 leaves Jason's file to Jason.

Proposed schema

Keep businessVersion1, launch.by and launch.instances. Replace launch.max with launch.models and launch.limits. Example defaults, with provider/model values explicitly chosen by the operator rather than inferred:

{
  "launch": {
    "by": "pm",
    "instances": ["coder", "reviewer", "cto"],
    "models": {
      "opus": {"provider": "anthropic", "model": "claude-opus-5-5"},
      "sonnet": {"provider": "anthropic", "model": "claude-sonnet-5-5"}
    },
    "limits": {
      "models": {
        "anthropic": {"claude-opus-5-5": 4, "claude-sonnet-5-5": 4}
      }
    }
  }
}

Model-table keys are stable configuration ids; each entry requires a provider and an exact external model string. Each role's vars.model selects a table key, for example pm -> opus, coder/reviewer -> sonnet. Harness selection stays where it is today; row65 later converts harnesses through the loader. The table's provider/model override the provider/model used to launch that instance, and are recorded in its manifest, running-session registry and launch log. No provider allowlist or provider module is introduced here. Unknown references fail closed; matching a family substring disappears.

limits has only these optional keys:

  • total: count across all running sessions in the organization.
  • providers: object mapping exact provider names to counts.
  • models: object mapping provider names to objects mapping exact external model names to counts.

An absent key means unlimited. Empty objects are valid, and an empty limits object means no concurrency limits. null, fractions, negatives and strings refuse; accepted counts are nonnegative safe integers, with no configured upper bound beyond lossless JSON integer representation. 0 stops a new launch in that scope. Four and four are editable starter/migration values, not a validator ceiling or a value injected when a key is absent. Removing an entry explicitly unsets that limit. Aliases for the same provider/model share the same counter, so a second table key cannot bypass a model limit. Provider/model limits need not have a currently selected role: they can be configured before changing an instance's model.

Migration and Jason's exact change

mosaic limits migrate --business ID --models FILE prints the converted whole document by default. Only explicit --write by a verified human uses the write path. FILE supplies the model table and the exact mapping from old external model strings to table keys. No provider inference, substring routing or automatic write on read/start. Unknown/ambiguous mappings refuse with the unchanged input. Repeating on an already migrated valid document is idempotent.

For old launch.max = {opus:4,sonnet:4}, map each family to one explicitly supplied table key and translate those values to the two corresponding provider/model limits. Preserve launch.by, launch.instances, harnesses, credentials, role definitions and unrelated document values. Convert each role's effective old model selection to its mapped table key; use explicit instance overrides where a selection was inherited. Do not alter a shared system execution setting. Require mappings for launch.by and every launchable instance. Do not invent a total8 or provider8 limit: the old shape had neither. Legacy family limits that require multiple exact models per family cannot be faithfully split; refuse and require an operator's explicit new limits rather than silently widening capacity.

The example above illustrates the exact shape, not a claim about Jason's provider, ids or instances. The fixture migration produces the replacement launch block plus per-instance vars.model changes. The build packet will include that exact JSON patch and command for Jason, with provider/model mapping explicitly supplied and reviewed through Sage/Mos. It will not assert that anthropic is Jason's provider or execute a live migration. The new validator refuses an unmigrated launch.max with a migration instruction. Re-pin therefore follows Jason's applied migration and Mos's go; the old host must not launch between schema migration and the coordinated re-pin.

One write path, fail closed

A trusted host module owns writes to the resolved businessFilePath for a validated organization id. It accepts an authenticated actor supplied by the human channel, never an actor string from a browser/CLI payload. Limits endpoints may change only launch limits/model-table entries and role model selection. The shared internal module can validate a whole-file replacement for row123's later chief-of-staff verb; this row grants no chief-of-staff writer and exposes no unrestricted browser file write.

Serialize writes per organization with a filesystem lock shared by writers; compare a submitted revision/hash against the current bytes to refuse lost updates. Read the current regular file without following a symlink, validate the entire proposed document using the existing role-aware validator, then prepare same-directory temporary bytes. Preserve an immutable previous version and an append-only actor/change record with field paths, old/new values and before/after hashes. Fsync evidence and candidate before atomic rename, then fsync the directory. Journal prepared/committed outcomes and recover an interrupted transaction by hashes; an uncertain outcome refuses new writes until reconciled. Invalid or stale proposals never replace the old file. Success is reported only after the committed receipt. No secret contents are logged: unrelated credential references remain references.

CLI and Settings use the same human-only read/update operation behind the existing human proof. No direct CLI bypass. CLI stays out of transport.mjs; Settings POST repeats the existing enabled-send, named-host, authenticated header, exact Origin, same-origin Fetch Metadata, body-size and strict-key checks. Both submit a revision and receive current normalized settings and counts; no retry on unknown outcome. GET remains available through the existing read path. Form inputs distinguish unset from0, show all three limit kinds and every instance's table selection, and report refusals.

Launch behavior and acceptance evidence

Within the launcher's existing serialized launch queue, re-read and validate current limits and model selections for every attempt, including host-start launches. A missing/invalid file refuses; never fall back to cached limits. Check total, then provider, then exact provider/model counters, counting the launcher itself and all still-running registry entries. A launch at the limit refuses because it would exceed it; unset imposes no check. Lowering a limit leaves existing sessions alive. Their recorded provider/model tuple continues to count even after their instance is assigned a different model. Each refusal logs the hit scope, configured value and running count; each launch logs harness/provider/model. Existing authority/credential/sandbox checks remain, and unrelated permission changes still follow their existing reload/re-pin rules.

Builder tests cover whole-file validation, atomic publication, previous bytes, actor/old/new receipts and crash/stale-write refusal; every limit kind unset/0/at/over; live re-read and unchanged existing sessions; exact model routing; fixture-only print/write migration; human-only CLI; form send checks. One killed mutant per acceptance check, listed in the packet. Targeted builder gate and mutants need Sage's slot. No product code starts until Sage posts this design note on #1616. Landing public/ changes awaits Mos (decision123); any live step still requires re-pin (decision109).

Superseding migration ruling — decisions 145 and sage-0254

The earlier optional write/local helper description is superseded: migration is print-only, has no --write and uses no organization writer or human proof. Only broker-backed mosaic limits and Settings write. Jason installs the output himself in Mos's window with the PM run idle and the re-pin in that same window. Both exact Claude table entries use provider anthropic, per Mos via Sage sage-0254. Map pm, cto and reviewer to opus; coder to sonnet. Keep exact model limits four/four, no total or provider limit, and leave execution.provider alone.

Round 2 rulings (Sage sage-0269; row 119 landed)

The post-119 launcher is chief, with assistant holder mos. The packet covers both new instances and all four existing ones. For inherited models the print-only command reads the existing system config through loadSystem and systemVars, preserving both provider and exact model; invalid/missing config refuses. Fully explicit input still needs no system-config read. Nothing writes that config. Initial role entry, migration-output installation and re-pin happen in one Mos window with the PM idle; Sage supplies the first-chief-session step. Uncertain transaction evidence gates organization writes only. Startup, launch and read paths validate the whole file on disk without consulting receipts; write-time recovery still refuses uncertainty until reconciled.