Repository navigation
Releases: passioncode-ai/fabric-agent-adapter
Release list
v0.57.0: fabric-agent-adapter
The contract pin moves to 3830a33 (v0.1.0+2026-10-27.6) from 00e050ba (FAA-176).
Added
comparisonas a human step (DEC-0142, contract CT-186).humanStepin an action declaration may be
comparison: the person compares two codes shown on two screens, or chooses to share without a comparison. Both
kits' action-declaration checks take it (fabric_actions.py,fabric-actions.mjs,actions-vectors.json
regenerated: 68 cases, 12 declarations), the probe passes a declaration that uses it, and interop.md, delegation.md
and working-with-fabric's human gates (OA-5), which its refusal table cites, name it. A reader pinned before (a 0.56.0
kit, Fabric Dashboards before 0.8.9) refuses such a declaration, which Project Observatory declares on its share
buttons from OBS-264.- The protocol register gains the six peer-share revisions (DEC-0142,
fabric-peer-share/1and its parts) and
org-adaptation/1andorg-adaptation-receipt/1(DEC-0138) in both twins (feed-protocols-vectors.json), so a
service's well-known that names one is read as a known revision. - The provider entry's manifest path (DEC-0145, CT-192):
fabric_provider.pynames the schema's
MANIFEST_PATTERN, read as a search that ends at the string's end; the reader accepts the manifest's name after a
backslash, the writer still writes it with/and refuses the other with a sentence naming CT-192, and the CLI's
stem is read after either separator (CT-193). The contract's tenprovider.schema.jsonfixtures are vendored
(provider-fixtures.json) and tested in every run.
Changed
- On Windows,
fabric_provider.py'sservices_dir()(its FAC-SEM-013 check that an id is not a service's) and
providers_dir()read%LOCALAPPDATA%\passioncode-fabric(provider.md), as the service kit's readers already did. - agents-that-answer-people.md (A5) names the contract's
support-quality-daily.schema.json, says when a producer may
carrycalibration(CT-188) and that a reader validates against its own pinned copy (DEC-0144). - The hosted contract job runs
test_provider, andtest/validate.pyfails a workflow whose contract job leaves out a
module that holds a test gated on the contract.
Upgrading
- A bundle pinned to
00e050baor an earlier supported commit keeps working;adapt_project.py
accepts it (SUPPORTED_CONTRACT_COMMITS). A new scaffold writes3830a33. - DEC-0139 (the machines' report) and DEC-0138's organization server route reach no kit code: the adapter registers
their protocol revisions and implements neither side. DEC-0143 (secret.prompt, the local channel) and DEC-0142's
peer-share profile beyond its register entries reach no kit code either; Project Observatory implements them
(CT-186). - On Windows, a provider entry written by
fabric_provider.pynow goes to the folder Fabric reads
(%LOCALAPPDATA%\passioncode-fabric\providers). Fabric v0.3.15 and earlier read a manifest path only under~/or
/; a drive letter (C:/.../fabric-agent.json) is read by the Fabric release after v0.3.15 (CO-479). Until then
write the manifest path under~/. An entry an older kit wrote on Windows sits in
%USERPROFILE%\.local\share\passioncode-fabric\providers, which Fabric never read andremoveno longer finds:
write it again and delete the old file.
v0.56.0: fabric-agent-adapter
Stability and Windows: the debts of 0.53.0 to 0.55.0 that touch how the hooks, the board responder and the interop
gate behave.
Fixed
- The board responder's notices reach a service's event feed: they were sent at level
warn, which
JsonlEventLog.appendrefuses (its levels areinfo,notice,warning,error), soboard.down,
board.access.needed,board.slot.moved,board.slot.contested,board.request.held,board.request.skipped,
board.reconcile.needed,board.attempt.lostandboard.outcome.invalidwere dropped whereveremit=log.append
(the reference service included, since 0.28.0;board.slot.movedsince 0.53.0). They are sent atwarningnow, in
both twins, as is the newboard.slot.refused(FAA-163). Each notice names itssubject({type, id}: its request,
else its capability, else its episode, such as a block's cause, else the Project), so a host tells one request's,
capability's or cause's episode from another's. In Python the subject goes to anemitwhose signature names a
subjectparameter with no value bound (JsonlEventLog.appendnow names one); any otheremit(only**kwargs, a
subject bound withfunctools.partial, a signature that cannot be read) is called as 0.55.0 called it, once, without
one. The Node responder always passes it in its options object, which anemitthat does not read it ignores. - working-with-fabric on Windows (FAA-175, FAA-177). A turn names the repositories it touched on Windows too:
Git Bash/c/...and/cygdrive/c/...,C:/...and quotedC:\...paths, andgit.exe -C(any case); Bash
text is split with Git Bash's own quoting, so an unquoted backslash is an escape there too. Acdinside( ), a
pipeline's stage or a list sent to the background no longer moves the folder a later relative-Cis read
against; a newline ends a command, a here-document's body is text (a#comment no longer hides its lines, and
<<<opens none),&>only redirects,git -C /a -C bruns in/a/b, andpushdand acdafter{orthen
are read. Cygwin and MSYS Python read paths as POSIX. Before, the Stop hook did not read the
transcript on Windows at all, and a turn from Windows came withoutrepositories. The hooks runpython3, which
must name Python 3.9 or later in Git Bash there (session-hooks.md; FAA-183 makes the plugin find it itself). - A relative
git -Caftercd(FAA-166) is read against the folder the command last changed into with an
absolutecd; after a relative or unknowncdit is dropped, never read against the session's folder. - The board responder's recovery (FAA-163, FAA-158). A refused enroll of one capability no longer stops the
others: it is retried on its own, the pause doubling, and said once asboard.slot.refused. A slot's move is written
under the responder's lock and only while its record is unchanged, so an operator's take-back at the same moment
wins. The slot code moved to its own twin modules,fabric_board_slots.pyandfabric-board-slots.mjs. - An idle stop leaves the board within 3 s (FAA-173): a responder with no work in hand closes its session with a
confirmedDELETEinside the hard exit (close(timeout): a timeout not above 0 is the session's own, and a session built
with one takes 15 s, in both twins),
instead of letting the slot lapse (a board call already in flight when the
stop comes runs first, and when the join ends before the DELETE the slot lapses as before). The slot records are forgotten only after
a 2xx: a 404 (the session already gone) keeps them, since a move made before it ended is read at the next
com.status. A refusal of the whole board session (not_availableincluded) is the board down, never a refused
capability; a capability refused, or contested, again after it was held is said again, once per capability. A clean
leave forgets only the records this session confirmed (a slot it held, paused under the operator's hold included, or
onecom.statusshowed unmoved) and no fenced call has put in doubt since, so a slot moved while the service was
down staysmovedwhen the session is dropped at connect or recovery stops early, and is never enrolled again on
its own. A paused or retrying slot readscom.statusbefore each new ask, so a move during the operator's hold is
said.Checkpoint.leave(project, held)takesheldas a required argument in both twins. action_gatefails closed (FAA-139): a spend that is not a finite number is read as no estimate, which a
mandate'sperCallMaxUsdceiling refuses (a mandate with no ceiling covers it; the question is with the contract,
FAA-185), and a project that cannot be read is never covered by a mandate limited toprojects. Anullmandate in
_metareads as an unreadable one, not as none (FAA-178; Python'sapproval_step, while a Node service passes{}
tojobStepitself, interop.md). A job's start (job_step) reads spend and project as its answer does, and keeps
the spend as it read it (a NaN is no estimate, never a bareNaNin the job file).fabric_secretsmatches the contract's ECMAScript reading (FAA-136):\s,.and case folding as JavaScript
reads them, so both ports flag the same values.secret_urlalso reads a special scheme asnew URLdoes: every
slash or backslash afterhttps:(orhttp,ftp,ws,wss) is skipped and a backslash before the query reads
as a slash, sohttps:u:p@hostandhttps:\\u:p@hostcarry a password in Python as in Node. Python splits the URL
by hand rather than withurlsplit, which refuses a[or]outside an IPv6 host (https://u:p[w@h/), and drops
one leading?from a parameter's name asURLSearchParamsdoes. Wherenew URLrefuses a host (none, a port over
65535), Python still reads the password and the parameters, so it can flag more than Node, never less. The
source.repositorycheck of a descriptor and of a provider entry reads a special scheme's password the same way
(https:bot:pw@host), up to the user information's last@(https://me@corp.example:pw@host), and says so
before it judges the host. The query and the fragment are read percent-encoded, asnew URLhands them over.
fabric_http.redact_urlreads a special scheme the same way (below).- What the kit sends, refuses and redacts as a URL (FAA-136; the remaining differences are FAA-187):
redact_url: the URL anHttpErrorcarries forhttps:u:pw@hosthas no password, as in Node; a URL of any
other scheme (a misspelledhtps:included) keeps only its scheme in both twins (htps:<redacted>), since its
user information may sit anywhere in the rest. It first trims C0 controls and space from the ends and removes
tabs and line breaks, asnew URLdoes, sohttps://u:pw@hostkeeps no password on Python 3.9 either. A
string with no scheme (ht tps:,1ttps:, a leading byte order mark or no-break space) and a URL whose host or
portnew URLrefuses (a password holding/, a port that is not ASCII digits such as1_2or+1, which
Python 3.9'sint()takes, a bracketed host that is no IPv6 address, aws,wssorftpURL with no host or
a port above 65535) read as<unparseable url>; the scheme is read by the WHATWG rule, not byurlsplit, whose
rule differs between 3.9 patch releases. On Python a host holding%reads as<unparseable url>too, where
Node writes it decoded. The path is not redacted in either twin (Node writes it asnew URLnormalises it,
Python as given), so a key never belongs in it.check_urlandrequest: the same ports and bracketed hosts (an IPv6 zone included) are refused. On Python a
backslash,%, space or control character in the host (a Unicode space and a C1 control included) is refused
too, since urllib would decode it or dial another host (h.example%3A8443reached port 8443), and so is a space
or ASCII control character before the URL's first#, or a character outside ASCII in its path or query, which
urllib would send and http.client refuse: such a URL is refused before it is sent (kindrefused), where 0.55.0
retried it and answered kindconnection. Python'scheck_urlchecks the host's shape, not its name (FAA-187
(14), (16)). The Node twin reads several of these asnew URLdoes, among them a tab or line break removed, a
space or a character outside ASCII percent-encoded, a backslash as/, an extra slash before the host skipped
and a%host decoded (refused when the decoded host is not allowed, ash.example%3A8443).- What is sent: the default transport sends a URL up to its first
#in both twins (urllib alone kept everything
before the last one, so/x#a#basked for/x#a); a transport you pass receives the URL as given. ASCII
whitespace at the ends, which urllib andnew URLboth trim, is trimmed, so a URL ending in a line break is still
sent, as in 0.55.0 (on Python 3.9 a URL with a leading space too, which 0.55.0 refused there). On Python a URL
whose part before its first#ends in a Unicode space (a no-break or ideographic space) is now refused; 0.55.0
sent it trimmed when the space ended the URL and failed every call when a fragment followed it. Node keeps it
percent-encoded. - The OpenRouter clients trim a base URL's ends first: ASCII whitespace and trailing slashes in both, and on Node
every C0 control there and a tab or line break anywhere, asnew URLreads them. So.../v1\n,.../v1/and,
on Node,.../v1\n/are sent to.../v1/chat/completions. Both then refuse, when they are built, a base URL
holding a query or a fragment (kindconfig, read beforecheck_url;/chat/completionswould land in it, so a path-routed endpoint
such as OpenRouter failed every call in 0.55.0). Python also refuses, throughcheck_url(kind `refu...
v0.55.0: fabric-agent-adapter
Added
- creating-fabric-agents: agents that answer people (FAA-162, board request
1ad39d4e). A new reference,
references/agents-that-answer-people.md, lists what an agent that answers people (a support chat, mail, store
reviews, a group) carries from its first release, each part with the check that proves it: a turn record with a trace
id and a fixed outcome list, onesupport.quality_dailyper closed UTC day and channel withnotMeasurednamed,
model lanes whose fallback, checker and judge are other vendors with fallback recoveries counted, a writer change only
after an offline replay, false-state gates before the model checker, a checker that sees the material before the
instructions, numbers and account state from tools, the live tail re-read before sending, repair then research then a
hold that comes back by itself, hand-offs by cause and never as answers, a commitments ledger, a knowledge client that
reads the service'sanswerflag, a gap queue, and a feed re-read when a reader starts on a new event kind. Its
sources are agent-stack's support quality standard and the practices from an audit of five such agents, linked at
their commits (the practices at agent-stack PR #70 until it merges). Step 0 of the skill gains the row "Does it answer
people?" and Step 1 links the reference. New scenario evalan-agent-that-answers-peopleand its prose test
(test/test_answering_agents_prose.py), which fails when a part or its check is removed. The support-agent recipe now
says it is the draft-and-check step of such an agent, not the agent: used alone it hands the message to a person at
the first refusal (the refused draft is not kept), and its Lookup is a relevance rule an agent that answers people
replaces with the knowledge service'sanswerflag. It says the calling service routespolicybefore it, maps its
hand-offs to causes (a failed check goes to repair and counts asinabilityonly at a hold on the material, a hold
after an outage or at the research time cap beinginfrastructure;no-knowledgeandmodel-unsureareinability
and open a gap; a money reason is never researched;model:*isinfrastructure,model:ceilingandmodel:payment
also thelimitoutcome), and tells such an agent to pass anon_handoffthat redacts what it stores, in place of
the outbox, and to keep 30 days or fewer;from_configtakeson_handoff=, and a call toanswerfrom inside
on_handoffis refused at once withReentryErrorrather than hanging (copysupport_agent.pyagain to get both);
building-fabric-services and the README point at it. Backlog FAA-116 records what is left for the recipe.
v0.54.0: fabric-agent-adapter
Changed
- Contract pin
00e050ba(v0.1.0+2026-10-27.1, DEC-0141) (FAA-159, CT-149, CT-177). A job's step under the
operator's mandate is read as its start was: under the job's own capability, its action the step's request key
(MJ-1). The kits'action_decision/actionDecisionnow read the reference's way: for an action whose tool is
fabric.job.getthe mandate is read undermandateCapability, and without a usable one (absent, empty, blank, not a
capability name, orfabric.job.getitself) the answer is the service's faultmandate-capability-missing; any
other action reads its own tool, whatevermandateCapabilitysays. Before, an absent capability was read as
fabric.job.get, a non-name string as given, andmandateCapabilitywas read on any action.action_gate/
actionGateanswer that fault to the caller asoperator-decision-required(data.detail
mandate-capability-missing), the fault beside it for the service's log; nothing is applied, so the job stays
input_required.job_step/jobStepread an unusable job capability alike at the start: the step waits
input_required, its message namingmandate-capability-missing, and the Python tool server hands that fault (and any
otherjob_stepanswers, such asnow-unreadable) toon_faultat the start as at the answer. It is the one service fault a caller reads by name:on_fault's and the
delegation reference's rule (a caller sees a fault only asunknown-state) now names it. So a job whose tool is not a capability name is no
longer covered by anactions-only mandate at either end, and a mandate whosecapabilitiesnamefabric.job.get
covers no step of another capability. Any service gating a job's answer itself passes the job's capability
(actionGate({mandateCapability: private.capability})in Node,action_gate(..., mandate_capability=)in Python
outsideMcpToolServer, which passes it already); without it every answer a mandate used to cover now waits for the
operator.refusal_from_action/refusalFromActionanswer the fault the same way, soaction_refusal_tool_result/
actionRefusalToolResultagree with the gate. The vendoredactions-vectors.jsongains the contract'sjobStepsgroup (14
cases) and its two newaction-callfixtures (18 in all); both twins run them. Bundles issued under the previous pin
1197a90dstay valid (adapt_project.py). - working-with-fabric: a leading agent covering a job's approval step names the job's capability in
capabilitieswithoutactions(the step's request key is the service's own), and readsdata.detail
mandate-capability-missingas a step no mandate covers: only the operator's decision answers it. New scenario eval
lead-covers-a-job-step-by-its-capability. building-fabric-services' interop reference drops the sentence that
read an answer without a capability underfabric.job.get.
v0.53.1: fabric-agent-adapter
0.53.0 was tagged and never released on npm or GitHub (the plugin marketplace, which reads main, offered it until
0.53.1 merged): its release run failed on Windows, where the FAA-140 tests read a Bash
command's POSIX paths through Windows path rules. 0.53.1 is 0.53.0 with this fix.
Fixed
- No transcript read on Windows (FAA-175): working-with-fabric's Stop hook never reads the session transcript on
Windows, even once Fabric takesrepositories; a Bash command's paths there are not the host's. Windows sessions send
their turn facts as before 0.53.0, withoutrepositories.BashPathsand the four tests that need the hook itself to
read (names from Edit and Bash paths, at most ten, one checkout named once, another checkout of the start repository)
are skipped on Windows; the rest run there, the worker's with the read turned on in the test, and a new test checks that
the mark turns nothing on there.
v0.52.0: fabric-agent-adapter
Changed
- Pinned to the contract release
v0.1.0+2026-10-26.12(1197a90d) (FAA-138). It carries DEC-0126 to DEC-0137;
e6c4fc16,a3a66eb0and52c7d798stay supported. Brief:
docs/evidence/specs/2026-10-26-faa-138-134-repin-1197a90d-brief.md. - DEC-0126, where the operator said the words (
source). A mandate and a decision may carrysource; both kits
read it as the contract's schema does, return it in the record (recordAs,ctx.record,input_records), keep it
on a hand-over, and build it withoperator_mandate(source=)/operator_decision(source=)(Node:source).
request_meta(..., target_protocols=)/requestMeta(traceparent, {targetProtocols})send it only to a callee whose
well-knownprotocolslistoperator-source/1, and otherwise keep it in the caller's own record (OA-7; never through
Fabric'sagent.callyet, FAA-146).McpToolServer.well_known_protocols(protocols)addsoperator-source/1when
the server readssource. Nothing writes it to an event, a usage report, a log or a page (a test holds that). - A job's approval step reads the operator's mandate (contract interop C3.3,
conformance.md; part of FAA-135):
job_step/jobStep,CallContext.approval_step,request_input(steps=)/requestInput(..., {steps}); with
input_actionson, an answer's_meta.mandatethat covers a kept step is read too, under the job's own capability
as at the start (action_gate(mandate_capability=)/actionGate({mandateCapability})). Each question replaces the
kept steps and an answer clears them, so a step outlives neither its question nor its answer. - DEC-0136 closes at the pin: MD-2a is the pinned contract's rule, the by-name test override is gone, and both
twins run the contract's 30handOncases; FAC-SEM-144's message ends(DEC-0125 MD-1, MD-2; DEC-0136 MD-2a).
Vectors at.12: mandate 40 cases and 5 fixtures, actions 67 cases and 16 action calls. - working-with-fabric, its board reference,
interop.mdand the shared operator-channel reference cite DEC-0126 OA-1
to OA-7 and drop the "ahead of the pin" wording; scenario evalrelay-source-goes-only-where-it-is-read. - DEC-0128 (DL-14a), a hub key per organization context.
hub_key_certified/hubKeyCertifiedtake the
token's context (context=/{context}), andverify_delegationanddelegation_gate(Node:verifyDelegation,
delegationGate) takeestate_context/estateContext. An organization's token verifies only under its context's
hub key (machines[].contextHubKeys) while the record pins the organization; a context that is neitherpersonal
nororg:<orgId>fails closed (kid-not-certified).verify_approvaltakes no context, as the contract declares. - Both estate readers check organization pins and context hub keys as the contract's
estate-record-rules.tsdoes at
.12:FAC-SEM-147is new;FAC-SEM-073,076,114and134read context hub keys. fabric_estatesplits its record rules into the new kit modulefabric_estate_rules(Node:
fabric-estate-rules.mjs), listed just before it inkit.json;fabric_estatere-exports every public name, so callers
keep their imports, andfabric_kit.py install --module fabric_estatecopies both.- An organization instance whose context the record does not pin still starts (
fabric_context); its tokens are
refusedkid-not-certified(brief D5). - Vectors regenerated at
.12:estate-vectors.json(the organizations positive, seven context and organization
semantic fixtures, 38 generated context and revision cases),hub-key-vectors.json(contextCases33,
contextVerifier10); the hostile estate inputs covercontextHubKeys,organizationsand hub-key contexts. - DEC-0135, who a reply reaches (narrowing). A reply reaches the sender of the message it answers; send
deliverTo: "thread"for every participant, or a list, and on a board pinned before DEC-0135, which refuses the
member, a new message to the thread instead. The kit's responder posts only fenced replies, which carry none.
The narrowing: once Fabric adopts DEC-0135 (planned for its 0.3.15; DR-5, for a reader it takes as pinned at or
after DEC-0135; Fabric to 0.3.14 keeps the old count for every reader),com.list {unread: true}and the unread
count cover only what was delivered to your Project, so a reply sent only to another Project no longer shows.
project-board.mdnow also says a threadcom.read_ackwithupTomarks what your Project did not send, as the
board always did. - DEC-0130 and DEC-0137, the family and the agent network (wording, no code change):
delegation.mdnames the
operator's family (T0 and T1) and the agent network, andestate-record.mdsays a tokenless call runs in the
instance's context. - The session hooks' notes on nudges follow Fabric 0.3.14's final behaviour (board
b605a636, Fabric
release/0.3.14b97ec351): a turn's answer shows a nudge, at most five, and a repeated turn answers the same; it
stays forfabric_work_listandfabric_heartbeat, so it may come again there. The nudges of turns spooled before a
/clearare therefore not lost (0.51.2's notes said they were).
Added
- Being seen correctly in Fabric (FAA-151, board
c42071bc): working-with-fabric Steps 4 and 5 claim the
person's request as one task titled with it in one line (origin: {kind: "person", ref}), file each split with
fabric_task_createwhile it is claimed, and link it withfabric_task_link(rel: "spawned") unless the answer
sayschained: truewithchained_tothe claimed task: always in a session opened from its own terminal, which
Fabric to 0.3.14 never links itself. They report the stage withstepandofSteps, and send a heartbeat with
phase: waitingbefore a wait (the wait innote, orwaitingOnfor a question asked withfabric_question_ask).
Scenario evalseen-correctly-goal-and-step. - Fabric's rule
work.check-every-three-minutes(FAA-141, Fabric TO-2, aske2a7bec4): working-with-fabric
Step 5 and creating-fabric-agents' autonomy reference tell an agent to callfabric_work_listevery three minutes
and before any wait, act first on itsnudgesandwaiting_for_you, read and mark its unread board answers, and
never leave a held task still. Scenario evalcheck-every-three-minutes. - Nine protocol register entries (DEC-0126, DEC-0127, DEC-0129, DEC-0131, DEC-0132):
fabric-machine-report/1,
machine-report/1,operator-source/1,org-authority/1,org-authority-draft/1,org-key-set/1,
org-key-set-draft/1,org-membership/1andorg-root-registration/1infabric_protocolsand
fabric-protocols.mjs, so the kits' register equals the contract's at the pin.
v0.51.2: fabric-agent-adapter
Added
- Reminders Fabric gives on a turn reach the agent (FAA-142, Fabric ask
dfb43ffb). From Fabric 0.3.14, the
answer to a turn'sfabric_session_observemay carrynudges: lapsed claims, unread board answers, requests nobody
moved. The Stop hook's worker keeps their text in the session's spool (nudges.json), and the UserPromptSubmit
hook prints each one once on the next prompt, after the unread line, as Fabric writes it (each startsFabric:; any other line gets the tagFabric reminder:): one line each, at most five lines of at most
400 characters. An empty or absent list prints nothing. A turn thatwatch-maildelivers has its nudges printed by
watch-mail, once. Not carried across/clear: nudges of turns spooled before it stay in the old id's folder and are
lost, since Fabric gives each nudge once and the turn's answer took it (carrying them would make the new id's folder before the launched-session check of FAA-68). Claude Code only: the Codex and Kimi
Code hooks run at session start and send no turns.
Fixed
- The hooks now name this release to Fabric: 0.51.1 still said 0.51.0, and
test/validate.pynow checks it.
Copylot check4028969a-aa35-48f9-9300-331da036fb1d: no findings.
v0.51.1: fabric-agent-adapter
The tag v0.51.0 was never published: its release run stopped at the Node 18 floor, so 0.51.1 is the first npm
release carrying everything listed under 0.51.0 below.
Fixed
- The tests of one instance per context (
test/fabric-context.test.mjs, FAA-125) skip on Linux under a Node that
cannot bind an abstract socket name exactly (Node 18 and 20), as every other lock test does since FAA-72. The kit
itself already refused there with a sentence; only the tests lacked the guard. references/package.mdno longer says the passkey countersignature waits fororg-authority/1: DEC-0129 decided
that protocol only as the organization's member list for relay admission, and the countersignature waits for the
decision that defines theorg-countersign/1verifier (contract finding on board thread6c50c84f).
v0.50.0: fabric-agent-adapter
Changed
- Pinned to the contract release
v0.1.0+2026-10-25(52c7d798) (FAA-117, contract CT-124). The release carries
DEC-0112 to DEC-0121; only DEC-0121 changes kit code, the others bind a node, an organization server or Project
Observatory and only regenerate vendored vectors (connections gains seven cases, the protocol register
fabric-actions/0.1).2f020a6stays supported. The brief is
docs/evidence/specs/2026-10-26-faa-117-118-repin-52c7d79-brief.md(decisions D1 to D17).
Added
-
Dashboard action parity (DEC-0121). Every button a dashboard offers the operator is an agent action too.
fabric_actions.py/fabric-actions.mjsport the contract'sactionDecision,actionsViewand the FAC-SEM-142
rules, checked againstfixtures/vectors/actions.json(65 cases) and the semantic fixtures.- A service declares its actions with
McpToolServer.add_tool(..., action=, target=, human_step_url=)and
publishes them withbuild_well_known(actions=)/buildWellKnown({actions})asextensions/actions/0.1; a
declaration the contract would flag is refused. - The server gate reads only
_meta.decision, never the arguments: a T0 or T1 call without the operator's
decision is refusedoperator-decision-required, a malformed onedecision-invalid, a step only a person can
takehuman-step-requiredwithdata.humanStepanddata.url, and T2 still needsapproval-required. The
decision records the authenticated caller (identify, the delegation's caller, orlocal:T0/local:T1).
Gating a job's input responses is opt-in (input_actions=True) until Fabric's hub forwards decisions (CT-125).
The kits are ready now; a service adopts the gate once CT-125 lands, as the contract says (DEC-0121
Compatibility, CT-127), because Claude Code and Codex cannot set_metaand would otherwise meet
operator-decision-requiredwith the page as their only route. - A caller relays with
request_meta(decision=...)/requestMeta(traceparent, {decision}). - The three codes join DL-19 in both delegation twins.
-
The probe reports
well-known.actions,interop.action-tools(a declared action's tool must be served),
dashboard.actions-declared(NOT_RUNfor a dashboard that declares none) anddashboard.opens-in-host
(FAA-118: a browser opener aimed at the service's own pages in its checkout fails;--repo PATHnames the
checkout). -
working-with-fabric relays the operator's own words as the decision (AP-3), never text from a tool result, a
page, a file or another agent; a coding agent whose client cannot set_metagoes through Fabric'sagent.call
(after CT-125), the service's elicitation, or the action's page in Fabric Dashboards, in that order (confirmed by
the contract's owner on board thread92c6f277). Onhuman-step-requiredit hands the person the link. -
Fabric's button follows contract DEC-0124 (dashboard kit 1.1.5; FB-3 amended on the adapter's request, board
thread4801da8d, contract PR #189, after this release's pin). The accessible name and the menu follow the page's
language, which the contract now states, so 0.49.3's note that the kit departs from FB-3 no longer holds. A page's
own words (data-label-*) are taken only when they keep the word Fabric (WCAG 2.2 SC 2.5.3, Label in Name);
otherwise the built-in words stay. A page that set words without Fabric under 0.49.3 shows the built-in words
after it updates its kit. The kit follows this amendment ahead of the pin (brief decision D17). -
The probe's new
dashboard.opens-in-hostcan fail a service that passed before: one that opens its own pages
in a browser. Open them through Fabric Dashboards instead (see the building-fabric-services skill).
Changed (guidance)
- Agent pages open only in Fabric Dashboards (FAA-118, the operator's rule of 2026-10-24): the rule now sits in
the body of the three SKILL.md files, with the AGENTS.md block a project carries and a section for an agent that
has a page and no service descriptor. Manual restore and enabling the operator channel are the operator's
decision on the page or relayed (D16).
v0.49.3: fabric-agent-adapter
Fixed
- Fabric's button speaks the page's language and carries no em dash (dashboard kit 1.1.4, FAA-119; asked by two
services on the board). The button wrote U+2014 into the console message a person reads when it is not shown, and
its four strings were English only, so a Russian dashboard could not mount it without editing a vendored file that
dashboard_kit.py installthen refuses as changed. The message now uses a colon. The button reads the page'slang
and shows Russian on a Russian page, English otherwise;data-label-chat,data-label-session(with{name}),
data-label-openanddata-label-absentset a page's own words. The strings were checked with Copylot in the
PassionCode.ai site voice (en-US report239ff62c-e0af-4b8b-96e1-0c45c5eca02a, ru-RU report
be37143c-aff1-4502-b9d0-f8b1be56bbf3, no findings). A test fails on U+2014 in any kit asset, and the WebKit test
opens a Russian page and a page with its own words. The button's accessible name follows the page too (Открыть Fabric, or
data-label-open), which departs from the pinned contract's FB-3 ("Open Fabric" in every case) the way Fabric
Dashboards' own button already does; the amendment to allow a localized name is proposed to the contract (board
thread4801da8d). - A remote service registers where its code lives (FAA-120, a finding from a service that patched its descriptor
after the kit wrote it).registerRemoteandregister_remotetakesource({repository}), which the contract's
service-descriptor.schema.jsondescribes and Fabric Dashboards reads. The descriptor check now validatessource
in every descriptor: an object withrepositoryonly, a URI of RFC 3986 characters (the same answer as the
contract's ajv-formatsurion 112 cases, brackets only around an IP-literal host), a host and a port up to 65535
when it names an authority (Fabric reads it withnew URLand drops a descriptor it cannot read), no
user:password@, and no value shaped like a credential;Noneandnullmean no source in
both twins. A provider entry'ssource.repository(fabric_provider.py) refuses a password or a credential the same
way. The probe's data rule and the SessionStart notice no longer raise on a descriptor whosesourceis not an
object.