From 4f88a0e9c248a7180e6fea78f0db654bc3a08f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D0=B1=D0=BE=D1=82?= Date: Mon, 12 Oct 2026 06:32:29 +0200 Subject: [PATCH 1/5] agent-stack 0.27.0: trust by machine, Fabric jobs, room in every description Integration wave 4 (sshlg-skills plan R23-R26): gateway.md opens delegation with the operator's trust tiers (T0/T1 no token, T2 full on-behalf-of model; money limits, human gates and no borrowed keys on every tier), pinned to Fabric main 5bf15e5c (ADR-0144 amendment 4, 19ad05ca) and fabric-workspace 714808ac; mcp.md names fabric-interop/0.1 jobs as the worked fallback for MCP Tasks (fabric-agent-contract bcada4a); agent-interop points to the Fabric adapter skills and observatory-log:handling-secrets; descriptions 935/936/938/911. Co-Authored-By: Claude Opus 5.5 (1M context) --- .claude-plugin/marketplace.json | 2 +- CHANGELOG.md | 25 +++++++++++++++++++ SKILL-CARD.md | 2 +- docs/evidence/verification.md | 12 ++++++++- package.json | 2 +- .../agent-stack/.claude-plugin/plugin.json | 2 +- .../agent-stack/skills/agent-evals/SKILL.md | 4 +-- .../agent-stack/skills/agent-harness/SKILL.md | 8 +++--- .../agent-stack/skills/agent-interop/SKILL.md | 12 +++++++-- .../agent-interop/references/gateway.md | 24 ++++++++++++++++++ .../skills/agent-interop/references/mcp.md | 16 ++++++++++++ .../skills/agent-orchestrator/SKILL.md | 6 ++--- 12 files changed, 99 insertions(+), 16 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index e1206d1..9278a4b 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -12,7 +12,7 @@ "displayName": "Agent Stack", "source": "./plugins/agent-stack", "description": "Four skills: agent-orchestrator — tool-calling loops, pipelines with checkpoints, provider routing with fallback, memory architecture, plus the wallet side of reselling LLM access; agent-evals — run/trace/thread evals, LLM judges, and fixtures grown from production; agent-interop — MCP servers and clients, A2A agent cards, the MCP Registry, and gateways; agent-harness — system prompts, tool shaping, workflow-vs-agent, and auditing an agent system.", - "version": "0.26.0", + "version": "0.27.0", "author": { "name": "ssheleg", "url": "https://x.com/sshlg93" diff --git a/CHANGELOG.md b/CHANGELOG.md index 8924d32..1b5e0a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +## 0.27.0 — trust by machine, Fabric jobs, and room in every description + +Integration wave 4 of the family's waves run (sshlg-skills plan R23–R26). + +- **Delegation starts with whether a token is needed (R23).** `agent-interop/references/gateway.md` + now opens its delegation section with the operator's trust tiers: + - T0, the operator's own machine, needs no token, approval or grant record. + - T1, the operator's enrolled machines, is the same. + - T2, another person's machine or an outside caller, uses the full on-behalf-of model. + - Money limits, human gates and "never spend another agent's key" hold on every tier. + - Pinned to Fabric ADR-0144 amendment 4 (`19ad05ca`, in Fabric main since `5bf15e5c`) + and to fabric-workspace `knowledge/authority.md` at `714808ac`. +- **Fabric's job extension as the worked fallback for MCP Tasks (R24).** + - `references/mcp.md` names `fabric-interop/0.1` jobs (`fabric.job.get` and `cancel`), + the five Task states, when `result`, `error` and `inputRequests` appear, and the result + envelope. + - Pinned to fabric-agent-contract `bcada4a`. + - The estate's `degraded`/`human_steps` reply convention has no contract home yet, so it + is not taught. +- **Pointers out, not copies (R25).** `agent-interop` names the Fabric adapter skills and + `observatory-log:handling-secrets` as the HOW for joining an estate and for credentials. +- **Every description has at least 30 characters of room (R26):** interop 935, harness 936, + orchestrator 938, evals 911, against the house cap of 970. The trigger lists and the + wording pinned by `fix-as-01.01` are unchanged. + ## 0.26.0 — delegation: which agent acts, for which person - **`agent-interop/references/gateway.md` gains the on-behalf-of model (B-155).** The file diff --git a/SKILL-CARD.md b/SKILL-CARD.md index f7ab541..063962e 100644 --- a/SKILL-CARD.md +++ b/SKILL-CARD.md @@ -5,7 +5,7 @@ | Field | Value | |---|---| | Pack | `agent-stack` | -| Version | `0.26.0` | +| Version | `0.27.0` | | Skills | `agent-orchestrator`, `agent-evals`, `agent-interop`, `agent-harness` | | License | MIT | | Source | https://github.com/ssheleg/agent-stack | diff --git a/docs/evidence/verification.md b/docs/evidence/verification.md index f0188b5..1d41735 100644 --- a/docs/evidence/verification.md +++ b/docs/evidence/verification.md @@ -10,7 +10,17 @@ This file exists because its absence read as zero exposure. `sshlg-skills` board --- -## Release candidate — v0.26.0 (2026-10-11) +## Release candidate — v0.27.0 (2026-10-12) + +| REQ | What ships | How it was confirmed | Confirmed | +|---|---|---|---| +| R23 | trust tiers T0/T1/T2 in `gateway.md`, pinned | text read against fabric-workspace `714808ac` `knowledge/authority.md` "Trust by machine" | never | +| R24 | `fabric-interop/0.1` jobs as the Tasks fallback, pinned `bcada4a` | text read against fabric-agent-contract `interop.md` §C3.2 | never | +| R25 | pointers to the Fabric adapter skills and `observatory-log:handling-secrets` | `audit_skill.py --house agent-interop`: 0 GAP, body 3502/4750 | never | +| R26 | descriptions 935/936/938/911 of 970 | `audit_skill.py --house` per skill | never | +| Gate | `npm test` | EXIT=0 on 2026-10-12 | never | + +## Released — v0.26.0 (2026-10-11) | REQ | What ships | How it was confirmed | Confirmed | |---|---|---|---| diff --git a/package.json b/package.json index a2fd739..bb3417d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ssheleg/agent-stack", - "version": "0.26.0", + "version": "0.27.0", "scripts": { "test": "python3 test/validate.py && python3 test/plant_guard_test.py && node test/installer_test.js && npm run test:audit", "test:audit": "for t in test/audit_regressions/*.py; do python3 \"$t\" || exit 1; done" diff --git a/plugins/agent-stack/.claude-plugin/plugin.json b/plugins/agent-stack/.claude-plugin/plugin.json index e16a555..c098862 100644 --- a/plugins/agent-stack/.claude-plugin/plugin.json +++ b/plugins/agent-stack/.claude-plugin/plugin.json @@ -3,7 +3,7 @@ "name": "agent-stack", "displayName": "Agent Stack", "description": "Four skills: agent-orchestrator — tool-calling loops, pipelines with checkpoints, provider routing with fallback, memory architecture, plus the wallet side of reselling LLM access; agent-evals — run/trace/thread evals, LLM judges, and fixtures grown from production; agent-interop — MCP servers and clients, A2A agent cards, the MCP Registry, and gateways; agent-harness — system prompts, tool shaping, workflow-vs-agent, and auditing an agent system.", - "version": "0.26.0", + "version": "0.27.0", "author": { "name": "ssheleg", "url": "https://x.com/sshlg93" diff --git a/plugins/agent-stack/skills/agent-evals/SKILL.md b/plugins/agent-stack/skills/agent-evals/SKILL.md index 357d87f..4268377 100644 --- a/plugins/agent-stack/skills/agent-evals/SKILL.md +++ b/plugins/agent-stack/skills/agent-evals/SKILL.md @@ -4,8 +4,8 @@ description: >- Use when measuring whether an agent actually works — building an eval suite, judging a trajectory rather than a final answer, turning production traces into regression fixtures, calibrating an LLM judge against human labels, or gating a release on offline - evals. Covers the three observability primitives (run, trace, thread) crossed with three - eval granularities (single-step, full-turn, multi-turn), the offline/online/ad-hoc + evals. Covers run, trace and thread crossed with single-step, full-turn and multi-turn + evals, the offline/online/ad-hoc timing axis, pass-fail rubrics over scalar scores, cheap code checks before model judges, the checker node as an evaluator inside the graph, simulated users with adversarial personas, annotation queues, and what to instrument for any of it. Triggers - "agent eval", "eval suite", "LLM judge", "regression fixture", diff --git a/plugins/agent-stack/skills/agent-harness/SKILL.md b/plugins/agent-stack/skills/agent-harness/SKILL.md index dcd37b3..4f31f01 100644 --- a/plugins/agent-stack/skills/agent-harness/SKILL.md +++ b/plugins/agent-stack/skills/agent-harness/SKILL.md @@ -2,10 +2,10 @@ name: agent-harness description: >- Use when the question is what the agent is TOLD rather than how its loop is wired — writing - or fixing a system prompt, shaping tools so the model picks the right one, deciding whether - a job wants a workflow or an agent, or choosing between ReAct, reflection and voting. Also - auditing an agent system somebody else built: tracks, evidence tiers and a prioritized plan - instead of a score, plus a scanner. Carries Pi as a worked kernel implementation — SDK, RPC + or fixing a system prompt, shaping tools so the model picks the right one, whether a job + wants a workflow or an agent, or choosing between ReAct, reflection and voting. Also + auditing someone else's agent system: tracks, evidence tiers, a prioritized plan instead of + a score, a scanner. Carries Pi as a worked kernel implementation — SDK, RPC and extension seams — for embedding or extending a harness. Triggers - "system prompt", "tool description", "agent picks the wrong tool", "agent loops forever", "prompt engineering", "harness engineering", "ReAct loop", "react pattern", "workflow or agent", diff --git a/plugins/agent-stack/skills/agent-interop/SKILL.md b/plugins/agent-stack/skills/agent-interop/SKILL.md index f755444..ac88465 100644 --- a/plugins/agent-stack/skills/agent-interop/SKILL.md +++ b/plugins/agent-stack/skills/agent-interop/SKILL.md @@ -4,8 +4,8 @@ description: >- Use when an agent must talk to something outside its own process — building or consuming an MCP server, exposing or calling an agent over A2A, publishing to the MCP Registry, or putting a gateway in front of agent traffic. Carries the MCP 2026-07-28 wire surface and what it - deprecated (server/discover, stateless per-request _meta, elicitation in form and URL mode, - subscriptions/listen; sampling, roots, logging and dynamic client registration going), + deprecated (server/discover, stateless _meta, form/URL elicitation, subscriptions/listen; + sampling, roots, logging and dynamic client registration going), A2A 1.0 agent cards, task states, three bindings, registry namespaces, server.json, tool federation, and what a gateway must do that an API gateway does not. Triggers - "MCP server", "MCP client", "A2A", "agent card", "agent interoperability", "MCP registry", "server.json", @@ -122,6 +122,14 @@ for a plugin's own server). **This skill owns the wire.** If the question is "wh protocol say", it is here; if it is "how does my skill declare that it needs one", it is there. The protocol is described in exactly one of the two places, and this is it. +**Against the Fabric adapter and Observatory skills.** Where an agent or service must join +a Fabric estate, the HOW is theirs: `fabric-agent-adapter:creating-fabric-agents`, +`:adapting-projects-to-fabric` and `:building-fabric-services` (the descriptor, the hub, +`fabric-service/0.1`); credentials an agent uses go through +`observatory-log:handling-secrets` (named vault slots, never a value). This skill keeps the +protocol they speak — MCP, A2A, the gateway, delegation — and points to them rather than +restating them. + **Against `agent-orchestrator`.** That skill routes between *model providers* and manages the loop's own context. This one routes between *processes*. They meet at exactly one point: an orchestrator that federates many MCP servers is subject to `references/mcp-scale.md`, and one diff --git a/plugins/agent-stack/skills/agent-interop/references/gateway.md b/plugins/agent-stack/skills/agent-interop/references/gateway.md index 387aafc..f0db014 100644 --- a/plugins/agent-stack/skills/agent-interop/references/gateway.md +++ b/plugins/agent-stack/skills/agent-interop/references/gateway.md @@ -126,6 +126,27 @@ callers apart, so no limit holds per caller) or **the callee's own host credenti by the caller** (a confused deputy with that service's full power). Neither says on whose behalf the work runs. +**First, whether a token is needed at all — trust by machine.** Proof is owed at a +boundary, and the operator's own machine is not one. Requiring a token, an approval and a +grant record for every local call buys ceremony, and nobody can attribute a cost to it. + +| Tier | Where the call comes from | What it needs | +|---|---|---| +| T0 | the operator's own machine | the operator's instruction is the authority: no delegation token, no approval, no grant record | +| T1 | another machine of the operator's, once it is enrolled in the operator's agent network | the same as T0 | +| T2 | another person's machine, or any outside caller | everything below: a share grant with limits, an expiry and a payer, a token per call, an approval per effect | + +Two rules hold on **every** tier: +- **Money limits and human gates.** Every spend limit (project, estate, each key's + ceiling) applies, and so does every gate a person holds: sign-in, consent, payment. +- **No borrowed keys.** An agent never spends another agent's key. It calls that agent's + capability, so the owner's wallet and limits apply. + +On a T0/T1 path the token below is optional and used for attribution only. A service +that takes calls across the machine boundary, or from outside people, requires it. +Enrolment is what moves a machine from T2 to T1. A machine is never trusted because it +is on the same network. + **The token.** One short signed document per call path, in the shape of OAuth 2.0 Token Exchange (RFC 8693): @@ -203,6 +224,9 @@ shape leaves open. ADR-0144) implements the token, the verifier, the meter flow, the approval and the refusal codes above — with schemas, positive/negative/semantic fixtures and a reference verifier: `docs/specification/delegation.md` in `passioncode-ai/fabric-agent-contract` at `8e49fbd`. Read its check table (DL-13) before writing a verifier in another language. +The trust tiers are Fabric ADR-0144 amendment 4 (`19ad05ca`), in Fabric's main since +`5bf15e5c`, and the operator's decision is recorded in `passioncode-ai/fabric-workspace` +`knowledge/authority.md`, *Trust by machine*, at `714808ac`. ## agentgateway, concretely diff --git a/plugins/agent-stack/skills/agent-interop/references/mcp.md b/plugins/agent-stack/skills/agent-interop/references/mcp.md index b164783..fc364b0 100644 --- a/plugins/agent-stack/skills/agent-interop/references/mcp.md +++ b/plugins/agent-stack/skills/agent-interop/references/mcp.md @@ -224,6 +224,22 @@ Opt-in, negotiated, and worth checking before inventing an equivalent: own is MCP-with-Tasks. But Tasks is an OPT-IN, NEGOTIATED extension — check the client/SDK actually supports it before building on it; where it is unsupported, the fallback is a chunked job or a caller-polled job id over plain `tools/call`, not a protocol change. + **Worked example of that fallback:** Fabric's `fabric-interop/0.1` jobs + (`passioncode-ai/fabric-agent-contract` `docs/specification/interop.md` §C3.2, at + `bcada4a`). + - A capability that may outlive one request declares `"job": true`. + - With Tasks negotiated, it MAY return an MCP Task. Otherwise it returns + `{"job": {"id", "status": "working"}}` in `structuredContent` and serves + `fabric.job.get {id}` / `fabric.job.cancel {id}`. + - The states are the MCP Task states: `working | input_required | completed | failed | + cancelled`. + - `result` is present exactly when the job is `completed`, `error` when it `failed`, + and `inputRequests` when it waits for input. + - The result envelope separates `done`, `proof` and `notVerified`, and carries `usage`. + - A job id survives a restart, and an unknown id answers `unknown-job`, never a fresh + job. + + A caller written against this works unchanged when the server later negotiates Tasks. - **MCP Apps** — interactive UI rendered inline in the conversation. - **Skills over MCP** — structured instruction sets discovered and consumed through MCP, which is how a server ships Agent Skills rather than only tools. diff --git a/plugins/agent-stack/skills/agent-orchestrator/SKILL.md b/plugins/agent-stack/skills/agent-orchestrator/SKILL.md index d9d5fce..4ee3a61 100644 --- a/plugins/agent-stack/skills/agent-orchestrator/SKILL.md +++ b/plugins/agent-stack/skills/agent-orchestrator/SKILL.md @@ -1,9 +1,9 @@ --- name: agent-orchestrator description: >- - Use when building an agent system — an orchestrator, an LLM-powered tool, a chatbot with tool - use, an AI pipeline — or metering and billing the LLM access it burns. Covers tool-calling - loops, pipelines with human checkpoints, provider routing with fallback/retry, memory + Use when building an agent system — an orchestrator, an LLM tool, a tool-using chatbot, an AI + pipeline — or metering and billing the LLM access it burns. Covers tool-calling + loops, human-checkpoint pipelines, provider routing, fallback/retry, memory architecture, retrieval and decay, context budgets, sub-agent coordination, error hierarchies; the work as a graph — parallel layers, fake edges, a checker before convergence; for resale: tiered wallets, one markup boundary, the saga across database and provider API, From 390a1693480193f61a424f6b89b1a064142a6b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D0=B1=D0=BE=D1=82?= Date: Mon, 12 Oct 2026 12:46:08 +0200 Subject: [PATCH 2/5] agent-stack 0.27.0 fix round: tiers per DL-25/DL-26, jobs and degraded per the contract, R25/R26 gated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three blind readings failed the first cut (4f88a0e). This round: - gateway.md: DL-26 replaces "optional, attribution only" and "a service taking calls across the boundary requires it" (per caller, not per service; a presented token verified, refused when invalid, used for attribution and narrowing); tier by origin (DL-25), T1 = RM-21 still open; five human gates and three money levels; token-less T0 metering (Observatory when-token, keyserver off, METER.md M0); DL-8 and DL-12 scoped; pin 8e49fbd -> 3cb03ab (DL-25/DL-26); stale "read-only" meter scope and "device check" corrected at the new pin; two new traps. - mcp.md: output and scope in the envelope, inputResponses (C3.3), the job handle for a caller without Tasks, the well-known degraded list (service.md at bcada4a); per-reply envelope deferred as B-156. - test/validate.py: description ratchet 940 (house 970 kept); check_interop_points_to_the_estate_skills (R25). CI negative self-tests for both; watched failing locally (941 fails, 940 passes; a removed building-fabric-services pointer fails). - test/audit_regressions/w4-fabric-doctrine.py: 5 cases, all fail on the first cut's gateway.md/mcp.md. - agent-evals names observability again (929); agent-interop adds "delegation token", "on behalf of", «делегирование» (937); all 46 hub triggers still substrings. - README, CHANGELOG and ledger rewritten for this round. R28 not included. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/validate.yml | 53 +++++++ CHANGELOG.md | 75 +++++++--- README.md | 3 +- docs/evidence/backlog.md | 1 + docs/evidence/verification.md | 13 +- .../agent-stack/skills/agent-evals/SKILL.md | 4 +- .../agent-stack/skills/agent-interop/SKILL.md | 17 +-- .../agent-interop/references/gateway.md | 136 ++++++++++++------ .../skills/agent-interop/references/mcp.md | 26 +++- test/audit_regressions/w4-fabric-doctrine.py | 122 ++++++++++++++++ test/validate.py | 34 +++++ 11 files changed, 401 insertions(+), 83 deletions(-) create mode 100644 test/audit_regressions/w4-fabric-doctrine.py diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index dcc406c..c7a04e4 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -126,6 +126,59 @@ jobs: echo "OK: validator catches front matter the host would truncate silently" fi + - name: Negative self-test (a description at 941 chars must fail; 940 passes) + run: | + set -eu + # Plan R26: this member keeps 30 characters of room under the house limit of 970. + # The plant replaces the whole description with a plain scalar of an exact length, + # so the boundary is tested at the character, not near it: 940 must pass and 941 + # must fail, and fail for the ratchet, not for something else the edit broke. + for n in 940 941; do + rm -rf /tmp/ratchet-copy && cp -R . /tmp/ratchet-copy + python3 test/plant_guard.py snap /tmp/ratchet-copy + N="$n" python3 - <<'PY' + import os, re + n = int(os.environ["N"]) + p = "/tmp/ratchet-copy/plugins/agent-stack/skills/agent-evals/SKILL.md" + s = open(p, encoding="utf-8").read() + s2, k = re.subn(r"^description: >-\n(?:[ \t]+.*\n)+", "description: " + "a" * n + "\n", s, count=1, flags=re.M) + assert k == 1, "PLANT DID NOT LAND: no folded description in agent-evals/SKILL.md" + open(p, "w", encoding="utf-8").write(s2) + PY + python3 test/plant_guard.py verify /tmp/ratchet-copy "description at $n chars" + if out=$(python3 /tmp/ratchet-copy/test/validate.py 2>&1); then + if [ "$n" = 941 ]; then echo "ERROR: validator accepted a 941-char description"; exit 1; fi + echo "OK: 940 chars passes" + else + if [ "$n" = 940 ]; then echo "ERROR: validator refused a 940-char description:"; echo "$out"; exit 1; fi + echo "$out" | grep -q "ratchet" || { echo "ERROR: 941 failed, but not on the ratchet:"; echo "$out"; exit 1; } + echo "OK: 941 chars fails on the ratchet" + fi + done + + - name: Negative self-test (a removed estate-skill pointer in agent-interop must fail) + run: | + set -eu + # Plan R25: agent-interop names the Fabric adapter skills and + # observatory-log:handling-secrets as the owners of the estate's HOW. + rm -rf /tmp/r25-copy && cp -R . /tmp/r25-copy + python3 test/plant_guard.py snap /tmp/r25-copy + python3 - <<'PY' + p = "/tmp/r25-copy/plugins/agent-stack/skills/agent-interop/SKILL.md" + s = open(p, encoding="utf-8").read() + name = "`fabric-agent-adapter:building-fabric-services`" + assert name in s, "PLANT DID NOT LAND: the pointer is not in agent-interop/SKILL.md" + open(p, "w", encoding="utf-8").write(s.replace(name, "the services skill")) + PY + python3 test/plant_guard.py verify /tmp/r25-copy "a removed estate-skill pointer" + if out=$(python3 /tmp/r25-copy/test/validate.py 2>&1); then + echo "ERROR: validator accepted agent-interop without its building-fabric-services pointer" + exit 1 + else + echo "$out" | grep -q "plan R25" || { echo "ERROR: failed, but not on the R25 check:"; echo "$out"; exit 1; } + echo "OK: validator refuses a missing estate-skill pointer" + fi + - name: Negative self-test (a reference nobody links must fail) run: | set -eu diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b5e0a8..bdad19b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,27 +1,58 @@ ## 0.27.0 — trust by machine, Fabric jobs, and room in every description -Integration wave 4 of the family's waves run (sshlg-skills plan R23–R26). - -- **Delegation starts with whether a token is needed (R23).** `agent-interop/references/gateway.md` - now opens its delegation section with the operator's trust tiers: - - T0, the operator's own machine, needs no token, approval or grant record. - - T1, the operator's enrolled machines, is the same. - - T2, another person's machine or an outside caller, uses the full on-behalf-of model. - - Money limits, human gates and "never spend another agent's key" hold on every tier. - - Pinned to Fabric ADR-0144 amendment 4 (`19ad05ca`, in Fabric main since `5bf15e5c`) - and to fabric-workspace `knowledge/authority.md` at `714808ac`. -- **Fabric's job extension as the worked fallback for MCP Tasks (R24).** - - `references/mcp.md` names `fabric-interop/0.1` jobs (`fabric.job.get` and `cancel`), - the five Task states, when `result`, `error` and `inputRequests` appear, and the result - envelope. - - Pinned to fabric-agent-contract `bcada4a`. - - The estate's `degraded`/`human_steps` reply convention has no contract home yet, so it - is not taught. -- **Pointers out, not copies (R25).** `agent-interop` names the Fabric adapter skills and - `observatory-log:handling-secrets` as the HOW for joining an estate and for credentials. -- **Every description has at least 30 characters of room (R26):** interop 935, harness 936, - orchestrator 938, evals 911, against the house cap of 970. The trigger lists and the - wording pinned by `fix-as-01.01` are unchanged. +Integration wave 4 of the family's waves run (sshlg-skills plan R23–R26). The first cut +(`4f88a0e`) failed three blind readings; this entry describes what ships after the fix round. + +- **Delegation starts with whether a caller needs a token (R23).** The delegation section of + `agent-interop/references/gateway.md` now opens with the trust tiers, each sentence citing + the contract rule it restates: + - T0 is the operator's own machine, T1 an enrolled machine of the same owner, and T2 + everyone else (DL-25). T1 enrolment is Fabric's roadmap item RM-21, still open, so no + machine is T1 yet. + - A tier belongs to the caller's origin, not its connection: a call relayed onto loopback + is T2 unless the relay proves the origin (DL-25). + - A callee must not require a token, an approval or a grant record from a T0 or T1 + caller, even when it serves T2 callers too (DL-26). A token such a caller presents is + verified, refused when invalid, and used for attribution and narrowing. + - Every tier keeps the money limits (L0, the project's limit, each key's ceiling) and all + five human gates: sign-in, consent, payment, top-up, a secret typed into a hidden + prompt. + - A token-less T0 call is metered by Observatory's defaults: `slotsRequireGrant: + when-token`, keyserver delegation `off` (`METER.md` M0). + - The paragraphs older than the tiers are scoped to match the amended contract: failing + closed with no issuer applies to T2 callers (DL-8), and retiring the descriptor token + applies across the machine boundary (DL-12). + - The delegation pin moves from `8e49fbd` to `3cb03ab`, which carries DL-25 and DL-26. + The rationale sentences that had no source are gone. At the new pin the meter's onward + token has an exact scope and effect `none` (not "read-only"), and a device approval + no longer exists (DEC-0045), so both sentences now say that. +- **Fabric jobs as the worked fallback for MCP Tasks, and the well-known `degraded` list + (R24).** `references/mcp.md`, pinned to fabric-agent-contract `bcada4a`: + - names `output` and `scope` in the result envelope; + - says how to answer an input request (`inputResponses`, §C3.3); + - replaces "works unchanged" with what the contract says: a caller that does not + negotiate Tasks keeps receiving the job handle, and its states are the Task states; + - teaches the service's well-known `degraded` list as `service.md` defines it: always + present, empty means full health, `setup: true` marks a setup state. + + The per-reply `degraded`/`human_steps` envelope has no contract home yet. It is named as + an estate convention and deferred to backlog row B-156. +- **Pointers out, not copies (R25).** `agent-interop` names + `fabric-agent-adapter:creating-fabric-agents`, `:adapting-projects-to-fabric` and + `:building-fabric-services`, and `observatory-log:handling-secrets`, each fully qualified. + A new named check in `test/validate.py` (`check_interop_points_to_the_estate_skills`) + fails when any of the four is missing. +- **Every description keeps 30 characters of room, and the gate holds it (R26).** + `test/validate.py` ratchets descriptions at 940 characters (house limit 970 unchanged). + Measured: evals 929, harness 936, interop 937, orchestrator 938. + - `agent-evals` names "observability" again. + - `agent-interop` adds "delegation token", "on behalf of" and «делегирование»; all 46 of + the hub's agent-stack triggers are still substrings of their descriptions. +- **Watched failing:** a 941-character description (940 passes), a removed + `building-fabric-services` pointer, and the first cut's `gateway.md` and `mcp.md` against + the new `test/audit_regressions/w4-fabric-doctrine.py` (5 of 5 cases fail). The first two + run as negative self-tests in CI. +- R28 (residue rows AG-06a/b) is not in this release and stays on the board. ## 0.26.0 — delegation: which agent acts, for which person diff --git a/README.md b/README.md index d1e8c7d..924c158 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,8 @@ without one. Six references: `mcp.md` (the wire and the full deprecation registe undoes both), `mcp-ship.md` (mounting, and the 404 that is really a double path), `registry.md` (`server.json`, namespace proof, the registry's three refusals), `a2a.md` (cards, task states, three bindings), `gateway.md` (what a gateway must do that -an API gateway does not). Plus a link map, and a verdict on each neighbouring standard — +an API gateway does not; delegation — which agent acts for which person — and trust by +machine: whether a caller needs a token at all, T0/T1/T2). Plus a link map, and a verdict on each neighbouring standard — ACP, AGNTCY, AP2, Agent Skills — so an agent stops guessing. **The harness skill** (`agent-harness/SKILL.md`) — the layer between the loop and the diff --git a/docs/evidence/backlog.md b/docs/evidence/backlog.md index c392a99..594fc4f 100644 --- a/docs/evidence/backlog.md +++ b/docs/evidence/backlog.md @@ -41,6 +41,7 @@ row per line, appended, so two runs closing two rows do not land on the same lin | B-134 | **§7 named four things to instrument and no format to carry them, and the obvious standard omits two of the four.** `grep -ri opentelemetry` over the pack returned one table cell in `agent-interop/references/mcp.md`. Meanwhile `gen_ai.evaluation.result` has no attribute for who produced a score — the field §7 requires by name — and the conventions carry no cost attribute at all while splitting usage across eleven token fields. | 2026-08-31 harness-engineering harvest, observability bundle (10 findings, one source) | 2 | 0 | 2 | **1.0** | **closed 2026-09-01, shipped in v0.23.0** — `references/otel-genai.md`: the span-name formulas and closed 17-value operation enum, the two Required attributes that make conformance a weak floor, the missing scorer field with OpenInference's `annotation.annotator_kind` as the extension to carry, the three content tiers and the upload hook that runs independently of opt-in **and** of the sampling decision, the eleven token attributes with the both-directions cost error, the disjoint OpenInference/OpenLLMetry vocabularies under one trace id, and the three senses of *replay*. Status recorded honestly: the spec moved repositories, its Schema URL reads `TODO`, and every `gen_ai.*` attribute is Development. | | B-135 | **The family audit asked for a debulk of two bodies at capacity, and the debulk was declined rather than done blind.** Wave AUDIT-WAVE-0906 flagged `agent-orchestrator/SKILL.md` and `agent-evals/SKILL.md` as near the working limit — re-measured at this commit by `audit_skill.py --house`: **4655/4750** (98.0%) and **4631/4750** (97.5%), both `0 GAP`. The family precedent (B-141 class) is that manufacturing headroom with no pending edit is negative-value work: room bought today is spent by nobody, and a trim chosen without the edit it serves cuts on the wrong line. B-129 already records the durable half — this body is at capacity and the remedy is a split, a routing decision. Debulk belongs to the release that next edits these bodies, priced into that release rather than shipped as its own. | family audit 2026-09-06, wave AUDIT-WAVE-0906 | 1 | 0 | 2 | **0.5** | **open.** Deliberately not scheduled: the trigger is the next edit to either body, and whichever release brings that edit pays the displacement (or executes B-129's split) as part of its own work. | | B-155 | **`agent-interop/references/gateway.md` names per-caller authorisation and per-hop policy (`:49`, `:108`) but no on-behalf-of model.** Nothing says how authority travels down a chain of agents. Fabric's delegation model (ADR-0144, CO-258) asks the doctrine to describe it: one issuer mints a short delegation JWT carrying an RFC 8693 `act` chain, one audience, capabilities, an effect ceiling and a grant; the callee verifies it offline against published JWKS; calls go direct, with no proxy in the path; `approval/1` and the common refusal codes apply. Doctrine only, no product change. Write it once the plan lands on Fabric main, citing that commit instead of the branch. | peer request fabric-workspace-91, 2026-10-11; plan `passioncode-ai/fabric@32046d14:docs/evidence/plans/2026-10-11-delegation-model/README.md` (branch agent/release-040-wave1) | 3 | 0 | 2 | **1.5** | **closed 2026-10-11** — 0.26.0, `gateway.md` § Delegation; pinned to Fabric main `812876cf` and the contract's `8e49fbd` | +| B-156 | **The per-reply `degraded` / `human_steps` envelope is taught nowhere, because it has no contract home.** Several of the estate's MCP tools (Copylot, Observatory, Research Agent) answer every call with `degraded` and `human_steps` beside the result. The contract defines `degraded` only on the service's well-known document (fabric-agent-contract `docs/specification/service.md` *Well-known document* at `bcada4a`), and `agent-interop/references/mcp.md` teaches that one. The per-reply form is an estate convention; teaching it as a contract would put a second, unsourced home beside the contract's. Teach it once the contract (or Fabric) gives it a rule to cite, and pin that commit. | sshlg-skills plan R24 (`docs/evidence/briefs/2026-10-11-integration-waves.md:117`, branch `waves/2026-10-11-integration`); wave 4 fix round, 2026-10-12 | 3 | 0 | 1 | **3** | **open** — deferred: waits on a contract rule for the per-reply envelope | ## Open, and why diff --git a/docs/evidence/verification.md b/docs/evidence/verification.md index 1d41735..0d81153 100644 --- a/docs/evidence/verification.md +++ b/docs/evidence/verification.md @@ -12,13 +12,16 @@ This file exists because its absence read as zero exposure. `sshlg-skills` board ## Release candidate — v0.27.0 (2026-10-12) +The first cut (`4f88a0e`) failed three blind readings (unit, seam, product). These rows +describe the fix round. + | REQ | What ships | How it was confirmed | Confirmed | |---|---|---|---| -| R23 | trust tiers T0/T1/T2 in `gateway.md`, pinned | text read against fabric-workspace `714808ac` `knowledge/authority.md` "Trust by machine" | never | -| R24 | `fabric-interop/0.1` jobs as the Tasks fallback, pinned `bcada4a` | text read against fabric-agent-contract `interop.md` §C3.2 | never | -| R25 | pointers to the Fabric adapter skills and `observatory-log:handling-secrets` | `audit_skill.py --house agent-interop`: 0 GAP, body 3502/4750 | never | -| R26 | descriptions 935/936/938/911 of 970 | `audit_skill.py --house` per skill | never | -| Gate | `npm test` | EXIT=0 on 2026-10-12 | never | +| R23 | `gateway.md` trust tiers rewritten to the contract: per-caller DL-26 (no token required of T0/T1, a presented token verified, refused when invalid, used for attribution and narrowing), tier by origin DL-25, T1 = RM-21 still open, five human gates, three money levels, token-less T0 metering (`METER.md` M0/M5/M6), DL-8 and DL-12 scoped, pin `3cb03ab` | each sentence read against fabric-agent-contract `delegation.md` at `3cb03ab` (`git show 3cb03ab:docs/specification/delegation.md \| grep -c DL-26` → 3), Fabric `19ad05ca` amendment 4, fabric-workspace `714808ac` `authority.md`, project-observatory-dashboard `METER.md` at `5b24038` and fabric-agent-adapter 0.20.0 `building-fabric-services/references/delegation.md:47-55`; `test/audit_regressions/w4-fabric-doctrine.py` passes 5/5 | **planted** — the first cut's `gateway.md`/`mcp.md` fail 5/5 cases | +| R24 | `mcp.md`: envelope with `output`/`scope`, `inputResponses` (§C3.3), the job handle for a caller without Tasks, the well-known `degraded` list (`service.md:93-101` at `bcada4a`); the per-reply envelope deferred as B-156 | text read against `interop.md` §C3.2–C3.3 and `service.md` at `bcada4a`; `w4-fabric-doctrine.py` case 5 | **planted** — the first cut fails case 5 | +| R25 | `agent-interop/SKILL.md` names the three `fabric-agent-adapter` skills and `observatory-log:handling-secrets`, fully qualified | `check_interop_points_to_the_estate_skills` in `test/validate.py`; CI step "a removed estate-skill pointer in agent-interop must fail" | **planted** — removing `fabric-agent-adapter:building-fabric-services` on a copy fails with `(plan R25)` | +| R26 | descriptions evals 929, harness 936, interop 937, orchestrator 938; ratchet 940 in `test/validate.py` | `audit_skill.py --house` per skill: 0 GAP, 19 PASS each; CI step "a description at 941 chars must fail; 940 passes" | **planted** — 941 fails on the ratchet, 940 passes | +| Gate | `npm test` | EXIT=0 on 2026-10-12 (`16 checks = 12 named + 4 per-skill`) | never | ## Released — v0.26.0 (2026-10-11) diff --git a/plugins/agent-stack/skills/agent-evals/SKILL.md b/plugins/agent-stack/skills/agent-evals/SKILL.md index 4268377..49f0f05 100644 --- a/plugins/agent-stack/skills/agent-evals/SKILL.md +++ b/plugins/agent-stack/skills/agent-evals/SKILL.md @@ -4,8 +4,8 @@ description: >- Use when measuring whether an agent actually works — building an eval suite, judging a trajectory rather than a final answer, turning production traces into regression fixtures, calibrating an LLM judge against human labels, or gating a release on offline - evals. Covers run, trace and thread crossed with single-step, full-turn and multi-turn - evals, the offline/online/ad-hoc + evals. Covers observability — run, trace and thread — crossed with single-step, full-turn + and multi-turn evals, the offline/online/ad-hoc timing axis, pass-fail rubrics over scalar scores, cheap code checks before model judges, the checker node as an evaluator inside the graph, simulated users with adversarial personas, annotation queues, and what to instrument for any of it. Triggers - "agent eval", "eval suite", "LLM judge", "regression fixture", diff --git a/plugins/agent-stack/skills/agent-interop/SKILL.md b/plugins/agent-stack/skills/agent-interop/SKILL.md index ac88465..88da616 100644 --- a/plugins/agent-stack/skills/agent-interop/SKILL.md +++ b/plugins/agent-stack/skills/agent-interop/SKILL.md @@ -1,17 +1,17 @@ --- name: agent-interop description: >- - Use when an agent must talk to something outside its own process — building or consuming an + Use when an agent talks to anything outside its process — building or consuming an MCP server, exposing or calling an agent over A2A, publishing to the MCP Registry, or - putting a gateway in front of agent traffic. Carries the MCP 2026-07-28 wire surface and what it + fronting agent traffic with a gateway. Carries the MCP 2026-07-28 wire surface and what it deprecated (server/discover, stateless _meta, form/URL elicitation, subscriptions/listen; sampling, roots, logging and dynamic client registration going), - A2A 1.0 agent cards, task states, three bindings, registry namespaces, server.json, tool - federation, and what a gateway must do that an API gateway does not. Triggers - "MCP server", + A2A 1.0 agent cards, task states, three bindings, registry namespaces, what a gateway adds + over an API gateway, and calls on behalf of a person. Triggers - "MCP server", "MCP client", "A2A", "agent card", "agent interoperability", "MCP registry", "server.json", "agentgateway", "tool federation", "MCP-сервер", "карточка агента", "интероперабельность - агентов", "реестр MCP", "шлюз для агентов". Not for designing one server's tool set, nor for - a skill's own construction — that is make-skill. + агентов", "реестр MCP", "шлюз для агентов", "delegation token", "on behalf of", + "делегирование". Not for one server's tool design, nor a skill's construction (make-skill). license: MIT --- @@ -109,7 +109,7 @@ an index; the trigger lives in the file, so the two cannot drift apart. | [`references/mcp-scale.md`](references/mcp-scale.md) | the host has **more servers and tools than context** — progressive discovery, programmatic tool calling, cache interaction, and the security surface each one opens | | [`references/a2a.md`](references/a2a.md) | the other side is **another agent, not a tool** — agent cards and discovery, the task lifecycle and its terminal states, three protocol bindings, streaming versus push | | [`references/registry.md`](references/registry.md) | you are **publishing a server** or building something that consumes the registry — `server.json`, reverse-DNS namespaces and how ownership is proved, the publish flow, and the registry's three refusals | -| [`references/gateway.md`](references/gateway.md) | agent traffic needs **one controlled seam** — what a gateway must do that an API gateway does not, federation and name collisions, where authorization actually belongs, and **delegation** — a token that says which agent acts for which person | +| [`references/gateway.md`](references/gateway.md) | agent traffic needs **one controlled seam** — what a gateway must do that an API gateway does not, federation and name collisions, where authorization actually belongs, and **delegation** — a token that says which agent acts for which person, and whether a call needs one at all (trust by machine: T0/T1/T2) | --- @@ -124,7 +124,8 @@ there. The protocol is described in exactly one of the two places, and this is i **Against the Fabric adapter and Observatory skills.** Where an agent or service must join a Fabric estate, the HOW is theirs: `fabric-agent-adapter:creating-fabric-agents`, -`:adapting-projects-to-fabric` and `:building-fabric-services` (the descriptor, the hub, +`fabric-agent-adapter:adapting-projects-to-fabric` and +`fabric-agent-adapter:building-fabric-services` (the descriptor, the hub, `fabric-service/0.1`); credentials an agent uses go through `observatory-log:handling-secrets` (named vault slots, never a value). This skill keeps the protocol they speak — MCP, A2A, the gateway, delegation — and points to them rather than diff --git a/plugins/agent-stack/skills/agent-interop/references/gateway.md b/plugins/agent-stack/skills/agent-interop/references/gateway.md index f0db014..e2bc5d5 100644 --- a/plugins/agent-stack/skills/agent-interop/references/gateway.md +++ b/plugins/agent-stack/skills/agent-interop/references/gateway.md @@ -2,13 +2,15 @@ **Load this when:** many clients meet many servers, tool access needs to differ per caller, somebody has asked who is allowed to call what and you cannot answer from one place, or an -agent calls another **on someone's behalf** and the callee has to know for whom. +agent calls another **on someone's behalf** and the callee has to know for whom — or whether +that caller needs a token at all. **Spec pinned:** agentgateway `1.4.x` from `agentgateway.dev/docs/standalone/latest` · read 2026-08-13 **Delegation pinned:** RFC 8693 §4.1–4.2, RFC 8725 §3.11, RFC 8785; worked example Fabric -`fabric-delegation/0.1` (`passioncode-ai/fabric-agent-contract@8e49fbd`, ADR-0144 at -`passioncode-ai/fabric@812876cf`) · read 2026-10-11 +`fabric-delegation/0.1` (`passioncode-ai/fabric-agent-contract@3cb03ab`, whose trust tiers +are DL-25 and DL-26; ADR-0144 at `passioncode-ai/fabric@812876cf`, amendment 4 at +`19ad05ca`) · read 2026-10-12 Linux Foundation, Apache-2.0, written in Rust. @@ -126,26 +128,56 @@ callers apart, so no limit holds per caller) or **the callee's own host credenti by the caller** (a confused deputy with that service's full power). Neither says on whose behalf the work runs. -**First, whether a token is needed at all — trust by machine.** Proof is owed at a -boundary, and the operator's own machine is not one. Requiring a token, an approval and a -grant record for every local call buys ceremony, and nobody can attribute a cost to it. - -| Tier | Where the call comes from | What it needs | -|---|---|---| -| T0 | the operator's own machine | the operator's instruction is the authority: no delegation token, no approval, no grant record | -| T1 | another machine of the operator's, once it is enrolled in the operator's agent network | the same as T0 | -| T2 | another person's machine, or any outside caller | everything below: a share grant with limits, an expiry and a payer, a token per call, an approval per effect | - -Two rules hold on **every** tier: -- **Money limits and human gates.** Every spend limit (project, estate, each key's - ceiling) applies, and so does every gate a person holds: sign-in, consent, payment. -- **No borrowed keys.** An agent never spends another agent's key. It calls that agent's - capability, so the owner's wallet and limits apply. - -On a T0/T1 path the token below is optional and used for attribution only. A service -that takes calls across the machine boundary, or from outside people, requires it. -Enrolment is what moves a machine from T2 to T1. A machine is never trusted because it -is on the same network. +**First, whether a token is needed at all — trust by machine.** Proof starts at the machine +boundary: on the operator's own machine, the operator's instruction is the authority (Fabric +ADR-0144 amendment 4, the operator's decision of 2026-10-11). In this block a code in +parentheses is a rule of the worked example's `delegation.md` at the pin above, and a sentence +that is this file's own rule says so. + +| Tier | Who the caller is | What places it there | What a call needs | +|---|---|---|---| +| T0 | on the operator's own machine: on loopback, under the owner's OS account | the service's local floor — loopback only, the `Host` and `Origin` guard, the descriptor token (DL-25; `service.md` §Authentication and network) | the operator's instruction: no delegation token, no `approval/1`, no grant record (DL-26) | +| T1 | another machine of the same owner, enrolled in the agent network | its enrolment, which is Fabric's roadmap item RM-21 and still open — so **no machine is T1 yet** (DL-25; fabric-workspace `knowledge/roadmap.md` RM-21) | the same as T0 (DL-26) | +| T2 | everyone else: another person's machine under a share grant, and any caller the callee cannot place in T0 or T1 | nothing places it; it is the default (DL-25) | the whole model below: a token per call, an approval for each effect that needs one, a grant with limits, an expiry and a payer (DL-26) | + +**A tier is the caller's origin's, not its connection's** (DL-25). A call relayed onto +loopback from off the machine — through a tunnel, a reverse proxy, or the hub forwarding a T2 +caller's call — is T2 unless the relay proves the origin's tier, and the hub attaches a T2 +origin's token to whatever it forwards. Loopback does not prove who connected: any process of +the operator's account can reach it and read its token files (`service.md`, "Loopback +reachability is not authorization"). T0 is trusted because the operator gave the +instruction, not because of the connection (§The boundary). + +**Tiers belong to callers, not to services.** A callee places each caller in one tier +(DL-25), and it MUST NOT require a delegation token, an `approval/1` or a grant record from a +T0 or T1 caller; no local call waits on the issuer (DL-26). A service that also serves T2 +callers still must not require a token from its T0 and T1 callers (this file's reading of +DL-25 with DL-26), and a service reached only from inside the boundary does not declare +token-only access at all (DL-16). A token that a T0 or T1 caller does present is verified as +any other (the check below, DL-13). When the check fails the call is refused: a present but +invalid token is never ignored (DL-26, DL-10). An accepted one is used for attribution and +for narrowing what the call may do (DL-26). + +Three rules hold on **every** tier (DL-26): +- **Money limits.** L0 — the estate's total across provider keys — the project's limit (L1) + and each provider key's own ceiling all bind (DL-26; fabric-workspace + `knowledge/authority.md` *Levels*). +- **Human gates.** Five stay with a person: a sign-in, a consent, a payment, a top-up, and a + secret typed into a hidden prompt (DL-26; ADR-0144 amendment 4). +- **No borrowed keys.** A credential source is never another agent's key or vault slot + (DL-26). Call that agent's capability instead, so its wallet and limits apply + (`authority.md` *The three rules*). + +**How a token-less T0 call is metered.** It carries no token to exchange, so it never reaches +the meter's grant check below — that flow is the T2 path (DL-21; `METER.md` M1). Observatory, the estate's meter, +ships the T0 settings as its defaults: `slotsRequireGrant: when-token` releases a vault slot +with no token and checks one that is present, and the keyserver's +`keyserverRequiresDelegation` is `off`. The strict modes (`always`, `agents`) are for a +machine that serves T2 callers (project-observatory-dashboard `docs/design/METER.md` M0, M5, +M6 at `5b24038`; `authority.md` *Trust by machine*). The spend still counts against L0: +with a provider scan of this month, the meter's estate total is what the providers charged +(the OpenRouter accounts it reads today), not only what passed its grant checks (`METER.md` +M3, *What L0 counts*). **The token.** One short signed document per call path, in the shape of OAuth 2.0 Token Exchange (RFC 8693): @@ -153,7 +185,7 @@ Exchange (RFC 8693): | Claim | Carries | The rule that makes it work | |---|---|---| | `sub` | the **person** the work is for | always a person — even when the grant was given to an agent, `sub` is whoever gave it; the agents are the next claim | -| `act` | the **actor chain** (RFC 8693 §4.1) | the agent calling now outermost, earlier actors nested inside; cap the depth (Fabric: 4) | +| `act` | the **actor chain** (RFC 8693 §4.1) | the agent calling now outermost, earlier actors nested inside; cap the depth (Fabric: 4, and 5 in a token for the meter) | | `aud` | **one** callee | a string, never an array: a token good at two callees is a token one of them can replay at the other | | `scope` | the callee's capability names (§4.2) | whole names, space-delimited | | effect ceiling | the most a call may *do* — read, draft, send, charge, delete, deploy… | external effects are not ordered against each other: a `delete` ceiling admits no `charge` | @@ -175,8 +207,10 @@ chain it cannot see. small clock leeway, audience is itself, chain depth, the called capability is in `scope`, its declared effect is under the ceiling. Keys come only from the published set, never from the token's header (`jku`, `jwk`, `x5u`). Then it acts for `sub`, names the outermost actor as its -caller and records the chain with the work. The issuer must be up to mint; with no issuer a -call fails closed — never back to a borrowed credential. +caller and records the chain with the work. For a T2 caller the issuer must be up to mint: +with no issuer that call is refused `issuer-unavailable` and does nothing — never back to a +borrowed credential (DL-8). Inside the boundary no call waits on the issuer; a T0 or T1 +caller with no hub calls without a token (DL-8, DL-26). **The issuer is not the route.** Calls carrying a token go **direct**, caller to callee, as `Authorization: Bearer`. Where something in the middle forwards a call because it holds the @@ -187,15 +221,19 @@ but the identity worth recording on its audit trail is the token's chain, not "t called". **The host credential is the host's.** A service's own descriptor token is for its host — -discovery, events, usage, login — and, once callers can mint, is not presented to call a -capability; existing callers that borrow it switch when the issuer exists. A -token never travels in a URL, an argument vector, a tool's arguments, a log line or a usage -report. +discovery, events, usage, login. **Across the machine boundary**, once callers can mint, it +is not presented to call a capability; existing callers that borrow it switch when the +issuer exists (DL-12). On the operator's own machine this is attribution hygiene, not a +requirement: a caller should not call a capability with another service's descriptor token, +because the callee then cannot tell that caller from its host (DL-12). A token never travels +in a URL, an argument vector, a tool's arguments, a log line or a usage report (DL-11). **Money is a separate check against a meter.** The token's remaining-spend figure informs and never authorizes. Before spending, the callee asks the meter with a token **of its own**: -exchanged onward with `aud` = the meter and read-only scope, because the token it holds names -itself as audience and cannot be handed over. The meter reserves against the grant's windows +exchanged onward with `aud` = the meter, `scope` exactly the meter's grant-check capability +and effect `none`, because the token it holds names itself as audience and cannot be handed +over (DL-21). This is the T2 path; a token-less T0 call is metered as the tier block above +says. The meter reserves against the grant's windows and every level above (Fabric: the estate total), and refuses `limit-reached` **naming the window** that ran out — a limit refusal without its window cannot be acted on. @@ -216,17 +254,25 @@ a denial. **What it does not buy.** A token file readable by the operator's account is readable by any process of that account. Delegation gives every hop an attributable name, narrowing and -per-caller limits; it does not protect against a hostile local process. That takes an -approval or a device check — and proof of possession (DPoP, mutual TLS) is a further step this -shape leaves open. +per-caller limits; it does not protect against a hostile local process. A product that +needs that protection adds an approval (DL-17; §The boundary). A device approval was removed +by DEC-0045 until a decision defines it, and proof of possession (DPoP, mutual TLS) is a +further step this shape leaves open (§Not decided here). **Worked example.** Fabric's `fabric-delegation/0.1` (contract DEC-0039, decided in Fabric ADR-0144) implements the token, the verifier, the meter flow, the approval and the refusal -codes above — with schemas, positive/negative/semantic fixtures and a reference verifier: `docs/specification/delegation.md` in `passioncode-ai/fabric-agent-contract` -at `8e49fbd`. Read its check table (DL-13) before writing a verifier in another language. -The trust tiers are Fabric ADR-0144 amendment 4 (`19ad05ca`), in Fabric's main since -`5bf15e5c`, and the operator's decision is recorded in `passioncode-ai/fabric-workspace` -`knowledge/authority.md`, *Trust by machine*, at `714808ac`. +codes above — with schemas, positive/negative/semantic fixtures and a reference verifier: +`docs/specification/delegation.md` in `passioncode-ai/fabric-agent-contract` at `3cb03ab`. +Read its check table (DL-13) before writing a verifier in another language. The tier rule is +its §Trust tiers — DL-25 (three tiers, placed by origin) and DL-26 (no token, approval or +grant record required inside the boundary) — which entered the contract in `063a725`; the +origin rule followed in `3cb03ab`. It implements Fabric ADR-0144 amendment 4 (`19ad05ca`, in +Fabric's main since `5bf15e5c`); the operator's decision is recorded in +`passioncode-ai/fabric-workspace` `knowledge/authority.md`, *Trust by machine*, at `714808ac`. +A second implementation, `fabric-agent-adapter` 0.20.0 +(`building-fabric-services/references/delegation.md`, *Who must present a token*), places +tiers the same way: its verifier takes the caller's tier and passes a T0 call that carries +no token. ## agentgateway, concretely @@ -293,7 +339,15 @@ Two consequences for whoever runs the gateway: - **Federating servers you do not trust into one namespace.** Federation makes a hostile server's tools look exactly as legitimate as everyone else's. - **Calling a service with its own descriptor token.** It works, it is unattributable, and it - hands the caller everything that token can do. Mint a delegation token for that one call. + hands the caller everything that token can do. Across the machine boundary, mint a + delegation token for that one call (DL-12); on the operator's own machine, call without + one rather than borrow the host's (DL-12, DL-26). +- **Placing a caller by its connection.** A tunnel, a reverse proxy or the hub forwarding puts + an off-machine caller on loopback. Its tier is its origin's, so it is T2 unless the relay + proves otherwise (DL-25). +- **Requiring a token of every caller because some callers are T2.** Tiers are per caller: a + T0 or T1 caller is never required to present one, and a token it does present is still + checked and refused when invalid (DL-26). - **`aud` as an array, or a token without `typ`.** Both let one signed document be replayed where it was never meant to be accepted. - **Checking spend from the token's claim.** It was the remainder at mint time; the meter diff --git a/plugins/agent-stack/skills/agent-interop/references/mcp.md b/plugins/agent-stack/skills/agent-interop/references/mcp.md index fc364b0..492c96b 100644 --- a/plugins/agent-stack/skills/agent-interop/references/mcp.md +++ b/plugins/agent-stack/skills/agent-interop/references/mcp.md @@ -225,7 +225,7 @@ Opt-in, negotiated, and worth checking before inventing an equivalent: actually supports it before building on it; where it is unsupported, the fallback is a chunked job or a caller-polled job id over plain `tools/call`, not a protocol change. **Worked example of that fallback:** Fabric's `fabric-interop/0.1` jobs - (`passioncode-ai/fabric-agent-contract` `docs/specification/interop.md` §C3.2, at + (`passioncode-ai/fabric-agent-contract` `docs/specification/interop.md` §C3.2–C3.3, at `bcada4a`). - A capability that may outlive one request declares `"job": true`. - With Tasks negotiated, it MAY return an MCP Task. Otherwise it returns @@ -235,11 +235,29 @@ Opt-in, negotiated, and worth checking before inventing an equivalent: cancelled`. - `result` is present exactly when the job is `completed`, `error` when it `failed`, and `inputRequests` when it waits for input. - - The result envelope separates `done`, `proof` and `notVerified`, and carries `usage`. + - `result` is the full result envelope, exactly what a synchronous call of the same + capability returns: `done`, `proof`, `scope` and `notVerified` beside `output` (the + value of the tool's `outputSchema`) and `usage`. - A job id survives a restart, and an unknown id answers `unknown-job`, never a fresh job. - - A caller written against this works unchanged when the server later negotiates Tasks. + - **Answering an input request** (§C3.3): the caller sends `fabric.job.get` with + `inputResponses`, a map from each request key to `{action: accept | decline | cancel, + content?}`; under a Task the answer goes through `tasks/update` instead. A form never + asks for a secret; a secret goes through URL mode. + + A caller that does not negotiate Tasks keeps receiving the job handle, and the states it + polls are the Task states. + + **Health is a separate document.** The same estate's services publish a well-known + document (`GET /.well-known/fabric-service`; `docs/specification/service.md`, *Well-known + document*, at `bcada4a`) whose `degraded` list is always present: an empty list asserts + full health, and each entry names a `source` and a `reason`. An entry with `setup: true` + is a setup state — the source waits for a person to finish setting it up, a key not + entered or a model not chosen — not an outage: a watcher shows it as waiting for a person + and opens no outage alert. An entry without `setup` is a failure. `status: ready` with a + non-empty list is inconsistent (`FAC-SEM-011`). Some of the estate's tools also carry + `degraded` and `human_steps` on every reply; that per-reply envelope is an estate + convention with no contract home yet, so it is not taught here as a contract. - **MCP Apps** — interactive UI rendered inline in the conversation. - **Skills over MCP** — structured instruction sets discovered and consumed through MCP, which is how a server ships Agent Skills rather than only tools. diff --git a/test/audit_regressions/w4-fabric-doctrine.py b/test/audit_regressions/w4-fabric-doctrine.py new file mode 100644 index 0000000..94314c6 --- /dev/null +++ b/test/audit_regressions/w4-fabric-doctrine.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +"""W4 — the Fabric doctrine in agent-interop says what its sources say +(sshlg-skills plan R23 and R24, wave 4 fix round, 2026-10-12). + +The finding: three blind readings of 0.27.0's first cut found the trust-tier block +contradicting the rule it cited. It made a token mandatory for any service taking +calls across the machine boundary (DL-26 forbids requiring one from a T0 or T1 caller, +per caller, not per service), said a present token was "for attribution only" (DL-26: +verified, refused when invalid, used for attribution AND narrowing), placed callers by +network rather than by origin (DL-25), listed three of five human gates, and kept two +pre-tier sentences (fail closed with no issuer; the descriptor token retired) that the +amended DL-8 and DL-12 scope to the machine boundary. mcp.md claimed a caller "works +unchanged" when Tasks is negotiated, omitted `output` and `scope` from the envelope, +never said how an input request is answered, and did not teach the well-known +`degraded` list the contract defines. + +The fix under test: the sourced sentences are present, the unsourced ones are gone, +and the pins name the commit that carries DL-25 and DL-26 (`3cb03ab`). + +Standard library only. +""" +import os +import sys + +sys.dont_write_bytecode = True + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.dirname(os.path.dirname(HERE)) +REFS = os.path.join(ROOT, "plugins", "agent-stack", "skills", "agent-interop", "references") +GATEWAY = os.path.join(REFS, "gateway.md") +MCP = os.path.join(REFS, "mcp.md") + +failures = [] + + +def case(name, fn): + try: + fn() + print(f" ok {name}") + except AssertionError as e: + failures.append(f"{name}: {e}") + print(f"FAIL {name}: {e}") + + +def flat(path): + with open(path, encoding="utf-8") as fh: + return " ".join(fh.read().split()) + + +def t_token_rule_is_dl26(): + g = flat(GATEWAY) + assert "MUST NOT require a delegation token, an `approval/1` or a grant record from a T0 or T1 caller" in g, \ + "DL-26's MUST NOT is missing" + assert "still must not require a token from its T0 and T1 callers" in g, \ + "tiers per caller, not per service, is missing" + assert "a present but invalid token is never ignored" in g + assert "used for attribution and for narrowing" in g + for stale in ("optional and used for attribution only", + "takes calls across the machine boundary, or from outside people, requires it", + "nobody can attribute a cost"): + assert stale not in g, f"unsourced sentence survived: {stale!r}" + + +def t_tier_by_origin(): + g = flat(GATEWAY) + assert "A tier is the caller's origin's, not its connection's" in g + assert "is T2 unless the relay proves the origin's tier" in g + assert "T0 is trusted because the operator gave the instruction, not because of the connection" in g + assert "RM-21" in g and "no machine is T1 yet" in g, "T1 enrolment is not named as RM-21, still open" + for stale in ("same network", "Enrolment is what moves a machine from T2 to T1"): + assert stale not in g, f"unsourced sentence survived: {stale!r}" + + +def t_pre_tier_text_scoped(): + g = flat(GATEWAY) + assert "with no issuer a call fails closed" not in g, "fail-closed is not scoped to T2 (DL-8)" + assert "For a T2 caller the issuer must be up to mint" in g + assert "**Across the machine boundary**, once callers can mint" in g, "DL-12 is not scoped" + assert "8e49fbd" not in g, "the delegation pin predates DL-25/DL-26" + assert g.count("3cb03ab") >= 2, "header and worked example must both pin 3cb03ab" + assert "read 2026-10-12" in g + + +def t_gates_and_money(): + g = flat(GATEWAY) + assert "a sign-in, a consent, a payment, a top-up, and a secret typed into a hidden prompt" in g, \ + "the five human gates are not all listed" + assert "L0 — the estate's total across provider keys — the project's limit (L1) and each provider key's own ceiling" in g + assert "`slotsRequireGrant: when-token`" in g and "`keyserverRequiresDelegation` is `off`" in g, \ + "token-less T0 metering is not named" + assert "This is the T2 path" in g + + +def t_mcp_jobs_and_degraded(): + m = flat(MCP) + assert "works unchanged when the server later negotiates Tasks" not in m, \ + "the unsourced forward-compatibility claim survived" + assert "A caller that does not negotiate Tasks keeps receiving the job handle" in m + assert "`done`, `proof`, `scope` and `notVerified` beside `output`" in m + assert "`inputResponses`" in m and "`tasks/update`" in m + assert "whose `degraded` list is always present: an empty list asserts full health" in m + assert "`setup: true`" in m + assert "estate convention with no contract home yet" in m + + +def main(): + case("the token rule is DL-26: per caller, verified, refused, narrowing", t_token_rule_is_dl26) + case("a tier is placed by origin; T1 waits on RM-21", t_tier_by_origin) + case("fail-closed and the descriptor token are scoped; pins carry DL-25/DL-26", + t_pre_tier_text_scoped) + case("five human gates, three money levels, token-less metering", t_gates_and_money) + case("mcp.md: job handle, envelope, input responses, well-known degraded", + t_mcp_jobs_and_degraded) + if failures: + print(f"\n{len(failures)} failure(s)") + return 1 + print("\nall green") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/test/validate.py b/test/validate.py index c3fe51a..f1d48d9 100644 --- a/test/validate.py +++ b/test/validate.py @@ -48,6 +48,12 @@ # 5% under the 1024 cap. Not this repo's number: `DESC_TARGET` in make-skill's # `scripts/audit_skill.py`, the family's authority on skill budgets. DESC_WORKING_LIMIT = 970 +# This member's ratchet, 30 characters under the house limit (sshlg-skills plan R26, +# 2026-10-12). Every description was brought to at least 30 characters of room so the +# next "not for X" clause or trigger fits without a rewrite; without a gate the room is +# spent one phrase at a time and nobody sees it go. The house limit above stays the +# family's number, and its message stays the one past 970. +DESC_RATCHET = 940 FAILURES = [] # Skills whose references describe an external specification somebody else @@ -279,6 +285,13 @@ def scalar(block, key): "of cap cannot absorb the clause that says what this skill is NOT for, and that " "clause is what stops a sibling stealing its triggers" ) + elif len(fm_desc) > DESC_RATCHET: + fail( + f"{name}/SKILL.md: description is {len(fm_desc)} chars, past this member's " + f"{DESC_RATCHET}-char ratchet (house limit {DESC_WORKING_LIMIT}, hard cap 1024). " + "The room under the house limit is kept on purpose (plan R26): make room by " + "rewording, never by raising the ratchet" + ) if fm_desc and re.search(r"<[a-zA-Z/]", fm_desc): fail(f"{name}/SKILL.md: description must not contain angle-bracket tags") @@ -1179,8 +1192,29 @@ def check_shipped_front_matter_survives_a_real_reader(): _disclose_routing(f"front matter vs a strict reader — {(proc.stderr or 'the checker could not look').strip()}") +def check_interop_points_to_the_estate_skills(): + """agent-interop names the skills that own the HOW of joining a Fabric estate. + + Plan R25 (wave 4, 2026-10-12): the protocol lives here, the estate's procedures live in + `fabric-agent-adapter` and `observatory-log`. A pointer that silently disappears sends + a reader back to restating them here, which is the second home R25 exists to prevent. + Each name is checked fully qualified, so a shortened `:skill` form cannot pass for it. + """ + path = os.path.join(ROOT, "plugins", "agent-stack", "skills", "agent-interop", "SKILL.md") + with open(path, encoding="utf-8") as fh: + text = fh.read() + for qualified in ("fabric-agent-adapter:creating-fabric-agents", + "fabric-agent-adapter:adapting-projects-to-fabric", + "fabric-agent-adapter:building-fabric-services", + "observatory-log:handling-secrets"): + if f"`{qualified}`" not in text: + fail(f"agent-interop/SKILL.md: no pointer to `{qualified}` (plan R25) -- the estate's " + "HOW belongs to that skill, and this skill must name it rather than restate it") + + check_ledger_tables_keep_their_shape() check_shipped_front_matter_survives_a_real_reader() +check_interop_points_to_the_estate_skills() # Notes print on EVERY run, pass or fail. A budget warning that only appeared beside a From 86b7656d00e6f585ccbeb1752fd44621e4b9529b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D0=B1=D0=BE=D1=82?= Date: Mon, 12 Oct 2026 12:50:15 +0200 Subject: [PATCH 3/5] ci: the working-limit plant sets an exact length (971) instead of padding by 53 The fixed 53-character pad reached 970 only while agent-evals sat above 917; at 911 (0.27.0's first cut) it planted nothing, the validator rightly passed, and validate run 38204211436 reported the guard broken. The plant now replaces the description with exactly 971 characters and requires the working-limit message, so it fails for the right reason or says so. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/validate.yml | 18 +++++++++++------- CHANGELOG.md | 4 ++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index c7a04e4..1c23f99 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -703,23 +703,27 @@ jobs: # Under the 1024 cap and over the 970 working limit — the state three of four # skills shipped in, one with five characters of headroom. The hard-cap check # passes on this plant, so it is the working limit or nothing. + # + # The plant sets an EXACT length, 971. It used to prepend a fixed 53-character + # line, which reached 970 only while agent-evals sat above 917: at 911 (0.27.0's + # first cut) the padded description was 964, the validator rightly passed it, and + # this step reported a working guard as broken. A plant measured from the + # current text has a shelf life; one that sets the length does not. python3 - <<'PY' import pathlib, re p = pathlib.Path("/tmp/desc-copy/plugins/agent-stack/skills/agent-evals/SKILL.md") s = p.read_text(encoding="utf-8") - # Anchored on the front-matter key, not on any sentence inside the description: - # the words get reflowed and a prose anchor stops planting silently. - m = re.search(r"(?m)^description: >-\n", s) - assert m, "PLANT DID NOT LAND: no folded description block to grow" - out = s[:m.end()] + " padding to reach the working limit and no further.\n" + s[m.end():] - assert out != s, "PLANT DID NOT LAND" + # Anchored on the front-matter key, not on any sentence inside the description. + out, k = re.subn(r"(?m)^description: >-\n(?:[ \t]+.*\n)+", "description: " + "a" * 971 + "\n", s, count=1) + assert k == 1, "PLANT DID NOT LAND: no folded description block to replace" p.write_text(out, encoding="utf-8") PY python3 test/plant_guard.py verify /tmp/desc-copy "Negative self-test (a description must stay inside the working limit)" - if python3 /tmp/desc-copy/test/validate.py; then + if out=$(python3 /tmp/desc-copy/test/validate.py 2>&1); then echo "ERROR: validator accepted a description past the house working limit" exit 1 else + echo "$out" | grep -q "970-char working limit" || { echo "ERROR: 971 failed, but not on the working limit:"; echo "$out"; exit 1; } echo "OK: a description cannot drift past the working limit" fi diff --git a/CHANGELOG.md b/CHANGELOG.md index bdad19b..309e1b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,10 @@ Integration wave 4 of the family's waves run (sshlg-skills plan R23–R26). The `building-fabric-services` pointer, and the first cut's `gateway.md` and `mcp.md` against the new `test/audit_regressions/w4-fabric-doctrine.py` (5 of 5 cases fail). The first two run as negative self-tests in CI. +- **A CI plant that had stopped planting.** "A description must stay inside the working + limit" prepended a fixed 53 characters, which reached 970 only while agent-evals sat above + 917. At 911, in the first cut, it planted nothing and failed run 38204211436. It now sets + an exact length of 971 and requires the working-limit message. - R28 (residue rows AG-06a/b) is not in this release and stays on the board. ## 0.26.0 — delegation: which agent acts, for which person From 33f98047d27cd190a6663d814bc36da796785d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D0=B1=D0=BE=D1=82?= Date: Mon, 12 Oct 2026 13:11:03 +0200 Subject: [PATCH 4/5] agent-stack 0.27.0 final polish: approval scoped to T2, descriptor token is the host's, R28 named MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second blind reading: unit pass, seam pass, product pass with risks. Closes the cheap risks without another round: approval/1 paragraph scoped to T2 (DL-26); T0 placement no longer lists the descriptor token (DL-12); the undecided T1 scoping proposal is named; mcp.md names degraded, human_steps and sources as the estate's per-reply convention (B-156 stays open); agent-interop triggers on "токен делегирования" (939 chars, ratchet 940); AG-06a/b cite plan R28; B-157 for the one-way adapter back-links. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 10 ++++++++++ docs/evidence/backlog.md | 5 +++-- plugins/agent-stack/skills/agent-interop/SKILL.md | 2 +- .../skills/agent-interop/references/gateway.md | 8 +++++--- .../agent-stack/skills/agent-interop/references/mcp.md | 2 +- 5 files changed, 20 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 309e1b0..a884c36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,16 @@ Integration wave 4 of the family's waves run (sshlg-skills plan R23–R26). The first cut (`4f88a0e`) failed three blind readings; this entry describes what ships after the fix round. +**Final polish after the second reading** (unit and seam pass, product pass with risks): +the approval-document paragraph is scoped to T2 (a T0/T1 caller presents no `approval/1`); +the T0 row no longer lists the descriptor token as what places a caller — it is the host's +own credential (DL-12); the T1 row names the undecided proposal to scope T1 in +`authority.md`; `mcp.md` names all three per-reply keys (`degraded`, `human_steps`, +`sources`) while still not teaching them as a contract (B-156); the agent-interop trigger is +"токен делегирования", not "делегирование", so sub-agent delegation stays with +agent-orchestrator (939 chars); AG-06a/b name plan R28 as deferred; B-157 records that the +adapter's back-links run one way. + - **Delegation starts with whether a caller needs a token (R23).** The delegation section of `agent-interop/references/gateway.md` now opens with the trust tiers, each sentence citing the contract rule it restates: diff --git a/docs/evidence/backlog.md b/docs/evidence/backlog.md index 594fc4f..7861d85 100644 --- a/docs/evidence/backlog.md +++ b/docs/evidence/backlog.md @@ -42,11 +42,12 @@ row per line, appended, so two runs closing two rows do not land on the same lin | B-135 | **The family audit asked for a debulk of two bodies at capacity, and the debulk was declined rather than done blind.** Wave AUDIT-WAVE-0906 flagged `agent-orchestrator/SKILL.md` and `agent-evals/SKILL.md` as near the working limit — re-measured at this commit by `audit_skill.py --house`: **4655/4750** (98.0%) and **4631/4750** (97.5%), both `0 GAP`. The family precedent (B-141 class) is that manufacturing headroom with no pending edit is negative-value work: room bought today is spent by nobody, and a trim chosen without the edit it serves cuts on the wrong line. B-129 already records the durable half — this body is at capacity and the remedy is a split, a routing decision. Debulk belongs to the release that next edits these bodies, priced into that release rather than shipped as its own. | family audit 2026-09-06, wave AUDIT-WAVE-0906 | 1 | 0 | 2 | **0.5** | **open.** Deliberately not scheduled: the trigger is the next edit to either body, and whichever release brings that edit pays the displacement (or executes B-129's split) as part of its own work. | | B-155 | **`agent-interop/references/gateway.md` names per-caller authorisation and per-hop policy (`:49`, `:108`) but no on-behalf-of model.** Nothing says how authority travels down a chain of agents. Fabric's delegation model (ADR-0144, CO-258) asks the doctrine to describe it: one issuer mints a short delegation JWT carrying an RFC 8693 `act` chain, one audience, capabilities, an effect ceiling and a grant; the callee verifies it offline against published JWKS; calls go direct, with no proxy in the path; `approval/1` and the common refusal codes apply. Doctrine only, no product change. Write it once the plan lands on Fabric main, citing that commit instead of the branch. | peer request fabric-workspace-91, 2026-10-11; plan `passioncode-ai/fabric@32046d14:docs/evidence/plans/2026-10-11-delegation-model/README.md` (branch agent/release-040-wave1) | 3 | 0 | 2 | **1.5** | **closed 2026-10-11** — 0.26.0, `gateway.md` § Delegation; pinned to Fabric main `812876cf` and the contract's `8e49fbd` | | B-156 | **The per-reply `degraded` / `human_steps` envelope is taught nowhere, because it has no contract home.** Several of the estate's MCP tools (Copylot, Observatory, Research Agent) answer every call with `degraded` and `human_steps` beside the result. The contract defines `degraded` only on the service's well-known document (fabric-agent-contract `docs/specification/service.md` *Well-known document* at `bcada4a`), and `agent-interop/references/mcp.md` teaches that one. The per-reply form is an estate convention; teaching it as a contract would put a second, unsourced home beside the contract's. Teach it once the contract (or Fabric) gives it a rule to cite, and pin that commit. | sshlg-skills plan R24 (`docs/evidence/briefs/2026-10-11-integration-waves.md:117`, branch `waves/2026-10-11-integration`); wave 4 fix round, 2026-10-12 | 3 | 0 | 1 | **3** | **open** — deferred: waits on a contract rule for the per-reply envelope | +| B-157 | **The back-links to the Fabric adapter run one way.** agent-interop now points to `fabric-agent-adapter`'s `creating-fabric-agents`, `adapting-projects-to-fabric` and `building-fabric-services`; of those, only `adapting-projects-to-fabric/SKILL.md:25-26` points back (wire questions). The adapter belongs to the Fabric owner: ask there for a pointer from the other two to agent-interop's tier doctrine. Nothing contradicts today — the adapter carries its own copy of the tier rule (`delegation.md:47-55`, 0.20.0). | wave 4 seam reading, 2026-10-12 | 1 | 0 | 1 | **1** | **open** — an ask to the Fabric adapter's owner | ## Open, and why | id | What | Why it is still open | |---|---|---| -| AG-06a | **A temp tree created outside `test/` is still invisible.** `check_temp_trees_go_through_the_residue_ledger` reads this repository's own suite. A tree made by anything else — a script under `plugins/`, a future helper outside `test/` — is not accounted for, and `residue.report()` counts only what `workspace()` handed out. The mechanism catches a *reviewed* bypass; it does not observe `$TMPDIR`. Observing it would mean a run claiming ownership of directories it cannot prove it made, which is the reaping mistake `agent-sync` refuses by design. | Open: the ledger reads this repository's own suite, so a tree made anywhere else is still unattributable. | -| AG-06b | **The 2592 existing trees are reported and left alone.** `find "$TMPDIR" -maxdepth 4 -type f -path '*/copy/sub/b.sh' \\| wc -l` → **2592** on 2026-08-20, of which **16 were added by this run's own verification** — one run of the leaking fixture before the port, and one of the v0.12.0 archive's copy of it. They are plain `tmpXXXXXXXX` and indistinguishable from every other program's, so they are named rather than swept. The identical fixture still leaks in **`seo-aeo-audit`**, which another agent holds; referred, not edited. | Open by decision: state this run cannot prove is its own is reported, never deleted. | +| AG-06a | **A temp tree created outside `test/` is still invisible.** `check_temp_trees_go_through_the_residue_ledger` reads this repository's own suite. A tree made by anything else — a script under `plugins/`, a future helper outside `test/` — is not accounted for, and `residue.report()` counts only what `workspace()` handed out. The mechanism catches a *reviewed* bypass; it does not observe `$TMPDIR`. Observing it would mean a run claiming ownership of directories it cannot prove it made, which is the reaping mistake `agent-sync` refuses by design. | Open: the ledger reads this repository's own suite, so a tree made anywhere else is still unattributable. Plan R28 of the hub's 2026-10-11 integration waves; deferred from wave 4 W4-A, 2026-10-12. | +| AG-06b | **The 2592 existing trees are reported and left alone.** `find "$TMPDIR" -maxdepth 4 -type f -path '*/copy/sub/b.sh' \\| wc -l` → **2592** on 2026-08-20, of which **16 were added by this run's own verification** — one run of the leaking fixture before the port, and one of the v0.12.0 archive's copy of it. They are plain `tmpXXXXXXXX` and indistinguishable from every other program's, so they are named rather than swept. The identical fixture still leaks in **`seo-aeo-audit`**, which another agent holds; referred, not edited. | Open by decision: state this run cannot prove is its own is reported, never deleted. Plan R28 of the hub's 2026-10-11 integration waves; deferred from wave 4 W4-A, 2026-10-12. | | AG-04a | **This board's own priority is a scalar.** The header computes `P = blast × (1 + age_runs) / effort` while `audit.md` now argues that publishing axes beats multiplying them. The two are not the same claim — the audit is doctrine this pack ships to users about somebody else's system, the board is one repository's own triage — but the tension is real and it is not resolved by saying so. The method's home is the umbrella board (`sshlg-skills/docs/evidence/backlog.md`), which another agent holds; the check added here deliberately scopes to documents under `plugins/`, so it neither polices nor blesses this file. Referred to the umbrella. | Open: the board's formula and `audit.md` disagree, and neither has been chosen. | diff --git a/plugins/agent-stack/skills/agent-interop/SKILL.md b/plugins/agent-stack/skills/agent-interop/SKILL.md index 88da616..531e2d2 100644 --- a/plugins/agent-stack/skills/agent-interop/SKILL.md +++ b/plugins/agent-stack/skills/agent-interop/SKILL.md @@ -11,7 +11,7 @@ description: >- "MCP client", "A2A", "agent card", "agent interoperability", "MCP registry", "server.json", "agentgateway", "tool federation", "MCP-сервер", "карточка агента", "интероперабельность агентов", "реестр MCP", "шлюз для агентов", "delegation token", "on behalf of", - "делегирование". Not for one server's tool design, nor a skill's construction (make-skill). + "токен делегирования". Not for one server's tool design, nor skill construction (make-skill). license: MIT --- diff --git a/plugins/agent-stack/skills/agent-interop/references/gateway.md b/plugins/agent-stack/skills/agent-interop/references/gateway.md index e2bc5d5..bf94a4c 100644 --- a/plugins/agent-stack/skills/agent-interop/references/gateway.md +++ b/plugins/agent-stack/skills/agent-interop/references/gateway.md @@ -136,8 +136,8 @@ that is this file's own rule says so. | Tier | Who the caller is | What places it there | What a call needs | |---|---|---|---| -| T0 | on the operator's own machine: on loopback, under the owner's OS account | the service's local floor — loopback only, the `Host` and `Origin` guard, the descriptor token (DL-25; `service.md` §Authentication and network) | the operator's instruction: no delegation token, no `approval/1`, no grant record (DL-26) | -| T1 | another machine of the same owner, enrolled in the agent network | its enrolment, which is Fabric's roadmap item RM-21 and still open — so **no machine is T1 yet** (DL-25; fabric-workspace `knowledge/roadmap.md` RM-21) | the same as T0 (DL-26) | +| T0 | on the operator's own machine: on loopback, under the owner's OS account | the service's local floor — loopback only, the `Host` and `Origin` guard, the owner's OS account (DL-25; `service.md` §Authentication and network). The descriptor token is the host's own credential, not the caller's proof (DL-12) | the operator's instruction: no delegation token, no `approval/1`, no grant record (DL-26) | +| T1 | another machine of the same owner, enrolled in the agent network | its enrolment, which is Fabric's roadmap item RM-21 and still open — so **no machine is T1 yet** (DL-25; fabric-workspace `knowledge/roadmap.md` RM-21). A proposal in `authority.md` to scope T1 by machine identity is not decided | the same as T0 (DL-26) | | T2 | everyone else: another person's machine under a share grant, and any caller the callee cannot place in T0 or T1 | nothing places it; it is the default (DL-25) | the whole model below: a token per call, an approval for each effect that needs one, a grant with limits, an expiry and a payer (DL-26) | **A tier is the caller's origin's, not its connection's** (DL-25). A call relayed onto @@ -237,7 +237,9 @@ says. The meter reserves against the grant's windows and every level above (Fabric: the estate total), and refuses `limit-reached` **naming the window** that ran out — a limit refusal without its window cannot be acted on. -**An approval is a document, not a ceremony.** When an action needs a person, the callee +**An approval is a document, not a ceremony — on T2.** A T0 or T1 caller presents no +`approval/1` (DL-26); there the five human gates above are the person's part. When a T2 +caller's action needs a person, the callee refuses `approval-required` with the action's digest — SHA-256 of its RFC 8785 canonical JSON, so two encodings of one action cannot get two digests. The action must be plain JSON values: a date, a map or an `undefined` member would serialise as `{}` and let two actions diff --git a/plugins/agent-stack/skills/agent-interop/references/mcp.md b/plugins/agent-stack/skills/agent-interop/references/mcp.md index 492c96b..cd9360d 100644 --- a/plugins/agent-stack/skills/agent-interop/references/mcp.md +++ b/plugins/agent-stack/skills/agent-interop/references/mcp.md @@ -256,7 +256,7 @@ Opt-in, negotiated, and worth checking before inventing an equivalent: entered or a model not chosen — not an outage: a watcher shows it as waiting for a person and opens no outage alert. An entry without `setup` is a failure. `status: ready` with a non-empty list is inconsistent (`FAC-SEM-011`). Some of the estate's tools also carry - `degraded` and `human_steps` on every reply; that per-reply envelope is an estate + `degraded`, `human_steps` and `sources` on every reply; that per-reply envelope is an estate convention with no contract home yet, so it is not taught here as a contract. - **MCP Apps** — interactive UI rendered inline in the conversation. - **Skills over MCP** — structured instruction sets discovered and consumed through MCP, From b7d51601ac6229118832ea30d61d5f90154be125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D0=B1=D0=BE=D1=82?= Date: Mon, 12 Oct 2026 13:14:21 +0200 Subject: [PATCH 5/5] =?UTF-8?q?backlog:=20B-158=20=E2=80=94=20the=20residu?= =?UTF-8?q?e=20check=20misses=20an=20aliased=20temp-dir=20call=20(closed?= =?UTF-8?q?=20in=20make-skill=200.31.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/evidence/backlog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/evidence/backlog.md b/docs/evidence/backlog.md index 7861d85..7e86516 100644 --- a/docs/evidence/backlog.md +++ b/docs/evidence/backlog.md @@ -43,6 +43,7 @@ row per line, appended, so two runs closing two rows do not land on the same lin | B-155 | **`agent-interop/references/gateway.md` names per-caller authorisation and per-hop policy (`:49`, `:108`) but no on-behalf-of model.** Nothing says how authority travels down a chain of agents. Fabric's delegation model (ADR-0144, CO-258) asks the doctrine to describe it: one issuer mints a short delegation JWT carrying an RFC 8693 `act` chain, one audience, capabilities, an effect ceiling and a grant; the callee verifies it offline against published JWKS; calls go direct, with no proxy in the path; `approval/1` and the common refusal codes apply. Doctrine only, no product change. Write it once the plan lands on Fabric main, citing that commit instead of the branch. | peer request fabric-workspace-91, 2026-10-11; plan `passioncode-ai/fabric@32046d14:docs/evidence/plans/2026-10-11-delegation-model/README.md` (branch agent/release-040-wave1) | 3 | 0 | 2 | **1.5** | **closed 2026-10-11** — 0.26.0, `gateway.md` § Delegation; pinned to Fabric main `812876cf` and the contract's `8e49fbd` | | B-156 | **The per-reply `degraded` / `human_steps` envelope is taught nowhere, because it has no contract home.** Several of the estate's MCP tools (Copylot, Observatory, Research Agent) answer every call with `degraded` and `human_steps` beside the result. The contract defines `degraded` only on the service's well-known document (fabric-agent-contract `docs/specification/service.md` *Well-known document* at `bcada4a`), and `agent-interop/references/mcp.md` teaches that one. The per-reply form is an estate convention; teaching it as a contract would put a second, unsourced home beside the contract's. Teach it once the contract (or Fabric) gives it a rule to cite, and pin that commit. | sshlg-skills plan R24 (`docs/evidence/briefs/2026-10-11-integration-waves.md:117`, branch `waves/2026-10-11-integration`); wave 4 fix round, 2026-10-12 | 3 | 0 | 1 | **3** | **open** — deferred: waits on a contract rule for the per-reply envelope | | B-157 | **The back-links to the Fabric adapter run one way.** agent-interop now points to `fabric-agent-adapter`'s `creating-fabric-agents`, `adapting-projects-to-fabric` and `building-fabric-services`; of those, only `adapting-projects-to-fabric/SKILL.md:25-26` points back (wire questions). The adapter belongs to the Fabric owner: ask there for a pointer from the other two to agent-interop's tier doctrine. Nothing contradicts today — the adapter carries its own copy of the tier rule (`delegation.md:47-55`, 0.20.0). | wave 4 seam reading, 2026-10-12 | 1 | 0 | 1 | **1** | **open** — an ask to the Fabric adapter's owner | +| B-158 | **The residue-ledger check misses an aliased temp-dir call.** `TEMP_MAKERS` (`test/validate.py`) excludes every dotted receiver but `tempfile.`, so `import tempfile as tf; tf.mkdtemp()` and `from tempfile import mkdtemp as m` pass. make-skill 0.31.0 ported this check and closed the gap there (any receiver, renamed from-imports refused, two negative controls); port that back. No current file here uses an alias. | make-skill W4-B seam reading, 2026-10-12 | 1 | 0 | 1 | **1** | **open** | ## Open, and why