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
4 changes: 2 additions & 2 deletions cmd/gatekeeper/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ func TestRunMint_MultipleSidecarsConfig_SecondResolves(t *testing.T) {

const spawnEnv = "GATEKEEPER_TEST_MAIN_SPAWN_LR86779F"
t.Setenv(spawnEnv, "spawn-main-1")
spawnPath := filepath.Join(spawnDir, "crew-agent-spawn-spawn-main-1")
spawnPath := filepath.Join(spawnDir, "spawn-spawn-main-1")
if err := os.WriteFile(spawnPath, []byte("peaches"), 0o600); err != nil {
t.Fatalf("setup: write spawn sidecar file: %v", err)
}
Expand All @@ -346,7 +346,7 @@ attestation:
file_prefix: lore-agent-name-
session_id_env: GATEKEEPER_TEST_MAIN_SESSION_LR86779F_UNSET
- dir: `+spawnDir+`
file_prefix: crew-agent-spawn-
file_prefix: spawn-
session_id_env: `+spawnEnv+`
`)

Expand Down
12 changes: 7 additions & 5 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,18 @@ attestation:
# spawned subagents. The example below shows both — a session-scoped
# namespace (crew-manifest's write-agent-sidecar.sh,
# /tmp/lore-agent-name-<session_id>, keyed on CLAUDE_CODE_SESSION_ID)
# and a per-spawn namespace (/tmp/crew-agent-spawn-<spawn_id>, keyed on
# CREW_SPAWN_AGENT_ID) — but nothing here hardcodes either plugin as a
# dependency; leave the list empty to disable this layer entirely.
# and a per-spawn namespace (/tmp/spawn-<spawn_id>, keyed on
# MY_HARNESS_SPAWN_ID — a fictional example var; substitute your own
# harness's per-spawn env var name) — but nothing here hardcodes either
# plugin as a dependency; leave the list empty to disable this layer
# entirely.
sidecars:
- dir: /tmp
file_prefix: lore-agent-name-
session_id_env: CLAUDE_CODE_SESSION_ID
- dir: /tmp
file_prefix: crew-agent-spawn-
session_id_env: CREW_SPAWN_AGENT_ID
file_prefix: spawn-
session_id_env: MY_HARNESS_SPAWN_ID
# Deprecated: the legacy singular `sidecar:` block (dir/file_prefix/
# session_id_env) is still accepted for back-compat and, when fully
# configured, is tried before the `sidecars:` list above. New
Expand Down
46 changes: 22 additions & 24 deletions docs/SIDECAR-READ-CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ harness exists.

**No tool names are normative here.** Directory paths, filename prefixes, and
environment variable names below are illustrative placeholders. Section
"Worked example" cites one deployed configuration and one reference
implementation to make the shapes concrete — it documents an example, not a
required naming scheme.
"Worked example" cites one reference implementation to make the shapes
concrete — it documents an example, not a required naming scheme.

## Convention, not coupling

Expand Down Expand Up @@ -172,45 +171,44 @@ it.

## Worked example (illustrative — not normative naming)

Clagentic: Gatekeeper's own deployed configuration and Go reference
implementation are cited here as one concrete instance of the shapes above.
Nothing in this section is a required name, path, or environment variable
for any other consumer.
Clagentic: Gatekeeper's Go reference implementation is cited here to show
one way the shapes above can be implemented. The config snippet below uses
the same field names as `config.example.yaml`'s `attestation.sidecars`,
but the directory, prefix, and env-var values are illustrative
placeholders only — not any deployment's actual configuration. Nothing in
this section is a required name, path, or environment variable for any
other consumer; per section 6, those values always come from each
consumer's own config.

**Config shape** (same fields as `config.example.yaml`'s
`attestation.sidecars`, reordered here to lead with spawn-first per
section 2 — see the note below on Gatekeeper's currently deployed order):
`attestation.sidecars`, ordered spawn-first per section 2):

```yaml
attestation:
sidecars:
# Spawn-scoped namespace — checked first, per section 2's spawn-first
# resolution order. Short-lived subagent invocations, one file per
# concurrent spawn.
- dir: /tmp
file_prefix: crew-agent-spawn-
session_id_env: CREW_SPAWN_AGENT_ID
- dir: <dir>
file_prefix: <file_prefix>
session_id_env: <SPAWN_IDENTITY_ENV>
# Session-scoped namespace — checked second. A lead/interactive
# process. Owned by LORE; prefix is not renamed (see section 7 above).
- dir: /tmp
file_prefix: lore-agent-name-
session_id_env: CLAUDE_CODE_SESSION_ID
# process. If pointed at LORE's session sidecar, its prefix is not
# renamed (see section 7 above).
- dir: <dir>
file_prefix: <file_prefix>
session_id_env: <SESSION_IDENTITY_ENV>
```

Both `dir`, `file_prefix`, and the id-env-var name are ordinary config
values (section 6) — nothing about `/tmp`, `lore-agent-name-`, or
`CLAUDE_CODE_SESSION_ID` is hardcoded in Gatekeeper's Go source.
values (section 6) — a consumer's Go (or other language) source never
hardcodes them.

The order above follows section 2's spawn-first resolution
recommendation. A local deployment MAY choose a different order as its own
config decision — the list order is read straight from `config.yaml` and
Gatekeeper's chain does not enforce spawn-before-session itself (see
"Reference read implementation" below). Gatekeeper's own currently
deployed `config.example.yaml` lists the session-scoped entry first; that
is a config ordering value to reconcile locally against section 2 (a
separate, non-doc change), and does not change the contract itself. This
worked example leads with spawn-first so it illustrates the contract's own
recommendation rather than appearing to contradict it.
"Reference read implementation" below).

**Reference read implementation**
([`internal/attestation/sidecar.go`](../internal/attestation/sidecar.go)):
Expand Down
10 changes: 5 additions & 5 deletions internal/attestation/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func TestNewChain_MultipleSidecars_FirstMatchWins(t *testing.T) {
if err := os.WriteFile(sessionPath, []byte("holden"), 0o600); err != nil {
t.Fatalf("setup: write session sidecar file: %v", err)
}
spawnPath := filepath.Join(spawnDir, "crew-agent-spawn-spawn-9")
spawnPath := filepath.Join(spawnDir, "spawn-spawn-9")
if err := os.WriteFile(spawnPath, []byte("peaches"), 0o600); err != nil {
t.Fatalf("setup: write spawn sidecar file: %v", err)
}
Expand All @@ -117,7 +117,7 @@ func TestNewChain_MultipleSidecars_FirstMatchWins(t *testing.T) {
},
{
Dir: spawnDir,
FilePrefix: "crew-agent-spawn-",
FilePrefix: "spawn-",
SessionIDEnv: spawnEnv,
},
},
Expand Down Expand Up @@ -150,7 +150,7 @@ func TestNewChain_MultipleSidecars_FirstDeclinesSecondResolves(t *testing.T) {
os.Unsetenv(sessionEnv)
t.Setenv(spawnEnv, "spawn-10")

spawnPath := filepath.Join(spawnDir, "crew-agent-spawn-spawn-10")
spawnPath := filepath.Join(spawnDir, "spawn-spawn-10")
if err := os.WriteFile(spawnPath, []byte("bobbie"), 0o600); err != nil {
t.Fatalf("setup: write spawn sidecar file: %v", err)
}
Expand All @@ -164,7 +164,7 @@ func TestNewChain_MultipleSidecars_FirstDeclinesSecondResolves(t *testing.T) {
},
{
Dir: spawnDir,
FilePrefix: "crew-agent-spawn-",
FilePrefix: "spawn-",
SessionIDEnv: spawnEnv,
},
},
Expand Down Expand Up @@ -204,7 +204,7 @@ func TestNewChain_MultipleSidecars_BothAbsent_BuiltinFallback(t *testing.T) {
},
{
Dir: spawnDir,
FilePrefix: "crew-agent-spawn-",
FilePrefix: "spawn-",
SessionIDEnv: spawnEnv,
},
},
Expand Down
8 changes: 4 additions & 4 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ attestation:
file_prefix: lore-agent-name-
session_id_env: CLAUDE_CODE_SESSION_ID
- dir: /tmp
file_prefix: crew-agent-spawn-
session_id_env: CREW_SPAWN_AGENT_ID
file_prefix: spawn-
session_id_env: MY_HARNESS_SPAWN_ID
`)

cfg, err := Load(path)
Expand All @@ -216,8 +216,8 @@ attestation:
if cfg.Attestation.Sidecars[0].FilePrefix != "lore-agent-name-" {
t.Errorf("Sidecars[0].FilePrefix = %q, want %q", cfg.Attestation.Sidecars[0].FilePrefix, "lore-agent-name-")
}
if cfg.Attestation.Sidecars[1].FilePrefix != "crew-agent-spawn-" {
t.Errorf("Sidecars[1].FilePrefix = %q, want %q", cfg.Attestation.Sidecars[1].FilePrefix, "crew-agent-spawn-")
if cfg.Attestation.Sidecars[1].FilePrefix != "spawn-" {
t.Errorf("Sidecars[1].FilePrefix = %q, want %q", cfg.Attestation.Sidecars[1].FilePrefix, "spawn-")
}

resolved := cfg.Attestation.ResolveSidecars()
Expand Down
Loading