Skip to content

Commit b6142bd

Browse files
Merge pull request #96 from open-coder-ai/w37/d2-vendor-config-schema
2 parents 17a4f04 + bf28f74 commit b6142bd

30 files changed

Lines changed: 3126 additions & 17 deletions

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,29 @@ versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
88

9+
### Added
10+
- **The vendor config schema and all 12 entries** (`docs/design/dialect-families.md` D2):
11+
`src/agentseam/data/vendors/schema.json` and one `data/vendors/<agent>.json` per adapted
12+
agent, unused by the runtime yet (D3+ wires an engine onto this data). Every entry is
13+
produced by `tools/recount_vendor_config.py`, which derives each field by executing or
14+
AST-reading the current adapter and its D1 golden fixture rather than by hand -- module
15+
constants read directly, `parse()`'s field-fallback chains AST-walked, and every gate's
16+
grammar/`honours_escalate`/`honours_transform` replayed straight out of the frozen (payload ->
17+
stdout) wire truth. Family assignment and the marker-based `claims` table are the design's
18+
own stated judgment calls, recorded as small tables and pinned by
19+
`tests/test_vendor_config_claims.py`, which mutates a real claimed payload per declared
20+
marker and replays `claims()`. Per-claim evidence (owner decision 2026-09-01,
21+
`plan/agentseam-project.md` in org-plan) gives every claim group a `basis` (from
22+
`matrix_terms.BASES`), `date`, and the test that exercises it; schema validation fails a
23+
claim with none. `tools/validate_vendor_config.py` is a small stdlib-only JSON Schema
24+
subset validator (no third-party dependency) run as a test, not a runtime cost.
25+
`tests/test_vendor_config.py` adds the §3.3 consistency tests (config gates are a subset
26+
of the matrix's block-capable events; `config_path` agrees with the matrix) and answers
27+
both [h] hypotheses the design carried into D2: the `reject_probes` device stays at one
28+
named predicate (`looks_like_claude_code`, used by two vendors) against the ~3-probe
29+
budget, and PR #89's tuple-restoring loader is order-safe for `fields`' order-sensitive
30+
chains (demonstrated, not merely asserted).
31+
932
### Changed
1033
- **The `Decision` vocabulary is aligned to the Agent Control Standard's verdict names**
1134
(`plan/acs-alignment-and-delegation.md` §1 in the org-plan repo). `ASK` is renamed to
@@ -47,6 +70,11 @@ versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4770
- **`pyproject.toml`'s `version` was unchecked against `agentseam.__version__`.** Only
4871
`CITATION.cff` was verified to track the package; a wheel could ship with the two
4972
disagreeing. `test_repo_standards.py` now checks both.
73+
- **`matrix.json`'s `kimi_code` row named the wrong config file (`config.toml`, missing
74+
the `~/.kimi-code/` directory `kimi_code.py`'s own `CONFIG_PATH` has always used).** Found
75+
while implementing D2's `config_path` agrees with the matrix consistency test.
76+
`data/vendors/*.json` was not shipped as package data; `pyproject.toml`'s
77+
`package-data` now also matches `data/vendors/*.json`.
5078

5179
### Internal
5280
- `claude_code`, `gemini_cli`, `grok`, and `junie` shared an identical `hook_config` body;

docs/design/dialect-families.md

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ Consequences, applied to today's code:
105105
| :--- | :--- |
106106
| The five grammar renderers G1–G5 | which grammar each event group uses, and the word tables filling it |
107107
| Ordered-fallback field extraction machinery, `tool_input_of` (`contract.py:139-150`) | the per-vendor key chains (e.g. path = `file_path→absolute_path→path` `gemini_cli.py:79`; content gated to write tools `gemini_cli.py:71-73`; output = `last_assistant_message` `junie.py:65`) |
108-
| `degrade()` semantics (`dispatch.py:26-38`) and its generated-runtime twin (`bundler_templates.py:26-40`) | per-gate capability flags (`honours_ask`, `honours_rewrite`, `honours_block`) and the degradation-note strings, verbatim from today's adapters so wire output stays byte-identical |
108+
| `degrade()` semantics (`dispatch.py:26-38`) and its generated-runtime twin (`bundler_templates.py:26-40`) | per-gate capability flags (`honours_escalate`, `honours_transform`, `honours_block`) and the degradation-note strings, verbatim from today's adapters so wire output stays byte-identical |
109109
| Shape-inference `claims`/event-naming for cursor, windsurf, antigravity (`cursor.py:96-98`, `windsurf.py:27-32`, `antigravity.py:25-31`) | marker-based `claims`: required markers, foreign markers, client_type allowlists (`junie.py:45-48`, `tabnine.py:52-55`, `gemini_cli.py:57-64`, `codex_cli.py:43-52`) |
110110
| vscode_copilot's memory-tool argument scheme (`vscode_copilot.py:77-83,103-107`) | `MEMORY_TOOLS` / `WRITE_TOOLS` / `SHELL_TOOLS` name lists |
111111
| kimi's TOML emitter (`kimi_code.py:155-164`) — one renderer, selected by `config_format` | `config_path`, `config_format`, hook-entry wrapper style and extra entry fields (`commandWindows` `codex_cli.py:137`, `name` `tabnine.py:113`, `failClosed`+`version` `cursor.py:225-228`, group nesting `antigravity.py:117-127`, no-wrapper `devin.py:121-132`, extra `pre_mcp_tool_use` wiring `windsurf.py:106-107`) |
@@ -152,13 +152,13 @@ Consequences, applied to today's code:
152152
"bare_allow": "inert", // allow_semantics.py:70-77 key, cross-checked
153153
"answer_events": ["BeforeTool", "AfterTool", "BeforeAgent", "AfterAgent"],
154154
"gates": { // gemini_cli.py:104-121
155-
"BeforeTool": { "grammar": "G1", "honours_ask": true, "honours_rewrite": true },
156-
"AfterTool": { "grammar": "G1", "honours_ask": false },
157-
"BeforeAgent":{ "grammar": "G1", "honours_ask": false },
158-
"AfterAgent": { "grammar": "G1", "honours_ask": false }
155+
"BeforeTool": { "grammar": "G1", "honours_escalate": true, "honours_transform": true },
156+
"AfterTool": { "grammar": "G1", "honours_escalate": false },
157+
"BeforeAgent":{ "grammar": "G1", "honours_escalate": false },
158+
"AfterAgent": { "grammar": "G1", "honours_escalate": false }
159159
},
160160
"words": { "allow": "allow", "deny": "deny", "ask": "ask", "block": "deny" },
161-
"rewrite_grammar": "hook_specific_tool_input", // gemini_cli.py:119-120
161+
"transform_grammar": "hook_specific_tool_input", // gemini_cli.py:119-120
162162
"degrade_notes": { // verbatim today's strings, gemini_cli.py:109-117
163163
"ask_unhonoured": "%s (confirmation required; %s cannot prompt from a hook)"
164164
}
@@ -167,7 +167,15 @@ Consequences, applied to today's code:
167167
}
168168
```
169169

170-
Where the degradation lives (the brief's codex question): `honours_ask: false` on the
170+
Field names above (`honours_escalate`, `honours_transform`, `transform_grammar`) follow the
171+
post-W35 ACS vocabulary (`contract.py`: `escalate`/`transform`), not the pre-ACS `ask`/
172+
`rewrite` this section was drafted with at W31, before ACS alignment was sequenced ahead of
173+
D2 (org-plan `plan/agentseam-project.md`: "D2 schema (written in ACS words)"). Vendor
174+
wire-word *data* is untouched by that rename: `vocabulary`/`words` above are correct as
175+
written, because that is the literal word gemini_cli's own dialect speaks on the wire, not
176+
agentseam's name for the concept.
177+
178+
Where the degradation lives (the brief's codex question): `honours_escalate: false` on the
171179
gate + the vendor's note string. codex_cli's ask→deny with *"Codex CLI does not support
172180
ask; asking would fail open"* (`codex_cli.py:121-124`) is a flag plus a string — no
173181
conditional survives in config. The engine's degrade step already exists twice
@@ -269,7 +277,7 @@ instructions/capture suites (untouched paths).
269277
**Must be written:**
270278

271279
1. Config schema validation: every `data/vendors/*.json` validates; unknown keys are
272-
an error (a typo'd `honours_ask` must fail loud, not fail open).
280+
an error (a typo'd `honours_escalate` must fail loud, not fail open).
273281
2. Config completeness: every `ADAPTERS` name is config-backed; every config gate maps
274282
to a matrix block-capable event; `config_path`/`bare_allow` agree with
275283
`MATRIX`/`ALLOW_SEMANTICS`.
@@ -307,12 +315,19 @@ first, D3/D4 swap cleanly — they share only D1+D2.
307315

308316
- **[h]** Bundle size estimates in §4 are composition arithmetic, not measurements;
309317
D3 should publish real numbers next to them.
310-
- **[h]** The `reject_probes: ["looks_like_claude_code"]` device (named engine
318+
- **[v]** (was [h]) The `reject_probes: ["looks_like_claude_code"]` device (named engine
311319
predicates referenced from config) is the narrowest crack in the code/config line;
312320
if D2 finds more than ~3 named probes are needed, that is evidence the line is drawn
313-
wrong, and the design should be revisited rather than the list grown.
321+
wrong, and the design should be revisited rather than the list grown. **Answered by D2**:
322+
exactly one named probe (`looks_like_claude_code`), used by `gemini_cli` and `devin` --
323+
well inside the budget (`tests/test_vendor_config.py::
324+
test_reject_probes_stay_under_the_three_probe_budget`). The design stands as written.
314325
- **[h]** vscode_copilot's dual-casing may be cleaner as two config entries sharing a
315326
family than one entry with paired key chains; decide in D3 with the fixtures open.
316-
- **[h]** PR #89's loader restores every JSON array as a tuple; `fields` chains here
317-
are order-sensitive lists and unaffected, but D2 must confirm the loader's
318-
tuple-restore doesn't collide with schema types.
327+
- **[v]** (was [h]) PR #89's loader restores every JSON array as a tuple; `fields` chains
328+
here are order-sensitive lists and unaffected, but D2 must confirm the loader's
329+
tuple-restore doesn't collide with schema types. **Answered by D2**: a tuple preserves
330+
the exact sequence its JSON array was written in, so the restore is order-safe; nothing
331+
in `vendor_config.py` or its tests ever compares one of these tuples against a list
332+
literal with `==` (`tests/test_vendor_config.py::
333+
test_loader_tuple_restore_preserves_field_chain_order`).

examples/generated/README.md

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/generated/kimi_code.md

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ where = ["src"]
5959
# The vendor tables live in data/*.json and are read at import time. Without this the
6060
# wheel ships without them and every import raises FileNotFoundError from a pip install
6161
# while working perfectly from a source checkout -- the failure chock#87 caught on itself.
62-
agentseam = ["data/*.json"]
62+
agentseam = ["data/*.json", "data/vendors/*.json"]
6363

6464
[tool.pytest.ini_options]
6565
testpaths = ["tests"]

src/agentseam/data/matrix.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@
158158
"kimi_code": {
159159
"display": "Kimi Code CLI",
160160
"tier": "block",
161-
"config": "config.toml",
161+
"config": "~/.kimi-code/config.toml",
162162
"verified": {
163163
"basis": "vendor-docs",
164164
"version": "CLI",
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
{
2+
"agent": "antigravity",
3+
"claims": {
4+
"mode": "shape_inferred"
5+
},
6+
"config_format": "json",
7+
"config_path": ".agents/hooks.json",
8+
"display": "Antigravity",
9+
"events": {},
10+
"evidence": {
11+
"claims": {
12+
"basis": "vendor-docs",
13+
"date": "2026-08-26",
14+
"test": "tests/test_examples.py::test_each_payload_is_claimed_by_its_own_adapter"
15+
},
16+
"config_path": {
17+
"basis": "vendor-docs",
18+
"date": "2026-08-26",
19+
"test": "tests/test_vendor_config.py::test_config_path_agrees_with_matrix"
20+
},
21+
"events": {
22+
"basis": "vendor-docs",
23+
"date": "2026-08-26",
24+
"test": "tests/test_examples.py::test_each_payload_parses_to_the_event_it_is_filed_under"
25+
},
26+
"family": {
27+
"basis": "vendor-docs",
28+
"date": "2026-08-26",
29+
"test": "tests/test_golden_fixtures.py::test_wire_output_matches_the_frozen_fixture"
30+
},
31+
"fields": {
32+
"basis": "vendor-docs",
33+
"date": "2026-08-26",
34+
"test": "tests/test_vendor_config.py::test_entries_match_recount"
35+
},
36+
"hook_entry": {
37+
"basis": "vendor-docs",
38+
"date": "2026-08-26",
39+
"test": "tests/test_golden_fixtures.py::test_hook_config_matches_the_frozen_fixture_on_both_matcher_paths"
40+
},
41+
"tools": {
42+
"basis": "vendor-docs",
43+
"date": "2026-08-26",
44+
"test": "tests/test_vendor_config.py::test_entries_match_recount"
45+
},
46+
"verdicts": {
47+
"basis": "vendor-docs",
48+
"date": "2026-08-26",
49+
"test": "tests/test_golden_fixtures.py::test_wire_output_matches_the_frozen_fixture"
50+
}
51+
},
52+
"family": "antigravity",
53+
"fields": {
54+
"command": [
55+
"toolCall.args.CommandLine"
56+
],
57+
"content": [
58+
"toolCall.args.CodeContent",
59+
"toolCall.args.ReplacementContent",
60+
"toolCall.args.ReplacementChunks[].ReplacementContent"
61+
],
62+
"cwd": [
63+
"toolCall.args.Cwd",
64+
"workspacePaths[0]"
65+
],
66+
"output": [
67+
"error"
68+
],
69+
"path": [
70+
"toolCall.args.TargetFile",
71+
"toolCall.args.AbsolutePath"
72+
],
73+
"session_id": [
74+
"conversationId"
75+
],
76+
"tool": [
77+
"toolCall.name"
78+
],
79+
"tool_use_id": [
80+
"stepIdx"
81+
]
82+
},
83+
"hook_entry": {
84+
"group": "agentseam",
85+
"matcher": true,
86+
"wrapper": "hooks_map"
87+
},
88+
"needs_trust": false,
89+
"tools": {},
90+
"verdicts": {
91+
"answer_events": [
92+
"PreToolUse",
93+
"Stop"
94+
],
95+
"bare_allow": "unverified",
96+
"gates": {
97+
"PreToolUse": {
98+
"grammar": "G1",
99+
"honours_escalate": true,
100+
"honours_transform": false
101+
},
102+
"Stop": {
103+
"grammar": "G1",
104+
"honours_escalate": false,
105+
"honours_transform": false
106+
}
107+
},
108+
"vocabulary": [
109+
"allow",
110+
"ask",
111+
"continue",
112+
"deny",
113+
"deny_unless_prior_grant",
114+
"force_ask",
115+
"stop"
116+
],
117+
"vocabulary_basis": "verified"
118+
},
119+
"wire_events": {
120+
"post_tool": "PostToolUse",
121+
"pre_tool": "PreToolUse",
122+
"stop": "Stop"
123+
}
124+
}

0 commit comments

Comments
 (0)