@passioncode-ai/passioncode 0.2.44 → 0.2.46
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 +17 -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 +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_provider.py +5 -1
- package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +73 -18
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +9 -7
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md +43 -8
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/support_agent.py +28 -7
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/board.md +70 -20
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/connectors.md +7 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +9 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +14 -5
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +2 -1
- 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 +4 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-link.mjs +16 -9
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-session.mjs +87 -156
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board-slots.mjs +180 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-board.mjs +1 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +4 -2
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +2 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop-rules.mjs +15 -3
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-interop.mjs +2 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-openrouter.mjs +12 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +12 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board.py +1 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_link.py +32 -13
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_session.py +115 -147
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_board_slots.py +173 -0
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_connections.py +9 -42
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +53 -4
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +7 -3
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_events.py +4 -2
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +87 -11
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop.py +19 -8
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_interop_rules.py +30 -6
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_openrouter.py +9 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +15 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +73 -18
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_usage.py +1 -1
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +222 -24
- package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +155 -21
- package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +4 -1
- package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agents-that-answer-people.md +139 -0
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +10 -10
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +2 -1
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +39 -13
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +6 -2
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_cli.py +17 -3
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +23 -6
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_mcp.py +10 -39
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_paths.py +190 -0
- package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/host_hooks.py +1 -1
- package/payload/plugins/passioncode/.claude-plugin/plugin.json +1 -1
|
@@ -13,7 +13,10 @@ once, and `sample.draft` is a job that stops for a titled choice before it compl
|
|
|
13
13
|
Opt-in, off by default: `serve --board-project <Project uuid>` also answers that Project's `sample.echo` requests on
|
|
14
14
|
Fabric's project board (fabric-project-comms/0.1, FAA-12b) with fabric_board.Responder, from a board.respond grant whose
|
|
15
15
|
credential is in `<data>/board.credential` (0600; `--board-credential` names another file). The request body is data:
|
|
16
|
-
the gate admits plain text of at most 2,000 characters, and the answer is a reply carrying the same text.
|
|
16
|
+
the gate admits plain text of at most 2,000 characters, and the answer is a reply carrying the same text. Its
|
|
17
|
+
dashboard's Take back has the tool `board.take-back`, which the manifest lists when registered with the same
|
|
18
|
+
`--board-project`; a serve whose board mode differs from the registered manifest at its start logs
|
|
19
|
+
`service.manifest.mismatch`.
|
|
17
20
|
"""
|
|
18
21
|
|
|
19
22
|
from __future__ import annotations
|
|
@@ -52,8 +55,29 @@ PAGE = """<!doctype html><html lang="en"><head><meta charset="utf-8">
|
|
|
52
55
|
<nav class="fd-nav" aria-label="Sections"><ul class="fd-nav-list"><li><a href="#events" aria-current="page">Events</a></li>
|
|
53
56
|
<li><a href="#about">About</a></li></ul></nav></div>
|
|
54
57
|
<main class="fd-page"><section id="events"><h2>Events</h2><ul>{rows}</ul></section>
|
|
55
|
-
<section id="about"><h2>About</h2><p class="fd-hint">A sample fabric-service/0.1 service built on the kit.</p></section></main>
|
|
58
|
+
{board}<section id="about"><h2>About</h2><p class="fd-hint">A sample fabric-service/0.1 service built on the kit.</p></section></main>
|
|
56
59
|
</body></html>"""
|
|
60
|
+
# FAA-157: a moved board slot comes back only by the operator's act, a button on this page. The button posts from the
|
|
61
|
+
# operator's signed-in dashboard session with the request header; a service token alone cannot reach it.
|
|
62
|
+
BOARD_SECTION = """<section id="board"><h2>Project board</h2><p class="fd-hint">These slots were moved to another
|
|
63
|
+
session; this service no longer answers them there. Take back asks for one again: the service enrolls once the other
|
|
64
|
+
session lets it go, or at once when it restarts with accepted work for the slot.</p><ul>{items}</ul>
|
|
65
|
+
<script src="/board.js" defer></script></section>"""
|
|
66
|
+
#: The dashboard's Take back button and its tool (FAA-157, DEC-0121): the operator's decision, relayed in `_meta`.
|
|
67
|
+
TAKE_BACK = {"id": "board.take-back", "title": "Take back", "tool": "board.take-back", "gate": "relayed"}
|
|
68
|
+
TAKE_BACK_INPUT = {"type": "object", "properties": {"capability": {"type": "string"}}, "required": ["capability"],
|
|
69
|
+
"additionalProperties": False}
|
|
70
|
+
BOARD_ITEM = '<li>{capability} <button type="button" data-take-back="{capability}">Take back</button></li>'
|
|
71
|
+
BOARD_JS = """document.addEventListener('click', function (e) {
|
|
72
|
+
var cap = e.target && e.target.getAttribute && e.target.getAttribute('data-take-back');
|
|
73
|
+
if (!cap) return;
|
|
74
|
+
e.target.disabled = true;
|
|
75
|
+
fetch('/api/board/take-back', {method: 'POST', credentials: 'same-origin',
|
|
76
|
+
headers: {'Content-Type': 'application/json', 'X-Fabric-Request': '1'}, body: JSON.stringify({capability: cap})})
|
|
77
|
+
.then(function (r) { e.target.textContent = r.ok ? 'Asked back: waiting for the other session' : 'Refused (' + r.status + ')'; })
|
|
78
|
+
.catch(function () { e.target.disabled = false; e.target.textContent = 'Failed: try again'; });
|
|
79
|
+
});
|
|
80
|
+
"""
|
|
57
81
|
|
|
58
82
|
|
|
59
83
|
def _schema(name: str, body: Dict[str, Any]) -> Dict[str, Any]:
|
|
@@ -72,10 +96,26 @@ CAPABILITIES = [
|
|
|
72
96
|
"output": _schema("sample.draft-output", {"type": "object", "additionalProperties": False,
|
|
73
97
|
"properties": {"title": {"type": "string"}, "body": {"type": "string"}}, "required": ["title", "body"]})},
|
|
74
98
|
]
|
|
99
|
+
#: The take-back tool as a manifest capability (0.56.0 product r28): every tool the well-known lists is in the
|
|
100
|
+
#: manifest, so `register --board-project` writes it there.
|
|
101
|
+
TAKE_BACK_CAPABILITY: Dict[str, Any] = {
|
|
102
|
+
"name": TAKE_BACK["tool"], "effect": "change-policy", "idempotency": "required", "job": False,
|
|
103
|
+
"description": "Take back a board slot the operator moved away.",
|
|
104
|
+
# A relayed action: a probe call carries no operator decision, so it is refused and changes nothing (unit r28).
|
|
105
|
+
"probeAssertion": "is refused as operator-decision-required without the operator's decision in _meta",
|
|
106
|
+
"input": _schema("board.take-back-input", TAKE_BACK_INPUT),
|
|
107
|
+
"output": _schema("board.take-back-output", {"type": "object", "additionalProperties": False,
|
|
108
|
+
"properties": {"capability": {"type": "string"}, "moved": {"const": False}},
|
|
109
|
+
"required": ["capability", "moved"]})}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def served_capabilities(board: bool) -> list:
|
|
113
|
+
return CAPABILITIES + ([TAKE_BACK_CAPABILITY] if board else [])
|
|
75
114
|
|
|
76
115
|
|
|
77
116
|
class Service:
|
|
78
117
|
def __init__(self, args: argparse.Namespace):
|
|
118
|
+
self.args = args
|
|
79
119
|
self.id = args.id
|
|
80
120
|
self.instance = args.instance
|
|
81
121
|
self.name = args.name
|
|
@@ -110,9 +150,73 @@ class Service:
|
|
|
110
150
|
self.board = fbd.Responder(hub=fhb.HubClient(), project_id=self.board_project, capabilities=[self.board_capability],
|
|
111
151
|
credential_file=self.board_credential, checkpoint_file=self.data / "board-checkpoint.json",
|
|
112
152
|
handler=self.board_answer, admit=self.board_admit, emit=self.log.append, service=self.id)
|
|
153
|
+
# Every button has a tool (DEC-0121): the page's Take back is this relayed action, the operator's decision.
|
|
154
|
+
cap = TAKE_BACK_CAPABILITY
|
|
155
|
+
self.mcp.add_tool(fi.tool_for_capability(cap, cap["input"], cap["output"]), self.board_take_back_tool,
|
|
156
|
+
action=TAKE_BACK, target="capability")
|
|
113
157
|
self.log.append("service.started", "info", "%s started on build %s." % (self.name, self.build["commit"]))
|
|
158
|
+
self.check_manifest()
|
|
159
|
+
|
|
160
|
+
def check_manifest(self) -> None:
|
|
161
|
+
"""The registered manifest is register's to write, never serve's (0.56.0 seam r28): a serve whose board mode
|
|
162
|
+
differs from it at its start says so, and the probe fails until it is registered again (interop.tools-match when
|
|
163
|
+
the manifest lists a take-back this run does not serve, interop.well-known-capabilities when it lacks one this run
|
|
164
|
+
lists)."""
|
|
165
|
+
try:
|
|
166
|
+
listed = {c.get("name") for c in json.loads((self.data / "fabric-agent.json").read_text(encoding="utf-8"))["capabilities"]
|
|
167
|
+
if isinstance(c, dict)}
|
|
168
|
+
except (OSError, ValueError, KeyError, TypeError, AttributeError):
|
|
169
|
+
return # not registered here, or not a manifest of this shape: nothing to compare
|
|
170
|
+
if (TAKE_BACK["tool"] in listed) != (self.board is not None):
|
|
171
|
+
self.log.append("service.manifest.mismatch", "warning",
|
|
172
|
+
"The registered manifest %s the take-back tool, which this run %s: register again %s --board-project."
|
|
173
|
+
% ("lists" if TAKE_BACK["tool"] in listed else "lacks",
|
|
174
|
+
"does not serve" if self.board is None else "serves", "without" if self.board is None else "with"))
|
|
114
175
|
|
|
115
176
|
# --- the project board (opt-in): the input gate and the answer -------------------
|
|
177
|
+
def board_moved(self) -> list:
|
|
178
|
+
"""The capabilities whose slot the operator moved to another session (FAA-155): the page offers their take-back.
|
|
179
|
+
The live slot or the checkpoint's record says so: before a restart has reconnected (or while Fabric is down) the
|
|
180
|
+
record alone knows the move (0.56.0 seam r3)."""
|
|
181
|
+
if not self.board:
|
|
182
|
+
return []
|
|
183
|
+
live = {c for c, slot in dict(self.board.slots).items() if slot.get("state") == "moved"}
|
|
184
|
+
kept = {c for c, record in dict(self.board.checkpoint.slots).items()
|
|
185
|
+
if isinstance(record, dict) and record.get("project") == self.board_project and "moved" in record}
|
|
186
|
+
return sorted((live | kept) & set(self.board.capabilities))
|
|
187
|
+
|
|
188
|
+
def board_take_back(self, capability: Any, record: Any = None) -> Dict[str, Any]:
|
|
189
|
+
"""The operator's take-back (FAA-157): the next tick enrolls once the other session lets the slot go. `record` is
|
|
190
|
+
the act the action gate recorded (by, via, caller), kept in the event; never the decision's words. A repeat for a
|
|
191
|
+
capability of this service that is no longer moved (its first answer lost) answers the same, from the page's
|
|
192
|
+
route as from the tool (0.56.0 seam r6)."""
|
|
193
|
+
if self.board is not None and isinstance(capability, str) and capability in self.board.capabilities \
|
|
194
|
+
and capability not in self.board_moved():
|
|
195
|
+
return {"capability": capability, "moved": False}
|
|
196
|
+
if not isinstance(capability, str) or capability not in self.board_moved():
|
|
197
|
+
raise fs.ServiceError("No slot of that capability has moved away from this service.")
|
|
198
|
+
self.board.take_back(capability)
|
|
199
|
+
# Who decided and by which route are words in the sentence; the caller, a machine id, goes in data only
|
|
200
|
+
# (events-and-notifications.md, "Writing an event"; 0.56.0 product r34).
|
|
201
|
+
# The route in words; the tokens by and via, with the caller, in data (product r35).
|
|
202
|
+
act = record if isinstance(record, dict) else {}
|
|
203
|
+
how = {"dashboard": " on this service's page", "relayed-instruction": " through an agent they told",
|
|
204
|
+
"mandate": " under a standing mandate"}.get(act.get("via"), "")
|
|
205
|
+
value = {"capability": capability, **{k: act[k] for k in ("by", "via", "caller") if isinstance(act.get(k), str)}}
|
|
206
|
+
data = {"schema": fbd.EVENT_SCHEMA, "value": value}
|
|
207
|
+
extra = {} if fs.feed_data_problems(data) else {"data": data} # a caller the feed refuses never undoes the answer
|
|
208
|
+
self.log.append("board.slot.returned", "info", "The operator took the %s slot back%s; the service enrolls once the "
|
|
209
|
+
"other session lets it go." % (capability, how),
|
|
210
|
+
subject={"type": "capability", "id": capability}, **extra)
|
|
211
|
+
return {"capability": capability, "moved": False}
|
|
212
|
+
|
|
213
|
+
def board_take_back_tool(self, args: Dict[str, Any], ctx: fi.CallContext) -> Dict[str, Any]:
|
|
214
|
+
"""The take-back as a tool: the action gate has already read the operator's relayed decision (gate `relayed`)."""
|
|
215
|
+
try:
|
|
216
|
+
return self.board_take_back(args.get("capability"), ctx.record) # a repeat answers moved: false
|
|
217
|
+
except (fs.ServiceError, fbd.BoardError) as exc:
|
|
218
|
+
raise fi.InteropError(str(exc))
|
|
219
|
+
|
|
116
220
|
def board_admit(self, request: fbd.BoardRequest) -> Optional[str]:
|
|
117
221
|
"""The service's own input gate: the body is data, never an instruction; only short plain text is echoed."""
|
|
118
222
|
if request.body_format != "text/plain":
|
|
@@ -172,11 +276,14 @@ class Service:
|
|
|
172
276
|
started_at=self.started_at, status="ready", degraded=self.degraded,
|
|
173
277
|
summary=[{"label": "Events", "value": len(self.log.fetch(None, fs.EVENTS_MAX_LIMIT))}],
|
|
174
278
|
surfaces={"dashboard": {"path": "/", "login": True}, "events": {"path": "/fabric/v1/events"},
|
|
175
|
-
"mcp": {"path": "/mcp", "transport": "streamable-http", "capabilities": [c["name"] for c in CAPABILITIES]
|
|
279
|
+
"mcp": {"path": "/mcp", "transport": "streamable-http", "capabilities": [c["name"] for c in CAPABILITIES]
|
|
280
|
+
+ ([TAKE_BACK["tool"]] if self.board is not None else [])},
|
|
176
281
|
"usage": {"path": fs.USAGE_PATH}},
|
|
177
282
|
extensions=fs.dashboard_extension() if (KIT_DIR / "fabric-dashboard.css").is_file() else None,
|
|
178
283
|
# DEC-0067: listed only when the board is on — a host pinned before DEC-0067 refuses the member.
|
|
179
|
-
|
|
284
|
+
# DEC-0126 OA-7: a server serving a relayed action reads `source`, so it lists operator-source/1 too.
|
|
285
|
+
protocols=self.mcp.well_known_protocols(["fabric-service/0.1", "fabric-project-comms/0.1"]) if self.board is not None else None,
|
|
286
|
+
actions=self.mcp.action_table() or None,
|
|
180
287
|
)
|
|
181
288
|
|
|
182
289
|
|
|
@@ -195,7 +302,8 @@ def make_handler(svc: Service):
|
|
|
195
302
|
self.send_response(status)
|
|
196
303
|
self.send_header("Cache-Control", "no-store")
|
|
197
304
|
self.send_header("X-Content-Type-Options", "nosniff")
|
|
198
|
-
self.send_header("Content-Security-Policy", "default-src 'none'; style-src 'self'; script-src 'self';
|
|
305
|
+
self.send_header("Content-Security-Policy", "default-src 'none'; style-src 'self'; script-src 'self'; connect-src 'self'; "
|
|
306
|
+
"frame-ancestors 'self'")
|
|
199
307
|
if body is not None:
|
|
200
308
|
self.send_header("Content-Type", content_type)
|
|
201
309
|
self.send_header("Content-Length", str(len(payload)))
|
|
@@ -261,8 +369,12 @@ def make_handler(svc: Service):
|
|
|
261
369
|
return self._send(401, "Open this dashboard from Fabric Dashboards.", content_type="text/plain")
|
|
262
370
|
rows = "".join("<li>%s: %s</li>" % (html.escape(e["at"]), html.escape(e["text"]))
|
|
263
371
|
for e in reversed(svc.log.fetch(None, 20)))
|
|
264
|
-
|
|
372
|
+
moved = "".join(BOARD_ITEM.format(capability=html.escape(c)) for c in svc.board_moved())
|
|
373
|
+
return self._send(200, PAGE.format(name=html.escape(svc.name), agent=html.escape("%s.%s" % (svc.id, svc.instance)), status="ready", rows=rows,
|
|
374
|
+
board=BOARD_SECTION.format(items=moved) if moved else ""),
|
|
265
375
|
content_type="text/html; charset=utf-8")
|
|
376
|
+
if url.path == "/board.js" and svc.board is not None:
|
|
377
|
+
return self._send(200, BOARD_JS, content_type="text/javascript; charset=utf-8")
|
|
266
378
|
self._send(404, {"error": "Not found."})
|
|
267
379
|
|
|
268
380
|
def _length(self, cap: int) -> "int | None":
|
|
@@ -290,7 +402,8 @@ def make_handler(svc: Service):
|
|
|
290
402
|
return self._send(400, {"jsonrpc": "2.0", "id": None, "error": {"code": -32700, "message": "Parse error."}})
|
|
291
403
|
try:
|
|
292
404
|
with svc.drain.work():
|
|
293
|
-
|
|
405
|
+
# T0 (DL-25): a loopback call the service token authenticated is the local floor's caller.
|
|
406
|
+
response = svc.mcp.handle(message, self.headers, tier="T0")
|
|
294
407
|
except fs.Stopping as exc:
|
|
295
408
|
return self._send(503, {"jsonrpc": "2.0", "id": None, "error": {"code": -32000, "message": str(exc)}},
|
|
296
409
|
{"Retry-After": "5"})
|
|
@@ -307,6 +420,21 @@ def make_handler(svc: Service):
|
|
|
307
420
|
return self._send(403, {"error": "Missing request header."})
|
|
308
421
|
svc.codes.logout(fs.cookie_value(self.headers.get("Cookie")))
|
|
309
422
|
return self._send(204, None, {"Set-Cookie": fs.expired_session_cookie_header()})
|
|
423
|
+
if url.path == "/api/board/take-back" and svc.board is not None:
|
|
424
|
+
if not self._session_ok(): # the operator's dashboard session only: never the service token (FAA-157)
|
|
425
|
+
return self._send(401, {"error": "Sign in from Fabric Dashboards first."})
|
|
426
|
+
if self.headers.get("X-Fabric-Request") != "1":
|
|
427
|
+
return self._send(403, {"error": "Missing request header."})
|
|
428
|
+
length = self._length(4096)
|
|
429
|
+
if length is None:
|
|
430
|
+
return
|
|
431
|
+
try:
|
|
432
|
+
body = json.loads(self.rfile.read(length) or b"{}")
|
|
433
|
+
# The event names who decided, as the tool's does: the operator, at the dashboard (0.56.0 seam r7).
|
|
434
|
+
return self._send(200, svc.board_take_back(body.get("capability") if isinstance(body, dict) else None,
|
|
435
|
+
{"by": "operator", "via": "dashboard", "caller": "the signed-in page"}))
|
|
436
|
+
except (ValueError, RecursionError, fs.ServiceError, fbd.BoardError) as exc:
|
|
437
|
+
return self._send(400, {"error": str(exc)})
|
|
310
438
|
if url.path == "/api/emit":
|
|
311
439
|
if not (self._session_ok() or fs.token_matches(self.headers.get("Authorization"), svc.token)):
|
|
312
440
|
return self._send(401, {"error": "Sign in first."})
|
|
@@ -343,11 +471,6 @@ def serve(args: argparse.Namespace) -> int:
|
|
|
343
471
|
# hand-over that hangs is cut by the kit's hard exit, inside launchd's ExitTimeOut.
|
|
344
472
|
import threading
|
|
345
473
|
board_stop = threading.Event()
|
|
346
|
-
board_thread = None
|
|
347
|
-
if svc.board is not None:
|
|
348
|
-
board_thread = threading.Thread(target=svc.board.run, args=(board_stop, 5.0), name="board-responder", daemon=True)
|
|
349
|
-
board_thread.start()
|
|
350
|
-
|
|
351
474
|
stop_asked: list = []
|
|
352
475
|
|
|
353
476
|
def stopping() -> None:
|
|
@@ -355,30 +478,39 @@ def serve(args: argparse.Namespace) -> int:
|
|
|
355
478
|
board_stop.set() # stop claiming; the responder drains its request and leaves its board session
|
|
356
479
|
svc.log.append("service.stopping", "info", "%s is stopping." % svc.name)
|
|
357
480
|
svc.drain.install(lambda drained: server.shutdown(), on_stopping=stopping)
|
|
481
|
+
# The board thread starts only once the stop handler is installed: a SIGTERM in between would end the process with
|
|
482
|
+
# no drain and no DELETE, and the slot would stay held until its lease lapses (0.56.0 seam r6).
|
|
483
|
+
board_thread = None
|
|
484
|
+
if svc.board is not None:
|
|
485
|
+
board_thread = threading.Thread(target=svc.board.run, args=(board_stop, 5.0), name="board-responder", daemon=True)
|
|
486
|
+
board_thread.start()
|
|
358
487
|
try:
|
|
359
488
|
server.serve_forever(poll_interval=0.2)
|
|
360
489
|
finally:
|
|
361
490
|
board_stop.set()
|
|
362
491
|
if board_thread is not None:
|
|
363
492
|
# The responder drains (5 s at most) and leaves its board session, which releases its slots and forgets their
|
|
364
|
-
# records (FAA-155): joined, so the process does not end before that.
|
|
365
|
-
#
|
|
366
|
-
|
|
493
|
+
# records (FAA-155): joined, so the process does not end before that. Idle, it waits 3 s for the DELETE and the
|
|
494
|
+
# join ends within LC-01's 5 s (FAA-173); with work, it is bounded by the kit's hard exit, 8 + 2 s after the
|
|
495
|
+
# stop was asked: 1.5 s are left for the rest of the stop.
|
|
496
|
+
bound = 4.5 if svc.board.current is None else 8.5
|
|
497
|
+
board_thread.join(max(0.5, bound - (time.monotonic() - (stop_asked[0] if stop_asked else time.monotonic()))))
|
|
367
498
|
server.server_close()
|
|
368
499
|
svc.lock.release()
|
|
369
500
|
return 0
|
|
370
501
|
|
|
371
502
|
|
|
372
|
-
def write_manifest(args: argparse.Namespace, data: Path) -> Path:
|
|
373
|
-
"""The provider manifest for this service, naming its descriptor (G-07), with its schemas beside it
|
|
503
|
+
def write_manifest(args: argparse.Namespace, data: Path, board: bool = False) -> Path:
|
|
504
|
+
"""The provider manifest for this service, naming its descriptor (G-07), with its schemas beside it; with `board`,
|
|
505
|
+
the take-back tool too."""
|
|
374
506
|
schemas = data / "fabric" / "schemas"
|
|
375
|
-
for cap in
|
|
507
|
+
for cap in served_capabilities(board):
|
|
376
508
|
for side in ("input", "output"):
|
|
377
509
|
fs.atomic_write(schemas / ("%s-%s.schema.json" % (cap["name"], side)),
|
|
378
510
|
(json.dumps(cap[side], indent=2) + "\n").encode(), 0o600)
|
|
379
511
|
origin = "http://127.0.0.1:%d" % args.port
|
|
380
512
|
capabilities = []
|
|
381
|
-
for cap in
|
|
513
|
+
for cap in served_capabilities(board):
|
|
382
514
|
capabilities.append({
|
|
383
515
|
"id": "urn:fabric:capability:%s" % cap["name"], "name": cap["name"], "description": cap["description"],
|
|
384
516
|
"inputSchema": cap["input"]["$id"], "outputSchema": cap["output"]["$id"],
|
|
@@ -388,7 +520,7 @@ def write_manifest(args: argparse.Namespace, data: Path) -> Path:
|
|
|
388
520
|
"requiredFeatures": ["tool:%s" % cap["name"]],
|
|
389
521
|
"probes": [{"id": "%s-shape" % cap["name"].replace(".", "-"), "inputFixture": "urn:fabric:fixture:%s" % cap["name"],
|
|
390
522
|
"outputSchema": cap["output"]["$id"], "timeoutMs": 5000, "sideEffectCeiling": "none",
|
|
391
|
-
"assertions": ["returns a value valid against its output schema"]}]},
|
|
523
|
+
"assertions": [cap.get("probeAssertion", "returns a value valid against its output schema")]}]},
|
|
392
524
|
"extensions": {fi.EXTENSION_KEY: {"job": cap["job"]}},
|
|
393
525
|
})
|
|
394
526
|
digest = hashlib.sha256(json.dumps(capabilities, sort_keys=True).encode()).hexdigest()
|
|
@@ -408,7 +540,7 @@ def write_manifest(args: argparse.Namespace, data: Path) -> Path:
|
|
|
408
540
|
|
|
409
541
|
def register(args: argparse.Namespace) -> int:
|
|
410
542
|
data = Path(args.data_dir)
|
|
411
|
-
manifest = write_manifest(args, data)
|
|
543
|
+
manifest = write_manifest(args, data, board=bool(getattr(args, "board_project", None)))
|
|
412
544
|
descriptor = {
|
|
413
545
|
"protocol": fs.PROTOCOL, "id": args.id, "instance": args.instance, "name": args.name,
|
|
414
546
|
"summary": "Sample fabric-service/0.1 service.",
|
|
@@ -449,6 +581,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
449
581
|
p.add_argument("--board-capability", default="sample.echo")
|
|
450
582
|
p.add_argument("--board-credential", help="the board.respond credential file (0600); default <data>/board.credential")
|
|
451
583
|
else:
|
|
584
|
+
p.add_argument("--board-project", help="the Project the service will answer on the board: lists its take-back "
|
|
585
|
+
"tool in the manifest (serve with the same --board-project)")
|
|
452
586
|
p.add_argument("--services-dir")
|
|
453
587
|
p.add_argument("--label")
|
|
454
588
|
p.add_argument("--plist")
|
|
@@ -5,7 +5,7 @@ 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. Ships in one plugin with adapting-projects-to-fabric, whose scripts it reuses.
|
|
6
6
|
metadata:
|
|
7
7
|
author: PassionCode.ai
|
|
8
|
-
version: "0.
|
|
8
|
+
version: "0.56.0"
|
|
9
9
|
contract-version: "0.1.0"
|
|
10
10
|
contract-commit: "00e050ba7429c0c60261cec871779e0fd6aeeb28"
|
|
11
11
|
---
|
|
@@ -61,6 +61,7 @@ No file is created until every row has an answer. An answer of "later" fails the
|
|
|
61
61
|
| **Who pays?** | one key per agent and context with its own ceiling, from the estate's key manager, read from the environment; checked before every call; a personal key never pays for an organization |
|
|
62
62
|
| **How does it learn?** | decisions recorded with reasons, one trace per turn, every failure a planted fixture first, history replayed, learnings proposed and never self-approved (references/orchestration-and-learning.md) |
|
|
63
63
|
| **Text for people?** | if it writes anything a person reads (a reply, a message, a post, a report, a UI string), its prompt forbids the em dash (U+2014) and any substitute for it, and a test fails on U+2014 in new output, from the first commit (the resilience reference, *Text for people*) |
|
|
64
|
+
| **Does it answer people?** | if it answers people in a chat, by mail, in store reviews or in a group, it carries from its first release the turn record and the daily `support.quality_daily` report, the lane table with vendor exclusion and fallback recoveries counted, a writer change only after an offline replay, false-state gates and a checker that sees the material first, numbers and account state from tools, the live tail re-read before sending, repair then research then a hold that comes back, hand-offs by cause and never counted or judged as answers, a commitments ledger, a knowledge client that reads the service's `answer` flag, a gap queue and a feed re-read on a new event kind (references/agents-that-answer-people.md) |
|
|
64
65
|
|
|
65
66
|
## Step 1: the knowledge intake
|
|
66
67
|
|
|
@@ -93,6 +94,8 @@ prior art of its own:
|
|
|
93
94
|
choice, several kinds of input and output, budgets, OpenRouter keys.
|
|
94
95
|
- [orchestration-and-learning.md](references/orchestration-and-learning.md): the work graph,
|
|
95
96
|
calling other agents, stops for a person, learning loops, the loop guard.
|
|
97
|
+
- [agents-that-answer-people.md](references/agents-that-answer-people.md): what an agent that
|
|
98
|
+
answers people carries from its first release, each part with the check that proves it.
|
|
96
99
|
|
|
97
100
|
## Step 2: the project skeleton
|
|
98
101
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Agents that answer people: what they carry from the first release
|
|
2
|
+
|
|
3
|
+
**Load this when the agent you are building answers people**: private chats, mail, store reviews, group chats, any
|
|
4
|
+
channel where a person asks and the agent writes back. Every item below is a part of the agent, built and tested in its
|
|
5
|
+
first release, not a later improvement. Each comes from the sources below: what the quality standard requires (A1,
|
|
6
|
+
A5), and what an audit of five production agents found working or failing (P1 to P8). The practices are field
|
|
7
|
+
evidence: one estate, five agents, no control; their numbers are that estate's, never a forecast.
|
|
8
|
+
|
|
9
|
+
Sources (public): the support quality standard,
|
|
10
|
+
[`support-quality.md`](https://github.com/ssheleg/agent-stack/blob/28a0303c42038778e2650fc51096e589f4035239/plugins/agent-stack/skills/agent-evals/references/support-quality.md)
|
|
11
|
+
(A1 the record, A5 `support.quality_daily`), and the practices from an audit of five such agents,
|
|
12
|
+
[`support-practices.md`](https://github.com/ssheleg/agent-stack/blob/f882358e3fb9627f6edb20db3981818912a9844c/plugins/agent-stack/skills/agent-evals/references/support-practices.md)
|
|
13
|
+
(P1 to P8), both in the `agent-evals` skill of [ssheleg/agent-stack](https://github.com/ssheleg/agent-stack); the
|
|
14
|
+
practices arrive with [PR #70](https://github.com/ssheleg/agent-stack/pull/70), linked here at its commit until it
|
|
15
|
+
merges. The standard says how such an agent is measured; the practices say what worked and what failed. This page
|
|
16
|
+
lists parts that have a check, and the check that proves each; it leaves to the sources the standard's A2 families and
|
|
17
|
+
per-channel targets, A3 (the rubric), A4 (the judge and its calibration), A6 (answer first, the queue's SLA, a safe
|
|
18
|
+
default) and the rest of its checks that caught real defects, and the practices' other items of P1, P3, P5, P6, P7 and
|
|
19
|
+
P8. Read both sources before you design the reply pipeline. The kit's support-agent recipe (building-fabric-services,
|
|
20
|
+
`recipes/support-agent/`) is one draft-and-check step of such an agent, not the agent: used alone it hands the message
|
|
21
|
+
to a person at the first refusal (the refused draft is not kept), where this page repairs, researches and holds.
|
|
22
|
+
|
|
23
|
+
## Contents
|
|
24
|
+
|
|
25
|
+
- [The record and the daily report](#the-record-and-the-daily-report)
|
|
26
|
+
- [Model lanes](#model-lanes)
|
|
27
|
+
- [Checks before a reply is sent](#checks-before-a-reply-is-sent)
|
|
28
|
+
- [When the check refuses](#when-the-check-refuses)
|
|
29
|
+
- [Hand-offs and promises](#hand-offs-and-promises)
|
|
30
|
+
- [Knowledge and gaps](#knowledge-and-gaps)
|
|
31
|
+
- [Reading the events feed](#reading-the-events-feed)
|
|
32
|
+
- [Checklist](#checklist)
|
|
33
|
+
|
|
34
|
+
## The record and the daily report
|
|
35
|
+
|
|
36
|
+
- **Every inbound message is a turn with a trace id** (A1): the id is on the turn row, on each model call of that turn,
|
|
37
|
+
on its log line and on any copy a person reads. The turn stores its outcome (answered, general guidance, not covered,
|
|
38
|
+
handed off, small talk, limit, error), the hand-off reason and category, the latency, the knowledge revision the
|
|
39
|
+
answer used, and whether the channel accepted the reply. Conversation text stays inside the agent, redacted, for at
|
|
40
|
+
most 30 days. Check: a test that a turn without a trace id, or with an outcome outside the list, is refused.
|
|
41
|
+
- **The agent publishes one `support.quality_daily` per closed UTC day and channel** (A5) on its events feed
|
|
42
|
+
(`/fabric/v1/events`; building-fabric-services, `references/events-and-notifications.md`): counts and codes only, the
|
|
43
|
+
judge model and rubric version, and `notMeasured` naming every family not computed that day. A metric not measured is
|
|
44
|
+
null and named, never 0. The day goes in the event's `data` (`{schema, value}`, as `fabric_events.py` writes it), at
|
|
45
|
+
level `info` and without a notification. Until the contract carries the schema (A7), the producer names and pins its
|
|
46
|
+
own: `data.schema` is the absolute URI of the payload schema the agent publishes, and `data.value` stays within
|
|
47
|
+
DEC-0070's bounds (at most 32 members, each a scalar, a list of scalars or a flat object; per-lane counts as objects
|
|
48
|
+
such as `laneFailures` and `laneRecovered`; building-fabric-services, `references/protocol.md`). Check: the producer's
|
|
49
|
+
day passes `make_event`; a planted day that writes 0 for a family not computed fails; and a test runs every
|
|
50
|
+
`notMeasured` name the producer can emit through the published name pattern, iterating the producer's own list.
|
|
51
|
+
|
|
52
|
+
## Model lanes
|
|
53
|
+
|
|
54
|
+
- **A table of lanes** (P4): classifier, writer, fallback writer, checker, research, judge. The checker is another
|
|
55
|
+
vendor than the writer; the judge is another vendor than both, at temperature 0, on an open-vendor model where it
|
|
56
|
+
reads what people wrote. Declare them as classes in the profile, where each lane's fallback is another vendor than its
|
|
57
|
+
primary ([models and providers](models-and-providers.md)); per turn the router picks the checker against the model
|
|
58
|
+
that actually drafted, a fallback writer included ([resilience and
|
|
59
|
+
autonomy](resilience-and-autonomy.md#model-answers-are-untrusted-until-accepted), item 3). A lane that reads what
|
|
60
|
+
people wrote has an open-vendor route (DEC-0083). Check: the config loader refuses a fallback that shares its own
|
|
61
|
+
lane's primary vendor, a profile where some writer route has no checker route of another vendor, and a judge with no
|
|
62
|
+
route outside the vendors of the writers and the checkers (the vendor is the prefix before `/`).
|
|
63
|
+
- **Count what the fallback recovered** (P4), not only what the first attempt lost: a day's report says how many calls
|
|
64
|
+
failed and how many of those the fallback answered. Check: a test that a recovered call counts once as a failure of
|
|
65
|
+
the primary and once as a recovery, and is never counted as an error of the turn.
|
|
66
|
+
- **A writer model or reasoning budget changes only after an offline replay** (P4) of last week's judged sample, judged
|
|
67
|
+
blind, on the current model and the candidates. Check: the config loader refuses a change of the writer lane's model
|
|
68
|
+
or reasoning budget that names no replay report.
|
|
69
|
+
|
|
70
|
+
## Checks before a reply is sent
|
|
71
|
+
|
|
72
|
+
- **Deterministic false-state gates come first** (P2): the reply may not say that money arrived, that something is live,
|
|
73
|
+
enabled or fixed, or that a person did something, unless the system backs it. Check: planted drafts with each false
|
|
74
|
+
claim are refused before any model checker runs.
|
|
75
|
+
- **The checker sees the material, and sees it before the instructions** (P2): the knowledge items and account state the
|
|
76
|
+
writer used come first in its context, the operator's instructions after. A checker that receives the material cut
|
|
77
|
+
short after long instructions refuses correct answers as invented. Check: a planted correct answer whose material is
|
|
78
|
+
longer than the instructions passes the checker.
|
|
79
|
+
- **Numbers and account state come from tools**, never from the model (P1): prices from a calculator, the person's
|
|
80
|
+
account state and order from the system, injected per turn. Check: a planted draft with a price or an account state
|
|
81
|
+
that no tool result of the turn carries is refused.
|
|
82
|
+
- **Before sending, re-read the conversation's live tail** (P1): if a person or the agent already answered, skip the
|
|
83
|
+
turn. Check: a reply that lands in the conversation while the draft is checked makes the agent send nothing.
|
|
84
|
+
|
|
85
|
+
## When the check refuses
|
|
86
|
+
|
|
87
|
+
- **Repair, then research, then hold** (P2): one repair with the checker's reason; then one research turn on a stronger
|
|
88
|
+
model that sees both refused drafts and approved past answers, under a hard time cap; only then an honest holding
|
|
89
|
+
line. A refusal for a money reason is never researched. Check: a planted refusal walks all three steps in order.
|
|
90
|
+
- **A hold comes back by itself** (P2): when a person or the knowledge base answers the held question, the conversation
|
|
91
|
+
is run again and the person gets the answer without asking twice. Check: closing the gap behind a held turn produces
|
|
92
|
+
one reply in that conversation, and only one.
|
|
93
|
+
|
|
94
|
+
## Hand-offs and promises
|
|
95
|
+
|
|
96
|
+
- **A hand-off is never an answer** (P3): it is not counted as answered, and hand-off and fallback templates stay out of
|
|
97
|
+
the judge's sample. Check: the daily report counts a hand-off under hand-offs, and the judge's sample builder leaves
|
|
98
|
+
templated replies out.
|
|
99
|
+
- **Hand-offs are counted by cause** (P3): `policy`, `inability` and `infrastructure` apart, so the largest number names
|
|
100
|
+
what to fix. A hold after a checker or model outage, or at the research time cap, is `infrastructure`; only a hold
|
|
101
|
+
after a refusal on the material is `inability`. Check: a checker outage produces `infrastructure` hand-offs and holds,
|
|
102
|
+
never `inability`.
|
|
103
|
+
- **A commitments ledger** (P3): every "I will pass this on and come back" is a row with an owner and a due date; the
|
|
104
|
+
week's report counts promises made and closed. Check: a reply that promises to come back without a ledger row is
|
|
105
|
+
refused.
|
|
106
|
+
|
|
107
|
+
## Knowledge and gaps
|
|
108
|
+
|
|
109
|
+
- **The knowledge client keeps no threshold of its own** (P1, P8): the knowledge service decides whether an item may be
|
|
110
|
+
given as an answer and returns an `answer` flag per item; the client reads the flag and nothing else. Small closed
|
|
111
|
+
rule sets (prices, payment terms, refunds, sign-in) go into the prompt whole, dated and ordered by owner. Check: a
|
|
112
|
+
test that changing the client's configuration cannot make an item with `answer: false` an answer.
|
|
113
|
+
- **Every abstention and every inability, a hold on the material included, becomes a gap in a queue** (P7), restated
|
|
114
|
+
without personal data and grouped by meaning. When a gap closes, the client asks again and rewrites the held answer
|
|
115
|
+
only once the new answer is confident. Check: a closed gap whose new answer comes back with `answer: false` leaves the
|
|
116
|
+
hold in place, and a hold after a checker outage opens no gap.
|
|
117
|
+
|
|
118
|
+
## Reading the events feed
|
|
119
|
+
|
|
120
|
+
- **A reader that starts accepting a new event kind re-reads its feed once** (P6), from before its cursor: a reader that
|
|
121
|
+
only moves forward has already passed the first events of that kind. The re-read reaches back only as far as the
|
|
122
|
+
producer's feed keeps (at least 7 days or 1000 events, building-fabric-services
|
|
123
|
+
`references/events-and-notifications.md`); an older day is read from the producer's own store or not at all. Check: a
|
|
124
|
+
test adds a kind to the reader and sees the earlier events of that kind delivered once.
|
|
125
|
+
|
|
126
|
+
## Checklist
|
|
127
|
+
|
|
128
|
+
- [ ] Turn record with a trace id and an outcome from the fixed list (A1)
|
|
129
|
+
- [ ] `support.quality_daily` per closed UTC day and channel; `notMeasured` names tested against the pattern (A5)
|
|
130
|
+
- [ ] Lane table: fallback, checker and judge of other vendors; fallback recoveries counted
|
|
131
|
+
- [ ] A writer model or reasoning budget changed only after an offline replay
|
|
132
|
+
- [ ] False-state gates before the model checker; the checker sees the material before the instructions
|
|
133
|
+
- [ ] Numbers and account state from tools; the live tail re-read before sending
|
|
134
|
+
- [ ] Repair, research, hold; a hold that comes back by itself
|
|
135
|
+
- [ ] Hand-offs by cause, never counted or judged as answers
|
|
136
|
+
- [ ] A commitments ledger: promises made and closed per week
|
|
137
|
+
- [ ] A knowledge client that reads the service's `answer` flag and keeps no threshold
|
|
138
|
+
- [ ] A gap queue that asks again after a gap closes
|
|
139
|
+
- [ ] A reader that adds an event kind re-reads its feed once
|
|
@@ -12,13 +12,13 @@ description: >-
|
|
|
12
12
|
messaging people.
|
|
13
13
|
license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
|
|
14
14
|
compatibility: >-
|
|
15
|
-
Needs the Fabric desktop app (
|
|
16
|
-
The status script
|
|
17
|
-
local-lifecycle MCP server;
|
|
15
|
+
Needs the Fabric desktop app (v0.3.5 or later for fabric-mcp and the fabric_work tools) and an MCP client.
|
|
16
|
+
The hooks and status script need python3 3.9+ (3.11+ to edit Kimi Code's config.toml). Starting Fabric needs the
|
|
17
|
+
local-lifecycle MCP server; else the person opens Fabric. The full session hooks run in Claude Code from
|
|
18
18
|
this plugin; Codex/Kimi use command hooks; Hermes/OpenCode/OpenClaw/Pi use native context plugins after opt-in.
|
|
19
19
|
metadata:
|
|
20
20
|
author: PassionCode.ai
|
|
21
|
-
version: "0.
|
|
21
|
+
version: "0.56.0"
|
|
22
22
|
---
|
|
23
23
|
|
|
24
24
|
# Working with Fabric
|
|
@@ -133,17 +133,17 @@ board](references/project-board.md) before you submit, reply, or wait for an ans
|
|
|
133
133
|
|
|
134
134
|
## Step 4: before you start, who is working on what, then claim
|
|
135
135
|
|
|
136
|
-
Fabric
|
|
136
|
+
Fabric v0.3.5+ (REQ-38):
|
|
137
137
|
|
|
138
138
|
1. `fabric_work_list`: each live claim (agent, how it came in, task and the backlog row it links, what it said it
|
|
139
139
|
will touch, since, last sign of life), open tasks nobody holds, agent-sync leases read-only. If it cannot be
|
|
140
140
|
read, the answer says so: never tell the person "nobody is working here".
|
|
141
141
|
2. `fabric_work_claim` with your task (a board id, a backlog row as `source: {repository, path, id}`, or a new
|
|
142
142
|
row's title with its evidence) and what you will touch (folders, files or patterns). Claim the person's request
|
|
143
|
-
as ONE task whose title is that request in one line, with `origin: {kind: "person", ref: <where they said it: the session or thread>}
|
|
144
|
-
|
|
143
|
+
as ONE task whose title is that request in one line, with `origin: {kind: "person", ref: <where they said it: the session or thread>}`
|
|
144
|
+
(Fabric 0.3.14 shows it as your goal). File each piece you split off with
|
|
145
145
|
`fabric_task_create` while that task is claimed, and read the answer's `spawned_from` (Fabric 0.3.14 links the piece
|
|
146
|
-
from your single live claim). `created: false` with a `code` filed nothing (`source-unresolved` names the `part` of
|
|
146
|
+
from your single live claim). An error may have filed it anyway: find it by title in `fabric_work_list` before filing again. `created: false` with a `code` filed nothing (`source-unresolved` names the `part` of
|
|
147
147
|
`source` that failed): correct the source or file without it, and never link. Then `proposed: true`, even with `spawned_from: null`: nothing was filed, so do not
|
|
148
148
|
file or link that piece; tell the person it waits for the operator and go on with the rest. Otherwise, your claimed task's id there: it is linked, link nothing. Another task's id: link
|
|
149
149
|
nothing either (Fabric chose that parent, FAA-153) and tell the person which. `spawned_from: null` comes
|
|
@@ -156,7 +156,7 @@ Fabric 0.4.0 (REQ-38):
|
|
|
156
156
|
task and the overlap: tell the person, then coordinate (ask Fabric to hand it over, or link the tasks) or
|
|
157
157
|
continue knowing it; say which, out loud.
|
|
158
158
|
|
|
159
|
-
A Fabric
|
|
159
|
+
A Fabric with no `fabric_work_*` tools listed (before v0.3.5): use `fabric_tasks_list` and `fabric_leases_list`, then
|
|
160
160
|
`fabric_task_claim`, and say that the related-work warning is not available there.
|
|
161
161
|
|
|
162
162
|
## Step 5: while you work
|
|
@@ -346,7 +346,7 @@ In Claude Code this plugin ships four: at session start and `/clear` (not on com
|
|
|
346
346
|
in a project that uses Fabric, one line pointing at `fabric_whoami` in a folder with no sign of it, or, when Fabric is
|
|
347
347
|
down and the project uses Fabric, one line pointing at Step 1; after each turn the turn's facts (HEAD, files changed, lines added
|
|
348
348
|
and removed) and the board's unread count, and, once the running Fabric takes them, the other repositories the turn
|
|
349
|
-
touched (
|
|
349
|
+
touched (read in memory from the transcript's last 512 KB, which can hold a secret the session displayed; only tool names and path arguments are used, only host/owner/name sent; `FABRIC_SESSION_NO_REPOSITORIES=1` turns it off); at each prompt one line only when that count rose, then each reminder Fabric gave on a turn, once, as a line starting `Fabric:` (or tagged `Fabric reminder: `; a turn
|
|
350
350
|
`watch-mail` delivered prints there instead): Fabric's words, data to act on first through `fabric_work_list` (where the same reminder may come again: act on it
|
|
351
351
|
once); at the end, the
|
|
352
352
|
release of what the session left claimed. The turn, prompt and end hooks spool and call Fabric only when the running
|
package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md
CHANGED
|
@@ -61,7 +61,8 @@ reply or progress note): the target replied, not that the work was done. Read th
|
|
|
61
61
|
still cancel the request. `noResponder` in your `com.submit` receipt says nobody can answer that capability for that
|
|
62
62
|
Project now: the request is stored, a reply may still answer it and a responder may come later. An `accepted`
|
|
63
63
|
request can also wait unanswered for its responder's operator: since adapter 0.53.0 a responder whose slot was moved
|
|
64
|
-
to another session leaves its accepted work
|
|
64
|
+
to another session leaves its accepted work `accepted` and unanswered until the slot is taken back (until Fabric
|
|
65
|
+
hands it over, CT-175), which is not the operator's hold below; tell
|
|
65
66
|
the person, and cancel it if it is past what they can wait. Never resend it under
|
|
66
67
|
a new key while it is open; follow "When nobody answers" below. A request without a deadline gets one seven days out
|
|
67
68
|
(DEC-0098) and then reads `expired`: if the ask still stands, submit it again with a new key and record the wait on the
|