Ein lokales Desktop-Werkzeug einbinden
Ein Agent kann ein Werkzeug treiben, das auf einer echten Maschine liegt: einer GPU-Workstation, einer lizenzierten CAD-Kiste, einem Build-Host. Dafuer schreiben Sie eine Deklaration, Sie aendern nicht SupaCloud. Ein neues Werkzeug ist eine neue Deklaration plus ein Capability-Label; es gibt keinen Code zu ergaenzen und nichts auszurollen.
Diese Seite ist der vollstaendige Vertrag. Wenn Sie ihr folgen, laeuft das Werkzeug — und der Agent kann einen echten Fehlschlag von einer Maschine unterscheiden, die nie gefragt wurde.
Die Form
Abschnitt betitelt „Die Form“Deklarationen liegen auf einer ssh_host-Resource, in einem desktop-Block. Die
Resource traegt bereits Adresse, Login-Benutzer, SSH-Zugangsdaten (im Secret-Bag)
und die Host-Key-Bindung; der desktop-Block ergaenzt, was dort ausgefuehrt
werden darf.
{ "host": "workstation.tailnet", "port": 22, "username": "runner", "host_key_fingerprint": "SHA256:…", "desktop": { "read_command": "cat --", "stat_command": "/usr/local/bin/mtime-ms", "max_report_bytes": 524288, "tools": { "kicad": { "…": "eine Deklaration je Werkzeug" } }, "gui_only_tools": [], "input_command": null, "screenshot_command": null }}An read_command und stat_command haengt der Treiber genau einen geprueften
Pfad-Token an. read_command gibt eine Datei auf stdout aus; stat_command gibt
deren Aenderungszeit als Epoch-Millisekunden aus (Epoch-Sekunden werden ebenfalls
verstanden). Sie werden deklariert statt angenommen, weil cat / type /
Get-Content nicht auf jedem Host dasselbe Wort sind.
Ohne read_command kann nichts zurueckgelesen werden — und jeder Lauf auf diesem
Host sagt das, statt den Exit-Code als Urteil auszugeben.
Die sechs Achsen einer Werkzeug-Deklaration
Abschnitt betitelt „Die sechs Achsen einer Werkzeug-Deklaration“1. Identitaet und Capability-Label
Abschnitt betitelt „1. Identitaet und Capability-Label“"kicad": { "label": "kicad" }Der Schluessel (kicad) ist das, was ein Agent als tool uebergibt. Das label
ist das freie, flache Capability-Label, das der Runner fuer die Platzierung meldet
— blender, gpu, windows, was immer Ihr Enrollment meldet. Nichts in SupaCloud
zerlegt es, und nirgends existiert eine Liste bekannter Werkzeuge; ein Label ist ein
undurchsichtiger Token, der passt oder nicht.
2. Aufloesung des Binaries
Abschnitt betitelt „2. Aufloesung des Binaries“"binary": "kicad-cli","binary_env": "KICAD_CLI","probe_command": "which"An probe_command wird binary angehaengt; es muss den aufgeloesten Pfad ausgeben.
Gibt es nichts aus, ist das Werkzeug nicht verfuegbar — ein benannter Zustand,
der als verdict: "untrusted" mit tool_availability.available: false
zurueckkommt, und die Arbeit laeuft gar nicht erst. Das ist absichtlich etwas
anderes als ein fehlgeschlagener Lauf: „der Exporter ist nicht installiert” und
„die Platine ist durch die DRC gefallen” duerfen nie gleich aussehen.
binary_env wird in der Absage genannt, damit dem Betreiber gesagt wird, welche
Variable den Pfad ueberschreiben wuerde.
Lassen Sie alle drei weg, findet keine Pruefung statt.
3. Versionsvertrag
Abschnitt betitelt „3. Versionsvertrag“"version_command": "kicad-cli version","version": "10.0.5","version_contract": "exact"Drei Semantiken, und die Deklaration sagt, welche gilt:
version_contract |
Bedeutung | Wann richtig |
|---|---|---|
exact |
muss der deklarierten Version gleichen | eine deterministische Pipeline, deren Ergebnis ein Fertigungsartefakt ist — die Ausgabe eines Exporters kann sich zwischen Patch-Releases aendern, und ein abweichender Gerber ist eine andere Platine |
minimum |
muss >= der deklarierten Version sein | ein Werkzeug, das man wegen seiner Faehigkeiten nutzt, wo neuer in Ordnung ist |
minor_series |
gleiches MAJOR.MINOR, Patch >= deklariert | eine LTS-Linie, die Patch-Kompatibilitaet zusagt |
Ein unbekanntes Wort faellt auf exact zurueck — die strengste Lesart, damit ein
Tippfehler nie still ausweitet, was Sie akzeptieren. Eine Version, die den Vertrag
verletzt, blockiert den Lauf genauso wie ein fehlendes Binary.
Und ebenso eine Version, die sich nicht lesen laesst: scheitert Ihr
version_command, endet es ungleich null oder fehlt es ganz, waehrend eine version
deklariert ist, wird der Lauf blockiert statt durchgewunken. Eine Festlegung, die
niemand prueft, ist schlechter als keine, denn die Deklaration behauptet, sie werde
durchgesetzt — und das ist nicht hypothetisch: OpenSCAD lieferte jahrelang ein
--version aus, das mit 1 endete. Rufen Sie tool.list einmal mit probe: true
auf und lesen Sie reported_version, bevor Sie sich auf eine Festlegung verlassen.
Lassen Sie version weg, wird nichts geprueft.
4. Einstiegspunkt und Argumentform
Abschnitt betitelt „4. Einstiegspunkt und Argumentform“"command": "python /opt/bw-tools/pcb-release-pipeline/bin/validate_release.py --package-root /srv/boards/frg1","cwd": "/srv/boards/frg1","arg_allowlist": ["--release"],"timeout_ms": 900000cwd ist der Ort, gegen den Ihre deklarierten relativen Evidenzpfade aufgeloest
werden — Report, Log und Artefakte. Auf das Kommando wird es bewusst nicht
angewandt: ein cd X && …-Praefix ist in Windows PowerShell 5.1 ein Syntaxfehler und
wechselt in cmd.exe nicht das Laufwerk, und der entfernte Dialekt ist nicht
erkennbar — derselbe Grund, aus dem Argumente abgewiesen statt escaped werden.
Ihr Einstiegspunkt stellt sich selbst — absoluter Pfad oder eigene Klammer
(sh -lc 'cd … && …').
Die Pipeline ist der Vertrag. Deklarieren Sie den Einstiegspunkt, den die Pipeline selbst dokumentiert; bauen Sie nie einen eigenen Werkzeugaufruf und zeigen Sie nie auf das nackte Binary, wenn eine Pipeline es umschliesst. Ein Treiber, der die Ausgabe eines Werkzeugs selbst interpretiert, waere eine zweite Wahrheit neben der der Pipeline.
Der Agent darf Argumente anhaengen, und jedes muss frei von Leerzeichen,
Shell-Metazeichen und jedem ..-Segment sein. Ein Token, das das nicht ist, wird
namentlich abgewiesen, nicht maskiert — der entfernte Shell-Dialekt ist nicht
wissbar (ein Windows-OpenSSH-Login landet je nach Registry-Schluessel in cmd.exe
oder PowerShell), also ist korrektes Maskieren nichts, was sich tun laesst.
Freiformiger Inhalt gehoert in script, das ueber stdin reist — und stdin zerlegt
keine Shell.
arg_allowlist schraenkt, wenn nicht leer, ein, welche Flags der Agent
uebergeben darf. Reine Operanden sind davon unberuehrt.
5. Der Evidenz-Vertrag — der wichtige
Abschnitt betitelt „5. Der Evidenz-Vertrag — der wichtige“"report": "08_Test_Validation/aggregate.json","report_kind": "pcb_aggregate","log": "Artifacts/unity/editor.log"Ein blosser Exit-Code ist nie die Antwort. Die Deklaration sagt, wo die Pipeline ihr eigenes Urteil schreibt und wie es zu lesen ist:
report_kind |
Liest | Anmerkung |
|---|---|---|
fmctl_envelope |
status + exit_code |
details ist menschlicher Freitext und geht nie ins Urteil ein |
pcb_aggregate |
status, und unabhaengig davon native_kicad und counts.critical |
ohne Release-Modus ignoriert der oberste Status ein gescheitertes natives Tor absichtlich; deshalb werden beide gelesen und jedes darf Nein sagen. manufacturing_status ist fest verdrahtet und ist Kontext, nie ein Urteil |
publish_ready |
Vorhandensein der Marke + id/assets |
die Marke wird zu Laufbeginn geloescht und erst nach allen Toren wieder geschrieben, ihr frisches Vorhandensein ist das Urteil; das Build-Manifest wird nicht gelesen, weil es nicht vorab geleert wird und ein Ueberbleibsel sein kann |
json_pointer |
den Wert an Ihrem pass_pointer |
zaehlen Sie die akzeptierten Werte in pass_values auf — oder die scheiternden in fail_values; nichts wird erraten |
opaque |
nichts | die Datei wird geholt und gezeigt, und das Ergebnis sagt ausdruecklich, dass es kein Urteil traegt |
Wenn die eigene Bestehensregel der Pipeline eine Anzahl ist statt eines Wortes, zaehlen Sie auf, was scheitert, statt was besteht:
"report_kind": "json_pointer","pass_pointer": "/summary/parts","fail_values": ["0"]Nur fail_values zu deklarieren kehrt die Regel um — jeder Wert besteht ausser den
genannten —, denn die bestehenden Werte einer Anzahl lassen sich nicht aufzaehlen.
Verbot gewinnt: ein Wert in fail_values ist rot, was immer pass_values sagt. Das
ist keine Bequemlichkeit. Die 3D-Pipeline weist ihre eigene FreeCAD-Stufe mit
if (!objects.parts.length) throw ab, und eine Deklaration, die nur die Form des
Reports pruefen kann, haette eine leere Konvertierung gruen genannt.
Zwei Wachen gelten fuer jede Art, und Sie konfigurieren sie nicht:
- Frische. Der Report muss nachweislich zu diesem Lauf gehoeren — per mtime
(
stat_command) oder, auf einem Host ohne mtime, dadurch, dass die Datei vor dem Lauf nicht existierte oder sich ueber den Lauf hinweg geaendert hat. Ein Report, der sich nicht datieren laesst, istuntrusted, nie gruen. Diese Wache existiert, weil ein Werkzeug, das vor dem Schreiben seines Reports abbricht, den vorigen gruenen Report liegen laesst — und ein Ueberbleibsel sieht genau wie ein frischer aus. - Widerspruch. Ein Gruen verlangt, dass alle verfuegbaren Signale uebereinstimmen — der Report, dessen eigener Exit-Code und der Prozess-Exit. Jeder Widerspruch ist ein Fehlschlag. Diese Wache existiert, weil ein Werkzeug, das bei Fehlschlag 0 zurueckgibt, Ihnen eine in sich stimmige Luege reicht.
log ist eine zusaetzliche Datei, die geholt und nach den Markern eines
Batch-Harness durchsucht wird (error CS, Aborting batchmode, Application will terminate with return code N). Der Scan kann ein Gruen nur wegnehmen, nie
gewaehren: Compile-Fehler erreichen keinen Report, aber „kein Marker gefunden” ist
auch das, wonach ein leeres oder nie geschriebenes Log aussieht.
6. Artefakte und Provenienz
Abschnitt betitelt „6. Artefakte und Provenienz“"artifacts": ["Artifacts/unity/health.json", "Artifacts/assets/report.json"],"provenance": "kicad-cli-native"artifacts werden nach dem Lauf geholt und mit dem Ergebnis zurueckgegeben, das
zur Nutzlast der MCP-Aufruf-Audit-Zeile wird. Deklarieren Sie alles, was Sie spaeter
lesen wollen: der Report-Baum einer Maschine ist meist gitignored und lokal, also
ist Evidenz, die nicht gehoben wird, mit dem Ende des Laufs weg.
provenance ist eine freie Markierung auf dem Ergebnis, damit ein Leser erkennt,
welcher Weg ein Ergebnis erzeugt hat — nicht bloss, dass eines da ist.
7. Wohin ein erzeugtes Asset gehoert
Abschnitt betitelt „7. Wohin ein erzeugtes Asset gehoert“Text-Artefakte reisen inline im Ergebnis mit. Binaerdateien nicht — und sie
duerfen niemals roh committet werden. Ein Repository, das pro Iteration eine 40 MB
grosse .blend schluckt, ist binnen einer Woche unbrauchbar, und kein
History-Rewrite holt das zurueck.
Deklarieren Sie darum einmal je Host, wohin dessen Binaerdateien gehen:
"assets": { "lfs_patterns": ["*.blend", "*.fbx", "*.png"], "external_resource": "renders"}lfs_patterns— Binaerdateien, die das Projekt tatsaechlich neben seiner Quelle versioniert (ein Referenz-Render, ein Golden Mesh). Sie werden ueber Git LFS verfolgt: der Commit traegt einen Zeiger, nicht die Bytes.*.exttrifft ueber die Endung, Gross-/Kleinschreibung egal; alles andere trifft den Dateinamen exakt.external_resource— der Name einerseafile_webdav-Resource, die alles Uebrige aufnimmt: Turntables, Zwischen-Bakes, Lauf-Ausgaben, die das Repository gar nicht verfolgen soll.
Jedes gehobene Artefakt traegt dann ein storage-Objekt, das seinen Platz nennt:
"storage": { "route": "git_lfs", "stored": false, "pattern": "*.blend", "gitattributes": "*.blend filter=lfs diff=lfs merge=lfs -text"}Die gitattributes-Zeile steht mit Absicht woertlich da — tragen Sie sie in die
.gitattributes des Repositories ein, bevor Sie etwas Passendes committen. Das
abschliessende -text ist keine Zier: ohne es normalisiert Git womoeglich die
Zeilenenden einer Datei, die es fuer textartig haelt, und beschaedigt das Asset
beim Auschecken.
Wer nichts deklariert, bekommt jede Binaerdatei als "route": "unrouted" —
mit einer Begruendung, die das fehlende Muster benennt. Das ist Absicht. Der
naheliegende naechste Griff eines Agenten mit einer unerklaerten Binaerdatei ist
git add, und genau das soll diese Regel verhindern. Die Route wird deshalb nie
geraten.
stored ist immer false. SupaCloud leitet das Asset weiter, es bewegt es
nicht. Die Bytes wirklich in eine externe Bibliothek zu liefern braeuchte deren
Zugangsdaten auf dem Laufpfad — und ein Desktop-Runner ist standardmaessig
untrusted und secret-denied (siehe die Isolationsnotiz weiter unten). Lesen Sie die
Route als Anweisung an den, der committet oder hochlaedt: Agent oder Mensch.
Ein durchgerechnetes Beispiel
Abschnitt betitelt „Ein durchgerechnetes Beispiel“Eine KiCad-Freigabepruefung auf einer Linux-Workstation, von Anfang bis Ende.
{ "host": "bench-01.tailnet", "username": "runner", "host_key_fingerprint": "SHA256:9k2…", "desktop": { "read_command": "cat --", "stat_command": "/usr/local/bin/mtime-ms", "tools": { "kicad": { "label": "kicad", "binary": "kicad-cli", "binary_env": "KICAD_CLI", "probe_command": "which", "version_command": "kicad-cli version", "version": "10.0.5", "version_contract": "exact", "command": "python /opt/bw-tools/pcb-release-pipeline/bin/validate_release.py --package-root /srv/boards/frg1", "cwd": "/srv/boards/frg1", "arg_allowlist": ["--release", "--allow-missing-kicad"], "report": "08_Test_Validation/PCB_Release_Pipeline_RC3/aggregate.json", "report_kind": "pcb_aggregate", "artifacts": [ "08_Test_Validation/PCB_Release_Pipeline_RC3/native_kicad/native_kicad.json" ], "provenance": "kicad-cli-native", "timeout_ms": 1800000 } } }}mtime-ms ist ein zweizeiliges Skript, das Sie einmal auf dem Host installieren:
#!/bin/sh# gibt die Aenderungszeit einer Datei in Epoch-Millisekunden aus[ -e "$1" ] || exit 1echo $(( $(stat -c %Y "$1") * 1000 ))Ein Agent entdeckt und startet es dann so:
tool.list { "host": "bench-01", "probe": true }tool.run { "host": "bench-01", "tool": "kicad", "args": ["--release"] }und bekommt ein Urteil zurueck, mit dem er arbeiten kann — samt der Begruendung:
{ "tool": "kicad", "verdict": "failed", "truth_source": "envelope", "provenance": "kicad-cli-native", "tool_availability": { "available": true, "reported_version": "10.0.5", "version_satisfied": true }, "evidence": { "state": "fresh", "contract": "pcb_aggregate", "provenance": { "fields_read": ["status", "native_kicad", "counts.critical"], "values": { "status": "fail", "native_kicad": "fail", "critical": 1 } } }, "warnings": ["native_kicad is FAIL — ein natives ERC/DRC/Export-Tor ist gescheitert. …"], "artifacts": [{ "path": "…/native_kicad.json", "lifted": true, "encoding": "utf-8" }]}Beginnen Sie mit einer Referenz-Deklaration
Abschnitt betitelt „Beginnen Sie mit einer Referenz-Deklaration“docs/desktop-tools/reference-declarations.json liefert einen fertigen Eintrag fuer
jedes Werkzeug, das dieses Haus tatsaechlich betreibt — Blender, Unity, FreeCAD,
KiCad und OpenSCAD (zweimal: einmal als Batch-Skript, einmal mit maschinenlesbarem
Report). Kopieren Sie das entry-Objekt in Ihren desktop.tools-Block und passen
Sie die Pfade an.
Jeder Eintrag traegt mehr als die Deklaration. Er nennt, woher jede Behauptung
stammt, warum genau dieser Evidenz-Vertrag fuer diese Pipeline der richtige ist,
was ungeprueft bleibt, und ein status-Wort, das Sie lesen sollten, bevor Sie einem
Gruen glauben:
grounded— Einstiegspunkt, Reportpfad und Bestehensregel wurden aus dem Quelltext der Pipeline selbst gelesen. Nicht gegen Hardware ausgefuehrt.declared— die Form stimmt und der Evidenz-Vertrag ist aus dem dokumentierten Verhalten des Werkzeugs hergeleitet, aber der Quelltext der Pipeline lag nicht vor. Einmal proben und laufen lassen, bevor Sie ihm glauben.
Die Deklarationen halten auch die werkzeugspezifischen Fallen fest, und die lohnen
sich selbst dann, wenn Sie etwas anderes deklarieren — es sind die Formen, die sich
wiederholen. FreeCADs Exit-Code ist ueberhaupt nicht zu glauben, weil FreeCADs
Interpreter das SystemExit des Skripts verschluckt; OpenSCADs --summary-file gibt
es in der letzten stabilen Ausgabe nicht; eine Pipeline, die ihre Argumente ueber
eine Umgebungsvariable nimmt, braucht einen kleinen Wrapper auf dem Host, weil der
Ausfuehrungskanal ein Kommando und stdin traegt, aber keine Umgebung.
Der GUI-Rueckfall
Abschnitt betitelt „Der GUI-Rueckfall“Manche Werkzeuge haben ueberhaupt keinen skriptbaren Weg. Nur fuer diese kann die Maschine ueber ihren Bildschirm getrieben werden:
"screenshot_command": "/usr/local/bin/desktop-shot","input_command": "/usr/local/bin/desktop-input","gui_only_tools": ["legacy_cam"]desktop.observe nimmt den Bildschirm auf; desktop.act spritzt ein Eingabeereignis
ein. Das Tor ist strukturell: ein Werkzeug mit deklariertem Einstiegspunkt kann nie
per GUI getrieben werden, und die Absage nennt stattdessen tool.run. Nur ein
Werkzeug, das Sie in gui_only_tools gelistet haben, darf es. Diese Reihenfolge ist
per Konfiguration nicht verhandelbar — deklarieren Sie ein Werkzeug beides, gewinnt
der Einstiegspunkt.
legacy_cam oben steht fuer eine Klasse, nicht fuer ein Produkt, das wir treiben.
Jedes Werkzeug der Referenz-Deklarationen hat einen kopflosen Weg, also ist
gui_only_tools auf diesen Hosts leer und dieser ganze Abschnitt bleibt ungenutzt —
das ist das beabsichtigte Ergebnis, keine Luecke.
Eines braucht dieser Rueckfall, was die Treiber nicht brauchen: eine interaktive
Desktop-Sitzung. Ein ueber SSH gestartetes Kommando landet auf Windows in einer
nicht-interaktiven Sitzung ohne Desktop, den man aufnehmen oder anklicken koennte.
Die kopflosen Treiber bleiben davon unberuehrt; desktop.observe und desktop.act
setzen voraus, dass die Maschine angemeldet ist und der Agent in dieser Sitzung
laeuft. Siehe
Einen Desktop-Host fuer SSH scharfschalten.
Beide Kommandos laufen auf der Maschine und rufen deren eigene OS-Eingabe-API. Nichts laeuft ueber VNC oder RDP; die bleiben ein Weg fuer einen Menschen zum Zuschauen, nie der Weg, auf dem etwas handelt.
Vertrauen, in klaren Worten
Abschnitt betitelt „Vertrauen, in klaren Worten“- Die Deklaration schreibt ein Betreiber, nie meldet sie der Runner. Sie entscheidet, welches Kommando auf dieser Maschine laeuft und welches Feld als Wahrheit geglaubt wird. Ein Runner, der seine eigene schriebe, wuerde sein eigenes Erfolgskriterium erklaeren — und ein Edge-Runner ist standardmaessig nicht vertrauenswuerdig.
- Die SSH-Zugangsdaten verlassen den Server nie. Der Agent nennt eine Resource; er sieht nie Schluesselmaterial, und die Werkzeuge auf der Maschine auch nicht.
- Werkzeug-Lizenzen bleiben auf der Maschine. SupaCloud schiebt kein Lizenz-Geheimnis auf einen Desktop-Host. Ein Dongle oder ein maschinengebundener Sitz hat nichts, was SupaCloud verwahren koennte.
- Die Maschine liegt ausserhalb der Container-Grenze. Ein Desktop-Host fuehrt die
Kommandos Ihres Agenten als echter Benutzer auf einem echten Betriebssystem aus —
ohne
cap_drop, ohne nur lesbares Wurzeldateisystem, ohne PID-Grenzen. Nehmen Sie eine dedizierte Workstation, nicht Ihren Alltagsrechner, und rechnen Sie damit, dass die Isolation eines Containers hier fehlt.
Ihr eigenes Werkzeug einbinden
Abschnitt betitelt „Ihr eigenes Werkzeug einbinden“- Installieren Sie Werkzeug und Pipeline auf der Maschine.
- Ergaenzen Sie einen Eintrag unter
desktop.toolsmit den sechs Achsen oben. Waehlen Sie diereport_kind, die zu dem passt, was die Pipeline wirklich schreibt; schreibt sie einen JSON-Report mit einem Erfolgsfeld, decktjson_pointerdas ohne jeden Code ab. - Ergaenzen Sie das Capability-Label am Runner, damit die Platzierung dorthin routen kann.
- Rufen Sie
tool.listmitprobe: trueund bestaetigen Sie, dass das Binary aufloest und die Version Ihren Vertrag erfuellt. - Lassen Sie es einmal laufen und lesen Sie den Block
evidence.provenance. Steht dortopaque, oder ist der Zustandstale, korrigieren Sie die Deklaration, bevor Sie einem Gruen glauben.
Kein Schritt beruehrt den Quelltext von SupaCloud.