--- source_sha: "39ef16198c21" --- # Apps { #artifacts } Eine **App** ist eine Seite, die ein Agent veröffentlicht hat: ein Bericht, ein kleines Dashboard, eine einseitige Zusammenfassung, die jemand im Browser öffnet. Sie hat einen Link, der gleich bleibt, wenn der Agent sie erneut veröffentlicht, sodass „veröffentliche jeden Montag die Zahlen der Woche auf dieser Seite“ ein Link ist, den man sich als Lesezeichen speichert, statt jede Woche einen neuen. Eine App ist keine Datei. Ein Diagramm, ein erzeugtes PDF und eine Datei im Workspace haben bereits ein Zuhause im Chat und im [Workspace](sandbox.md). Eine App ist das, was *ausgeliefert* wird: Sie hat einen Besitzer, eine Sichtbarkeit und Grants wie ein Agent oder ein Skill, und sie kann einen öffentlichen Link für jemanden ohne Konto bekommen. ## Kennzeichnung geteilter Seiten { #shared-page-attribution } Öffentliche App-Ansichten zeigen standardmäßig ein schwebendes Badge mit dem AgenticOS-Logo und einem GitHub-Link. Geteilte Unterhaltungen zeigen dasselbe Badge unter der Unterhaltung. Owner oder Admins können die Fußzeile für die gesamte Organisation unter **Organisationseinstellungen → Governance → AgenticOS auf geteilten Seiten** ausblenden. Beim nächsten Seitenaufruf gilt die aktuelle Einstellung. Passwortgeschützte Apps zeigen die Fußzeile erst nach dem Entsperren. Die Fußzeile gehört zur Ansicht; heruntergeladene Dateien und eingebettete App-Inhalte werden nicht verändert. ## App-Darstellung { #app-appearance } Die angemeldete App-Ansicht und ihr öffentlicher Link haben unabhängige Schaltflächen für **helle** und **dunkle** Darstellung. Apps starten mit einer hellen Präferenz, unabhängig vom Konsolen- oder Systemdesign. Ein Wechsel ändert nur die `prefers-color-scheme`-Präferenz der eingebetteten Seite: Die App wird weder neu geladen noch werden HTML, CSS oder gespeicherte Versionen umgeschrieben. Festgelegte Farben bleiben erhalten; Apps mit Unterstützung beider Modi können auf die Auswahl reagieren. Den Kontrast bestimmt weiterhin die Seite. In den mitgelieferten Stilen ist `--ao-muted` eine Hintergrundfarbe; sekundärer Text verwendet `--ao-muted-fg` oder die Klasse `.ao-muted`. Ein Hintergrundwert als Textfarbe kann in beiden Modi unlesbar sein. ## Eine veröffentlichen { #publishing-one } Schalten Sie für den Agent die Capability **Apps** ein. Sie fügt fünf Tools hinzu: `publish_artifact`, das das Modell aufruft, wenn das Ergebnis etwas ist, das eine Person öffnen sollte, statt es einmal im Chat zu lesen; `read_artifact`, das eine veröffentlichte Seite zurückliest; `list_artifacts`, das die Seiten nennt, die ein Run öffnen darf; `preview_artifact`, das eine Seite zeichnet und sagt, was darin schiefgegangen ist; und `share_artifact`, das darum bittet, eine Seite zu teilen. Die Seite kommt von einer von drei Stellen: - **Eine Datei im Workspace des Agents**, die auf `.html` oder `.md` endet. Das ist der übliche Fall für einen Agent mit der Capability [Sandbox](reference/capabilities.md#files-shell): Er schreibt `report.html`, führt aus, was auch immer sie baut, und veröffentlicht dann die Datei. Die Bytes werden über das eigene Workspace-Backend des Runs gelesen, deshalb funktioniert das auf jedem Sandbox-Backend. - **Die Seite inline übergeben**, für einen Agent ohne Workspace oder eine kurze Seite. - **Änderungen an der Seite**, die unter diesem Namen bereits veröffentlicht ist — siehe unten. Der Chat zeigt eine Karte für die veröffentlichte Seite. Die Karte verlinkt auf die Version, die *dieser* Run veröffentlicht hat, sodass ein späteres Lesen der Unterhaltung weiterhin zeigt, was darin veröffentlicht wurde, und nicht, was die Seite heute zeigt. ### Einen Teil einer Seite ändern { #changing-part-of-a-page } Ein geändertes Wort sollte nicht noch einmal die ganze Seite kosten. Der Agent ruft `read_artifact` mit dem Namen der Seite auf, das die aktuelle Version so liefert, wie sie geschrieben wurde, und dann `publish_artifact` mit demselben Namen und `edits`: exakte Ersetzungen, der Reihe nach angewendet. Jede muss genau eine Stelle der Seite treffen; eine, die keine oder mehrere trifft, geht zur Korrektur an das Modell zurück. Das Ergebnis ist eine neue Version wie jede andere. Die Änderungen tragen die Version, auf der sie gemacht wurden. Hat dazwischen ein anderer Run veröffentlicht, wird nichts geschrieben, und das Modell soll die Seite neu lesen, sodass eine geänderte Kopie einer älteren Seite nie eine neuere ersetzt. Das Lesen folgt derselben Regel wie das Öffnen in der Konsole: Die Person, für die der Run handelt, muss die Seite öffnen dürfen. Eine Seite über 100.000 Zeichen kommt abgeschnitten zurück und sagt das; eine Änderung kann trotzdem Text hinter dem Schnitt nennen. ### Eine Seite wiederfinden { #finding-a-page-again } Jede Nachricht ist ein neuer Run, und ein Run sieht die früheren Runden nur als Text, deshalb behält das Modell die Namen nicht, unter denen es veröffentlicht hat. `list_artifacts` führt die Seiten des Agents in der Umgebung des Runs auf, die neueste zuerst und höchstens 50: jede mit Namen, Titel, aktueller Version, Format, Live-Bindings, Sichtbarkeit, ob ein öffentlicher Link eingeschaltet ist, wann sie veröffentlicht wurde, ihrem Link in die Konsole und `may_edit` - ob eine Veröffentlichung unter diesem Namen aus diesem Run die Seite aktualisiert, statt abgelehnt zu werden. Das Modell prüft die Liste, bevor es einen neuen Namen wählt. Die Liste wird als die Person gelesen, für die der Run handelt, nach der Regel der Konsole: ihre eigenen Seiten, die der Organisation und die mit ihr geteilten, oder jede Seite für eine Rolle, die jede Seite sieht. In einem Gruppenchat sieht der Kanal nur Seiten der ganzen Organisation: nie die privaten Seiten oder die Team-Seiten der sprechenden Person und auch keine Seite, die nur mit ihr geteilt ist. Eine Oberfläche, an der niemand angemeldet ist, etwa ein öffentliches Widget, listet nichts auf. ## Live-Daten { #live-data } Eine Seite kann Datensätze aus [Virtual Tables](virtual-tables.md) zeigen, die aktuell bleiben, statt der Kopie, mit der sie veröffentlicht wurde. Der Agent übergibt `publish_artifact` dafür `bindings`: Jedes Binding besteht aus einem Namen und einer **Seitenabfrage** (Page Query) - einer Tabelle, den Feldern, die die Seite daraus liest, und optional Filtern, einer Sortierung und einem Limit. Die Seite ruft `agenticos.onData(render)` auf, und `render(data)` läuft mit den ersten Daten und erneut bei jeder Aktualisierung. `agenticos.data` ist ein Promise auf die ersten Daten. ```js agenticos.onData((data) => { // data.tickets = { status, records, fields, count, truncated, limit } }); ``` Jedes Binding kommt als `status` (`ok` oder `unavailable`), `records` (`{id, : value}`), `fields` (Label, Typ, Optionen und Zustand jedes Felds), `count`, `truncated` und `limit` an. Eine Seite, die innerhalb von 10 Sekunden keine Antwort bekommt, sieht jedes Binding als `unavailable`; ein danach hinzugefügter Listener fragt noch einmal. **Alle, die eine Live-Seite öffnen, lesen dieselben Datensätze**, gleich welchen eigenen Zugriff sie auf die Tabellen haben. Die Abfrage wird mit dem Zugriff des [Besitzers](#who-owns-a-page) der Seite gelesen, und erst dann, wenn der Besitzer zugestimmt hat, sie zu teilen ([Einwilligung](#consent-the-owner-decides-who-reads-the-data)). Eine Seite ist also für Daten gedacht, die jeder sehen darf, der sie öffnen kann. Die Datensätze werden gelesen, wenn jemand die Seite ansieht, und nie in ihr gespeichert. ### Was ein Binding einfriert { #what-a-binding-freezes } Eine Seitenabfrage ist kein Link auf eine gespeicherte Ansicht. Der Agent nennt eine `table` oder eine [gespeicherte Ansicht](virtual-tables.md#saved-views) per ID oder exaktem Namen, und die Plattform **kopiert** die Filter und die Sortierung der Ansicht beim Veröffentlichen in die Seite. Wird die Ansicht später bearbeitet oder gelöscht, ändert das nichts an der Seite. | Im Binding | Bedeutung | |---|---| | `table` oder `view` | Die zu lesende Tabelle oder eine Ansicht, deren Filter und Sortierung kopiert werden. Nicht beides | | `fields` | Jeder Name, den die Seite liest, einer Spalte per ID oder Label zugeordnet | | `filters` | `{column, op, value}`, alle müssen zutreffen; zu den Filtern der Ansicht hinzugefügt | | `sort` | `{by, direction}`; ersetzt die Sortierung der Ansicht | | `limit` | Höchstens 1.000, zugleich der Standardwert; ersetzt das der Ansicht | `visible_columns` und `group_by` einer Ansicht werden ignoriert: Die `fields` des Bindings bestimmen die Spalten. Ein Filter darf eine Spalte nennen, die die Seite nicht liest, und der Besitzer erfährt, welche. Eine Seitenabfrage hat keine Freitextsuche, deshalb **lässt sich eine Ansicht mit Suche nicht binden**; das Veröffentlichen wird abgelehnt und nennt sie. Eine Ansicht, deren Filter eine nicht mehr vorhandene Spalte nennt, wird genauso abgelehnt. Formeln, Lookups, Rollups und Link-Labels sind erlaubt. Sie lesen mehr als die Spalte, die die Seite nennt, daher hält die Abfrage alles fest, was sie erreicht: die Eingabespalten einer Formel, den Link und das Ziel eines Lookups oder Rollups, jede verknüpfte Tabelle und die Spalte, nach der jede verknüpfte Tabelle ihre Datensätze beschriftet. Der Spec des Agents muss Lesezugriff auf die Tabelle freigeben, und die Person hinter dem Run muss jede Tabelle lesen können, die die Abfrage erreicht, denn niemand kann binden, was er nicht lesen darf. Das Ergebnis der Veröffentlichung beschreibt jede Abfrage in Worten - ihre Tabelle, Filter, Reihenfolge, Limit und jedes Feld mit dem, was es liest (`Status (single_select)`, `Pay band: formula over Salary, Level`). Es ist der Text, dem der Besitzer zustimmt, deshalb prüft der Agent, dass er das Gemeinte wiedergibt. Ein unverändert übernommenes Binding meldet `kept`. Der Name eines Bindings besteht aus Kleinbuchstaben, Ziffern und `_`. Ein Feld darf so heißen, wie seine Spalte beschriftet ist: Wörter aus Buchstaben in beliebiger Schreibung, Ziffern und `_`, getrennt durch je ein Leerzeichen, etwa `Status` oder `Created At`. Beide sind höchstens 64 Zeichen lang, und `__proto__`, `constructor` und `prototype` sind für beide reserviert (für ein Feld außerdem `id`). Eine Seite hat höchstens 5 Bindings, ein Binding höchstens 50 Felder. Version 3 des [Seiten-Kits](#the-library-set) zeichnet ein Binding für den Agent: `AO.records("#tickets", data.tickets)` innerhalb von `agenticos.onData` stellt die Datensätze als Tabelle dar, mit einem Kopf aus den Labels der Felder, Zahlen und Datumswerten rechtsbündig, Auswahlwerten als Badges und jedem Wert als Text geschrieben. Bevor die Daten da sind, zeigt es eine Ladezeile, und ein Binding, das `unavailable` oder leer ist, und eine gekürzte Liste bekommen je eine eigene Meldung. `AO.onData` in Version 3 ist derselbe Aufruf wie `agenticos.onData`. Eine gebundene Seite, deren Skript nie nach seinen Daten fragt, wird trotzdem veröffentlicht, mit einer [Warnung](#the-page-contract-and-the-publish-warnings). ### Der Workflow-Log als Live-Daten { #a-workflows-log } Ein Binding kann auch den Log eines [Workflows](workflows.md) lesen - die Zeilen, die die Quick Logs seiner Schritte geschrieben haben -, um ein Dashboard auf einer Seite zu bauen, die aktuell bleibt. Gib dem Binding `"kind": "workflow_log"` (ein Binding ohne `kind` liest eine Tabelle, wie jedes bisher veröffentlichte). Es ist eine zweite Art von Seitenabfrage und folgt denselben Regeln: Es wird beim Veröffentlichen aufgelöst, eingefroren, mit dem Zugriff des [Besitzers](#who-owns-a-page) der Seite gelesen und erst nach seiner [Zustimmung](#consent-the-owner-decides-who-reads-the-data). | Im Binding | Bedeutung | |---|---| | `workflow` | Die ID oder der exakte Name des Workflows. Es muss einer sein, den die Workflows-Capability des Agents bindet, wie bei einer [Aktion](#actions), und die Person hinter dem Run muss ihn sehen können - der Besitzer der Seite ebenso, wenn die Veröffentlichung das Binding ändert | | `step` | Optional: nur die Zeilen dieses Schritts, per ID, Label oder - bei einem Schritt ohne Label - dem Namen seiner Art, etwa `Send email`, wenn er eindeutig ist. Er muss in der Live-Version oder im Entwurf sein. Ein Schritt ohne Quick Log ist erlaubt, und das Ergebnis der Veröffentlichung vermerkt, dass er keine Zeilen liest | | `outcomes` | Optional: nur Zeilen mit einem dieser Ausgänge - einem Port oder `error`. Jeder trifft zu, und eine Zeile mit mehreren Ports, gespeichert als `a, b`, zählt für jeden | | `run` | Optional: eine Run-ID oder ihre ersten 4 oder mehr Zeichen | | `include_test` | Auch Zeilen von Testläufen. Standardmäßig aus | | `lines` | Die neuesten N Zeilen liefern, die neueste zuerst. Bis zu 500 in einem Binding; die eigene Route des Logs blättert zu je 100 | | `per_day` mit `days` und `tz` | Stattdessen die Zahl der Zeilen pro Tag und Ausgang liefern: `days` ist 7, 30 oder 90, heute eingeschlossen, und `tz` ein IANA-Name, dessen Tage gezählt werden (standardmäßig UTC) | Gib `lines` oder `per_day` an, nicht beides. Es sind die Filter und Limits von `GET /workflows/{id}/logs` und `GET /workflows/{id}/logs/summary`, vom selben Code bedient. Der Workflow und der Schritt werden beim Veröffentlichen zu IDs aufgelöst und mit den Filtern gespeichert: Wird der Workflow oder der Schritt später umbenannt, ändert das nichts an der Seite. Ein unbekannter Workflow, einer, den der Agent nicht bindet oder die Person nicht sehen kann (gleich formuliert, damit eine Ablehnung nie bestätigt, dass er existiert), ein unbekannter Schritt, ein Filter, der nicht stimmt, oder ein Limit über 500 lehnt das Veröffentlichen ab, und die Meldung sagt, was. Die Seite erhält es über denselben Aufruf `agenticos.onData`. Mit `lines` kommt ein Binding als `status`, `records` (jeweils `{id, created_at, step, outcome, status, message, run_id}`), `count`, `truncated` und `limit`, nach den Datensatz- und Byte-Limits der Seite gekürzt wie das einer Tabelle. Mit `per_day` kommt es als `status`, `days` (jeder Tag des Fensters, der älteste zuerst, je `{day, counts: {outcome: n}}`), `total`, `window_days` und `tz`, ohne Datensatz-Limit. ```js agenticos.onData((data) => { const week = data.errors_per_day; if (week.status !== "ok") return; AO.chart("#errors", { type: "bar", data: { labels: week.days.map((d) => d.day), datasets: [{ label: "Errors", data: week.days.map((d) => d.counts.error ?? 0) }], }, }); }); ``` veröffentlicht mit `bindings: {"errors_per_day": {"kind": "workflow_log", "workflow": "Order intake", "outcomes": ["error"], "per_day": true, "days": 30, "tz": "Europe/Warsaw"}}`. Das Ergebnis der Veröffentlichung und die Zustimmung des Besitzers sagen es in Worten: welcher Workflow, welche Filter und ob die neuesten N Zeilen oder Zählungen pro Tag über D Tage geliefert werden. Jede Änderung an einem Log-Binding - Workflow, Schritt, Ausgänge, Run, Testzeilen, `lines` oder `per_day`, Limit, Tage oder Zeitzone - ändert die Abfragen der Seite, braucht also erneut die Zustimmung des Besitzers, und eine [Prüfung der Datenänderung](#consent-the-owner-decides-who-reads-the-data) markiert sie als zeigt mehr. Ein Log hat keine Spalten, kann also nicht abdriften; stattdessen ist das Binding `unavailable`, wenn der Besitzer den Workflow nicht mehr sehen kann (`creator_lost_access`) oder der Schritt nicht mehr darin ist (`source_changed`, mit dem Namen `step`). Zwei Grenzen unterscheiden sich von denen einer Tabelle: Eine Seite, die einen Log liest, **kann keinen öffentlichen Link haben** - das Veröffentlichen eines Log-Bindings wird abgelehnt, solange der Link an ist, der Link lässt sich nicht einschalten oder an eine Version anheften, die einen Log liest, und eine solche Version lässt sich nicht wiederherstellen, solange der Link an ist. Ein Log-Binding wird auf einer öffentlichen Seite nie ausgeliefert. Die Rollen, die jede Tabelle lesen, lesen den Log einer Seite nur dann ohne Zustimmung, wenn sie auch jeden Workflow sehen, was jede eingebaute Rolle erfüllt, die jede Tabelle liest; eine Rolle, die das nicht erfüllt, wartet wie ein Editor auf die Zustimmung des Besitzers. ### Wer was sieht { #who-sees-what } Welche Version ein Betrachter ausgeliefert bekommt und ob seine Daten für ihn gelesen werden, hängt davon ab, wer er ist. | Betrachter | Ausgeliefert | Seine Daten | |---|---|---| | Der Besitzer der Seite | Die neueste Version | Immer | | Owner, Admin, Builder und Operator, auch nur lesend | Die neueste Version | Immer, für die Rollen, die jede Tabelle lesen (`tables:view` bei ALL) | | Jeder andere, der die Seite bearbeiten darf | Die neueste Version, damit der Agent eines Editors weiter am Aktuellen arbeitet | Solange die Einwilligung des Besitzers für diese Version gilt | | Alle anderen, die sie öffnen können (die **Zielgruppe**) | Die neueste Version, der je zugestimmt wurde oder die keine Daten liest | Solange die Einwilligung des Besitzers dafür gilt | Die Zielgruppe bekommt nie eine Version ausgeliefert, deren Teilen niemand zugestimmt hat. Gilt die Einwilligung für die Daten nicht mehr - ein Editor hat sie zurückgezogen oder der Besitzer hat gewechselt -, bekommt die Zielgruppe weiterhin diese **ganze Seite**, und jedes Binding kommt als `unavailable` mit dem Grund `awaiting_consent` an; die Seite sagt „The data appears once the page's owner confirms it.“ Ist keine Version überhaupt eingewilligt oder datenfrei, sieht die Zielgruppe „This page isn't available yet“ und sonst nichts. Ein Binding ist außerdem `unavailable`, wenn sich das, was es liest, seit der Veröffentlichung der Seite geändert hat. Der Grund wird nur jemandem genannt, der die Seite bearbeiten darf; ein Leser sieht, wie viele Quellen nicht verfügbar sind. | Grund | Was passiert ist | |---|---| | `creator_gone` | Der Besitzer der Seite ist kein aktives Mitglied mehr | | `creator_lost_access` | Der Besitzer kann eine Tabelle, die die Abfrage erreicht, nicht mehr lesen oder den Workflow, dessen Log sie liest, nicht mehr sehen | | `source_changed` | Der Schritt eines Log-Bindings ist nicht mehr im Workflow (`step` wird genannt), oder die Bedeutung einer Spalte hat sich geändert (ihr Typ, eine Formel, ein Link- oder Lookup-Ziel, ein Aggregat, eine archivierte oder wiederhergestellte Option), eine Spalte oder Tabelle wurde archiviert, oder eine verknüpfte Tabelle beschriftet ihre Datensätze jetzt nach einer anderen Spalte. Das aktuelle Label der Spalte wird genannt | | `awaiting_consent` | Der Besitzer hat das Teilen der Daten dieser Version nicht bestätigt, oder die Einwilligung wurde zurückgezogen | | `public_link` | Ein Log-Binding wurde auf einer öffentlichen Seite angefordert, die nie eines ausliefert | Fällt ein Binding aus, arbeiten die anderen weiter. Die Seite wird nie durch eine Tabellenänderung blockiert: Nichts hindert eine Spalte daran, geändert zu werden, und das Binding liefert nichts mehr, bis der Agent die Seite erneut veröffentlicht, wodurch die Abfrage gegen die Tabelle im heutigen Zustand aufgelöst wird. Eine Spalte umzubenennen ändert ein Binding nicht; die Seite liest sie weiter unter dem Namen, den der Agent vergeben hat. Eine Option zu einem Select hinzuzufügen oder eine umzubenennen, ändert es ebenfalls nicht. ### Einwilligung: Der Besitzer entscheidet, wer die Daten liest { #consent-the-owner-decides-who-reads-the-data } Die Daten einer Seite werden mit dem Zugriff ihres Besitzers gelesen, deshalb entscheidet der Besitzer pro Version, wer sie sonst noch liest. Diese Entscheidung ist die **Einwilligung** (Consent): Der Besitzer prüft die Seitenabfragen in Worten - Tabelle, Filter, Reihenfolge, Limit und Felder jeder Abfrage, jede verknüpfte Tabelle, die sie erreicht, und wer sie lesen wird - und bestätigt. Die Prüfung nennt, wer die Seite sehen wird: die Rollen, die jede Seite sehen (Owner, Admin, Builder und Operator), die Sichtbarkeit der Seite, die Personen, mit denen sie geteilt ist, und die Gruppen, einschließlich künftiger Mitglieder. Der Besitzer kann nur bestätigen, was er selbst liest. Die Daten einer Version befinden sich in einem dieser Zustände: | Zustand | Bedeutung | |---|---| | `not_needed` | Die Version hat keine Bindings | | `pending` | Niemand hat sie bestätigt. Die Zielgruppe behält die Version, die sie hatte | | `consented` | Der Besitzer hat sie bestätigt, oder ihr wurde automatisch zugestimmt | | `declined` | Eine Anfrage, sie zu bestätigen, wurde abgelehnt | | `withdrawn` | Eine Einwilligung, die nicht mehr gilt: Ein Editor hat sie zurückgenommen, oder ihr Besitzer ist nicht mehr der Besitzer der Seite | Einer neuen Version wird **automatisch** zugestimmt, wenn ihre Seitenabfragen - und alles, was sie erreichen - dieselben sind, denen der Besitzer bereits zugestimmt hat. Andernfalls ist sie `pending`: Das gilt auch für die erste gebundene Version, sofern der Besitzer nicht die [automatische Einwilligung](#auto-consent) eingeschaltet hat. Der Besitzer, und nur er selbst, bestätigt in der Seitenanzeige. Solange die neueste Version wartet, sagt das die Leiste unter dem Titel der Seite, mit **Confirm data**: Es öffnet, was die Version teilt und mit wem, und die Bestätigung trägt einen Fingerabdruck dessen, was genau angezeigt wurde. Wurde die Seite dazwischen erneut veröffentlicht oder haben sich ihre Abfragen verschoben, wird die Bestätigung abgelehnt, und der Besitzer schaut erneut hin. Das Bestätigen prüft außerdem, dass der Besitzer weiterhin jede Tabelle lesen kann, die die Abfrage erreicht. Ein Owner oder Admin darf für den Besitzer bestätigen. Das wird als von ihm für den Besitzer eingewilligt auditiert, und der Zugriff des Besitzers deckt die Daten weiterhin. Wer einen `edit`-Grant hat, kann das nicht: Ein Grant zum Bearbeiten der Seite ist keine Befugnis darüber, wessen Daten sie liest. **Zurückziehen** nimmt die Einwilligung zurück. Jeder, der die Seite bearbeiten darf, nutzt als er selbst **Stop sharing data** im Menü der Seitenanzeige. Jede bisher erteilte Einwilligung wird ungültig, nicht nur die letzte, und die automatische Einwilligung schaltet sich aus. Auch jeder `edit_data`-Grant auf der Seite fällt auf `edit` zurück, sodass die nächste erneute Veröffentlichung eines Daten-Editors auf den Besitzer wartet, statt das Zurückziehen rückgängig zu machen. Die Zielgruppe behält die Seite und verliert ihre Daten, bis der Besitzer erneut bestätigt. Das Zurückziehen schränkt nur ein, wer liest, deshalb darf es jeder Editor. Verlässt der Besitzer die Organisation, verfallen seine Einwilligungen mit seiner Mitgliedschaft. Ein Besitzer, der entfernt und erneut eingeladen wird, fängt von vorn an. Ein Besitzer, der deaktiviert und wieder aktiviert wird, belebt seine Einwilligungen neu: Das Reaktivieren ist ein kontoweiter Vorgang, in jeder Organisation, zu der das Konto gehört. #### Erneut veröffentlichen { #republishing } Veröffentlicht ein anderer Editor als der Besitzer eine Version, die andere Daten liest, ist die Version `pending`, und die Plattform stellt eine **Versionsanfrage** für den Besitzer - siehe [Freigabeanfragen](#share-requests). Bis der Besitzer entscheidet, sieht die Zielgruppe weiterhin die frühere Version. Das Ergebnis der Veröffentlichung sagt „The audience still sees v2 until Alex confirms this version's data in the console.“ (mit dem Namen des Besitzers oder „the page's owner“). Ein Run ohne Person dahinter kann eine gebundene Seite nur unverändert erneut veröffentlichen - dieselben Bytes und dieselben Abfragen, wofür es weder eine Person noch eine Einwilligung braucht. Er kann den Code, der die Datensätze des Besitzers empfängt, nicht austauschen. Eine Veröffentlichung durch einen Editor wird abgelehnt, wenn der Besitzer der Seite eine Tabelle, die die Abfrage erreicht, nicht lesen kann. #### Automatische Einwilligung { #auto-consent } Der Besitzer kann für eine Seite **Share new versions automatically** einschalten. Standardmäßig ist es aus, und nur der Besitzer schaltet es als er selbst ein, nie ein Agent. Es fragt zuerst: „Any data change your runs make to this page, including new tables and columns, reaches everyone who can open it without asking you. This includes API runs.“ Der Audit-Eintrag hält den bestätigten Text fest. Es deckt nur die eigenen erneuten Veröffentlichungen des Besitzers ab. Die erneute Veröffentlichung eines Editors wartet weiterhin. Automatische Einwilligungen werden als automatisch vermerkt, und erreicht eine mehr als die Version, die vor ihr ausgeliefert wurde, führt die Leiste des Besitzers sie auf: „v4 was shared automatically and shows more data: +Deals, +Salary“. Der Besitzer oder ein Owner oder Admin schaltet es aus, und auch das Zurückziehen oder das Übergeben der Seite an jemand anderen schaltet es aus. Es deckt nie einen Run ab, der begann, als ein Administrator den Besitzer imitierte, so spät er auch veröffentlicht: Ein Hintergrund-Run, ein Workflow-Schritt oder ein nach einer Genehmigung fortgesetzter Run trägt das mit sich, und dasselbe gilt für den Grant eines Daten-Editors (unten). Das erneute Veröffentlichen von Abfragen, die der Besitzer schon bestätigt hat, ist weiterhin eingewilligt. #### Daten-Editoren { #data-editors } Der Freigabe-Dialog hat eine Grant-Stufe über `edit`: **Can edit, including data** (`edit_data`), nur für Seiten. Sie ist eine dauerhafte Einwilligung in die erneuten Veröffentlichungen dieser Person, eine Änderung der Abfrage eingeschlossen. Nur der Besitzer oder ein Owner oder Admin erteilt sie, als er selbst, nach einem Popup: „Alex can change the data everyone sees on this page, including new tables and columns, without asking you. This also covers their API runs.“ Bei einer Gruppe sagt das Popup „Anyone in Sales, now or later, can change ...“. Der Grant wird mit dem bestätigten Text auditiert. Eine erneute Veröffentlichung durch jemanden mit `edit_data` wird automatisch eingewilligt und als über seinen Grant erfolgt vermerkt. Er muss weiterhin jede Tabelle lesen können, die die Abfrage erreicht, und der Besitzer ebenfalls. Der Rollenscope von Owner oder Admin impliziert ihn nie. Jeder, der die Seite bearbeiten darf, senkt oder entfernt ihn, und ein Wechsel des Besitzers lässt jeden `edit_data`-Grant auf `edit` fallen. ### Wem eine Seite gehört { #who-owns-a-page } Der Besitzer einer Seite ist die Person, für die der erste veröffentlichende Run gehandelt hat. Er ändert sich nie von selbst und ist der einzige Datenbesitzer der Seite: die Person, deren Zugriff die Daten liest und deren Einwilligung sie teilt. Wird der Besitzer gelöscht, hat die Seite keinen. Niemand kann eine Seite für sich beanspruchen. Der Besitzer übergibt sie mit **Change owner** im Menü der Seitenanzeige an ein anderes aktives Mitglied der Organisation, und ein Owner oder Admin darf jede Seite neu zuweisen, auch eine, deren Besitzer weg ist. Jeder tut das als er selbst, nicht durch Impersonation. Ein Grant zum Bearbeiten der Seite erlaubt es nicht. Es gibt dafür kein Agent-Tool. Der Wechsel erfolgt sofort, ohne Annahmeschritt. Die Daten der Seite erreichen die Zielgruppe nicht mehr, bis der neue Besitzer sie bestätigt, jede bisherige Einwilligung wird endgültig ungültig (die Seite zurückzugeben belebt also nie eine alte), die automatische Einwilligung schaltet sich aus, jede ausstehende Anfrage wird hinfällig, jeder `edit_data`-Grant fällt auf `edit` zurück, und der alte und der neue Besitzer werden benachrichtigt, außer wer den Wechsel vorgenommen hat. Er wird als `artifact.owner_changed` auditiert. Einer Seite ohne Besitzer die Sichtbarkeit `private` zu geben, gilt als Übernahme und ist nur einem Owner oder Admin möglich. Es gelten dieselben Regeln. ### Freigabeanfragen { #share-requests } Zu erweitern, wer eine Seite öffnen kann, die Daten liest - eine weitere Sichtbarkeit oder ein neuer Grant oder eine höhere Stufe -, ist die Entscheidung des Besitzers oder die eines Owners oder Admins für ihn. Einschränken steht jedem Editor offen. Eine Seite, die keine Daten liest, behält die üblichen Freigaberegeln. Wer sonst die Seite bearbeiten darf und versucht, sie zu erweitern, bekommt keine Ablehnung. Die Plattform stellt eine Anfrage. Es gibt drei Arten: | Art | Gestellt von | Bittet | |---|---|---| | `audience` | Einem Editor, im Share-Dialog oder durch das `share_artifact` seines Agents | Zu erweitern, wer die Seite öffnet | | `access` | Einem Mitglied der Organisation der Seite, das ihren Link ohne Zugriff öffnet | Die Seite öffnen zu dürfen, mit einer optionalen Notiz | | `version` | Der Veröffentlichung eines anderen Editors, dessen Version andere Daten liest | Diese Version zu bestätigen | Ein Mitglied kann pro Seite je eine ausstehende `audience`- und eine ausstehende `access`-Anfrage haben, und eine Seite hat eine ausstehende `version`-Anfrage; eine neuere ersetzt die ältere. Anfragen sind standardmäßig auf zehn pro Stunde je Person und Organisation begrenzt ([`RATE_LIMIT_ARTIFACT_SHARE_REQUESTS_PER_HOUR`](configuration.md)). Eine `version`-Anfrage zählt auch gegen das Kontingent des Veröffentlichenden: Danach ersetzt eine erneute Veröffentlichung die ausstehende Anfrage weiterhin, der Besitzer wird aber nicht erneut benachrichtigt. Der Besitzer der Seite entscheidet. Hat sie keinen Besitzer mehr, können die Owner und Admins ablehnen; um zu genehmigen, übergeben sie die Seite zuerst an ein Mitglied (**Change owner**), das die Daten bestätigt. Eine Anfrage wird ohne Entscheidung hinfällig, wenn sich die Abfragen der Seite ändern, der Besitzer wechselt oder die Seite archiviert wird. Der Besitzer wird in der Konsole und per E-Mail benachrichtigt, wie unter **Settings → Notifications** eingestellt („Page requests“). Die Benachrichtigung nennt die Seite und den Anfragenden, nie ihre Daten. Beantwortet werden sie in einem Abschnitt **Requests** des Share-Dialogs, und eine Schaltfläche **Requests** in der Leiste der Seitenanzeige zeigt, wie viele warten. Eine `audience`- oder `access`-Anfrage zu genehmigen, wendet die Sichtbarkeit und die Grants an, um die sie gebeten hat, jeweils nur dort, wo es noch erweitert. **Es willigt nie in Daten ein**: Hat die Version, die die Zielgruppe ausgeliefert bekommt, keine Einwilligung, die gilt, wird das Genehmigen mit „Confirm the page's data before sharing it“ abgelehnt, und der Besitzer bestätigt zuerst die Daten und genehmigt dann. Ablehnen teilt nichts. Eine `audience`- oder `access`-Anfrage, die unter einer Mitgliedschaft gestellt wurde, die inzwischen beendet ist (das Mitglied wurde entfernt und erneut eingeladen), lässt sich nicht genehmigen; lehnen Sie sie ab. Die Liste der Anfragen (`GET /api/v1/artifacts/{id}/share-requests`) enthält immer alle ausstehenden Anfragen. Mit `include_decided=true` fügt sie die entschiedenen und hinfälligen hinzu, die aufbewahrt werden, neueste zuerst und seitenweise mit `skip` und `limit` (standardmäßig 50, höchstens 100); `total` zählt alle, und der Freigabe-Dialog sagt, wenn er nur die neuesten entschiedenen zeigt. Der Anfragende erfährt die Entscheidung nicht: Dass die Seite sich öffnet oder nicht, ist die Antwort. Eine Seite, die keine Daten liest, nimmt eine einfache `access`-Anfrage entgegen, die jeder Editor von ihr genehmigen darf. #### Zugriff anfragen { #requesting-access } Ein Mitglied der Organisation, das den Link einer Seite öffnet, die es nicht öffnen kann, sieht „You don't have access to this page“, ein Notizfeld und **Request access**. Sonst wird nichts preisgegeben: kein Titel, kein Besitzer, kein Agent. Ein abgemeldeter Besucher oder jemand aus einer anderen Organisation bekommt das übliche „nicht gefunden“. #### Eine geänderte Version prüfen { #reviewing-a-changed-version } Eine `version`-Anfrage öffnet dem Besitzer die Seite auf der ausstehenden Version, mit ihren echten Daten. Die Leiste sagt, wer die Daten geändert hat, die die Version zeigt, mit **Review data change**. Das Panel vergleicht die Seitenabfragen der Version mit der, die die Zielgruppe jetzt sieht, Binding für Binding und in Worten: Felder, Filter, Reihenfolge, Limit und verknüpfte Tabellen, hinzugefügt, entfernt oder geändert, jeweils markiert als **Shows more**, **Shows less** oder ohne Änderung, neue Bindings mit ihrer vollständigen Offenlegung und unveränderte eingeklappt. Ein Schalter, **What others see**, zeigt die Seite so, wie die Zielgruppe sie hat. Das Panel nennt, wer die Version veröffentlicht hat und wann, und verlinkt auf die Unterhaltung, wenn der Besitzer sie öffnen darf. **Approve** bestätigt die Version. **Decline** hält die Zielgruppe auf der früheren Version und markiert die Version in ihrem Verlauf als `declined`; eine spätere erneute Veröffentlichung fragt erneut. In der Liste Requests bietet eine Versionsanfrage **Review data change**, nicht Approve: Sie wird aus der Prüfung heraus bestätigt. ### Eine Seite aus einem Agent heraus teilen { #sharing-a-page-from-an-agent } `share_artifact(name, visibility?, people?)` lässt einen Agent darum bitten, eine Seite zu teilen: `visibility` ist `team` oder `org`, `people` sind Mitglieder per vollem Namen oder E-Mail und Gruppen per Namen, jeweils auf Leseebene angefragt. Es **teilt nie etwas**. Es stellt eine `audience`-Anfrage für die Person, für die der Run handelt - der eigene Run des Besitzers eingeschlossen - und antwortet „Waiting for Alex to approve in the page's Share dialog“. Die Genehmigung ist eine Person in der Konsole, als sie selbst angemeldet, nie ein Tool-Aufruf. Ein Name, der auf niemanden oder auf mehrere Personen passt, wird zurückgemeldet, nie geraten. Es bittet um nichts Öffentliches: Der öffentliche Link wird nur in der Konsole eingerichtet. Ein Run ohne Person dahinter, etwa ein Widget-Besucher, kann nicht anfragen. ### Was der Betrachter sieht { #what-the-viewer-sees } Eine Seite mit Bindings trägt ein Badge **Live** auf ihrer Karte, im Artefakt-Widget des Dashboards und in der Seitenanzeige. In der Seitenanzeige pulsiert sein Punkt, während die Daten eintreffen, und wird bernsteinfarben, wenn einige Quellen für diesen Betrachter nicht verfügbar sind. Ein Betrachter, der keine davon lesen kann, sieht kein Badge, und auf einer älteren Version gibt es ebenfalls keines. Die Seitenanzeige hat **Refresh**, das die Daten erneut liest und eine Seite, die `agenticos.onData` verwendet, an Ort und Stelle aktualisiert; der Tooltip der Schaltfläche sagt, wann zuletzt gelesen wurde. Daneben liegt ein Menü für die automatische Aktualisierung: aus, jede Minute, alle 5 oder 15 Minuten oder jede Stunde. Jeder Betrachter wählt selbst, und die Wahl wird pro Seite in diesem Browser gespeichert. Die automatische Aktualisierung tut nichts, solange der Tab verborgen ist, und liest einmal, wenn der Tab zurückkommt und ein Takt verpasst wurde. Sie hört auf, wenn die Daten abgelehnt werden (die Seite ist für diesen Betrachter nicht mehr da) und wenn keine Quelle der Seite gelesen werden kann, bis der Betrachter auf Refresh klickt und ein Lesen antwortet. Ein Fehler, der nichts über die Seite aussagt, etwa ein Serverfehler, lässt sie weiter abfragen. Eine Antwort „busy“ (429) wird zweimal im Abstand von einer Sekunde wiederholt, bevor der Betrachter „Busy, try again in a moment.“ liest. Eine Zeile unter der Leiste erscheint nur, wenn etwas Aufmerksamkeit braucht: eine Version, die auf Bestätigung wartet, eine Anfrage zur Prüfung oder Quellen, die nicht gelesen werden können. Eine abgelehnte Seite sagt „This page's live data is not available to you.“ Ein Leser, dessen Quellen nicht gelesen werden können, sieht, wie viele („2 of 3 sources unavailable to you“). Ein Editor sieht außerdem für jede Quelle den Grund, mit einem Link **Open the table** zur gebundenen Tabelle. Veröffentlicht ein Agent eine neue Version, während jemand die aktuelle offen hat, wechselt die Seitenanzeige beim nächsten Lesen (einer manuellen oder automatischen Aktualisierung) auf die Version, die dem Betrachter jetzt ausgeliefert wird: Die Seite wird neu geladen, mit ihren eigenen Daten. Wer im Versionsmenü eine Version gewählt hat, bleibt bei ihr. Auf einer älteren Version werden keine Live-Daten gelesen. Die Seitenanzeige sagt „Live data only on the current version.“ mit **Open current** und, für einen Editor, **Restore** für diese Version. Eine Wiederherstellung löst die Seitenabfragen der Version gegen die Tabellen im heutigen Zustand auf, ist also eine neue Abfrage, wenn eine Tabelle sich weiterentwickelt hat, und wartet wie jede andere erneute Veröffentlichung auf die Einwilligung. ### Wie die Daten die Seite erreichen { #how-the-data-reaches-the-page } Öffnet die Konsole eine Seite, fragt sie nach einer signierten Adresse, die trägt, wer der Betrachter ist. Die Content-Route liest jedes Binding **einmal, beim Laden**, und schreibt das Ergebnis in das Plattform-Skript der Seite, sodass der erste `onData`-Aufruf seine Daten bereits hat und kein weiterer Roundtrip folgt. Ein [öffentlicher Link oder ein Embed](#live-data-on-a-public-link) wird auf dieselbe Weise beschrieben, unter strengeren Bedingungen. Für eine Vorschau, eine Karte oder eine Version aus dem Verlauf wird nichts eingefügt. Refresh und die automatische Aktualisierung nutzen weiterhin die Daten-Route und ein `postMessage` der Konsole, das jetzt für alle, denen diese Version ausgeliefert wird, dieselben Zeilen der Seitenabfrage beantwortet. Die Route wird nie gecacht, die Adresse ist kurzlebig, und der Inhalt eines Datensatzes bleibt aus den Logs heraus. Ein Binding, das nicht rechtzeitig gelesen werden kann oder in der Datenbank fehlschlägt, wird als `unavailable` eingefügt, und die Seite wird trotzdem dargestellt. ### Grenzen { #limits } Jede Antwort ist begrenzt, damit eine einzelne Seite nicht das Deployment lahmlegen kann: | Grenze | Wert | |---|---| | Bindings pro Seite | 5 | | Datensätze pro Binding | 1.000 | | Aufbereitete Daten pro Seite | 4 MiB, gerecht auf die Bindings verteilt | | Eine Abfrage | höchstens 2 s | | Eine Seite | Frist von 5 s | | Datenladevorgänge | 30 pro Minute je Betrachter, einer gleichzeitig je Betrachter und eine Obergrenze pro Prozess | | Lesevorgänge nur der Gründe (`rows=false`, Leiste des Editors) | Dieselben Grenzen, getrennt von den Datenladevorgängen gezählt | | Freigabeanfragen | 10 pro Stunde je Person und Organisation (audience, access und version) | Ein gekürztes Binding meldet `truncated: true`, mit `limit` auf `records` oder `bytes` und `count` als Zahl der ausgelieferten Datensätze. `bytes` deckt auch ein Lesen ab, dessen gemessene Kosten aufgebraucht waren, bevor die aufbereiteten Daten den Anteil der Seite gefüllt hatten. Die Frist wird vor jeder Abfrage geprüft, ohne eine abzubrechen: Jede Abfrage läuft höchstens so lange wie das Kleinere aus 2 s und der Restzeit der 5 s, und sind die 5 s verbraucht, beginnt keine Abfrage mehr, sodass eine Seite in etwa 5 s antwortet und die Wanduhrzeit etwa 7 s erreichen kann. Eingefügte Ladevorgänge zählen wie Aktualisierungen gegen die 30 pro Minute. Jenseits eines Rate-Limits antwortet die Daten-Route mit 429, die Konsole wiederholt eine Antwort „busy“ zweimal, und eine Seite, die mit ihren Daten ausgeliefert wurde, bekommt Bindings mit `busy`. Ist Redis ausgefallen, zählen die Untergrenze des Rate-Limits und das Limit gleichzeitiger Ladevorgänge pro API-Worker, und die Obergrenze pro Prozess begrenzt weiterhin jeden Worker. ### Wer eine gebundene Seite veröffentlichen darf { #who-may-publish-a-bound-page } Eine Seite bindet sich nur dann an eine Tabelle, wenn der Spec des veröffentlichenden Agents Lesezugriff auf sie freigibt und die Person hinter dem Run jede Tabelle liest, die die Abfrage erreicht, geprüft beim Veröffentlichen der Seite oder beim Wiederherstellen einer Version. Wird eines von beiden später entzogen, hält das bereits veröffentlichte Seiten nicht an; der Zugriff des Besitzers wird bei jedem Laden erneut geprüft. Eine gebundene Seite kann nur ein Run **ändern**, der für ein Mitglied handelt (eine unveränderte erneute Veröffentlichung, mit denselben Bytes und Abfragen, braucht keine Person): das angemeldete Mitglied (Web-Chat, HTTP-API), ein verknüpfter Absender in einem Kanal oder der Ersteller eines Zeitplans. Der Besucher eines Widgets, eines Embeds oder einer gehosteten Seite, ein nicht verknüpfter Absender in einem Kanal und die Zustellung eines Ereignis-Triggers eines Agents (GitHub, E-Mail, Webhook-Relay) zählen nicht, ebenso wenig ein Workflow-Run, den ein öffentlicher Eingang zugelassen hat, oder irgendein Run in der Kette eines solchen Runs. ## Aktionen { #actions } Eine Seite kann einen [Workflow](workflows.md) starten, aber nur, wenn eine Person zustimmt. Die Seite handelt nicht: Sie fragt, die Konsole zeigt der Person, was geschehen würde, und der Run gehört ihr. So wird eine Seite zu einer kleinen App - ein Formular, das eine Anfrage einreicht, ein Knopf, der eine Rechnung freigibt -, ohne dass die Seite selbst irgendeine Macht hält. ### Aktionen deklarieren { #declaring-actions } Der Agent deklariert die Workflows, die eine Seite starten darf, beim Veröffentlichen mit dem Argument `actions` von `publish_artifact`: bis zu fünf Einträge, jeweils ein Workflow (seine ID oder sein exakter Name) und ein **Label**, das Wort, mit dem die Seite ihn nennt. ```json {"actions": [{"workflow": "Approve invoice", "label": "Approve"}]} ``` Ein Workflow kann nur dann eine Aktion sein, wenn alles Folgende zutrifft: - Der veröffentlichende Agent hat ihn in der [Workflows-Capability](reference/capabilities.md#workflows), sodass das, was eine Seite starten darf, Teil dessen ist, was der Autor des Agents geprüft hat; - er ist live (veröffentlicht, nicht archiviert) und startet aus einem **Manual**- oder **API**-Trigger; - die Person hinter dem Run darf ihn ansehen und ausführen. Einen, den sie nicht darf, beantwortet die Plattform, als wäre er nicht gebunden, sodass eine Abweisung nie einen Workflow nennt, den die Person nicht sehen kann. Das Label ist reiner Text von höchstens 60 Zeichen, aus dem Steuer- und unsichtbare Zeichen entfernt wurden; ist es leer, wird es zum Namen des Workflows. Wird `actions` weggelassen, bleiben die der aktuellen Version erhalten, und `[]` entfernt sie. Das Ergebnis der Veröffentlichung führt jede deklarierte Aktion in Worten auf oder sagt `kept`. Eine Markdown-Seite kann keine Aktionen haben. Ein Run ohne Person dahinter kann sie weder hinzufügen noch ändern und kann nur eine Seite, die bereits welche hat, unverändert erneut veröffentlichen. Beim Wiederherstellen einer älteren Version werden ihre Aktionen erneut gegen den Agent in seinem heutigen Zustand geprüft, und sie wird abgewiesen, wenn eine nicht mehr besteht. Aktionen werden mit der Version gespeichert, sodass eine neue Version die ganze Liste ersetzt und eine ältere ihre eigene behält. Einen Workflow später vom Agent zu lösen ändert keine veröffentlichte Version; der eigene Zustand des Workflows und der Zugriff jedes Betrachters werden bei jedem Öffnen der Seite und erneut beim Start eines Runs geprüft. ### Wer welche Aktionen sieht { #who-sees-which-actions } Wenn die Konsole eine Seite öffnet, listet sie die Aktionen, die die **ausgelieferte Version** deklariert und die dieser Betrachter jetzt ausführen darf: jede mit dem echten Namen des Workflows, dem Label der Seite und den Feldern, die sein Trigger verlangt. Eine, die der Betrachter nicht ausführen darf, bleibt weg. Die Leiste des Betrachters zeigt **This page can run: ...** und nennt die Workflows, jeweils ein Link auf die Runs dieses Workflows. Ohne welche wird nichts gezeigt. Eine Seite bekommt keine Aktionen, und ihr `run`-Aufruf wird sofort abgewiesen, wenn sie gezeigt wird: - auf einem öffentlichen Link, in einem Embed, in einer Vorschau oder im Thumbnail einer Karte; - als ältere Version aus dem Verlauf oder als eine Version, die nicht die ist, die diesem Betrachter ausgeliefert wird (ein Publikum wartet noch auf die Einwilligung für die neuere); - für ein Mitglied, das keinen ihrer Workflows ausführen darf, oder für eine Session, die imitiert wird. Die Start-Route weist all das ebenso ab, sodass eine Seite, die die Konsole umgeht, nichts gewänne. ### Eine aus der Seite ausführen { #running-one-from-the-page } In der Seite bittet `agenticos.run(action, input)` - `AO.run` im [Seitenkit](#the-library-set) - die Konsole, eine Aktion zu starten. `action` ist eine Workflow-ID, das Label der Seite oder der Name des Workflows. Die Konsole gleicht erst die ID exakt ab, dann das Label, dann den Namen, ohne Beachtung der Groß- und Kleinschreibung. Eine Stufe, die auf zwei Aktionen passt, trifft nichts und fällt nicht auf die nächste durch. `input` ist ein Objekt mit genau den Werten, die der Trigger des Workflows deklariert. Der Aufruf gibt ein Promise des abgeschlossenen Runs zurück, und `onRun` (`AO.onRun`) hört unterwegs jede Änderung: ```js AO.run("Approve", { invoice: "INV-204" }).then( (run) => show(run.status, run.output_text), (error) => show("not run", error.message), ); ``` Die Konsole startet nichts, bevor die Person zugestimmt hat. Jeder Run, egal wie viele davor kamen, öffnet einen Bestätigungsdialog: - sein Titel ist der **eigene Name des Workflows**, und das Label der Seite erscheint nur als Zitat; - er zeigt jeden Wert, den die Seite sendet, vollständig und als Text, wobei Zeichen, die nichts zeichnen oder den Text umordnen, ausgeschrieben werden (`\u{202E}`); - **Run** ist eine halbe Sekunde deaktiviert und bekommt nie den Fokus, sodass ein Klick, der schon unterwegs ist, nicht zustimmt; Cancel hat den Fokus; - es ist immer nur eine Frage offen, über Run- und Link-Dialoge hinweg, und eine Anfrage, die in der Zwischenzeit eintrifft, wird als `busy` abgewiesen; - nach **drei Ablehnungen** bei einem Öffnen der Seite (ein Neuladen des Frames behält die Zählung) hört die Konsole auf zu fragen und weist weitere Anfragen ab, bis die Seite erneut geöffnet wird. Eine Anfrage wird ohne Dialog abgewiesen, wenn sie zu früh kommt (der Frame ist noch nicht geladen), eine Aktion nennt, die die Seite nicht deklariert, nicht zu den Feldern des Workflows passt oder von etwas anderem kommt als dem eigenen Frame der Seite unter der Nonce, die die Konsole für dieses Laden ausgestellt hat. Eine Nachricht von einem anderen Frame oder von einer veralteten Seite wird ohne Antwort verworfen. Das Promise wird mit einem `Error` abgelehnt, dessen `message` ein kurzer Code ist, damit die Seite ihren eigenen Text formuliert: | Code | Wann | |---|---| | `cancelled` | Die Person hat abgelehnt | | `busy` | Eine andere Frage war offen | | `too_many_cancels` | Drei Ablehnungen; die Seite wird nicht erneut gefragt, bis sie neu geöffnet wird | | `not_ready` | Die Seite hat gefragt, bevor ihr Frame geladen war | | `unknown_action` | Keine deklarierte Aktion passt, die der Betrachter ausführen darf | | `invalid_input` | Ein Wert fehlt, ist unbekannt oder hat den falschen Typ; `error.problems` listet `{field, kind}` nur, wenn die Konsole das Problem gefunden hat, und fehlt, wenn der Server mit 422 geantwortet hat | | `rate_limited` | Zu viele Runs von dieser Person, oder die Admission-Quote des Aufrufers oder der Organisation | | `not_found`, `unavailable` | Die Aktion ist weg, nicht mehr von Hand startbar oder archiviert, oder die Seite kann hier gar keine Aktionen ausführen (ein öffentlicher Link, ein Embed, eine Vorschau, der Verlauf, ein Betrachter ohne ausführbare Aktion) | | `failed` | Der Run konnte nicht gestartet oder verfolgt werden | | `page_changed` | Die Seite wurde ersetzt, während die Frage offen war | ### Was der Run ist { #what-the-run-is } Der Run gehört dem **Betrachter**: Er startet mit dessen Berechtigungen und zählt gegen dessen Budget, als hätte er den Workflow aus der Konsole ausgeführt. Sein Trigger ist **App** (`app`), und er hält die Seite und die Version fest, die die Person ansah. Die Seite verleiht nichts; der Run wird abgewiesen, wenn die Person ihn nicht selbst hätte starten können. Ein Run, der auf eine Freigabe wartet oder darauf, dass jemand ihn ansieht, bleibt im [Run-Verlauf](workflows.md#running-a-workflow) des Workflows, und der Betrachter zeigt eine Zeile mit **Open run**. Die Seite bekommt nie den ganzen Run. Sie liest eine Projektion: den Status, den Antworttext, die strukturierte Antwort ohne Dateiverweise, die erste Zeile eines Fehlers (höchstens 200 Zeichen) und, solange der Run auf jemanden wartet, den Pfad dorthin in der Konsole. Nur die Person, die den Run gestartet hat, kann ihn lesen, und nur über die Seite, die ihn gestartet hat. Die Konsole liest den Run mit wachsender Verzögerung und sendet jede Änderung an die Seite. Ein Run, der auf jemanden wartet, wird zehn Minuten lang gelesen, in denen der Tab sichtbar ist. Danach wird das Promise der Seite mit dem letzten Zustand, `paused: true` und ohne `finished` erfüllt, und die Seite hört nichts mehr; die Zeile im Betrachter ist der Weg zum Run. Einer Seite, die neu geladen oder geschlossen wird, wird nichts gesagt. ### Grenzen und Audit { #limits-and-audit } | Grenze | Wert | |---|---| | Aktionen pro Seite | 5 | | Label | 60 Zeichen | | Aus einer Seite gestartete Runs | 10 pro Minute und Person und Seite (`RATE_LIMIT_ARTIFACT_ACTIONS_PER_MINUTE`) | | Ablehnungen, bevor die Konsole aufhört zu fragen | 3 pro Öffnen der Seite (ein Neuladen des Frames behält die Zählung) | | Ausstehende Arbeit | die eigene Admission-Quote der Workflow-Runs, die ebenfalls mit `rate_limited` antwortet | Jenseits einer Grenze sieht die Person eine Meldung, und die Seite bekommt `rate_limited`. Jeder Start wird als `artifact.action_started` auditiert, mit der Seite, der Version, dem Workflow und dem Run - nie die Eingabe, die der Person gehört und am Run selbst steht. Siehe [Governance](governance.md#audit) und [die Sicherheitsseite](security.md#a-page-that-starts-workflows). ## Ein Name, ein Link { #one-name-one-link } Die Identität einer App ist ihr **Name innerhalb des Agents** — `weekly-report`, `churn-dashboard`. Wer unter demselben Namen veröffentlicht, aktualisiert dieselbe App, von jeder Oberfläche aus: dem Chat, der API, einem [Trigger](triggers.md) oder einem Workflow. Ein neuer Name ergibt eine neue Seite. Der Name besteht aus Kleinbuchstaben, Ziffern und Bindestrichen, bis zu 64 Zeichen. Der Name gilt auch **innerhalb der Umgebung**, aus der der Run geantwortet hat. Ein Run in einer benannten [Umgebung](environments.md) — `staging`, `dev` — veröffentlicht eine eigene Seite neben der der Standardumgebung, sodass der Versuch einer neuen Version eines Agents nie die Seite neu veröffentlichen kann, die Leser in Produktion als Lesezeichen haben. Die Umgebung wird vom Run selbst gelesen, nie vom Modell, und die Liste und die Seite nennen sie. Den Namen teilen sich alle, die den Agent ausführen, die Seite aber nicht. Ein Run veröffentlicht eine bestehende App nur dann erneut, wenn die Person, für die er handelt, sie besitzt oder `artifacts:edit` darauf hat — aus der Rolle oder aus einem `edit`-Grant, dieselbe Regel wie bei der Verwaltung in der Konsole. Der Run jeder anderen Person erfährt, dass der Name vergeben ist, und veröffentlicht unter einem anderen. Eine Kollegin, die denselben geteilten Agent um einen `weekly-report` bittet, kann die Seite hinter Ihrem Link also nicht ersetzen. Jede Veröffentlichung ist eine neue **Version**. Nichts wird überschrieben, daher ist die Versionsliste auf der Seite der App die Geschichte der Seite. Zwei Dinge halten diese Geschichte begrenzt: - Wer genau die Bytes veröffentlicht, die die aktuelle Version enthält, fügt keine Version hinzu. Das Ergebnis lautet `unchanged`, und ein Zeitplan, der nichts Neues gefunden hat, lässt die Geschichte in Ruhe. Es zählt trotzdem als Veröffentlichung, die Uhr der Aufbewahrung beginnt also von vorn. - Nur die neuesten Versionen werden behalten, `ARTIFACT_MAX_VERSIONS` davon (standardmäßig 20). Eine ältere Version wird entfernt, wenn eine neue hinzukommt — außer einer, an die der öffentliche Link angeheftet ist. Eine Unterhaltung, die auf eine entfernte Version verlinkt, sagt, dass die Version nicht mehr aufbewahrt wird, und bietet die neueste an. **Restore this version** holt eine aufbewahrte Version zurück. Ein Mitglied, das die Seite bearbeiten darf, öffnet die Version und stellt sie wieder her; das fügt eine neue Version mit den Bytes der alten hinzu, sodass die Geschichte behält, was passiert ist, und sich die Wiederherstellung genauso rückgängig machen lässt. Nichts Neues wird gespeichert. Es wird im Audit-Trail festgehalten. Die neue Version sagt, woher sie stammt: Die Versionsliste zeigt sie als `v4 · restored from v2`, und die API liefert ihre Nummer in `restored_from`, das bei einer Veröffentlichung leer bleibt. ## Unterstützte Formate { #supported-formats } | Format | Was gespeichert wird | Was ausgeliefert wird | |---|---|---| | HTML (`.html`, `.htm` oder `format: html`) | Das Dokument, wie der Agent es geschrieben hat | Dasselbe Dokument, hinter einem kurzen Plattform-Skript (siehe [Wie die Seite isoliert wird](#how-the-page-is-isolated)) | | Markdown (`.md`, `.markdown` oder `format: markdown`) | Die Markdown-Quelle | Die Quelle, gerendert zu einer schlichten Seite, Tabellen eingeschlossen. Rohes HTML darin wird maskiert | Eine Version ist ein in sich geschlossenes Dokument von höchstens `ARTIFACT_MAX_BYTES` (standardmäßig 5 MiB). Es gibt keine Bündel aus mehreren Dateien: Betten Sie Ihr eigenes Skript, Ihre Stile und Bilder (als `data:`-URIs) in die eine Datei ein. !!! warning "Die Seite hat kein Netzwerk" Skripte laufen, ein von einer Bibliothek gezeichnetes Diagramm funktioniert also. Aber die Seite kann nichts von irgendwoher laden und nichts irgendwohin senden — ein CDN-Skript, eine Webschrift von einer URL und ein API-Aufruf schlagen alle fehl. Die Ausnahmen sind der Bibliothekssatz unten, den das Deployment selbst ausliefert, und die [Live-Daten](#live-data), die die Konsole einer Seite mit deklarierten Bindings übergibt. Das Tool sagt das dem Modell. Es ist Absicht, und [Wie die Seite isoliert wird](#how-the-page-is-isolated) sagt, warum. ### Der Bibliothekssatz { #the-library-set } Das Deployment liefert neben jeder Seite einige Dateien aus, damit das Modell nicht mehr in jede eine ganze Diagrammbibliothek kopiert. Eine Seite lädt sie über eine relative Adresse und funktioniert weiter, wenn das Deployment seine Inhalte später auf einen anderen Origin verlegt: | Adresse in der Seite | Was es ist | |---|---| | `lib/chart-4.5.1.umd.min.js` | Chart.js 4.5.1, als `window.Chart` | | `lib/d3-7.9.0.min.js` | d3 7.9.0, als `window.d3` | | `lib/lucide-1.46.0.min.js` | Lucide-1.46.0-Icons, als `window.lucide` - derselbe Satz, den die Konsole verwendet | | `lib/agenticos-3.css` | Das Seiten-Kit: das Aussehen der Konsole für Seiten, hell und dunkel, und die `ao-`-Komponenten, aus denen eine Seite gebaut wird | | `lib/agenticos-3.js` | `window.AO`: Chart.js im Stil der Konsole, Zahlenformate in der Sprache der Seite, Icons, Tabs, `AO.onData`, `AO.records` sowie `AO.run` und `AO.onRun` für [Aktionen](#actions) | | `lib/agenticos-2.css`, `lib/agenticos-2.js` | Die vorige Version des Kits, behalten für die Seiten, die gegen sie veröffentlicht wurden | | `lib/agenticos-1.css` | Die erste Version des Aussehens, behalten für die Seiten, die gegen sie veröffentlicht wurden | | `lib/three-0.186.0/three.module.js` | three.js 0.186.0 als ES-Modul, mit `addons/controls/OrbitControls.js` daneben - `three` und `three/addons/` per Import Map darauf abbilden | | `lib/mermaid-12.1.0.min.js` | Mermaid 12.1.0, als `window.mermaid` | | `lib/leaflet-1.9.4/leaflet.js`, `leaflet.css` | Leaflet 1.9.4, als `window.L` - zeichnet auf den [Kartenkacheln](#visuals) des Deployments, wenn es welche hat | | `lib/agenticos-map-2.js` | `window.AOMap.itinerary`: nummerierte Pins, der Reihe nach gezeigt und auf Wunsch einzeln gesetzt, eine Karte im Verhältnis 16:9 zur Breite der Seite, eine Linie zwischen ihnen und Karten für die Stopps - auf Kartenkacheln, oder ohne sie als Umriss der Stopps. `lib/agenticos-map-1.js`, das die Pins beim Laden setzt und eine feste Höhe hat, wird für die Seiten, die es nennen, weiter ausgeliefert | | `lib/agenticos-map-1.js` | `window.AOMap.itinerary`: nummerierte Pins, der Reihe nach gesetzt, eine Linie zwischen ihnen, Pause und Wiederholung und Karten für die Stopps - auf Kartenkacheln, oder ohne sie als Umriss der Stopps | | `lib/topojson-client-3.1.0.min.js`, `lib/world-atlas-2.0.2-110m.js`, `lib/agenticos-worldmap-1.js` | `window.AOWorldMap.draw`: eine Weltkarte als SVG - Länder nach Wert schattiert, Pins und Großkreisrouten - ohne Kachelserver; nach d3 in dieser Reihenfolge laden | Jeder Name trägt seine Version und wird ein Jahr lang gecacht. Ein Upgrade fügt eine neue Datei neben der alten hinzu, sodass eine Seite, die gegen eine Version veröffentlicht wurde, weiter genau diese bekommt, und eine ausgelieferte Datei wird nie geändert. Nichts wird von außerhalb des Deployments geholt, also funktioniert ein Deployment ohne Internetzugang genauso. Die Dateien sind in `backend/app/core/catalog/artifact_lib/` aufgeführt. Die Dateien antworten mit `Access-Control-Allow-Origin: *`, weil ein Modulskript im CORS-Modus geladen wird und eine Seite in einem opaken Origin `Origin: null` sendet - ohne das würde three.js nicht importiert. Version 3 behält jede Klasse und jeden Aufruf von Version 2. Sie verzeiht außerdem Markup, das ein Modell nachlässig schreibt: Titel, Untertitel und Metazeile, die direkt in den Header gesetzt sind, stehen untereinander, statt sich über ihn zu verteilen, eine Tabelle ohne `` bekommt trotzdem ihre Kopfzeile, und eine Meldung, die in den Wrapper einer Tabelle geschrieben ist, bekommt Abstand. Neu sind Lade- und Hinweiszeilen, eine Beschriftungszeile, ein Live-Punkt, Listen, eine Werkzeugleiste mit Eingabefeldern sowie Icon und Aktionen im Header. Icons, die ein Skript nach dem Laden der Seite hinzufügt, wie es die Datensätze einer Live-Seite tun, werden ebenfalls gezeichnet. Der mitgelieferte [Skill](skills.md) **`artifact-pages`** bringt einem Agent bei, sie zu nutzen: das verbindliche Grundgerüst, jede Klasse des Kits, drei Vorlagen (ein Dashboard, ein Bericht und eine mit `AO.records` gezeichnete Live-Tabelle), den Hausstil, Icons statt Emoji und wie man eine Seite mit `read_artifact` ändert. Eine neue Organisation bekommt ihn mit den anderen mitgelieferten Skills, eine bestehende über `seed-skills`, und der Tab **Page style** der Capability Apps im Builder bietet ihn an. Bearbeiten Sie den Skill, um Ihre eigene Marke zu beschreiben, und der Agent folgt ihr. ### Der Seitenvertrag und die Warnungen beim Veröffentlichen { #the-page-contract-and-the-publish-warnings } Der Agent kann die Seite, die er veröffentlicht, nicht sehen, deshalb sagt ihm die Plattform, wie eine Seite aussehen muss und was an der gesendeten falsch aussieht. Die Capability Apps gibt jedem Agent, der sie hat, einen kurzen **Seitenvertrag** in seine Anweisungen, ob der Skill gebunden ist oder nicht: das Grundgerüst (Doctype, Viewport, `lang`, Stylesheet und Skript des Kits in dieser Reihenfolge, `main.ao-page` und der Header), eine abgeschlossene Liste der meistgenutzten Klassen des Kits, Lucide-Icons statt Emoji und `AO.records` für gebundene Daten. Bietet der Run des Agents den Skill `artifact-pages` an und hat ihn nicht geladen, wird seine erste vollständige HTML-Seite einmal zurückgeschickt, mit der Bitte, den Skill zu laden und die Seite neu zu bauen. Das geschieht einmal pro Run; jede neue Nachricht ist ein neuer Run, es kann also in einer späteren Runde wieder geschehen. Eine Änderung wird nie zurückgeschickt. Eine veröffentlichte HTML-Seite wird danach geprüft, und das Ergebnis trägt eine `warning` mit einer Zeile für jedes gefundene Problem. Die Seite wird so oder so veröffentlicht. Die Prüfungen: - kein `` in der ersten Zeile oder kein Viewport-Meta-Tag; - eine `ao-`-Klasse, im Markup oder in einem Skript genannt, die weder die von der Seite geladene Version des Kits noch ihr eigenes `