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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Detailed guidance lives in `docs/contributing/` and topic-specific guides under
| [Shell Scripting](docs/contributing/shell-scripting.md) | Writing or reviewing shell scripts — covers `gh api --paginate` pitfalls, jq patterns, and stdout contamination in command substitution |
| [Forge Abstraction](docs/contributing/forge-abstraction.md) | Adding forge operations — covers `forge.Client` interface rules |
| [Harness Composition](docs/contributing/harness-composition.md) | Changing merge functions in `internal/harness/` — covers the invariant between compose and forge merge functions |
| [Harness Field Reference](docs/contributing/harness-fields.md) | Adding or modifying fields in `Harness` or `ForgeConfig` — covers field classifications, merge rules, and the `ForgeConfig` struct |
| [CEL Triggers](docs/contributing/cel-triggers.md) | Writing or reviewing harness `trigger` CEL expressions or `.feature` CEL filters — covers normalized transition kinds |
| [ADRs](docs/contributing/adrs.md) | Touching `docs/ADRs/` or reviewing ADR changes — covers immutability and status rules |
| [Sandbox Topology](docs/contributing/sandbox-topology.md) | Modifying sandbox images, CI image pulling, or agent harness configs |
Expand Down
9 changes: 7 additions & 2 deletions docs/ADRs/0045-forge-portable-harness-schema.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "45. Forge-portable harness schema"
status: Accepted
status: Superseded
relates_to:
- agent-architecture
- agent-infrastructure
Expand All @@ -17,7 +17,12 @@ Date: 2026-05-27

## Status

Accepted
Superseded by [ADR-0088](0088-cel-guarded-overlays.md) (CEL-guarded overlays)

> **Living reference:** The field classification tables, `ForgeConfig` struct
> definition, and merge rules in this ADR reflect the state at the time of
> acceptance. For the current authoritative version, see
> [Harness Field Reference](../contributing/harness-fields.md).

## Context

Expand Down
5 changes: 5 additions & 0 deletions docs/ADRs/0088-cel-guarded-overlays.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ Date: 2026-08-14

Accepted

> **Living reference:** The overlay resolution pipeline, CEL environment,
> and merge rules described here are maintained in
> [Harness Field Reference](../contributing/harness-fields.md). Update
> that document (not this ADR) when overlay behavior evolves.

## Context

[ADR 0045](0045-forge-portable-harness-schema.md) added a `forge:` block to
Expand Down
8 changes: 5 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,11 @@ repo baseline and overrides)
- Forge-portable harness schema: `role` and `slug` move into the harness
YAML (eliminating the config.yaml `agents:` block dependency), and a
`forge:` section separates platform-specific config from platform-neutral
fields (see ADR-0045 for the full list of forge-overridable fields). Forge blocks inherit from
top-level defaults and override only deltas
([ADR 0045](ADRs/0045-forge-portable-harness-schema.md)).
fields (see [Harness Field Reference](contributing/harness-fields.md)
for the current field classifications and merge rules). Forge blocks
inherit from top-level defaults and override only deltas
([ADR 0045](ADRs/0045-forge-portable-harness-schema.md), superseded by
[ADR 0088](ADRs/0088-cel-guarded-overlays.md)).
- Unified env var delivery: a single `env:` key with `runner` and `sandbox`
sub-maps replaces `runner_env` and manual `.env` files. The runner generates
the sandbox `.env` file from `env.sandbox` at bootstrap. `runner_env` is
Expand Down
18 changes: 15 additions & 3 deletions docs/contributing/harness-composition.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ When adding or modifying a field in the `Harness` or `ForgeConfig`
structs:

