Skip to content

Slim ACS-Core: relax MODIFY, system/ping, and wrapped MCP to SHOULD - #21

Open
bar-capsule wants to merge 2 commits into
Agent-Control-Standard:mainfrom
bar-capsule:bar/slim-core
Open

Slim ACS-Core: relax MODIFY, system/ping, and wrapped MCP to SHOULD#21
bar-capsule wants to merge 2 commits into
Agent-Control-Standard:mainfrom
bar-capsule:bar/slim-core

Conversation

@bar-capsule

@bar-capsule bar-capsule commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Lowers the ACS-Core adoption floor by relaxing three items to conditional requirements within Core, without removing anything from Core or introducing new profiles. The behavior contract that makes ACS distinct (SessionContext + Intent, the hook lifecycle minimum, decision honoring) stays mandatory.

Three changes

Item Before After
MODIFY disposition MUST-support SHOULD-support. Composition rules (§6.3) still normative for implementers. Deployments that do not implement MODIFY declare so in the handshake; Guardians MUST NOT return MODIFY to such deployments (substitute DENY with audit).
Liveness system/ping MUST-implement SHOULD-implement. MAY be omitted by deployments where both parties co-locate or rely on transport-level liveness.
Wrapped MCP protocols/MCP/* MUST-implement SHOULD-implement by deployments that govern MCP tool calls. MAY be omitted by deployments that do not use MCP. Support is declared in the handshake.

What stays mandatory in ACS-Core

The seven things that make ACS a meaningful runtime governance contract, not a stateless single-call policy evaluator:

  1. Handshake with capability negotiation
  2. JSON-RPC 2.0 envelope with required fields (request_id, timestamp, acs_version, metadata)
  3. Hook taxonomy minimum: sessionStart/agentTrigger, userMessage, toolCallRequest, toolCallResult, agentResponse, sessionEnd
  4. Dispositions ALLOW, DENY, ASK, DEFER (the four needed to express any policy outcome)
  5. SessionContext with rolling chain_hash and the Intent invariant (the runtime context that makes governance meaningful, not just per-call yes/no)
  6. Replay protection (request_id UUID + timestamp + Guardian replay rejection)
  7. Baseline integrity (HMAC-SHA256 over the canonical envelope)
  8. Decision-honoring behavior contract (the Observed Agent MUST wait for and apply the verdict; this is what distinguishes ACS from a hook surface a framework can ignore)

Why these three specifically

All three add real deployment friction without being load-bearing for the spec's core property (preventing the agent from acting on injected reasoning):

  • MODIFY requires the agent to rewrite its own outbound payload according to Guardian-provided modifications. That's qualitatively different from ALLOW/DENY/ASK/DEFER, which are state-machine outcomes the agent doesn't have to construct. A framework that doesn't implement MODIFY can still ALLOW/DENY/ASK/DEFER cleanly.

  • system/ping is useful for managed-Guardian deployments where the agent needs to verify reachability before sending real traffic. Co-located deployments don't need it; transport-level liveness covers the same property.

  • Wrapped MCP forces every conformant framework to be MCP-aware, including frameworks whose deployments don't use MCP at all. Declaring MCP support in the handshake is the right cut.

  • ACS picks "stateful and meaningful on hooks." SessionContext with a hash-chained audit and an immutable Intent are required in Core; the dispositions you actually implement track the policy outcomes a framework can produce. Compositional risk is governable because the Guardian sees the running session, not just one call. The cost is that ACS-Core is a slightly higher implementation bar than "wire up one hook and return a verdict."

This PR reduces the gap on the implementation-bar axis without giving up the statefulness or the meaningful hook minimum that distinguish ACS.

What this is NOT changing

  • Section 6.3 MODIFY composition rules: still normative for implementers
  • SessionContext and Intent in Core: explicitly preserved (this is our differentiation from a stateless evaluator)
  • The hook taxonomy minimum: 6 hooks remain MUST-implement
  • Decision honoring (§6.4): unchanged
  • Fail-open default (on_decision_failure: proceed): unchanged

Open questions for working-group review

  1. Handshake field for MODIFY-incapable clients. The PR says deployments not implementing MODIFY "declare so in the handshake," but doesn't pin the field name. Suggest a follow-up that adds a dispositions_supported field to ClientHello, parallel to the existing approver_types_supported. Could ride this PR or land separately.

  2. FIDES interaction. FIDES-style enforcement uses MODIFY for redaction (modifications.redactions). A FIDES Guardian targeting a MODIFY-incapable client would need to substitute DENY for actions that would otherwise be redaction-rewritten. Worth flagging in the FIDES paradigm-binding docs.

  3. Wrapped MCP signalling. The PR says MCP support "is declared in the handshake." Today the handshake's wrapped_protocols field carries this implicitly. Worth making explicit in a follow-up that absence of MCP from wrapped_protocols means the deployment is MCP-incapable.

Test plan

  • Existing schemas under specification/v0.1.0/ remain valid (no schema changes in this PR)
  • Markdown lint passes on docs/spec/conformance.md
  • No internal links broken

🤖 Generated with Claude Code

bar-capsule and others added 2 commits June 15, 2026 11:09
Lowers the adoption floor for frameworks claiming ACS-Core conformance
by relaxing three items within Core to conditional requirements,
without removing them from Core or introducing new profiles. The
hook taxonomy minimum, dispositions ALLOW/DENY/ASK/DEFER, SessionContext
with chain hash and Intent, replay protection, baseline HMAC-SHA256
integrity, and decision-honoring behavior remain MUST.

Changes:

- Dispositions: ALLOW/DENY/ASK/DEFER stay MUST. MODIFY moves to SHOULD,
  with composition rules ([..]/specification.md#63-modify-composition-normative)
  still normative for implementers. Deployments that do not implement
  MODIFY declare so in the handshake; Guardians MUST NOT return MODIFY
  to such deployments (DENY with audit substitution).

- Liveness `system/ping`: SHOULD-implement. MAY be omitted by deployments
  where both parties co-locate or rely on transport-level liveness.

- Wrapped MCP `protocols/MCP/*`: SHOULD-implement by deployments that
  govern MCP tool calls. MAY be omitted by deployments that do not use
  MCP. Support is declared in the handshake.

What stays mandatory in Core (the differentiation from a stateless
single-call evaluator): SessionContext + chain_hash + Intent option,
the 6-hook lifecycle minimum, four dispositions, replay protection,
HMAC baseline integrity, and decision honoring. These preserve the
runtime governance properties that distinguish ACS from a stateless
policy evaluator.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds subagent lifecycle hooks to the Core minimum (8 hooks instead of 6).

Rationale (Ariel): a sub-agent is itself an Observed Agent under
delegated authority. Without subagentStart/subagentStop on the Core
floor, a Guardian is blind to cross-agent propagation and to the
confused-deputy attack class that delegation enables. ACS already has
the schemas, the AgBOM models subagent components, and the wire shape
is identical to other steps -- the cost of mandating these is
marginal, the cost of leaving them optional is a structural gap in
runtime governance.

Frameworks without a sub-agent abstraction satisfy this requirement
vacuously: the hooks never fire, the framework still declares them as
implemented in the handshake.

Net Core hook count: 6 -> 8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bar-capsule added a commit to bar-capsule/ACS that referenced this pull request Jun 15, 2026
PR Agent-Control-Standard#21 promotes subagentStart and subagentStop from SHOULD to MUST in
the ACS-Core hook minimum (8 hooks instead of 6). Update the FAQ to
match:

- "How do I make my framework ACS-conformant?" lists subagent hooks in
  the mandatory floor with a short justification.
- "What if my framework does not have a particular hook surface?" no
  longer lists subagent hooks under "Additional hooks (SHOULD)";
  they are now part of Core.

Frameworks without a sub-agent abstraction satisfy this requirement
vacuously, which is the same shape as the merged spec's "implement
when observable" pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bar-capsule

Copy link
Copy Markdown
Collaborator Author

Update: subagent hooks promoted to mandatory (per Ariel)

Added subagentStart and subagentStop to the mandatory Core hook set in commit 3ec047f.

Net Core after both relaxations and this addition:

What was Core Status now
Handshake MUST (unchanged)
JSON-RPC envelope MUST (unchanged)
Hook taxonomy minimum MUST, now 8 hooks (sessionStart, userMessage/agentTrigger, toolCallRequest, toolCallResult, agentResponse, sessionEnd, subagentStart, subagentStop)
ALLOW / DENY dispositions MUST (unchanged)
ASK / DEFER dispositions MUST (unchanged)
MODIFY disposition SHOULD (relaxed)
SessionContext + chain_hash + Intent MUST (unchanged)
Replay protection MUST (unchanged)
HMAC-SHA256 baseline MUST (unchanged)
Decision honoring MUST (unchanged)
system/ping liveness SHOULD (relaxed)
Wrapped MCP SHOULD when MCP used (relaxed)

Rationale for the subagent promotion (Ariel's reasoning): a sub-agent is itself an Observed Agent operating under delegated authority. Without subagentStart / subagentStop on the Core floor, the Guardian is structurally blind to cross-agent propagation — the confused-deputy attack class. The schemas already exist, the AgBOM models subagent components, the wire shape is identical to other steps. The cost of mandating is marginal; the cost of leaving optional is a gap in runtime governance.

Frameworks without a sub-agent abstraction satisfy this vacuously (the hooks never fire). Same shape as the existing "implement when observable" pattern.

Shape of the slim: dropping the three things that add infrastructure burden (MODIFY composition rules, system/ping plumbing, MCP awareness for non-MCP deployments), adding the two things that close a structural governance gap. The PR's adoption-floor goal still holds — a framework can implement the 8 hooks and the rest of Core in one focused session — and the security floor is materially better.

FAQ updated in parallel (PR #20, commit 27c9d07).

@rocklambros

Copy link
Copy Markdown

Merge-order note (for whoever lands these): this one sets the ACS-Core floor, so it should go in before #20 and #22.

It promotes the subagent hooks to MUST and relaxes MODIFY, system/ping, and wrapped MCP to SHOULD. Two things downstream depend on that:

No git conflict with either (this only touches conformance.md), so the ordering is about content staying consistent, not about rebasing. Suggested sequence: #21, then #20, then #22. @bar-capsule

@afogel

afogel commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

this work should actually also bump the patch version @bar-capsule

@rocklambros

Copy link
Copy Markdown

@bar-capsule

I spent a while on this one because four lines that change what a MUST means are worth more scrutiny than four lines usually get. The direction is defensible and I'll say why below, but there are two places where the text as written can't be implemented, and one that I think gets the security trade backwards.

Taking them in order of how much they block.

The MODIFY declaration has nowhere to live

Line 20 says deployments that don't implement MODIFY "MUST advertise this in the handshake." I went to check what field that would be, and there isn't one. Here's the full ClientHello property set from specification/v0.1.0/handshake.json:

acs_versions_supported, max_payload_size_bytes, methods_implemented,
profiles_supported, provenance_producer, transports_supported, wrapped_protocols

Nothing about dispositions, on either message shape. And a vendor can't just invent one, because the schema description says "Unknown fields MUST be ignored at every level," so a conformant peer is required to throw the extension away.

The part that made me think this was an oversight rather than a decision: §9.2 already solved this exact problem for ASK, and it went the other way. specification.md:275 says "ACS does not put this declaration on the wire in v0.1; it is part of the Guardian's policy bundle." So if line 20 lands as written, ACS carries two contradictory mechanisms for the same problem class, and one of them points at a field that doesn't exist.

I think Wrapped MCP's demotion is what made this feel safe, because that one does land on a real field (wrapped_protocols). MODIFY has no equivalent.

Either add dispositions_supported to ClientHello in handshake.json and specification.md §4 as part of this change, or follow the §9.2 precedent and make it a policy-bundle fact. Please don't ship the prose without one of the two.

Nothing defines what an agent does with a MODIFY it can't apply

This is the one I'd fix even if you disagree with everything else here.

Once MODIFY is optional, some agents will receive one anyway, because the declaration above doesn't work yet and handshakes are the first thing an integration gets wrong. I went looking for the defined behavior and couldn't find it. specification.md:146 fails closed only on malformed modifications, which is a different case. specification.md:154 says the agent MUST apply the decision, which it can't.

So an implementer writes the obvious three lines: unknown or unapplicable disposition, proceed with the original payload. Under a house style where on_decision_failure already defaults to proceed, that's the natural reading. Now a Guardian that meant to redact an API key out of a tool argument gets the unredacted key shipped, and its audit log records a MODIFY that never happened.

Before this PR that was flatly non-conformant and a test could assert it. After, it's an unspecified path inside a negotiated capability, and nothing can call it a violation.

One sentence fixes it, mirroring §6.3: an Observed Agent that receives a disposition it cannot apply MUST treat the decision as DENY and MUST record an audit event.

While you're in there, the substitution on line 20 mandates an audit event with no reason_code. §9.2 mandates approver_unavailable for the parallel ASK case, and specification.md:129 says "UIs and meta-policies SHOULD switch on these rather than parsing reasoning text." Without a code, the substitution rate is the one metric that would catch a client lying about its capability, and it's only reachable by grepping log prose. modify_unsupported would match the existing pattern.

The subagent clause has two readings and both are defensible

I like the reasoning here more than the other three changes, and I think the confused-deputy argument is right. The clause still needs work, because I read it twice and got two different answers.

Reading one: "Frameworks that do not have a sub-agent abstraction" means the framework genuinely has no such concept. Under that reading Claude Code and Cursor both have subagents, so neither gets the exemption.

Reading two: the parenthetical "(the hooks never fire)" is the operative test, so a framework that emits no subagent event satisfies it vacuously regardless of whether the concept exists.

Those give opposite answers for the same deployment, which means the clause can't settle a conformance dispute. Worse under reading two, any deployment can claim vacuity by declining to implement the emitter, which makes the promotion from SHOULD to MUST a no-op.

The deeper problem is that "has no sub-agent abstraction" is a fact about a framework's internals and not a fact on the wire. A deployment that spawns subagents and doesn't instrument them sends a ClientHello that's byte-identical to one from a framework that genuinely has none. No Guardian, auditor, or test can tell them apart. Every other omission in this PR got a declaration mechanism ("Support is declared in the handshake" on line 26, "MUST advertise this in the handshake" on line 20), and this one didn't.

Two more things I'd want you to look at before settling this:

hooks.md:24 marks subagentStop as not decision-eligible, and its section says "Decision: Audit only." So the confused-deputy defense the clause invokes is delivered by subagentStart, which is a real gate. Mandating both couples a control to a record, and they're different normative animals.

And subagent-stop.json requires final_chain_hash with a 64-hex pattern. Cursor's adapter in #22 documents that field as "genuinely unknowable (Cursor maintains no chain)" and omits the hook rather than fabricate one, which I think is the right call. As written, this PR forces frameworks like that to pick between documented non-conformance and fabricating an integrity value, and fabricating it corrupts the exact artifact the requirement exists to produce. That's a spec bug in subagent-stop.json independent of this PR, but this PR is what makes it bite.

My suggestion: make the vacuity claim a wire fact (a ClientHello boolean a Guardian can refuse on and an auditor can hold a vendor to), or split the pair so subagentStart is MUST and subagentStop is SHOULD. If neither is appetizing right now, leave both at SHOULD, because an honest SHOULD is worth more than a MUST nobody can check.

One thing this PR can't fix but should probably acknowledge

While tracing the subagent change I hit something bigger. ACS spends 428 lines constraining what the Observed Agent MUST emit and never constrains what the Guardian MUST evaluate. The ServerHello's methods_evaluated says it plainly: "Methods listed by the client but absent here are NOT evaluated ... Clients MAY still emit them for audit but MUST treat them as ALLOW-by-default."

So a Guardian can accept every subagent hook a conformant client emits and evaluate none of them, and both parties stay conformant. Every hook-coverage claim in ACS is unfalsifiable from the enforcement side.

That's not yours to solve in a four-line PR. But this is the first change whose stated security rationale depends on the half that's missing, so it's worth a sentence in the PR body at least, and probably an issue.

Wrapped MCP: the fallback doesn't cover what you'd want it to

Line 26 lets deployments "that do not use MCP" omit the namespace. The problem is what happens to deployments that do.

hooks.md:342 scopes the documented fallback carefully: deployments "MAY collapse MCP tool calls into steps/toolCallRequest," and it lists what stays in the namespace, including prompt fetches and resource reads. protocols/MCP/resources/read is the canonical prompt-injection ingress, where attacker-controlled external content enters the agent's context. Right now it's a mandatory gated method. After this change an adopter can omit the namespace, collapse tool calls per hooks.md:342, and leave resource reads with no hook at all, because the one steps/* hook that could catch them is knowledgeRetrieval and line 19 leaves that at SHOULD.

There's a timing problem underneath it. "Deployments that do not use MCP" is assessed once, at handshake, and MCP servers get added at runtime. specification.md:77 says mid-flight renegotiation is undefined in v0.1, and §17.1's recovery for CAPABILITY_NOT_NEGOTIATED says "re-handshake to negotiate the method," pointing at a capability §4.1 says doesn't exist yet. So an agent that adds an MCP server at minute ten has no conformant path to start wrapping.

Suggestion: condition the requirement on wrapped_protocols containing MCP at any point in the session rather than at handshake, and say explicitly that adding an MCP server mid-session in a deployment that declared none is non-conformant until v0.2 ships renegotiation.

The ping justification points at something that isn't defined

Line 25 lets deployments omit system/ping when they "rely on transport-level liveness signals." I went looking for what those are and found nothing. §3 lists the transports as HTTP(S) and stdio, and §11 doesn't define a liveness mechanism for either. stdio in particular has none.

I'll grant the strongest form of your case here, because specification.md:369 already says a successful ping "does not prove the enforcement path is healthy" and recommends monitoring hook-path decision failures instead. That's a good argument that mandatory ping was partly theater.

The trouble is the recommended alternative. Decision-failure monitoring depends on an audit stream, and in #22 that stream is ACS_AUDIT lines on the adapter's stderr, which none of the three example configs route anywhere. So chain the defaults: startup posture defaults to proceed, on_decision_failure defaults to proceed, and now liveness is optional. Someone kills the Guardian and gets new sessions starting unguarded, every hook failing open, and no mandatory probe that would notice.

I'd either strike the unanchored justification, or define what a transport-level liveness signal is per transport, or require ACS-Core deployments to declare a liveness mechanism rather than merely permit omitting one.

Small stuff, quick to fix

The quick-reference table at line 83 still says ping is mandatory, in this same file, 58 lines below the bullet that makes it optional:

| `acs-core` | Handshake, envelope, hook taxonomy, dispositions, SessionContext + published
chain head, replay protection, baseline signature (HMAC-SHA256), decision honoring, ping |
Always (mandatory) |

That row also doesn't mention the subagent hooks you just promoted, says "dispositions" unqualified, and has no Wrapped MCP entry. Since it's the row people paste into comparison matrices, I'd update it in this PR. docs/acs.md:11 has the same problem and still lists "wrapped MCP, five dispositions ... and system/ping liveness" as ACS-Core.

Line 30's guarantee paragraph is also untouched and still says the agent "honors the Guardian's decisions," which is now true for four of five. One clause would fix it, something like noting a minimum-conformant deployment can permit or refuse an action but cannot alter it, and redaction requires MODIFY support.

Version

conformance.md:15 says "A v0.1.0-conformant deployment MUST implement ACS-Core," and this PR changes what ACS-Core requires with version.txt untouched. There's no VERSIONING.md or CHANGELOG.md in the repo (I checked), and sync_version.yml only triggers on version.txt, so nothing in CI notices.

The practical effect is that two peers who disagree about whether MODIFY is required both send "0.1.0" and negotiate successfully. A deployment that passed a review last month is non-conformant today on subagent hooks, and one that failed now passes, and there's no artifact an auditor can use to establish which floor applied when.

I know the project is pre-1.0 and SemVer says you owe nobody compatibility below 1.0.0. That argument works right up until #20's FAQ starts telling compliance teams to rely on this, which it does. A 0.1.1 bump plus a changelog line is cheap insurance.

Sequencing

These three PRs aren't textually conflicting (I checked, the file sets are disjoint), but #20 and #22 both restate what this PR changes. #20's FAQ already carries your subagent language nearly verbatim at line 124 and hasn't picked up any of the three relaxations, so it'll contradict this PR on three of four items the moment both land. #22's conformance suite cites conformance.md by line number, and since your diff is a same-line-count edit (both files are 89 lines, hunks are 19,20c19,20 and 25,26c25,26), every citation stays numerically valid while four of them start quoting text that no longer exists.

This one should go first, with #20 rebased against the final text and #22 updating its citations. I've left the details on both of those PRs.

Last thing, and it's procedural rather than technical: CONTRIBUTING.md:42 asks for git commit -s, and neither commit here has a sign-off. Same across #20 and #22. Worth a rebase before merge, and probably worth a DCO check in CI so it stops being a manual thing anyone has to remember.

@afogel afogel left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants