Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions host-setup/agent-safety/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,11 @@ Requirements 1 to 7 judge a command's text, read the way a simple lexer reads it
instruction set carried no tier table. It was taken as "the frontier model", and the work ran on
the most capable and most expensive model nobody had asked for. Read the tier mapping from the
hub's `spec/model-tiers.json`, deployed beside the hook. Refuse a dispatch naming the
maintainer-only tier's model unless the session was launched with that tier allowed. An agent
never sets the allowance, for its own session or for one it launches. A mapping the hook cannot
read refuses every dispatch naming a model, since a guard that cannot tell what to block must
not go quiet. A session launched with the allowance blocks no model, so it needs no mapping.
maintainer-only tier's model unless the session was launched with that tier allowed. Setting the
allowance is the maintainer's act. An agent never does it, for its own session or for one it
launches, and that is conduct the hook does not enforce. A mapping the hook cannot read refuses
every dispatch naming a model, since a guard that cannot tell what to block must not go quiet. A
session launched with the allowance blocks no model, so it needs no mapping.

This one guards the maintainer's choice of model and his quota rather than the credentials or
the machine. It sits in the host layer all the same, because a dispatch happens inside the
Expand Down
11 changes: 11 additions & 0 deletions spec/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,17 @@ def model_tier_errors(root):
f"spec/model-tiers.json: tool {tool!r} must map each tier to a non-empty model name"
)
continue
if (
isinstance(reserved, str)
and reserved in models
and any(
t != reserved and m.lower() == models[reserved].lower() for t, m in models.items()
)
):
errors.append(
f"spec/model-tiers.json: tool {tool!r} must give maintainerOnly a model no other tier shares"
)
continue
rows.append(f"| {entry['name']} | " + " | ".join(f"`{models[t]}`" for t in tiers) + " |")
agents_path = root / "AGENTS.md"
if not agents_path.is_file():
Expand Down
10 changes: 10 additions & 0 deletions tests/test_spec_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,16 @@ def test_rejects_a_model_that_is_not_a_non_empty_string(self) -> None:

self.assertTrue(any("non-empty model name" in e for e in self.errors()))

def test_rejects_a_tier_sharing_the_maintainer_only_model(self) -> None:
self.models["frontier"] = "Fable"

self.assertTrue(any("no other tier shares" in e for e in self.errors()))

def test_reports_a_maintainer_only_value_that_is_not_a_string(self) -> None:
self.config["maintainerOnly"] = []

self.assertTrue(any("maintainerOnly" in e for e in self.errors()))

def test_rejects_a_tools_list_instead_of_an_object(self) -> None:
self.config["tools"] = []

Expand Down
Loading