1. **Determine the field type.** Is it a scalar, list, map, or pointer
struct? This determines the merge behavior (see ADR-0045 inheritance
rules).
struct? This determines the merge behavior (see
[Harness Field Reference](harness-fields.md) merge rules).
2. **Update `mergeBaseIntoChild`** if the field participates in `base:`
composition.
3. **Update `mergeForgeConfig`** if the field can appear under
Expand All @@ -71,6 +71,14 @@ structs:
blocks.
5. **Update tests** in `compose_test.go` and `forge_test.go` to cover
the new field in all affected functions.
6. **Update the [Harness Field Reference](harness-fields.md)** — If the
change adds a new field to `ForgeConfig`, moves a field between
classification tiers (top-level-only → forge-overridable or vice
versa), or changes merge semantics, update the relevant tables:
- Field classification tables ("Fields that can appear at both
levels" vs "Fields that stay at top level only")
- Merge and inheritance rules table
- `ForgeConfig` struct definition

## When reviewing PRs

Expand All @@ -83,8 +91,12 @@ matching `_test.go` file.

## Related

- [Harness Field Reference](harness-fields.md): Living reference for field
classifications, merge rules, and `ForgeConfig` struct — updated in step 6
- [ADR-0045](../ADRs/0045-forge-portable-harness-schema.md): Forge-portable
harness schema — defines the merge/inheritance rules
harness schema — original architectural decision (Superseded by ADR-0088)
- [ADR-0088](../ADRs/0088-cel-guarded-overlays.md): CEL-guarded overlays —
current overlay mechanism
- [ADR-0064](../ADRs/0064-deprecate-customized-directory-overlay.md):
Deprecate customized directory overlay
- Issue #5579: Harness field integration pipeline (complementary
Expand Down
160 changes: 160 additions & 0 deletions docs/contributing/harness-fields.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# Harness Field Reference

> **This is a living document.** It is the authoritative reference for harness
> field classifications, merge rules, and the `ForgeConfig` struct. Update
> this document whenever you add a new field to `Harness` or `ForgeConfig`,
> move a field between classification tiers, or change merge semantics.
>
> The architectural decisions behind these rules are recorded in
> [ADR-0045](../ADRs/0045-forge-portable-harness-schema.md) (forge-portable
> schema) and [ADR-0088](../ADRs/0088-cel-guarded-overlays.md) (CEL-guarded
> overlays). Those ADRs are point-in-time records; this document reflects the
> current state.

## Field classification

Harness fields are classified into two tiers based on whether they can be
overridden inside `forge.<platform>` blocks or `overlays:` entries.

### Fields that can appear at both levels

These fields can appear at the harness top level (as defaults) and inside
`ForgeConfig` (forge blocks or overlay entries):

| Field | Rationale |
|--------------------|----------------------------------------------------|
| `pre_script` | Scripts often call forge-specific CLIs (gh, glab) |
| `post_script` | Push, PR/MR creation is forge-specific |
| `skills` | Some skills wrap forge-specific APIs |
| `runner_env` | Token names and event URLs differ per forge |
| `validation_loop` | Validation scripts may call forge-specific tools |
| `policy` | Sandbox policies may need forge-specific filesystem or process rules; network access is managed via providers (ADR-0065) but non-network policy sections can still differ per forge |
| `providers` | Providers may need forge-specific entries (e.g., different API endpoints per platform); concatenated (top-level + forge) |
| `openshell` | OpenShell profiles may need forge-specific configuration; `profiles` concatenated (top-level + forge) |
| `host_files` | Host files may need forge-specific entries (e.g., different credential files per platform); deduplicated by `dest` path (child wins) |
| `env` | Env config (`runner` and `sandbox` sub-maps) may need forge-specific entries (e.g., different token names per forge); sub-maps merged independently, forge/child keys win (ADR-0055) |

### Fields that stay at top level only

These fields are platform-neutral and cannot be overridden per-forge or
per-overlay:

Comment thread
ralphbean marked this conversation as resolved.
| Field | Rationale |
|--------------------|----------------------------------------------------|
| `agent` | Agent definitions are forge-agnostic |
| `model` | Model selection is independent of forge |
| `image` | Container images are platform-neutral |
| `api_servers` | REST proxies abstract forge details |
| `plugins` | MCP plugins are forge-agnostic; can be local paths or URLs (ADR-0038) |
| `agent_input` | Agent prompt input is forge-agnostic |
| `timeout_minutes` | Timeouts are operational, not forge-specific |
| `sandbox_timeout_seconds` | Sandbox-level timeout, not forge-specific |
| `security` | Security scanning is forge-agnostic |
| `allowed_remote_resources` | URL allowlist for resource fetching (ADR 0038) |
| `description` | Documentation, no runtime effect |
| `role` | Agent identity is forge-agnostic |
| `slug` | Kept top-level; per-forge slug differences handled via `base` composition |
| `base` | Composition is a structural concern, not forge-specific |
| `doc` | Documentation path, no runtime effect |
| `effort` | Effort level is operational, not forge-specific |
| `readonly_repo` | Repo access mode is forge-agnostic |
| `allow_runtime_fetch` | Runtime fetch opt-in is forge-agnostic |
| `max_runtime_fetches` | Fetch cap is operational, not forge-specific |
| `trigger` | CEL trigger expression is evaluated against normalized events, not forge-specific (ADR-0061) |

## Merge and inheritance rules

When a forge block or overlay is merged into the harness top level, each
field type follows specific merge semantics. The same rules apply during
`base:` composition (base → child merging).

| Field type | Merge behavior | Nil vs empty |
|------------------|------------------------------------------------------|-------------------------------------------------------|
| Scalar fields | Forge/child value overrides top-level/base value | Absent = inherit from top level / base |
Comment thread
ralphbean marked this conversation as resolved.
| `skills` | Merged with deduplication by basename (forge/child overrides top-level/base) | Absent (nil) = inherit; `skills: []` = empty list merged with base (base entries are returned) |
| `runner_env` | Top-level/base map merged with forge/child map; forge/child keys win | Absent (nil) = inherit; `runner_env: {}` = no forge-specific keys (top-level env still inherited) |
| `validation_loop`| Forge/child value replaces top-level/base value entirely | Absent (nil) = inherit from top level / base; explicit empty struct = intended to mean "no validation" (see ADR-0045 open questions) |
| `providers` | Concatenated (top-level/base + forge/child) | Absent (nil) = inherit; `providers: []` = no forge-specific additions (top-level providers still apply) |
| `openshell` | `profiles` concatenated (top-level/base + forge/child) | Absent (nil) = inherit; empty `profiles: []` = no forge-specific additions |
| `host_files` | Concatenated (base + child); deduplicated by `dest` path (child wins) | Absent (nil) = inherit |
| `plugins` | Concatenated (base + child) | Absent (nil) = inherit |
| `api_servers` | Concatenated (base + child) | Absent (nil) = inherit |
| `env` | Sub-maps (`runner`, `sandbox`) merged independently; forge/child keys win (ADR-0055) | Absent (nil) = inherit |
| `security` | Child replaces base entirely (if non-nil) | Absent (nil) = inherit |
| `overlays` *(planned)* | Concatenated (base + child); first-match-wins at resolution (ADR-0088, not yet implemented) | Absent (nil) = inherit |

## `ForgeConfig` struct

The Go struct that holds per-forge (or per-overlay) configuration:

```go
// ForgeConfig holds platform-specific harness configuration.
// This is purely declarative YAML config — it selects which
// scripts, skills, host files, and env vars to use per platform. It is
Comment thread
ralphbean marked this conversation as resolved.
// distinct from the forge.Client interface (internal/forge/),
// which is the runtime abstraction for forge API operations.
type ForgeConfig struct {
PreScript string `yaml:"pre_script,omitempty"`
PostScript string `yaml:"post_script,omitempty"`
Policy string `yaml:"policy,omitempty"`
Skills []SkillEntry `yaml:"skills,omitempty"`
Providers []string `yaml:"providers,omitempty"`
OpenShell *OpenShellConfig `yaml:"openshell,omitempty"`
HostFiles []HostFile `yaml:"host_files,omitempty"`
ValidationLoop *ValidationLoop `yaml:"validation_loop,omitempty"`
RunnerEnv map[string]string `yaml:"runner_env,omitempty"`
Env *EnvConfig `yaml:"env,omitempty"`
}
```
Comment thread
ralphbean marked this conversation as resolved.

## Current resolution pipeline

The current forge resolution pipeline is:

```
Unmarshal → validateForge → ResolveForge(platform) → Validate
```

## Overlay resolution (planned — ADR-0088)

> **Note:** This section describes planned behavior from
> [ADR-0088](../ADRs/0088-cel-guarded-overlays.md). The overlay feature
> has not been implemented yet. The current implementation uses `forge:`
> blocks only.

`overlays:` is the planned successor to `forge:` blocks. Each overlay
entry will have a `when:` CEL expression and the same override fields as
`ForgeConfig`. The first entry whose `when` evaluates to true will be
merged; remaining entries will be skipped.

### Planned resolution pipeline

Comment thread
ralphbean marked this conversation as resolved.
```
Unmarshal → validateForge → validateOverlays →
ResolveForge(platform) → ResolveOverlays(event, config) → Validate
```

### Planned CEL environment

Overlay `when` expressions will be evaluated with:

| Variable | Type | Source |
|---|---|---|
| `event` | `normevent.Event` | The triggering event — fields like `source.system`, `entity.kind`, `transition.kind` |
| `runtime.forge` | `string` | Effective forge platform (precedence: CLI flag > config.forge > CI env vars) |
| `config` | `map[string]any` | Full per-repo config from `config.yaml` |

### Mutual exclusion

`forge:` and `overlays:` must not coexist in the same harness (post-merge).
`forge:` is deprecated; new harnesses should use `overlays:` once implemented.

## Related

- [ADR-0045](../ADRs/0045-forge-portable-harness-schema.md): Forge-portable
harness schema — original architectural decision (Superseded by ADR-0088)
- [ADR-0088](../ADRs/0088-cel-guarded-overlays.md): CEL-guarded overlays —
current overlay mechanism
- [Harness Composition](harness-composition.md): Merge function checklist
(step 6 references this document)
- Issue #5579: Harness field integration pipeline (complementary checklist)
2 changes: 1 addition & 1 deletion skills/author-fullsend-augmentations/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ Typical families (names and availability change — verify each run):
When file-level overrides exist in the checkout you are reading, prefer them
over whole-skill forks for single-file / few-file changes.

**Harness `skills:` merge caveat (still verify in ADR 0045 / compose docs):**
**Harness `skills:` merge caveat (verify in [Harness Field Reference](../../docs/contributing/harness-fields.md)):**
`base:` composition merges skill entries with deduplication by basename —
a child entry whose directory name matches a base skill **replaces** it
(child wins), not loads alongside. Say that plainly when recommending a fork.
Expand Down
Loading