Skip to content
Open
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
16,122 changes: 8,064 additions & 8,058 deletions docs/DESIGN.clause-staging.json

Large diffs are not rendered by default.

13 changes: 10 additions & 3 deletions docs/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -929,7 +929,7 @@ normative:
default is "absent", see Appendix A.1's “Omitted vs explicit defaults”.

For the cast: `performers.<id>.adapter` and `.model` are required; `allow_advisory_enforcement`
defaults to `false`; `extensions` defaults to absent; `bindings.<part>.performer` is required and
defaults to `false`; `inherit_repo_rules` defaults to `true`; `extensions` defaults to absent; `bindings.<part>.performer` is required and
`.fallbacks` defaults to `[]`. Every bound performer must exist, every part must have a binding, and
a fallback chain must be duplicate-free and must not contain its own primary.

Expand Down Expand Up @@ -1292,6 +1292,9 @@ adjacent, coherent wire-form specimens.
- The wire budget field is not `active_wall_clock_min`.
- A minutes-only wire field could not carry the remainder losslessly.
- `execute.request.extensions`, when present, contains only the namespace matching this adapter's id.
- `execute.request.inherit_repo_rules` is the performer's cast `inherit_repo_rules`; when it is
`false`, the adapter suppresses the repository's own agent rule files, such as `AGENTS.md` or
`CLAUDE.md`, where its vendor permits.
- `execute.result.failure` is present when `outcome` is `failed`.
- For waiting-human results and their complete blocking set, see §4's “Blocking handshake for
questions and proposals”.
Expand Down Expand Up @@ -1324,6 +1327,7 @@ execute(request) -> streams `event` notifications, then returns result
request: {
run_id, movement_id, attempt_id, score_revision,
model,
inherit_repo_rules,
brief: {
goal,
context?,
Expand Down Expand Up @@ -4886,7 +4890,7 @@ a defect.
|---|---|
| `partitur/score` | The whole validated score AST after defaults. Used for `score_hash`, `base_hash`, and snapshot identity. |
| `partitur/score-subtree` | `{selector, value}` — the stable selector of §9 **is** part of the preimage, alongside the subtree value. Two different pointers holding equal values are different facts, so hashing the value alone would let an impact entry claim the wrong location. Used for `before_hash` / `after_hash` in `actual_impact`. |
| `partitur/resolved-cast` | The fully resolved cast AST after layering (§1), with `performers` and `bindings` sorted by id and every effective default materialized. Used for `resolved_cast_hash` in `run.started`. |
| `partitur/resolved-cast` | The fully resolved cast AST after layering (§1), with `performers` and `bindings` sorted by id and every effective default materialized, except a performer's `inherit_repo_rules`, which is projected only when `false` so that casts that never name it keep their identity. Used for `resolved_cast_hash` in `run.started`. |
| `partitur/criterion-spec` | A.4.1 — a tagged union over criterion kinds. |
| `partitur/acceptance-spec` | A.4.2 — the effective compiled acceptance plan. |
| `partitur/change-set` | `{base_tree, result_tree}` |
Expand Down Expand Up @@ -5539,6 +5543,8 @@ field or payload in the adjacent coherent specimen.
without enforcement.
- `adapter.probed.execution_dependency_hash` is the A.5 identity of the now-fixed exact request
shape.
- `adapter.probed.inherit_repo_rules`, when present, is the Boolean
`execute.request.inherit_repo_rules` this attempt's request carries.

```text
performer.selected {
Expand Down Expand Up @@ -5566,7 +5572,8 @@ adapter.probed {
],
advisory_dimensions: [dimension],
execution_dependency_hash,
identity_versions
identity_versions,
inherit_repo_rules?
}
```

Expand Down
7 changes: 6 additions & 1 deletion internal/adapters/claude/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,6 @@ func buildCommand(request *protocol.ExecuteRequest, includeResume bool) (command
"--verbose",
"--model", request.Model,
"--permission-mode", "dontAsk",
"--setting-sources", "",
"--safe-mode",
"--strict-mcp-config",
"--no-chrome",
Expand All @@ -333,6 +332,12 @@ func buildCommand(request *protocol.ExecuteRequest, includeResume bool) (command
// Empty setting sources and safe mode suppress user/project settings,
// hooks, plugins, MCP servers, and related ambient customizations where
// Claude permits it. Managed policy and inherited credentials may remain.
// Setting sources are emptied only when the performer opts out of the
// repository's own rules; otherwise Claude's default sources, including
// the repository's CLAUDE.md, load.
if !request.InheritRepoRules {
args = append(args, "--setting-sources", "")
}
for _, directory := range addDirectories(request) {
args = append(args, "--add-dir", directory)
}
Expand Down
22 changes: 22 additions & 0 deletions internal/adapters/claude/adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -821,3 +821,25 @@ func readLines(t *testing.T, path string) []string {
}
return strings.Split(strings.TrimSpace(string(data)), "\n")
}

func TestBuildCommandStripsRepositoryRulesOnlyWhenNotInherited(t *testing.T) {
t.Parallel()
for _, inherit := range []bool{false, true} {
t.Run(fmt.Sprintf("inherit_%t", inherit), func(t *testing.T) {
t.Parallel()
request := testRequest("/workspace", "/artifacts")
request.InheritRepoRules = inherit
command, err := buildCommand(request, true)
if err != nil {
t.Fatal(err)
}
if got := slices.Contains(command.args, "--setting-sources"); got == inherit {
t.Fatalf("--setting-sources present = %t with inherit_repo_rules %t: %#v",
got, inherit, command.args)
}
if !inherit {
assertFlagValue(t, command.args, "--setting-sources", "")
}
})
}
}
6 changes: 5 additions & 1 deletion internal/adapters/codex/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,6 @@ func buildCommand(request *protocol.ExecuteRequest, includeResume bool) (command
"-C", childDir,
"--skip-git-repo-check",
"--ignore-user-config",
"--ignore-rules",
"-c", "sandbox_workspace_write.exclude_tmpdir_env_var=true",
"-c", "sandbox_workspace_write.exclude_slash_tmp=true",
// This lets an attempt write another concurrent run's scratch subtree.
Expand All @@ -338,6 +337,11 @@ func buildCommand(request *protocol.ExecuteRequest, includeResume bool) (command
}
// Ignoring user config preserves CODEX_HOME authentication but cannot
// remove managed policy or every integration built into the executable.
// Repository rules (AGENTS.md) are ignored only when the performer opts
// out of inheriting them.
if !request.InheritRepoRules {
args = append(args, "--ignore-rules")
}
if request.Grants.Network {
args = append(args,
"-c", "sandbox_workspace_write.network_access=true",
Expand Down
22 changes: 22 additions & 0 deletions internal/adapters/codex/adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1178,3 +1178,25 @@ func adapterGitText(t *testing.T, directory string, arguments ...string) string
}
return strings.TrimSpace(string(output))
}

func TestBuildCommandStripsRepositoryRulesOnlyWhenNotInherited(t *testing.T) {
t.Parallel()
for _, inherit := range []bool{false, true} {
t.Run(fmt.Sprintf("inherit_%t", inherit), func(t *testing.T) {
t.Parallel()
request := testRequest("/workspace", "/artifacts")
request.InheritRepoRules = inherit
command, err := buildCommand(request, true)
if err != nil {
t.Fatal(err)
}
if got := slices.Contains(command.args, "--ignore-rules"); got == inherit {
t.Fatalf("--ignore-rules present = %t with inherit_repo_rules %t: %#v",
got, inherit, command.args)
}
if !slices.Contains(command.args, "--ignore-user-config") {
t.Fatalf("--ignore-user-config missing: %#v", command.args)
}
})
}
}
1 change: 1 addition & 0 deletions internal/cast/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func performerView(id string, value performer) PerformerView {
Adapter: value.Adapter,
Model: value.Model,
AllowAdvisoryEnforcement: value.AllowAdvisoryEnforcement,
InheritRepoRules: value.InheritRepoRules,
Extensions: cloneMap(value.Extensions),
}
}
Expand Down
5 changes: 5 additions & 0 deletions internal/cast/projection.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ func (c *Cast) projectionValue() map[string]any {
"model": performer.Model,
"allow_advisory_enforcement": performer.AllowAdvisoryEnforcement,
}
// Projected only when it departs from the default, so casts that
// never name the option keep their existing identity.
if !performer.InheritRepoRules {
value["inherit_repo_rules"] = false
}
if performer.Extensions != nil {
value["extensions"] = cloneMap(performer.Extensions)
}
Expand Down
30 changes: 30 additions & 0 deletions internal/cast/projection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,33 @@ func TestNilResolvedCastHasNoProjection(t *testing.T) {
t.Fatal("nil cast hash succeeded")
}
}

func TestResolvedCastProjectionRecordsOnlyAnOptOutOfRepoRules(t *testing.T) {
t.Parallel()
resolved := mustResolve(t, Layer{
Origin: "project",
Data: encodeFixture(t, map[string]any{
"cast": "0.1",
"performers": map[string]any{
"inheriting": map[string]any{
"adapter": "claude",
"model": "model",
"inherit_repo_rules": true,
},
"isolated": map[string]any{
"adapter": "codex",
"model": "model",
"inherit_repo_rules": false,
},
},
}),
})
projection, err := resolved.ProjectionBytes()
if err != nil {
t.Fatal(err)
}
const want = `{"bindings":{},"cast":"0.1","performers":{"inheriting":{"adapter":"claude","allow_advisory_enforcement":false,"model":"model"},"isolated":{"adapter":"codex","allow_advisory_enforcement":false,"inherit_repo_rules":false,"model":"model"}}}`
if string(projection) != want {
t.Fatalf("projection = %s, want %s", projection, want)
}
}
7 changes: 6 additions & 1 deletion internal/cast/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,17 @@ func (d *layerDecoder) decodePerformers(value any) (map[string]performer, bool)
continue
}
d.fields(entry, pointer,
"adapter", "model", "allow_advisory_enforcement", "extensions")
"adapter", "model", "allow_advisory_enforcement", "inherit_repo_rules", "extensions")
adapter, _ := d.requiredString(entry, pointer, "adapter")
model, _ := d.requiredString(entry, pointer, "model")
advisory := false
if value := d.optionalBool(entry, pointer, "allow_advisory_enforcement"); value != nil {
advisory = *value
}
inheritRepoRules := true
if value := d.optionalBool(entry, pointer, "inherit_repo_rules"); value != nil {
inheritRepoRules = *value
}
var extensions map[string]any
if value, present := entry["extensions"]; present {
if decoded, ok := d.object(value, pointer+"/extensions"); ok {
Expand All @@ -136,6 +140,7 @@ func (d *layerDecoder) decodePerformers(value any) (map[string]performer, bool)
Adapter: adapter,
Model: model,
AllowAdvisoryEnforcement: advisory,
InheritRepoRules: inheritRepoRules,
Extensions: extensions,
Origin: d.origin,
}
Expand Down
35 changes: 34 additions & 1 deletion internal/cast/resolve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,12 @@ func TestCastDefaultsHaveEffectiveViewGoldens(t *testing.T) {
"plan": map[string]any{"performer": "primary"},
},
}
explicitPerformer := performerFixture("adapter", "model", boolPointer(false))
explicitPerformer["inherit_repo_rules"] = true
explicit := map[string]any{
"cast": "0.1",
"performers": map[string]any{
"primary": performerFixture("adapter", "model", boolPointer(false)),
"primary": explicitPerformer,
},
"bindings": map[string]any{
"plan": map[string]any{
Expand All @@ -215,6 +217,7 @@ func TestCastDefaultsHaveEffectiveViewGoldens(t *testing.T) {
wantPerformers := []PerformerView{{
ID: "primary", Adapter: "adapter", Model: "model",
AllowAdvisoryEnforcement: false,
InheritRepoRules: true,
Extensions: nil,
}}
wantBindings := []BindingView{{
Expand Down Expand Up @@ -306,6 +309,12 @@ func TestCastSchemaConformance(t *testing.T) {
{"advisory_not_null", func(root map[string]any) {
objectAtKey(root, "performers", "primary")["allow_advisory_enforcement"] = nil
}, "/performers/primary/allow_advisory_enforcement", "must_not_be_null"},
{"inherit_repo_rules_boolean", func(root map[string]any) {
objectAtKey(root, "performers", "primary")["inherit_repo_rules"] = "false"
}, "/performers/primary/inherit_repo_rules", "expected_boolean"},
{"inherit_repo_rules_not_null", func(root map[string]any) {
objectAtKey(root, "performers", "primary")["inherit_repo_rules"] = nil
}, "/performers/primary/inherit_repo_rules", "must_not_be_null"},
{"extensions_object", func(root map[string]any) {
objectAtKey(root, "performers", "primary")["extensions"] = []any{}
}, "/performers/primary/extensions", "expected_object"},
Expand Down Expand Up @@ -788,3 +797,27 @@ func compileScore(t *testing.T, parts ...string) *score.Score {
}
return compiled
}

func TestInheritRepoRulesDefaultsTrueAndAcceptsFalse(t *testing.T) {
t.Parallel()
document := map[string]any{
"cast": "0.1",
"performers": map[string]any{
"inheriting": performerFixture("claude", "model", nil),
"isolated": map[string]any{
"adapter": "codex",
"model": "model",
"inherit_repo_rules": false,
},
},
}
resolved := mustResolve(t, Layer{Origin: "project", Data: encodeFixture(t, document)})
inheriting, ok := resolved.Performer("inheriting")
if !ok || !inheriting.InheritRepoRules {
t.Fatalf("default performer = %#v, want InheritRepoRules true", inheriting)
}
isolated, ok := resolved.Performer("isolated")
if !ok || isolated.InheritRepoRules {
t.Fatalf("explicit performer = %#v, want InheritRepoRules false", isolated)
}
}
2 changes: 2 additions & 0 deletions internal/cast/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type PerformerView struct {
Adapter string
Model string
AllowAdvisoryEnforcement bool
InheritRepoRules bool
Extensions map[string]any
}

Expand Down Expand Up @@ -108,6 +109,7 @@ type performer struct {
Adapter string
Model string
AllowAdvisoryEnforcement bool
InheritRepoRules bool
Extensions map[string]any
Origin string
}
Expand Down
Loading
Loading