@passioncode-ai/passioncode 0.2.42 → 0.2.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/family.json +1 -1
- package/package.json +1 -1
- package/payload/.claude-plugin/marketplace.json +2 -2
- package/payload/manifest.json +8 -8
- package/payload/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/payload/plugins/fabric-agent-adapter/hooks/hooks.json +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +3 -3
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +2 -2
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +8 -4
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +82 -11
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +2 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +24 -8
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +2 -2
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-actions.mjs +22 -9
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +33 -3
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +146 -7
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +6 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop-rules.mjs +14 -6
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_actions.py +23 -13
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +34 -3
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +149 -45
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +6 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +9 -5
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop_rules.py +16 -5
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +276 -12
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +13 -1
- package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +3 -3
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +20 -13
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +4 -1
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +38 -9
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +11 -0
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +12 -3
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +11 -1
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +358 -14
- package/payload/plugins/passioncode/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.44 - 2026-10-27
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Fabric Agent Adapter is pinned at `v0.54.0`** (it was `v0.53.1`): the contract pin `v0.1.0+2026-10-27.1` (DEC-0141
|
|
8
|
+
MJ-1). A job's step under the operator's mandate is read under the job's own capability; a step no mandate can read
|
|
9
|
+
is answered `operator-decision-required` (`mandate-capability-missing`) and waits for the operator. working-with-fabric
|
|
10
|
+
names the job's capability without `actions` when it covers a job's steps.
|
|
11
|
+
|
|
12
|
+
## 0.2.43 - 2026-10-27
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **Fabric Agent Adapter is pinned at `v0.53.1`** (it was `v0.52.0`): working-with-fabric's Stop hook names the other
|
|
17
|
+
repositories a turn touched once Fabric takes them (never on Windows), a board responder stops when the operator
|
|
18
|
+
moves its slot, and Step 4 reads Fabric 0.3.14's answer when a piece is filed. `v0.53.0` was never released. Notes
|
|
19
|
+
are in the adapter's CHANGELOG.
|
|
20
|
+
|
|
3
21
|
## 0.2.42 - 2026-10-26
|
|
4
22
|
|
|
5
23
|
### Changed
|
package/family.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@passioncode-ai/passioncode",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.44",
|
|
4
4
|
"description": "Set up PassionCode.ai on a computer with one command — the apps from their verified releases, their MCP servers for every coding agent and the PassionCode.ai skills — and check and repair it with the same command or `doctor`.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"passioncode": "bin/passioncode.js"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"name": "fabric-agent-adapter",
|
|
12
12
|
"displayName": "Fabric Agent Adapter",
|
|
13
13
|
"source": "./plugins/fabric-agent-adapter",
|
|
14
|
-
"version": "0.
|
|
14
|
+
"version": "0.54.0",
|
|
15
15
|
"description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks.",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "PassionCode.ai",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"name": "passioncode",
|
|
36
36
|
"displayName": "PassionCode.ai",
|
|
37
37
|
"source": "./plugins/passioncode",
|
|
38
|
-
"version": "0.2.
|
|
38
|
+
"version": "0.2.44",
|
|
39
39
|
"description": "Set up and check PassionCode.ai on a computer with one command (setting-up-passioncode), the organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check that keeps the PassionCode.ai set current.",
|
|
40
40
|
"author": {
|
|
41
41
|
"name": "PassionCode.ai",
|
package/payload/manifest.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"family": "passioncode",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.44",
|
|
4
4
|
"release": true,
|
|
5
5
|
"members": [
|
|
6
6
|
{
|
|
7
7
|
"name": "fabric-agent-adapter",
|
|
8
8
|
"displayName": "Fabric Agent Adapter",
|
|
9
9
|
"repo": "passioncode-ai/fabric-agent-adapter",
|
|
10
|
-
"ref": "v0.
|
|
11
|
-
"commit": "
|
|
12
|
-
"version": "0.
|
|
10
|
+
"ref": "v0.54.0",
|
|
11
|
+
"commit": "5575cc6e9ea8087deb650d26c5827be94574619d",
|
|
12
|
+
"version": "0.54.0",
|
|
13
13
|
"via": "clone of https://***@github.com/passioncode-ai/fabric-agent-adapter.git",
|
|
14
14
|
"skills": [
|
|
15
15
|
"adapting-projects-to-fabric",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"legacyMarketplaces": [
|
|
24
24
|
"fabric-agent-adapter"
|
|
25
25
|
],
|
|
26
|
-
"contentHash": "sha256:
|
|
26
|
+
"contentHash": "sha256:c548471c7b5f632d7d19d2b88111caea380ef59397dea574386079fce945c2be",
|
|
27
27
|
"description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks.",
|
|
28
28
|
"author": {
|
|
29
29
|
"name": "PassionCode.ai",
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
"name": "passioncode",
|
|
63
63
|
"displayName": "PassionCode.ai",
|
|
64
64
|
"repo": "passioncode-ai/passioncode",
|
|
65
|
-
"ref": "v0.2.
|
|
65
|
+
"ref": "v0.2.44",
|
|
66
66
|
"commit": null,
|
|
67
|
-
"version": "0.2.
|
|
67
|
+
"version": "0.2.44",
|
|
68
68
|
"via": "this repository",
|
|
69
69
|
"skills": [
|
|
70
70
|
"setting-up-passioncode",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
],
|
|
73
73
|
"legacyPluginIds": [],
|
|
74
74
|
"legacyMarketplaces": [],
|
|
75
|
-
"contentHash": "sha256:
|
|
75
|
+
"contentHash": "sha256:f69bc17e5720c2097191aeb7442471232525710ba62e422c259214c828745583",
|
|
76
76
|
"description": "Set up and check PassionCode.ai on a computer with one command (setting-up-passioncode), the organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check that keeps the PassionCode.ai set current.",
|
|
77
77
|
"author": {
|
|
78
78
|
"name": "PassionCode.ai",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "fabric-agent-adapter",
|
|
4
4
|
"displayName": "Fabric Agent Adapter",
|
|
5
5
|
"description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks.",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.54.0",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "PassionCode.ai",
|
|
9
9
|
"url": "https://passioncode.ai/"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "Two silent-unless-relevant hook sets. The dashboard-kit notice: one line at session start or /clear in a repository that owns a Fabric service whose dashboard has no kit or an older one (fabric-dashboard/0.1, DEC-0036), phrased as an upgrade the agent may make itself. The Fabric session hooks (working-with-fabric, COM-04, FAA-12a): at session start or /clear, the preamble when Claude Code has the MCP server `fabric` and Fabric runs (fabric_whoami, then the project board through com.status and com.list, claim and release); when Fabric is down, one line, only in a project that uses Fabric (a Fabric contract file, a project-bound registration or a service descriptor naming the repository); nothing on compact or resume; each turn's facts (HEAD, files changed, lines added and removed, never content), the board's unread count and the session's end through Fabric, only when the running Fabric advertises session-hooks/1; at each prompt one line only when the unread count rose, then each nudge a turn's answer carried, once (watch-mail prints those of turns it delivered). All silent in a session Fabric launched. Every hook reads no token, prints at most one block, always exits 0, and hands any Fabric call to a detached worker with a 10 s deadline.",
|
|
2
|
+
"description": "Two silent-unless-relevant hook sets. The dashboard-kit notice: one line at session start or /clear in a repository that owns a Fabric service whose dashboard has no kit or an older one (fabric-dashboard/0.1, DEC-0036), phrased as an upgrade the agent may make itself. The Fabric session hooks (working-with-fabric, COM-04, FAA-12a): at session start or /clear, the preamble when Claude Code has the MCP server `fabric` and Fabric runs (fabric_whoami, then the project board through com.status and com.list, claim and release); when Fabric is down, one line, only in a project that uses Fabric (a Fabric contract file, a project-bound registration or a service descriptor naming the repository); nothing on compact or resume; each turn's facts (HEAD, files changed, lines added and removed, never content), the board's unread count, and, once the running Fabric's fabric_session_observe takes them (never on Windows), the other repositories the turn touched (the last 512 KB of the session transcript, message text and tool output included (a secret the session displayed can be there; read in memory only), is then read and only the current turn's tool names and path arguments are used; host/owner/name leave the machine, never a local path or text) and the session's end through Fabric, only when the running Fabric advertises session-hooks/1; at each prompt one line only when the unread count rose, then each nudge a turn's answer carried, once (watch-mail prints those of turns it delivered). All silent in a session Fabric launched. Every hook reads no Fabric token (a credential embedded in an origin URL is read by git config and dropped in memory, never spooled or sent), prints at most one block, always exits 0, and hands any Fabric call to a detached worker with a 10 s deadline.",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"SessionStart": [
|
|
5
5
|
{
|
|
@@ -5,9 +5,9 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
5
5
|
compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode.
|
|
6
6
|
metadata:
|
|
7
7
|
author: PassionCode.ai
|
|
8
|
-
version: "0.
|
|
8
|
+
version: "0.54.0"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
|
-
contract-commit: "
|
|
10
|
+
contract-commit: "00e050ba7429c0c60261cec871779e0fd6aeeb28"
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# Adapting projects to Fabric
|
|
@@ -76,7 +76,7 @@ Use exactly:
|
|
|
76
76
|
|
|
77
77
|
- contract version `0.1.0`;
|
|
78
78
|
- repository `https://github.com/passioncode-ai/fabric-agent-contract`;
|
|
79
|
-
- commit `
|
|
79
|
+
- commit `00e050ba7429c0c60261cec871779e0fd6aeeb28`.
|
|
80
80
|
|
|
81
81
|
This is the default for new bundles. When checking an issued legacy bundle, preserve its
|
|
82
82
|
explicit supported lock and use that exact clean checkout. Never silently upgrade or
|
|
@@ -22,10 +22,10 @@ import fabric_secrets as _secrets # noqa: E402 - the contract's credential dete
|
|
|
22
22
|
|
|
23
23
|
CONTRACT_VERSION = "0.1.0"
|
|
24
24
|
CONTRACT_REPOSITORY = "https://github.com/passioncode-ai/fabric-agent-contract"
|
|
25
|
-
CONTRACT_COMMIT = "
|
|
25
|
+
CONTRACT_COMMIT = "00e050ba7429c0c60261cec871779e0fd6aeeb28"
|
|
26
26
|
# #region supported-contract-revisions — docs: README.md#contract-pin
|
|
27
27
|
# Only this declaration may name a legacy revision in live files.
|
|
28
|
-
SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "e6c4fc16b415eec3190dd34d924d269405359dc9", "a3a66eb0052043847f6556348e37d34ca873a629", "52c7d798b9f4eb784eb4e29e717038a8912c759d", "2f020a696453ae299b123517f14957f6a1354379", "87d384ffce2757081b7ab227e5dad3dddd08328f", "0a4ce773c4b9e6bec40c6387eb3f988af7152c0e", "e742016dc396aa31e5a13a893489e43f15786c69", "bfa9a67aa3c6cb808c093e7d83cd59799f1ee7d0", "c55f2d5a11c9a0197e958e87212fca7ae1154769", "07042181bfae2a481aa0da1a5651f24c76669c48", "d8146cfb0a1c74fc6432284c4356bb0c864b37b5", "82e163665e156a08aa3b92e4711015582c06162c", "1636fa97262f4a26e2086572d540c2ee701e0010", "f00a7685e3861c27f7899c033fb850a145a1708f", "a046f57cf46ea9e3da57ff8ce6a3ed4d26d8774b", "fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0", "b291bf09e0a9f9fd3c16cf540832cd021203b7d1", "0cacd08b0e95b6abf131eae1206b60792138bc3c", "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
|
|
28
|
+
SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "1197a90dc4b57152cabcc71ad17db7559f35a27f", "e6c4fc16b415eec3190dd34d924d269405359dc9", "a3a66eb0052043847f6556348e37d34ca873a629", "52c7d798b9f4eb784eb4e29e717038a8912c759d", "2f020a696453ae299b123517f14957f6a1354379", "87d384ffce2757081b7ab227e5dad3dddd08328f", "0a4ce773c4b9e6bec40c6387eb3f988af7152c0e", "e742016dc396aa31e5a13a893489e43f15786c69", "bfa9a67aa3c6cb808c093e7d83cd59799f1ee7d0", "c55f2d5a11c9a0197e958e87212fca7ae1154769", "07042181bfae2a481aa0da1a5651f24c76669c48", "d8146cfb0a1c74fc6432284c4356bb0c864b37b5", "82e163665e156a08aa3b92e4711015582c06162c", "1636fa97262f4a26e2086572d540c2ee701e0010", "f00a7685e3861c27f7899c033fb850a145a1708f", "a046f57cf46ea9e3da57ff8ce6a3ed4d26d8774b", "fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0", "b291bf09e0a9f9fd3c16cf540832cd021203b7d1", "0cacd08b0e95b6abf131eae1206b60792138bc3c", "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
|
|
29
29
|
# #endregion supported-contract-revisions
|
|
30
30
|
INTEROP_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/interop/0.1"
|
|
31
31
|
MCP_REVISION = "2026-07-28"
|
|
@@ -14,10 +14,10 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
|
14
14
|
compatibility: Python 3.9+ or Node.js 20+ for the kits (Node.js 22+ for a Linux service's instance lock); the probe needs Python 3.9+. launchd steps are macOS-only; Linux services install a systemd user unit and Windows ones a per-user Scheduled Task with the kits' writers. Dashboard handoff optionally uses Fabric Dashboards MCP link/host_status/open; without it, report unresolved host capability. The contract checkout is optional.
|
|
15
15
|
metadata:
|
|
16
16
|
author: PassionCode.ai
|
|
17
|
-
version: "0.
|
|
17
|
+
version: "0.54.0"
|
|
18
18
|
contract-version: "0.1.0"
|
|
19
19
|
extension: "fabric-service/0.1"
|
|
20
|
-
extension-commit: "
|
|
20
|
+
extension-commit: "00e050ba7429c0c60261cec871779e0fd6aeeb28"
|
|
21
21
|
---
|
|
22
22
|
|
|
23
23
|
# Building Fabric services
|
|
@@ -102,7 +102,10 @@ When the service answers requests other Projects post to its Project on Fabric's
|
|
|
102
102
|
responder in its own process: [`scripts/fabric_hub.py`](scripts/fabric_hub.py) finds the hub and joins,
|
|
103
103
|
[`scripts/fabric_board.py`](scripts/fabric_board.py) enrolls, claims, accepts the exact digest, checkpoints before
|
|
104
104
|
every step and stops on `fenced`, `lease_expired` or `authority_changed` (Node: `fabric-hub.mjs`, `fabric-board.mjs`).
|
|
105
|
-
The request body is data behind the service's own input gate.
|
|
105
|
+
The request body is data behind the service's own input gate. From 0.53.0 a slot the operator moved to another
|
|
106
|
+
session stays moved until `take_back` (`takeBack`): give it an operator action with its tool and gate `relayed`, or it
|
|
107
|
+
stays moved for good (until Fabric's hub forwards decisions, CT-125, the operator reaches it through the service's page
|
|
108
|
+
or its elicitation). Read [the board reference](references/board.md).
|
|
106
109
|
|
|
107
110
|
## Step 2: the non-negotiables
|
|
108
111
|
|
|
@@ -256,7 +259,8 @@ or `human` with its `humanStep`) and declared with `build_well_known(actions=ser
|
|
|
256
259
|
`extensions/actions/0.1`. A `relayed` action called by the operator's own agent with their decision in
|
|
257
260
|
`_meta.decision` is the operator's act: never answer it "operator session required" or send them to the page.
|
|
258
261
|
A service adopts the relayed gate once Fabric's hub forwards decisions (CT-125), as the contract says; until then its tools
|
|
259
|
-
answer as today
|
|
262
|
+
answer as today, except the board responder's take-back action, declared `relayed` now and reached through the
|
|
263
|
+
page or elicitation until CT-125 ([board.md](references/board.md)). Read the interop reference's *Actions* section for when to adopt, the gate, its hooks and its
|
|
260
264
|
refusals.
|
|
261
265
|
|
|
262
266
|
**Say what the agent uses** (`fabric-connections/0.1`, DEC-0096) so Fabric Dashboards draws its chain: the other
|
package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md
CHANGED
|
@@ -16,6 +16,7 @@ skill's business (working-with-fabric, `references/project-board.md`).
|
|
|
16
16
|
- Effects, observation, evidence and the reply
|
|
17
17
|
- When the board says stop, and restarts
|
|
18
18
|
- When the operator holds work (DEC-0079)
|
|
19
|
+
- When the operator moves a slot to another session (FAA-155)
|
|
19
20
|
- What a board session may call
|
|
20
21
|
- Looking a Project up in the directory (FAC-SEM-107)
|
|
21
22
|
- Events, the probe and the live smoke
|
|
@@ -50,7 +51,11 @@ responder = fbd.Responder(
|
|
|
50
51
|
hub=fhb.HubClient(), project_id=PROJECT, capabilities=["video.render"],
|
|
51
52
|
credential_file=dirs["data"] / "board.credential", checkpoint_file=dirs["data"] / "board-checkpoint.json",
|
|
52
53
|
handler=render, admit=gate, observe=look_on_disk, emit=log.append, service="example-agent")
|
|
53
|
-
threading.Thread(target=responder.run, args=(stop_event, 5.0), daemon=True)
|
|
54
|
+
board = threading.Thread(target=responder.run, args=(stop_event, 5.0), daemon=True) # SIGTERM: set stop_event
|
|
55
|
+
board.start()
|
|
56
|
+
# ... at stop, after stop_event is set: wait for it to leave its board session (a clean leave forgets its slot
|
|
57
|
+
# records, FAA-155), bounded inside the kit's hard exit, as scripts/sample_service.py does
|
|
58
|
+
board.join(timeout)
|
|
54
59
|
```
|
|
55
60
|
|
|
56
61
|
Node: `new Responder({...same names in camelCase})`, then `responder.run({ signal, drain })`. A service that lists
|
|
@@ -63,7 +68,7 @@ One tick every 15 s ± 5 s (`step()` answers the wait):
|
|
|
63
68
|
|
|
64
69
|
| Step | Board call | Checkpoint written first |
|
|
65
70
|
|---|---|---|
|
|
66
|
-
| hold the slot | `com.enroll` (again at about 20 s while idle: the holder's enroll renews its 60 s lease) | none |
|
|
71
|
+
| hold the slot | `com.enroll` (again at about 20 s while idle: the holder's enroll renews its 60 s lease) | none for a first enroll or a live holder's renew; otherwise the slot's `asking` record (FAA-155) |
|
|
67
72
|
| take a request | `com.claim`, the same key while nothing is claimable | `claimed` |
|
|
68
73
|
| read it | `com.get`: the digest must equal the claim's, and the request must name this Project and capability | none |
|
|
69
74
|
| accept | `com.accept` with the claim's exact fence (request, generation, attempt, digest, revision) | `accepting`, then `accepted` |
|
|
@@ -73,7 +78,8 @@ One tick every 15 s ± 5 s (`step()` answers the wait):
|
|
|
73
78
|
| settle | `com.complete {state, observed, summary?, evidence?}` | `completing`, then the entry is dropped |
|
|
74
79
|
|
|
75
80
|
The checkpoint is fsynced before each call it announces. A write that fails stops the step (`board.blocked`): nothing
|
|
76
|
-
is sent that the checkpoint does not know about.
|
|
81
|
+
is sent that the checkpoint does not know about. The one exception is the slot record: an `asking` or generation
|
|
82
|
+
record that cannot be written is skipped and the enroll or replace still goes out (see below). A lost answer is sent again with the same idempotency key, so the
|
|
77
83
|
board returns the first receipt; while the lease still holds, the session and the running work stay and the call goes
|
|
78
84
|
again before the lease would lapse (FAA-77). One request is in flight at a time, and one fenced call: the fence is
|
|
79
85
|
built, sent and its receipt applied under one lock, so the tick's `com.renew` never carries a revision the handler's
|
|
@@ -109,10 +115,10 @@ once the kit reads it (FAA-135).
|
|
|
109
115
|
|
|
110
116
|
| Answer | What the kit does |
|
|
111
117
|
|---|---|
|
|
112
|
-
| `fenced` | the attempt is another responder's: stop the handler, drop the entry (nothing outside had begun) |
|
|
118
|
+
| `fenced` | the attempt is another responder's: stop the handler, drop the entry (nothing outside had begun); accepted work on a slot this responder recorded holding stays as `lapsed`, and the next tick asks `com.status` whether the slot [moved](#when-the-operator-moves-a-slot-to-another-session-faa-155) |
|
|
113
119
|
| `lease_expired` on accepted work | stop the handler; the board holds the request, and the kit takes it back with `com.responder_replace` and a transfer |
|
|
114
120
|
| `authority_changed`, `not_authorized` | stop claiming; one `board.access.needed` event (notify) saying what to ask for |
|
|
115
|
-
| `fenced` on enroll | another live holder: wait for it to lapse; never replace it on its own initiative |
|
|
121
|
+
| `fenced` on enroll or claim | another live holder: wait for it to lapse; never replace it on its own initiative. When this responder recorded holding the slot, it first reads `com.status`: a newer generation means the slot [moved](#when-the-operator-moves-a-slot-to-another-session-faa-155), and it is not taken again even after that holder lapses |
|
|
116
122
|
| lost after an effect began | keep the entry as `unknown`; `observe(entry)` is asked to settle it with `com.reconcile` |
|
|
117
123
|
| Fabric down, hub missing | bounded backoff (2 s doubling to 5 minutes, jittered), one `board.down` event, no spinning |
|
|
118
124
|
| one call without an answer while the lease holds | `board.down`, but the session and the running work stay; the call goes again, with the same key, before the lease lapses |
|
|
@@ -124,7 +130,8 @@ On restart the checkpoint is read back: settled, queued, claimed or `answered` r
|
|
|
124
130
|
the target Project's participant reply answered it, DEC-0098 and DEC-0102; not settled, the requester may still cancel
|
|
125
131
|
it, but no longer this responder's); `in_progress` and `outcome_unknown` are
|
|
126
132
|
reconciled from what `observe(entry)` answers (observed only; with no observation they stay unknown and notify);
|
|
127
|
-
accepted ones go back to this responder with `com.responder_replace {expectedGeneration, transfer}
|
|
133
|
+
accepted ones go back to this responder with `com.responder_replace {expectedGeneration, transfer}` (unless the slot
|
|
134
|
+
moved to another session meanwhile: they stay `lapsed` until take-back, [below](#when-the-operator-moves-a-slot-to-another-session-faa-155)), whose receipt's
|
|
128
135
|
`transferred` (DEC-0078: `{request, revision, attempt}`) carries the new fence. **A Fabric that does not answer
|
|
129
136
|
`transferred` yet leaves the request held** (`board.request.held`, notify): the kit never claims it back, because the
|
|
130
137
|
contract has no `com.claim` read-back and `com.claim` never moves an accepted request. A known outcome is sent after the
|
|
@@ -160,6 +167,70 @@ A receipt's request may carry `settledBy: "owner"` (DEC-0081): the estate owner
|
|
|
160
167
|
whose responder was gone. Read it as the owner's attestation, not the responder's; it appears only on `completed` or
|
|
161
168
|
`failed_known`, and its absence means the responder settled it.
|
|
162
169
|
|
|
170
|
+
## When the operator moves a slot to another session (FAA-155)
|
|
171
|
+
|
|
172
|
+
The contract's DEC-0140 PR-2 (proposed, after this kit's pin; Fabric plans it for 0.3.15) lets the operator move a
|
|
173
|
+
(Project, capability) slot to another live session of the Project: one transition, as `com.responder_replace` would make
|
|
174
|
+
it for the receiving session (generation up, `claimed` back to `queued`, `in_progress` to `outcome_unknown`, `accepted`
|
|
175
|
+
held and not transferred). The responder keeps that move instead of undoing it:
|
|
176
|
+
|
|
177
|
+
- It records the generation it holds each slot at, in the checkpoint file beside the requests (`slots: {<capability>:
|
|
178
|
+
{project, generation, moved?, asking?}}`). The record is written when a receipt's generation changes and before an
|
|
179
|
+
enroll or a replace that can change it; a live holder's renew (about every 20 s) cannot, and writes nothing. At a
|
|
180
|
+
clean leave (the service stopped; the session's DELETE released its slots) the record of a slot with no request left
|
|
181
|
+
in the checkpoint and not moved is forgotten (a moved slot's record stays, so a clean stop never undoes a move): the slot is free, and whoever takes it next is no move. A file without the record
|
|
182
|
+
reads as before; a kit before 0.53.0 reads the file, ignores the record and drops it at its first checkpoint write. So a service stopped cleanly with no request left in its checkpoint, rolled back to 0.52 and up again starts as on a first
|
|
183
|
+
start. After a crash the record stays: if 0.52 then ran, enrolled the slot and
|
|
184
|
+
wrote nothing to the checkpoint, the next 0.53 start reads that enroll as a move; bring it back with the service's take-back action (below).
|
|
185
|
+
- An enroll or a replace is written into that record with its key before it is sent (`asking`). When its answer is
|
|
186
|
+
lost, the next connect (or the next enroll) sends the same call again under the same key; the board answers the
|
|
187
|
+
stored receipt, whose generation is this service's own, and the slot is then asked for once more for the new board
|
|
188
|
+
session. A refusal is an answer and drops the `asking` record, so a move that happened meanwhile is still seen; except
|
|
189
|
+
`idempotency_window_expired` (a retired epoch refuses even a repeat), which leaves it unknown whether the call
|
|
190
|
+
landed: the whole record is forgotten and the slot reads as on a first start, never as a move. An
|
|
191
|
+
`asking` record that cannot be written (a full disk) is skipped, as the generation record is: the call goes out
|
|
192
|
+
unrecorded, and a lost answer to it, or a restart before the next receipt is recorded, may then read as a move.
|
|
193
|
+
- It reads the slot's generation from `com.status` at every connect (a restart), and after any call refused `fenced`:
|
|
194
|
+
at once for a claim or an enroll, at the next tick for a fenced call on running work. Only a responder with a record
|
|
195
|
+
asks, so a first start sends no extra call. A generation past the recorded one, with no own call of
|
|
196
|
+
this service left unanswered, is a move, whatever made it: the operator, or another session that took the slot while
|
|
197
|
+
this one was away or after it crashed. Before 0.53.0 a restarted responder took such a slot back by itself. So a
|
|
198
|
+
second responder of the same capability is no longer a standby once the slot changed hands: after any change of
|
|
199
|
+
holder it waits for take-back, not for the other's lapse.
|
|
200
|
+
- A `fenced` refusal on accepted work while the slot did not move (the generation unchanged) keeps the entry
|
|
201
|
+
`lapsed`; the next tick takes it back with a transfer and goes on (before 0.53.0 the entry was dropped and the
|
|
202
|
+
request left accepted on the board).
|
|
203
|
+
- Then the slot is `moved`: the running handler is told to stop (`ctx.stopped`); a `claimed` entry is dropped (the move
|
|
204
|
+
returned it to the queue); accepted work stays as `lapsed` and work whose effect began as `unknown` (only the slot's
|
|
205
|
+
holder, or the estate owner, reconciles it now). No `com.claim`, `com.renew`, `com.complete`, `com.enroll` or
|
|
206
|
+
`com.responder_replace` is sent for that capability again: not at recovery after a restart, and not when the new
|
|
207
|
+
holder's lease lapses, nor when com.status later names no generation for the slot (a recorded move stays until
|
|
208
|
+
take-back). Accepted requests stay held on the board, unanswered, until the slot is taken back (CT-175
|
|
209
|
+
will hand them to the new holder). One `board.slot.moved` event (warn, notify) says so, once, also across restarts
|
|
210
|
+
(the record keeps `moved`).
|
|
211
|
+
- A responder that never held the slot (a first start: no record, or a record of another Project), or whose record
|
|
212
|
+
equals the current generation, behaves as before: it enrolls, recovers and replaces its own lapsed work. A recorded or
|
|
213
|
+
reported generation that is not an integer of at least 1, or one not past the record (lower: the record is not this
|
|
214
|
+
board's), is not a move either: today's behaviour, not a stop, and the next receipt rewrites the record. A crash
|
|
215
|
+
between a receipt and its record's write leaves the `asking` record: the next start asks again under the same key and
|
|
216
|
+
records its own generation. Only a replay the board refuses, or one that ran for the first time (`repeated: false`)
|
|
217
|
+
after the slot had moved past the record, reads as a move: the safe side; a slot such a replay took is left to lapse
|
|
218
|
+
unused. A generation written `2.0` is `2`.
|
|
219
|
+
- Only the operator brings the slot back: `take_back(capability)` (`takeBack`) forgets the record; never edit the
|
|
220
|
+
checkpoint's `slots` record by hand to undo a move. In the same board
|
|
221
|
+
session the next tick enrolls once the other session lets the slot go (a live holder is waited for) and takes the work
|
|
222
|
+
left `lapsed` back with a transfer. A restart or reconnect before that, with accepted work held for the slot, takes it
|
|
223
|
+
back at once by `com.responder_replace`, as a restart did before 0.53.0: the operator asked for the slot back, so the
|
|
224
|
+
other session is replaced. In recovery, a refusal that leaves no slot held reconnects; one that leaves some held keeps
|
|
225
|
+
the session and serves them. The kit gives it no route of its own (the reference service has none yet,
|
|
226
|
+
FAA-157): a service built on the kit declares it as an operator action, a button with its tool ([Every button has a
|
|
227
|
+
tool](../SKILL.md), `fabric-actions/0.1`) with gate `relayed` (the operator's decision), never `none`, so no other
|
|
228
|
+
agent can call it on its own; on a server that reads mandates (`agent_id`) it also names its effect class, for example
|
|
229
|
+
`change-policy` (DEC-0125); until Fabric's hub forwards decisions (CT-125) the operator reaches it through the
|
|
230
|
+
service's page or its elicitation, and names it in its docs; otherwise a moved slot stays moved. A move back to this service's own session is not
|
|
231
|
+
detected yet: the receiving side and the hand-over of accepted requests (contract CT-175) wait for Fabric's session
|
|
232
|
+
surface.
|
|
233
|
+
|
|
163
234
|
## What a board session may call
|
|
164
235
|
|
|
165
236
|
A board session (`fabric.board.join`) is served the respond scope only: `com.status`, `com.list`, `com.get`,
|
|
@@ -183,8 +254,8 @@ raises `directory-invalid` rather than answering; an unlisted Project may still
|
|
|
183
254
|
Events go to `emit(kind, level, text, notify=…, data=…)` (`JsonlEventLog.append` fits) with `data {schema, value}`
|
|
184
255
|
checked by FAC-SEM-101 (no credential in data): `board.request.completed`, `board.request.refused`,
|
|
185
256
|
`board.request.skipped`, `board.request.reconciled`, `board.request.held`, `board.attempt.lost`,
|
|
186
|
-
`board.reconcile.needed`, `board.slot.contested`, `board.
|
|
187
|
-
notifying episode said once.
|
|
257
|
+
`board.reconcile.needed`, `board.slot.contested`, `board.slot.moved`, `board.access.needed`, `board.blocked`,
|
|
258
|
+
`board.down`, each notifying episode said once.
|
|
188
259
|
|
|
189
260
|
`check_service.py <id> [--board-kit <the service's fabric_board.py or fabric-board.mjs>]` rule **board.responder**:
|
|
190
261
|
`NOT_RUN` when the well-known `protocols` names no `fabric-project-comms` revision; `FAIL` for a revision this kit does
|
|
@@ -208,12 +279,12 @@ message, retryable, data)`.
|
|
|
208
279
|
|
|
209
280
|
Python (`fabric_board`): `Responder(*, hub, project_id, capabilities, credential_file, checkpoint_file, handler,
|
|
210
281
|
admit, observe=None, emit=None, service, now, rand, spawn, session_factory)` with `step()`, `run(stop, drain)`,
|
|
211
|
-
`shutdown(drain)`; `WorkContext.progress(note)`, `.begin_effect(summary)`, `.stopped`; `BoardSession(grant)`;
|
|
282
|
+
`shutdown(drain)`, `take_back(capability)`; `WorkContext.progress(note)`, `.begin_effect(summary)`, `.stopped`; `BoardSession(grant)`;
|
|
212
283
|
`Checkpoint(path)`; `comms_canonical(value)`, `comms_digest(operation, value)`, `receipt_problems(value, op)`,
|
|
213
284
|
`refusal_problems(value)`, `directory_findings(page)`, `directory_problems(page)`, `find_project(page, project_id,
|
|
214
285
|
capability)`, `artifact_ref(uri, content|path, label)`, `evidence_problems(list)`, `outcome_problems(outcome,
|
|
215
286
|
effect_began)`, `self_check()`; `BoardError`, `Stopped`.
|
|
216
287
|
|
|
217
288
|
Node: the same names in camelCase (`locateHub(root, {alive, estate, now})`, `new HubClient(root, {alive, timeout,
|
|
218
|
-
estate, post})`, `new Responder({...})` with async `step()`, `run({signal, drain})`, `shutdown(drain)
|
|
219
|
-
ctx.progress(note)`, `await ctx.beginEffect(summary)`; `selfCheck()`). Both kits read and write the same checkpoint file.
|
|
289
|
+
estate, post})`, `new Responder({...})` with async `step()`, `run({signal, drain})`, `shutdown(drain)`,
|
|
290
|
+
`takeBack(capability)`; `await ctx.progress(note)`, `await ctx.beginEffect(summary)`; `selfCheck()`). Both kits read and write the same checkpoint file.
|
|
@@ -112,7 +112,8 @@ operator's decision), `decision-invalid` (a decision for another action or objec
|
|
|
112
112
|
and `human-step-required`, which carries `data.humanStep` (`sign-in`, `consent`, `payment`, `secret`,
|
|
113
113
|
`spend-above-limit`, `passkey`) and `data.url` (an https URL or a path on the service's origin) beside `detail`.
|
|
114
114
|
`refusal_from_action` / `refusalFromAction` turn an action decision into that refusal; a service's own fault never
|
|
115
|
-
reaches the caller and reads as `unknown-state
|
|
115
|
+
reaches the caller and reads as `unknown-state`, but for `mandate-capability-missing` (DEC-0141 MJ-1): the caller
|
|
116
|
+
reads `operator-decision-required` with that `data.detail`, since no mandate covers the step and only a decision does. Since 0.51.0 the `operator-decision-required` message (free text in
|
|
116
117
|
DL-19, at most 500 characters) also names, in order, the routes for a client that cannot set `_meta`: Fabric's
|
|
117
118
|
`agent.call` with `call.decision` once the hub forwards it (CT-125), the service's own elicitation, else the action's
|
|
118
119
|
page as Fabric Dashboards' `open_link`; both kits carry the same sentence. The gate that answers them is in
|
package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md
CHANGED
|
@@ -148,7 +148,8 @@ and Fabric's hub forwarding decisions (CT-125) have landed (DEC-0121 *Compatibil
|
|
|
148
148
|
operator's own coding agents: Claude Code and Codex cannot set `_meta`, so until the hub forwards `call.decision`
|
|
149
149
|
a `relayed` gate turns "approve it" in chat into `operator-decision-required` with the page as the only route.
|
|
150
150
|
Until then a service keeps its tools as they answer today, or gates a `relayed` tool only where it offers its own
|
|
151
|
-
elicitation (the `elicited` hook).
|
|
151
|
+
elicitation (the `elicited` hook). The one exception is the board responder's take-back action: it is declared
|
|
152
|
+
`relayed` now and reached through the page or elicitation until CT-125 (`board.md`). This is D8's reason, which keeps `input_actions` off, applied to tools.
|
|
152
153
|
|
|
153
154
|
**Parity (AP-1).** Every action the dashboard, app or page lets a person take (approve, reject, choose, edit, pause,
|
|
154
155
|
resume, cancel, retry, send, publish, restore, set a policy, any other) has a tool on the MCP surface with the same
|
|
@@ -198,7 +199,10 @@ well_known = fs.build_well_known(..., surfaces=surfaces, actions=server.action_t
|
|
|
198
199
|
`unknown-state` (`approval-outcome-unreadable` when `approve` answers a refusal whose code or detail DL-18 does not know; an answer
|
|
199
200
|
that is neither `None` nor a dict is read as no approval, which fails closed; or
|
|
200
201
|
`caller-missing`: an action-gated server must pass `tier`, `identify` or `authorize`,
|
|
201
|
-
since `handle` defaults to T2 and a T2 call names nobody without them).
|
|
202
|
+
since `handle` defaults to T2 and a T2 call names nobody without them). One fault reaches the caller by name:
|
|
203
|
+
`mandate-capability-missing` (DEC-0141 MJ-1), read as `operator-decision-required` with that `data.detail`.
|
|
204
|
+
`on_fault` hears it from `ctx.approval_step` at a job's start under the job's tool, and at the answer under
|
|
205
|
+
`fabric.job.get`; at the start it hears any fault `job_step` answers (also `now-unreadable` from the server's clock).
|
|
202
206
|
- **Arguments with no canonical form** (a lone surrogate, a NaN or an infinity that Python's `json.loads` lets in,
|
|
203
207
|
nesting past the recursion limit) have no digest to bind: a relayed tool answers JSON-RPC `-32602`, never an exception.
|
|
204
208
|
- **The handler** reads `ctx.record` (`"caller"`, or `{by, via, caller}`) and writes it into the service's audit
|
|
@@ -285,10 +289,14 @@ server.add_tool(approve_tool, approve_reply, action=reply_action, target="reply_
|
|
|
285
289
|
when it reads `source`; a caller that sees no such entry sends the call without `source`, and it is taken as before.
|
|
286
290
|
- `action_gate(..., self_id=, effect=, caller_agent=, spend_usd=, project=)` / `actionGate({..., self, effect,
|
|
287
291
|
callerAgent, spendUsd, project})` is the same gate for a server of your own; `action_decision` / `actionDecision`
|
|
288
|
-
take `mandate`, `self`, `callerAgent`, `effect`, `spendUsd` and `
|
|
289
|
-
(
|
|
290
|
-
|
|
291
|
-
|
|
292
|
+
take `mandate`, `self`, `callerAgent`, `effect`, `spendUsd`, `project` and `mandateCapability` as the contract's
|
|
293
|
+
function does (DEC-0141 MJ-1): for an action whose tool is `fabric.job.get` (a job's input request) the mandate is
|
|
294
|
+
read under `mandateCapability`, the job's own capability, with the request key as the action; any other action reads
|
|
295
|
+
its own tool. `effect-missing`, `self-missing` and `mandate-capability-missing` (a job's step with no usable
|
|
296
|
+
capability: absent, empty, blank, not a capability name, or `fabric.job.get` itself) are the service's own faults;
|
|
297
|
+
like `effect`, `mandateCapability` is the service's own input, never the caller's. The gate answers
|
|
298
|
+
`mandate-capability-missing` to the caller as `operator-decision-required` (`data.detail` names it), the fault kept
|
|
299
|
+
beside it for the service's log, and nothing is applied, so the job stays `input_required`.
|
|
292
300
|
- **A job's approval step (interop C3.3).** A job reads the mandate its start call carried at its own approval step:
|
|
293
301
|
`ctx.approval_step(job_id, key, request, effect=, spend_usd=, project=, message=)` on the start call's context (a
|
|
294
302
|
server with `agent_id`) answers the record when the mandate covers the step `{capability: the job's, action: key,
|
|
@@ -301,10 +309,18 @@ server.add_tool(approve_tool, approve_reply, action=reply_action, target="reply_
|
|
|
301
309
|
replaces the kept steps, and an answer clears them: pass `steps=` again on every question whose answer a mandate may
|
|
302
310
|
cover. The answer's mandate is read under the job's own capability, as at the start (`action_gate(...,
|
|
303
311
|
mandate_capability=)` / `actionGate({mandateCapability})`), so the start and the answer agree whatever the tool is
|
|
304
|
-
named
|
|
312
|
+
named: a job whose tool is not a capability name has no capability a mandate can read its step under, and both its
|
|
313
|
+
start and its answer wait for the operator (`mandate-capability-missing` in the message). A mandate whose
|
|
314
|
+
`capabilities` name `fabric.job.get` covers no step of another capability.
|
|
305
315
|
Node has no tool server: a Node service reads `store.load(jobId).private.steps` and `.private.capability` BEFORE
|
|
306
316
|
`store.answer` (which clears the steps), gates the answer with `actionGate({..., mandateCapability:
|
|
307
|
-
private.capability})`, then answers.
|
|
317
|
+
private.capability})`, then answers. It logs the `fault` that `jobStep` and `actionGate` return beside their
|
|
318
|
+
answer, as `on_fault` does in Python: `mandate-capability-missing` at the start means the job's tool is not a
|
|
319
|
+
capability name; at the answer it also means `mandateCapability` was not passed. Any service that gates a job's
|
|
320
|
+
answer itself (Node `actionGate`, or Python `action_gate(..., mandate_capability=)` outside `McpToolServer`, whose
|
|
321
|
+
own gate passes it) passes the job's capability; without it every answer a mandate used to cover now waits for the
|
|
322
|
+
operator. `refusal_from_action` / `refusalFromAction` and so `actionRefusalToolResult` answer that fault as
|
|
323
|
+
`operator-decision-required` too.
|
|
308
324
|
|
|
309
325
|
**As a caller.** The agent leading a task states the mandate from the operator's own words with
|
|
310
326
|
`operator_mandate(task, instruction, scope, lead, at=, expires_at=)` / `operatorMandate({task, instruction, scope,
|
package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md
CHANGED
|
@@ -55,7 +55,7 @@ that document until `service.id` matches (at most 40 s) before it calls the serv
|
|
|
55
55
|
|
|
56
56
|
| Id | Rule | Here | Kit (Python / Node) |
|
|
57
57
|
|---|---|---|---|
|
|
58
|
-
| LC-01 | **Quit reaches exit within a deadline.** Every trigger (`SIGTERM`, `SIGINT`, logout, the app's own quit) ends the process: within 5 s idle, within 10 s with work in flight, then a hard exit. Nothing starts after quit begins | [Shutdown](#shutdown) | `Drain` / `Drain` |
|
|
58
|
+
| LC-01 | **Quit reaches exit within a deadline.** Every trigger (`SIGTERM`, `SIGINT`, logout, the app's own quit) ends the process: within 5 s idle, within 10 s with work in flight, then a hard exit (a board responder leaving its session can take an idle stop up to that hard exit). Nothing starts after quit begins | [Shutdown](#shutdown) | `Drain` / `Drain` |
|
|
59
59
|
| LC-02 | **Every child has an owner, a deadline and a process group.** No subprocess without a timeout; on timeout or the parent's exit the whole group goes; no orphan survives. Locks and shared resources are released in `finally` | your own code | none |
|
|
60
60
|
| LC-03 | **A job is bounded and exclusive.** One copy holds `service.lock`; a scheduled job has a watchdog shorter than its interval, shares its lock with every manual entry point, bounds every network call and records each run's outcome. Long work is journalled before it starts | [One copy under a supervisor](#one-copy-under-a-supervisor) | `hold_single_instance` / `holdSingleInstance` |
|
|
61
61
|
| LC-04 | **Credentials are silent after setup.** No background prompt; a credential read that is denied or absent is cached and backs off until a person acts | [Directories](#directories) (secret files 0600) | none |
|
package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# fabric-service/0.1: wire reference
|
|
2
2
|
|
|
3
|
-
Pinned to `fabric-agent-contract` commit `
|
|
3
|
+
Pinned to `fabric-agent-contract` commit `00e050ba7429c0c60261cec871779e0fd6aeeb28`
|
|
4
4
|
(`docs/specification/service.md`, DEC-0015; the usage report DEC-0021; Windows and Linux supervision
|
|
5
5
|
DEC-0032 and DEC-0033; the operator channel DEC-0034 and DEC-0035; the embedded dashboard DEC-0036;
|
|
6
6
|
delegation and the operator session's lifetime DEC-0039; the meter's call window DEC-0041; session handoff DEC-0042;
|
|
@@ -23,7 +23,7 @@ DEC-0124; the operator's mandate `mandate/1`, handed on narrowed with a task DEC
|
|
|
23
23
|
operator's agents, its `source` on a mandate and a decision DEC-0126; context hub keys and the organizations an estate
|
|
24
24
|
record pins DEC-0127 and DEC-0128; the family and the agent network named DEC-0130 and DEC-0137; a reply delivered to
|
|
25
25
|
the sender it answers, or to whom it names DEC-0135; a hand-over adds no naming list the scope in force leaves out
|
|
26
|
-
DEC-0136. The organization's member list, draft exchange, relay and the machines' report, DEC-0129, DEC-0131 to DEC-0134,
|
|
26
|
+
DEC-0136; a job's step under the mandate read under the job's own capability DEC-0141. The organization's member list, draft exchange, relay and the machines' report, DEC-0129, DEC-0131 to DEC-0134,
|
|
27
27
|
reach the kit only as protocol register entries). The contract's schemas are normative;
|
|
28
28
|
this page is the working summary. Extension key:
|
|
29
29
|
`https://fabric.passioncode.ai/agent-contract/extensions/service/0.1`.
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
//
|
|
4
4
|
// - actionDecision(inputs): whether a service takes a call of one declared action and as whose act it records it,
|
|
5
5
|
// exactly as the contract's actionDecision answers (AP-2, AP-4; DL-17, DL-18, DL-25). A `fault` is the service's
|
|
6
|
-
// own and is never sent to a caller
|
|
6
|
+
// own and is never sent to a caller as such; actionGate answers mandate-capability-missing as
|
|
7
|
+
// operator-decision-required (DEC-0141 MJ-1).
|
|
8
|
+
// - JOB_STEP_TOOL, jobCapability(value): the tool a job's answer comes through, and whether a job's capability can
|
|
9
|
+
// read its step under a mandate (a capability name other than fabric.job.get).
|
|
7
10
|
// - decisionShape(value): a decision as decision.schema.json reads it, `at` read strictly (the contract's strictTime).
|
|
8
11
|
// - actionsView(wellKnown): the host reader of the declaration, as the contract's actionsView.
|
|
9
12
|
// - actionsExtensionRules(wellKnown), actionRules(kind, value): FAC-SEM-142.
|
|
@@ -125,12 +128,12 @@ const refuse = (code) => ({ ok: false, code });
|
|
|
125
128
|
* (DEC-0126 OA-6). DEC-0125: a call with no decision that carries
|
|
126
129
|
* `mandate` (its `_meta.mandate`) is answered as mandateCovers answers, given `self` (the service's own agent id),
|
|
127
130
|
* `callerAgent` (only an agent id the service authenticated), `effect` (the action's effect class, the service's own
|
|
128
|
-
* knowledge), `spendUsd` and `project`: covered, {by: 'operator', via: 'mandate', task, chain, source?, caller}.
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
131
|
+
* knowledge), `spendUsd` and `project`: covered, {by: 'operator', via: 'mandate', task, chain, source?, caller}.
|
|
132
|
+
* DEC-0141 MJ-1: a job's input request (tool fabric.job.get) under a mandate is read under `mandateCapability`, the
|
|
133
|
+
* job's own capability (the tool its start call named), its action the request key; any other action reads its own
|
|
134
|
+
* tool. `{fault}` (action-invalid, caller-missing, now-unreadable, digest-missing, url-missing, effect-missing,
|
|
135
|
+
* self-missing, mandate-capability-missing) is the service's own and is never sent to a caller (actionGate answers
|
|
136
|
+
* mandate-capability-missing as operator-decision-required).
|
|
134
137
|
*/
|
|
135
138
|
export function actionDecision(inputs) {
|
|
136
139
|
const field = (key) => member(inputs, key);
|
|
@@ -166,6 +169,14 @@ export function actionDecision(inputs) {
|
|
|
166
169
|
return { ok: true, recordAs: { by: 'operator', via: 'relayed-instruction', caller, ...(has(decision, 'source') ? { source: { ...decision.source } } : {}) } };
|
|
167
170
|
}
|
|
168
171
|
|
|
172
|
+
/** The tool of a job's input request (interop C3.3, AP-2): the call that answers it is fabric.job.get (DEC-0141 MJ-1). */
|
|
173
|
+
export const JOB_STEP_TOOL = 'fabric.job.get';
|
|
174
|
+
|
|
175
|
+
/** A job's capability a mandate can read a step under (MJ-1): a capability name, never fabric.job.get itself. */
|
|
176
|
+
export function jobCapability(value) {
|
|
177
|
+
return typeof value === 'string' && CAPABILITY.test(value) && value !== JOB_STEP_TOOL;
|
|
178
|
+
}
|
|
179
|
+
|
|
169
180
|
/**
|
|
170
181
|
* A relayed action's call that carries a mandate and no decision (DEC-0125 MD-3): the mandate decides, as the contract's
|
|
171
182
|
* byMandate reads it: the service's own `effect` and agent id `self` are required (else a fault), its estimate
|
|
@@ -176,13 +187,15 @@ function byMandate(field, action, caller) {
|
|
|
176
187
|
const self = field('self');
|
|
177
188
|
if (typeof effect !== 'string') return { fault: 'effect-missing' };
|
|
178
189
|
if (typeof self !== 'string') return { fault: 'self-missing' };
|
|
190
|
+
// MJ-1: a job's step is read under the job's capability, never under the transport fabric.job.get itself.
|
|
191
|
+
const capability = action.tool === JOB_STEP_TOOL ? field('mandateCapability') : action.tool;
|
|
192
|
+
if (!jobCapability(capability)) return { fault: 'mandate-capability-missing' };
|
|
179
193
|
const spendUsd = field('spendUsd');
|
|
180
194
|
const project = field('project');
|
|
181
195
|
const answer = mandateCovers({
|
|
182
196
|
mandate: field('mandate'), tier: field('tier'), now: field('now'), self,
|
|
183
197
|
...(field('callerAgent') === undefined ? {} : { callerAgent: field('callerAgent') }),
|
|
184
|
-
|
|
185
|
-
call: { capability: typeof field('mandateCapability') === 'string' ? field('mandateCapability') : action.tool, action: action.id, effect, ...(typeof spendUsd === 'number' ? { spendUsd } : {}), ...(typeof project === 'string' ? { project } : {}) },
|
|
198
|
+
call: { capability, action: action.id, effect, ...(typeof spendUsd === 'number' ? { spendUsd } : {}), ...(typeof project === 'string' ? { project } : {}) },
|
|
186
199
|
});
|
|
187
200
|
if ('recordAs' in answer) return { ok: true, recordAs: { ...answer.recordAs, caller } };
|
|
188
201
|
return answer; // never `ignored`: the tier was read first, so a T2 call answered approval-required before (MD-5)
|