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: 9 additions & 0 deletions .github/workflows/validate-manifests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ jobs:
# workflows that keep the generated files in sync.
scripts/materialize-openclaw-platform-skills.py --check

- name: Validate agent platform catalog
run: |
python3 -m pip install --quiet pyyaml jsonschema
# --selftest also replays scripts/tests/fixtures/agent-platform/
# {valid,invalid} so a change that silently weakens a check (e.g.
# a reference no longer failing closed) fails CI even though the
# real platform-gitops/agent-platform/ catalog itself is fine.
scripts/validate-agent-platform.py --selftest

- name: Validate Argo local workdir invariants
run: scripts/validate-local-workdir.py

Expand Down
134 changes: 134 additions & 0 deletions platform-gitops/agent-platform/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Agent platform catalog

GitOps half of ADR 007 (`mctlhq/mctl-agents`
`docs/adr/007-agent-definition-execution-profile-contract.md`). This
directory holds the reviewed-in-Git `ExecutionProfile` catalog, the
platform policy that every profile and release intent is checked against,
and non-promotable `ReleaseBindingIntent` fixtures. Everything here is
validated by `scripts/validate-agent-platform.py`, wired into
`.github/workflows/validate-manifests.yml`.

This directory does **not** own, mirror, or replace:

- the canonical `AgentDefinition` body (prompt, triggers, identity) --
that stays at `mctl-agents/agents/_manifests/<agent>/agent.yaml`, and is
referenced here only via an exact `sourceManifest {repo, path, gitSha}`;
- immutable published versions, environment `ReleaseBinding` history, or
`lifecycleState` -- all three live in the mctl-api registry
(`mctl_publish_agent_version` / `mctl_promote_agent` /
`mctl_resolve_agent` / `mctl_rollback_agent`);
- the per-run `ExecutionPlan`/`ExecutionRecord` -- that is the runtime
resolver's job (mctl-agents issue #227), not implemented here.

## Layout

```text
platform-gitops/agent-platform/
policy.yaml policy ceilings + reference catalogs
execution-profiles/<name>/profile.yaml agents.mctl.ai/v1alpha2 ExecutionProfile
releases/<environment>/<agent>.yaml agents.mctl.ai/v1alpha2 ReleaseBindingIntent
schemas/execution-profile.schema.json
schemas/release-binding-intent.schema.json
```

## Source-of-truth boundaries (ADR 007, four layers)

