[{"data":1,"prerenderedAt":838},["ShallowReactive",2],{"blog-de-push-substituiert-push":3,"header-blog-translations-/de/blog/push-substituiert-push":835},{"id":4,"title":5,"author":6,"body":7,"date":819,"description":820,"draft":821,"extension":822,"image":823,"meta":824,"navigation":266,"path":825,"seo":826,"stem":827,"tags":828,"translationKey":833,"__hash__":834},"blog_de/blog/de/push-substituiert-push.md","Push substituiert Push","Patrick Hofmann",{"type":8,"value":9,"toc":811},"minimark",[10,19,26,37,42,65,79,83,86,93,96,99,130,133,137,140,143,172,183,187,190,414,417,717,724,728,748,751,765,769,780,791,801,804,807],[11,12,13,14,18],"p",{},"22:47 abends, Handy vibriert. Ein Agent will ein ",[15,16,17],"code",{},"git push"," auf einen Branch, für den er noch keinen Standing Grant hat. Lock Screen, Tap, Approve-Seite öffnet sich, Ja. Drei Sekunden, ohne den Rechner aufzumachen.",[11,20,21,22,25],{},"Das hat funktioniert. Jeder Approval-Prompt, den kein Pattern durchgelassen hat, ging über einen Telegram-Bot an mich. Bot-Token, ",[15,23,24],{},"chat_id"," pro User, ein bisschen Setup beim Onboarding. War eingebaut, lief.",[11,27,28,29,32,33,36],{},"Heute läuft derselbe Flow über Web Push direkt aus ",[15,30,31],{},"openape-free-idp"," und ",[15,34,35],{},"openape-chat",". VAPID, Service Worker, kein Telegram mehr.",[38,39,41],"h2",{"id":40},"eine-sache-die-das-nicht-ist","Eine Sache, die das nicht ist",[11,43,44,45,49,50,57,58,61,62,64],{},"Bevor das aussieht wie der nächste Schritt einer alten Story: das Blocking-Problem — ",[46,47,48],"em",{},"Agent wartet auf Approval, blockiert die Pipeline"," — ist nicht das, was hier gelöst wird. Das war ",[51,52,56],"a",{"href":53,"rel":54},"https://delta-mind.at/de/blog/wenn-dein-agent-nicht-tut-was-du-willst-frag-ihn-warum",[55],"nofollow","das Thema vom letzten Post"," und es wurde mit asynchronem Run + ",[15,59,60],{},"--wait"," + sysexits Exit-Code 75 gelöst. Der Pattern bleibt im Stack. Dass der Agent während der Wartezeit nicht weiß, was passiert, ist okay — kein Schaden, nur Sichtbarkeit, und dafür ist ",[15,63,60],{}," da.",[11,66,67,68,70,71,74,75,78],{},"Die zwei Dinge sind orthogonal. Async + ",[15,69,60],{}," kümmert sich um ",[46,72,73],{},"was macht der Agent während ich überlege",". Heute geht es nur um ",[46,76,77],{},"wie kommt die Anfrage zu mir",".",[38,80,82],{"id":81},"was-telegram-immer-war","Was Telegram immer war",[11,84,85],{},"Telegram war eine Erfüllung der Eigenschaft, nicht die Eigenschaft selbst.",[11,87,88,89,92],{},"Die Eigenschaft, die ich brauche, heißt ",[46,90,91],{},"out-of-band",". Approval-Anfragen dürfen nicht auf demselben Transport laufen, auf dem der Agent gerade arbeitet. Wenn der Agent über meinen Terminal-SSH-Tunnel arbeitet, kann die Notification nicht durch denselben Tunnel kommen — sonst sitze ich vor einem Terminal, in dem nichts passiert, und merke nicht, dass etwas auf mich wartet. Approval-Transport ≠ Worker-Transport. Das ist die Eigenschaft.",[11,94,95],{},"Telegram war die einfache Antwort: ein Drittanbieter, der per definitionem nichts mit meinem Agent-Stack zu tun hat. Out-of-Band war erfüllt, weil Telegram in einer anderen Welt lebt.",[11,97,98],{},"Aber der Drittanbieter brachte sein eigenes Setup mit:",[100,101,102,110,118,124],"ul",{},[103,104,105,109],"li",{},[106,107,108],"strong",{},"Bot-Token"," muss erstellt, rotiert und sicher gehalten werden",[103,111,112,117],{},[106,113,114,116],{},[15,115,24],{}," pro User"," muss bekannt sein, sonst geht keine Nachricht raus",[103,119,120,123],{},[106,121,122],{},"Telegram-Account vorausgesetzt"," — kein Account, kein Approval-Push",[103,125,126,129],{},[106,127,128],{},"Drittanbieter-Surface"," — alles was an Telegram geht, läuft durch Telegrams Infrastruktur",[11,131,132],{},"Was ich gewollt habe, war die Eigenschaft. Was ich gekriegt habe, war Eigenschaft + Konfigurations-Tail.",[38,134,136],{"id":135},"warum-web-push","Warum Web Push",[11,138,139],{},"Web Push ist out-of-band auf eine andere Art: der Browser hat seinen eigenen Push-Service (Apple, Mozilla autopush, FCM), der mit meinem Server nichts zu tun hat. Der IdP schickt eine VAPID-signierte Notification an den Push-Service, der stellt sie dem Browser zu, der Service Worker zeigt sie an, ich tappe drauf, lande auf der Approve-Seite.",[11,141,142],{},"Aus meiner Sicht: immer noch out-of-band, der Pfad geht nicht durch den Agent-Stack. Aus User-Sicht: alles ändert sich.",[100,144,145,151,160,166],{},[103,146,147,150],{},[106,148,149],{},"Kein Bot-Token zu rotieren"," — VAPID-Keypair liegt server-seitig, einmal generiert",[103,152,153,159],{},[106,154,155,156,158],{},"Kein ",[15,157,24],{},"-Setup"," — die Push-Subscription entsteht beim ersten Login auf dem Gerät, automatisch",[103,161,162,165],{},[106,163,164],{},"Kein Telegram-Account"," — jeder moderne Browser kann das",[103,167,168,171],{},[106,169,170],{},"Native Browser-Permission als expliziter Consent"," — der Browser fragt, der User entscheidet, OS-Standard-Dialog, nicht von mir gebaut, nicht umgehbar",[11,173,174,175,178,179,182],{},"Das letzte ist der Punkt, der mich am meisten überzeugt hat. Bei Telegram war der Consent diffus: ",[46,176,177],{},"du hast einen Account, du folgst einem Bot, also kriegst du Pushes",". Bei Web Push ist der Consent explizit, vom Browser durchgesetzt: ",[46,180,181],{},"erlaubst du dieser Origin, dir Notifications zu schicken? Ja/Nein."," Eine klare Antwort vom User, die ich nicht selbst eingeholt habe und nicht selbst durchsetzen muss.",[38,184,186],{"id":185},"wie-es-jetzt-aussieht","Wie es jetzt aussieht",[11,188,189],{},"Im IdP liegt ein VAPID-Keypair. Der Public Key ist in der Frontend-Bundle. Beim Login registriert der Browser den Service Worker, der eine Push-Subscription anlegt, die im IdP gespeichert wird. Wenn ein Agent einen Approval-Request stellt, signiert der Server die Notification mit dem VAPID-Private-Key und schickt sie an den Push-Service-Endpoint aus der Subscription.",[191,192,197],"pre",{"className":193,"code":194,"language":195,"meta":196,"style":196},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","// idp/server/push.ts (gekürzt)\nwebpush.setVapidDetails(\n  'mailto:patrick@delta-mind.at',\n  VAPID_PUBLIC,\n  VAPID_PRIVATE\n)\n\nawait webpush.sendNotification(\n  subscription, // { endpoint, keys: { p256dh, auth } }\n  JSON.stringify({\n    title: 'Approval erforderlich',\n    body: `${request.agent} möchte ${request.action}`,\n    data: { grantId: request.grantId },\n  })\n)\n","ts","",[15,198,199,208,225,241,249,255,261,268,285,297,314,334,374,401,409],{"__ignoreMap":196},[200,201,204],"span",{"class":202,"line":203},"line",1,[200,205,207],{"class":206},"sHwdD","// idp/server/push.ts (gekürzt)\n",[200,209,211,215,218,222],{"class":202,"line":210},2,[200,212,214],{"class":213},"sTEyZ","webpush",[200,216,78],{"class":217},"sMK4o",[200,219,221],{"class":220},"s2Zo4","setVapidDetails",[200,223,224],{"class":213},"(\n",[200,226,228,231,235,238],{"class":202,"line":227},3,[200,229,230],{"class":217},"  '",[200,232,234],{"class":233},"sfazB","mailto:patrick@delta-mind.at",[200,236,237],{"class":217},"'",[200,239,240],{"class":217},",\n",[200,242,244,247],{"class":202,"line":243},4,[200,245,246],{"class":213},"  VAPID_PUBLIC",[200,248,240],{"class":217},[200,250,252],{"class":202,"line":251},5,[200,253,254],{"class":213},"  VAPID_PRIVATE\n",[200,256,258],{"class":202,"line":257},6,[200,259,260],{"class":213},")\n",[200,262,264],{"class":202,"line":263},7,[200,265,267],{"emptyLinePlaceholder":266},true,"\n",[200,269,271,275,278,280,283],{"class":202,"line":270},8,[200,272,274],{"class":273},"s7zQu","await",[200,276,277],{"class":213}," webpush",[200,279,78],{"class":217},[200,281,282],{"class":220},"sendNotification",[200,284,224],{"class":213},[200,286,288,291,294],{"class":202,"line":287},9,[200,289,290],{"class":213},"  subscription",[200,292,293],{"class":217},",",[200,295,296],{"class":206}," // { endpoint, keys: { p256dh, auth } }\n",[200,298,300,303,305,308,311],{"class":202,"line":299},10,[200,301,302],{"class":213},"  JSON",[200,304,78],{"class":217},[200,306,307],{"class":220},"stringify",[200,309,310],{"class":213},"(",[200,312,313],{"class":217},"{\n",[200,315,317,321,324,327,330,332],{"class":202,"line":316},11,[200,318,320],{"class":319},"swJcz","    title",[200,322,323],{"class":217},":",[200,325,326],{"class":217}," '",[200,328,329],{"class":233},"Approval erforderlich",[200,331,237],{"class":217},[200,333,240],{"class":217},[200,335,337,340,342,345,348,350,353,356,359,362,364,366,369,372],{"class":202,"line":336},12,[200,338,339],{"class":319},"    body",[200,341,323],{"class":217},[200,343,344],{"class":217}," `${",[200,346,347],{"class":213},"request",[200,349,78],{"class":217},[200,351,352],{"class":213},"agent",[200,354,355],{"class":217},"}",[200,357,358],{"class":233}," möchte ",[200,360,361],{"class":217},"${",[200,363,347],{"class":213},[200,365,78],{"class":217},[200,367,368],{"class":213},"action",[200,370,371],{"class":217},"}`",[200,373,240],{"class":217},[200,375,377,380,382,385,388,390,393,395,398],{"class":202,"line":376},13,[200,378,379],{"class":319},"    data",[200,381,323],{"class":217},[200,383,384],{"class":217}," {",[200,386,387],{"class":319}," grantId",[200,389,323],{"class":217},[200,391,392],{"class":213}," request",[200,394,78],{"class":217},[200,396,397],{"class":213},"grantId ",[200,399,400],{"class":217},"},\n",[200,402,404,407],{"class":202,"line":403},14,[200,405,406],{"class":217},"  }",[200,408,260],{"class":213},[200,410,412],{"class":202,"line":411},15,[200,413,260],{"class":213},[11,415,416],{},"Der Service Worker empfängt das Event und zeigt die Notification an. Auf Tap öffnet er die Approve-URL — derselbe Endpoint wie früher, nur die Notification kam vorher anders rein.",[191,418,420],{"className":193,"code":419,"language":195,"meta":196,"style":196},"// public/sw.ts (gekürzt)\nself.addEventListener('push', (event) => {\n  const payload = event.data?.json()\n  event.waitUntil(\n    self.registration.showNotification(payload.title, {\n      body: payload.body,\n      data: payload.data,\n      requireInteraction: true,\n    })\n  )\n})\n\nself.addEventListener('notificationclick', (event) => {\n  event.notification.close()\n  const { grantId } = event.notification.data\n  event.waitUntil(self.clients.openWindow(`/approve/${grantId}`))\n})\n",[15,421,422,427,465,493,505,534,550,565,578,585,590,596,600,629,645,669,710],{"__ignoreMap":196},[200,423,424],{"class":202,"line":203},[200,425,426],{"class":206},"// public/sw.ts (gekürzt)\n",[200,428,429,432,434,437,439,441,444,446,448,451,455,458,462],{"class":202,"line":210},[200,430,431],{"class":213},"self",[200,433,78],{"class":217},[200,435,436],{"class":220},"addEventListener",[200,438,310],{"class":213},[200,440,237],{"class":217},[200,442,443],{"class":233},"push",[200,445,237],{"class":217},[200,447,293],{"class":217},[200,449,450],{"class":217}," (",[200,452,454],{"class":453},"sHdIc","event",[200,456,457],{"class":217},")",[200,459,461],{"class":460},"spNyl"," =>",[200,463,464],{"class":217}," {\n",[200,466,467,470,473,476,479,481,484,487,490],{"class":202,"line":227},[200,468,469],{"class":460},"  const",[200,471,472],{"class":213}," payload",[200,474,475],{"class":217}," =",[200,477,478],{"class":213}," event",[200,480,78],{"class":217},[200,482,483],{"class":213},"data",[200,485,486],{"class":217},"?.",[200,488,489],{"class":220},"json",[200,491,492],{"class":319},"()\n",[200,494,495,498,500,503],{"class":202,"line":243},[200,496,497],{"class":213},"  event",[200,499,78],{"class":217},[200,501,502],{"class":220},"waitUntil",[200,504,224],{"class":319},[200,506,507,510,512,515,517,520,522,525,527,530,532],{"class":202,"line":251},[200,508,509],{"class":213},"    self",[200,511,78],{"class":217},[200,513,514],{"class":213},"registration",[200,516,78],{"class":217},[200,518,519],{"class":220},"showNotification",[200,521,310],{"class":319},[200,523,524],{"class":213},"payload",[200,526,78],{"class":217},[200,528,529],{"class":213},"title",[200,531,293],{"class":217},[200,533,464],{"class":217},[200,535,536,539,541,543,545,548],{"class":202,"line":257},[200,537,538],{"class":319},"      body",[200,540,323],{"class":217},[200,542,472],{"class":213},[200,544,78],{"class":217},[200,546,547],{"class":213},"body",[200,549,240],{"class":217},[200,551,552,555,557,559,561,563],{"class":202,"line":263},[200,553,554],{"class":319},"      data",[200,556,323],{"class":217},[200,558,472],{"class":213},[200,560,78],{"class":217},[200,562,483],{"class":213},[200,564,240],{"class":217},[200,566,567,570,572,576],{"class":202,"line":270},[200,568,569],{"class":319},"      requireInteraction",[200,571,323],{"class":217},[200,573,575],{"class":574},"sfNiH"," true",[200,577,240],{"class":217},[200,579,580,583],{"class":202,"line":287},[200,581,582],{"class":217},"    }",[200,584,260],{"class":319},[200,586,587],{"class":202,"line":299},[200,588,589],{"class":319},"  )\n",[200,591,592,594],{"class":202,"line":316},[200,593,355],{"class":217},[200,595,260],{"class":213},[200,597,598],{"class":202,"line":336},[200,599,267],{"emptyLinePlaceholder":266},[200,601,602,604,606,608,610,612,615,617,619,621,623,625,627],{"class":202,"line":376},[200,603,431],{"class":213},[200,605,78],{"class":217},[200,607,436],{"class":220},[200,609,310],{"class":213},[200,611,237],{"class":217},[200,613,614],{"class":233},"notificationclick",[200,616,237],{"class":217},[200,618,293],{"class":217},[200,620,450],{"class":217},[200,622,454],{"class":453},[200,624,457],{"class":217},[200,626,461],{"class":460},[200,628,464],{"class":217},[200,630,631,633,635,638,640,643],{"class":202,"line":403},[200,632,497],{"class":213},[200,634,78],{"class":217},[200,636,637],{"class":213},"notification",[200,639,78],{"class":217},[200,641,642],{"class":220},"close",[200,644,492],{"class":319},[200,646,647,649,651,653,656,658,660,662,664,666],{"class":202,"line":411},[200,648,469],{"class":460},[200,650,384],{"class":217},[200,652,387],{"class":213},[200,654,655],{"class":217}," }",[200,657,475],{"class":217},[200,659,478],{"class":213},[200,661,78],{"class":217},[200,663,637],{"class":213},[200,665,78],{"class":217},[200,667,668],{"class":213},"data\n",[200,670,672,674,676,678,680,682,684,687,689,692,694,697,700,702,705,707],{"class":202,"line":671},16,[200,673,497],{"class":213},[200,675,78],{"class":217},[200,677,502],{"class":220},[200,679,310],{"class":319},[200,681,431],{"class":213},[200,683,78],{"class":217},[200,685,686],{"class":213},"clients",[200,688,78],{"class":217},[200,690,691],{"class":220},"openWindow",[200,693,310],{"class":319},[200,695,696],{"class":217},"`",[200,698,699],{"class":233},"/approve/",[200,701,361],{"class":217},[200,703,704],{"class":213},"grantId",[200,706,371],{"class":217},[200,708,709],{"class":319},"))\n",[200,711,713,715],{"class":202,"line":712},17,[200,714,355],{"class":217},[200,716,260],{"class":213},[11,718,719,720,723],{},"Das ist alles. Keine Bot-Bibliothek, keine Polling-Loop, keine externe Abhängigkeit außer ",[15,721,722],{},"web-push"," als Server-Lib — und das ist eine npm-Lib, kein Service.",[38,725,727],{"id":726},"was-weggefallen-ist","Was weggefallen ist",[100,729,730,733,738,741],{},[103,731,732],{},"Bot-Token-Rotation und der Operator-Pfad drumherum",[103,734,735,737],{},[15,736,24],{},"-Mapping und das Onboarding-Stück, das dieses Mapping anlegt",[103,739,740],{},"Die Begründung, warum der User für Approvals einen Telegram-Account braucht",[103,742,743,744,747],{},"Eine Klasse von ",[46,745,746],{},"was wenn Telegram down ist","-Fragen — die liegen jetzt verteilt auf Apple, Mozilla, Google, was strukturell besser ist als auf Telegram allein",[11,749,750],{},"Was geblieben ist:",[100,752,753,756,762],{},[103,754,755],{},"Die Out-of-Band-Eigenschaft (andere Implementierung, dieselbe Eigenschaft)",[103,757,758,759,761],{},"Der ",[15,760,60],{},"-Pattern für blockierende Workflows (orthogonal, im Stack geblieben)",[103,763,764],{},"Die Approve-Seite, der Standing-Grant-Match, der gesamte Authorization-Pfad",[38,766,768],{"id":767},"was-die-substitution-sichtbar-macht","Was die Substitution sichtbar macht",[11,770,771,772,775,776,779],{},"Beim Bauen war Telegram für mich ",[46,773,774],{},"Teil des Features",". Hätte mich jemand gefragt, was meine Approval-Notifications sind, hätte ich gesagt: ",[46,777,778],{},"Telegram-Bot, der mir Pushes schickt",". Das war konkret, das fühlte sich nach Antwort an.",[11,781,782,783,786,787,790],{},"Erst beim Substituieren wurde klar, dass das nicht die Antwort war. Telegram war ",[46,784,785],{},"eine Implementierung"," einer Eigenschaft. Die Eigenschaft war ",[46,788,789],{},"Notification über einen Kanal, der nicht der Agent-Kanal ist",". Telegram hat das erfüllt — und gleichzeitig die Eigenschaft mit eigenem Konfigurations-Krempel verbunden, der mit der Eigenschaft selbst nichts zu tun hatte.",[11,792,793,794,796,797,800],{},"Wenn man eine Implementierung tauscht und die Eigenschaft bleibt, war die Eigenschaft tragend. Wenn man eine Implementierung tauscht und etwas anderes auch wegfällt — Bot-Token, ",[15,795,24],{},", Drittanbieter-Voraussetzung — dann war das ",[46,798,799],{},"nicht"," tragend, sondern beim Implementieren mit eingewickelt.",[11,802,803],{},"Das ist nicht Telegram-spezifisch. Das ist das Pattern, das ich jedes Mal sehe, wenn ich einen Drittanbieter durch eine native Lösung ersetze: das, was bleibt, war strukturell. Das, was weggeht, war Erfüllungs-Detail.",[11,805,806],{},"Architektur erkennt man daran, dass sie eine Substitution überlebt.",[808,809,810],"style",{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":196,"searchDepth":210,"depth":210,"links":812},[813,814,815,816,817,818],{"id":40,"depth":210,"text":41},{"id":81,"depth":210,"text":82},{"id":135,"depth":210,"text":136},{"id":185,"depth":210,"text":186},{"id":726,"depth":210,"text":727},{"id":767,"depth":210,"text":768},"2026-05-01","Approval-Notifications liefen über einen Telegram-Bot. Web Push aus openape-free-idp und openape-chat ersetzt das jetzt — VAPID, Service Worker, native Browser-Permission. Gleiche Eigenschaft, anderer Transport. Erst die Substitution macht sichtbar, was tragend war.",false,"md",null,{},"/blog/de/push-substituiert-push",{"title":5,"description":820},"blog/de/push-substituiert-push",[829,830,831,832],"OpenApe","AI Agents","Infrastructure","Building in Public","push-substitutes-push","qLqOVQ8yHc3gQYkV7UcUcdE1LoPtHl9ZOKsQ5cC3nJs",{"de":836,"en":837},"/de/blog/push-substituiert-push","/en/blog/push-substitutes-push",1779001885912]