| Layer | Owns | Example |
|---|---|---|
| `mctl-agents` Git | Canonical `AgentDefinition`: identity, prompt sources, triggers, `executionProfileRef` | `agents/_manifests/implementer/agent.yaml` |
| `mctl-gitops` Git (this directory) | `ExecutionProfile` drafts, policy ceilings, `ReleaseBindingIntent` review fixtures | `execution-profiles/implementer-default/profile.yaml` |
| mctl-api registry | Immutable published versions, per-environment `ReleaseBinding` history, lifecycle (published/deprecated/disabled) | `mctl_publish_agent_version`, `mctl_promote_agent`, `mctl_resolve_agent` |
| Runtime resolver / Temporal / Argo | One frozen `ExecutionPlan` per run; the actual execution | `orchestrator/resolver.py` (mctl-agents #227), Argo Workflow object |

A file existing under `execution-profiles/` or `releases/` means only
"drafted, reviewed in Git" -- the `draft` state. Nothing in this
directory can ever mark a version `published`, `active`, `deprecated`, or
`disabled`; that authority belongs entirely to the mctl-api registry, and
`active` is itself derived from an environment binding, never stored as a
field. This is why neither JSON Schema in `schemas/` permits a
`lifecycleState` property anywhere -- see
`scripts/tests/fixtures/agent-platform/invalid/global-lifecycle-state/`
for the negative fixture that proves it fails closed.

## Effective-value extraction

The three initial profiles (`issue-investigator-default`,
`implementer-default`, `shepherd-default`) are migrated to be
behavior-preserving, not re-litigated. Their `budgetUsd`/`timeoutSeconds`
come from the values actually enforced in production today, which is not
always the `orchestrator/options.py` Python default:

| Agent | budgetUsd | timeoutSeconds | Source |
|---|---|---|---|
| issue-investigator | 3.00 | 7200 | `cwft-mctl-agents-investigate.yaml` `ISSUE_INVESTIGATOR_BUDGET_USD`; no per-op timeout override exists, so the CWFT's `activeDeadlineSeconds: 7200` is the effective ceiling |
| implementer | 20.00 | 2400 | `cwft-mctl-agents-implement.yaml` `IMPLEMENTER_BUDGET_USD` / `IMPLEMENTER_TIMEOUT_SECONDS` -- these CWFT overrides win over the Python defaults ($3 / 900s) |
| shepherd | 5.00 | 7200 | `cwft-mctl-agents-shepherd.yaml` `SHEPHERD_BUDGET_USD` (raised from the Tier 3 spec's $1.00 default); no per-tick timeout override exists, so `activeDeadlineSeconds: 7200` is the effective ceiling |

Any future profile bump must state which side (Python default vs. deployed
CWFT override) it is changing, and why -- silently reverting to the Python
default would be a behavior change this issue is explicitly scoped not to
make.

## Production dependency

Every `ReleaseBindingIntent` fixture under `releases/` today has
`spec.bindingSource: compatibility-fixture` and `spec.promotable: false`.
There is no real mctl-api-published v1alpha2 version of any of these three
agents or profiles yet -- that publish step, and the runtime resolver that
would actually consume a real binding (mctl-agents #227), are both
follow-up work. Until they land:

- these fixtures can never be promoted to production by anything reading
this directory (`scripts/validate-agent-platform.py` rejects any
`compatibility-fixture` binding with `promotable: true` as inconsistent);
- no running agent, CWFT, or mctl-api schema changes as a result of this
catalog existing;
- the `history`/`rollbackOf` fields on each intent exercise exact-pair
rollback semantics against a local fixture ledger only, standing in for
what will eventually be real mctl-api `ReleaseBinding` history once
registry reconciliation exists.

## Validation

```bash
python3 -m pip install --quiet pyyaml jsonschema
scripts/validate-agent-platform.py # real catalog only
scripts/validate-agent-platform.py --selftest # + replay scripts/tests/fixtures/agent-platform/
```

`--selftest` asserts every fixture under
`scripts/tests/fixtures/agent-platform/valid/` passes and every fixture
under `.../invalid/` fails, one fixture per ADR 007 validation
expectation:

| ADR 007 expectation | Fixture |
|---|---|
| owner required | `invalid/missing-owner/` |
| policyRef required | `invalid/missing-policy-ref/` |
| permissions required | `invalid/missing-permissions/` |
| bounded budget/timeout present | `invalid/missing-bounds/` |
| budget ceiling enforced | `invalid/budget-exceeds-ceiling/` |
| unknown tool fails closed | `invalid/unknown-tool/` |
| unknown skill fails closed | `invalid/unknown-skill/` |
| unknown model-policy task fails closed | `invalid/unknown-model-policy-task/` |
| mutation requires scope + approval | `invalid/mutation-without-approval/` |
| unapproved sandbox rejected | `invalid/unapproved-sandbox/` |
| no global lifecycleState field | `invalid/global-lifecycle-state/` |
| unknown release-intent profile reference fails closed | `invalid/release-missing-profile/` |
| ambiguous profile version rejected | `invalid/release-ambiguous-profile/` |
| incompatible profile version rejected | `invalid/release-incompatible-version/` |
| disabled version rejected | `invalid/release-disabled-version/` |
| independent-half rollback rejected | `invalid/rollback-independent-half/` |
| exact-pair rollback accepted | `valid/rollback-replay/` |

## Rollback

This catalog is additive and not runtime-load-bearing -- nothing resolves
against it yet. Reverting the commit that introduced it removes the
catalog, schemas, validator, and CI step with no effect on any running
agent, CWFT, or mctl-api state. Once real registry-backed bindings exist,
operational rollback always selects the exact previous registry tuple
(`mctl_rollback_agent`'s existing "revert to from_version" semantics),
never an independently chosen pair -- exactly what
`invalid/rollback-independent-half/` and `valid/rollback-replay/` pin down
here ahead of that integration.
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# ExecutionProfile for implementer (ADR 007). Preserves today's effective
# values: $20.00 budget and 2400s timeout are the production
# cwft-mctl-agents-implement.yaml overrides (IMPLEMENTER_BUDGET_USD /
# IMPLEMENTER_TIMEOUT_SECONDS), not the orchestrator/options.py Python
# defaults ($3 / 900s) -- see requirements.md's "Migration is
# behavior-preserving: effective deployed CWFT overrides win over Python
# defaults." This is the only one of the three initial profiles with
# repository.commit/pullRequestCreate: true, matching
# docs/agent-inventory.yaml's riskLevel: high / "the only agent that
# authors code" note (mctl-agents issue-226 design.md). merge stays false --
# the implementer opens PRs, it does not merge them.
apiVersion: agents.mctl.ai/v1alpha2
kind: ExecutionProfile
metadata:
name: implementer-default
owner: platform
spec:
version: "1.0.0"
modelPolicyRef:
task: service_agent
compatibility: ">=1.0.0 <2.0.0"
skills: []
tools:
- Read
- Write
- Edit
- Glob
- Grep
- Bash
- WebSearch
- WebFetch
- "mcp__mctl__*"
policyRef: production-code-author
permissions:
repository:
read: true
branchCreate: true
commit: true
pullRequestCreate: true
merge: false
kubernetes: none
network: approved-providers-only
mutationScopes:
- proposal-status
- target-repository-branch
- pull-request-create
budgetUsd: 20.00
timeoutSeconds: 2400
runtime:
entrypoint: orchestrator.run_implementer:implement
optionsBuilder: orchestrator.options:build_implementer_options
sandbox:
backend: argo
clusterWorkflowTemplate: mctl-agents-implement
approved: true
approval:
requiredBefore:
- code-authoring
evidence:
required:
- proposal
- diff
- tests
- pull-request
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# ExecutionProfile for issue-investigator (ADR 007). Preserves today's
# effective values: $3.00 budget (cwft-mctl-agents-investigate.yaml
# ISSUE_INVESTIGATOR_BUDGET_USD) and the CWFT's 7200s activeDeadlineSeconds
# (no per-op ISSUE_INVESTIGATOR_TIMEOUT_SECONDS override exists today, so the
# pod-level deadline is the effective timeout). Read-only against the target
# repository: repository.commit/branchCreate/pullRequestCreate/merge are all
# false, and the only mutation scopes are writing the proposal triplet and
# commenting on the source issue -- neither requires a human approval gate.
apiVersion: agents.mctl.ai/v1alpha2
kind: ExecutionProfile
metadata:
name: issue-investigator-default
owner: platform
spec:
version: "1.0.0"
modelPolicyRef:
task: issue_investigator
compatibility: ">=1.0.0 <2.0.0"
skills: []
tools:
- Read
- Grep
- Glob
- WebSearch
- WebFetch
- "mcp__mctl__*"
policyRef: read-only-investigation
permissions:
repository:
read: true
branchCreate: false
commit: false
pullRequestCreate: false
merge: false
kubernetes: none
network: approved-providers-only
mutationScopes:
- proposal-directory-write
- issue-comment
budgetUsd: 3.00
timeoutSeconds: 7200
runtime:
entrypoint: orchestrator.run_issue_investigator:investigate
optionsBuilder: orchestrator.options:build_issue_investigator_options
sandbox:
backend: argo
clusterWorkflowTemplate: mctl-agents-investigate
approved: true
approval:
requiredBefore: []
evidence:
required:
- proposal
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# ExecutionProfile for shepherd (ADR 007). Preserves today's effective
# values: $5.00 per-tick budget (cwft-mctl-agents-shepherd.yaml
# SHEPHERD_BUDGET_USD, raised from the Tier 3 spec's $1.00 default) and the
# CWFT's 7200s activeDeadlineSeconds (no per-tick SHEPHERD_TIMEOUT_SECONDS
# override exists today). No mcp__mctl__* tool and network stays
# approved-providers-only-scoped to GitHub, matching
# docs/agent-inventory.yaml's "mcp_servers={} / no mctl MCP access" note for
# this agent (mctl-agents issue-226 design.md). merge: true and the
# pull-request-merge mutation scope are the one high-risk capability this
# profile grants -- gated by the "merge" approval rule below.
apiVersion: agents.mctl.ai/v1alpha2
kind: ExecutionProfile
metadata:
name: shepherd-default
owner: platform
spec:
version: "1.0.0"
modelPolicyRef:
task: shepherd
compatibility: ">=1.0.0 <2.0.0"
skills: []
tools:
- Read
- Grep
- Glob
- Bash
- WebFetch
policyRef: pr-shepherd
permissions:
repository:
read: true
branchCreate: false
commit: false
pullRequestCreate: false
merge: true
kubernetes: none
network: approved-providers-only
mutationScopes:
- proposal-status
- pull-request-merge
budgetUsd: 5.00
timeoutSeconds: 7200
runtime:
entrypoint: orchestrator.run_shepherd:tick
optionsBuilder: orchestrator.options:build_shepherd_options
sandbox:
backend: argo
clusterWorkflowTemplate: mctl-agents-shepherd
approved: true
approval:
requiredBefore:
- merge
evidence:
required:
- pull-request
Loading
Loading