Autor: Mark

  • Vom offenen Scheunentor zum abgesicherten MCP-Server: KI-gestützte ABAP-Entwicklung mit ARC-1

    KI-Assistenten wie Claude können inzwischen richtig gut ABAP. Was ihnen fehlt, ist der Zugang zum System: lesen, suchen, aktivieren, am besten direkt über die ADT-Schnittstelle, die auch Eclipse benutzt. Die Brücke dafür heißt MCP (Model Context Protocol, ein offener Standard, über den KI-Clients mit Werkzeugen sprechen). Solche Brücken gibt es als Open Source. Die Frage ist nicht, ob das geht. Die Frage ist, wie kontrolliert.

    Genau daran bin ich beim ersten Anlauf fast gescheitert. Hier die ganze Geschichte, inklusive der Stellen, an denen ich mir an die Stirn gefasst habe.

    Runde 1: Es funktioniert. Und genau das ist das Problem.

    Der Plan: ein zentraler MCP-Server auf einer frischen Debian-Kiste, damit nicht jeder Entwickler lokal sein eigenes Setup pflegt. Installiert war der erste Kandidat schnell, ein Node-basierter ADT-MCP-Server, per npm auf den Server, HTTP-Modus an, Port gebunden, Verbindung zum SAP-Entwicklungssystem stand.

    Dann der Moment, in dem ich auf den laufenden Endpoint geschaut habe: keinerlei Client-Authentifizierung. Nicht falsch konfiguriert, sondern schlicht nicht vorgesehen, das Werkzeug ist als lokales Entwicklertool gebaut. Im LAN bedeutete das: Jeder, der die URL kennt, arbeitet mit vollen Entwicklerrechten auf dem SAP-System. Eine Tür ohne Schloss.

    Der erste Reparaturversuch klang clever: Die SAP-Credentials aus der Server-Konfiguration streichen und vom Client pro Request mitgeben. Dabei bin ich prompt in eine Denkfalle getappt: Umgebungsvariablen aus der Client-Konfiguration erreichen einen Remote-Server nie, die wirken nur bei lokal gestarteten Prozessen. Der einzige Kanal wären HTTP-Header gewesen. Und damit lägen die SAP-Passwörter wieder auf jedem Laptop und wandern bei jedem Request übers Netz. Genau davon wollte ich weg. Verworfen.

    Runde 2: ARC-1, oder: Sicherheit als Werkseinstellung

    Die Lösung kam mit ARC-1, einem Open-Source-MCP-Server für SAP-Systeme, der von vornherein für den Firmeneinsatz gebaut ist. Drei Eigenschaften haben mich überzeugt:

    Erstens verweigert er im HTTP-Modus schlicht den Start, wenn keine Authentifizierung konfiguriert ist. Zweitens ist ab Werk alles auf nur lesen gestellt, jede weitere Fähigkeit (schreiben, Tabellen ansehen, Transporte) ist ein einzelnes, bewusstes Opt-in. Drittens wird jeder Tool-Aufruf mit Personenzuordnung protokolliert. Kostenpunkt: 0 Euro Lizenz, läuft als Docker-Container.

    Das Architekturmodell dahinter ist der eigentliche Aha-Moment: zwei getrennte Anmeldungen. Hop 1 (Client zu ARC-1) läuft über persönliche API-Keys mit Rollenprofil, mein Key darf entwickeln, der meiner Kollegin erst mal nur lesen. Hop 2 (ARC-1 zu SAP) läuft über einen technischen SAP-User, dessen Passwort nur auf dem Server liegt. Kompromittierter Laptop? Ein Key wird gesperrt, das SAP-Passwort bleibt unberührt. Effektiv gilt: Server-Regeln UND Key-Profil UND SAP-Berechtigung müssen alle drei ja sagen.

    Die Compose-Datei dazu passt auf einen Bildschirm:

    services:
      arc1:
        image: ghcr.io/arc-mcp/arc-1
        restart: unless-stopped
        ports:
          - "127.0.0.1:3000:8080"
        environment:
          SAP_TRANSPORT: http-streamable
          SAP_URL: https://srv-sap01:44300
          SAP_CLIENT: "100"
          SAP_USER: <TECH_USER>
          SAP_PASSWORD: <PASSWORT>
          SAP_INSECURE: "true"
          ARC1_API_KEYS: "<KEY_1>:developer,<KEY_2>:viewer"

    Der Abend der kleinen Fehler

    Klingt glatt? War es nicht. Drei Stolperer in einer Stunde, alle lehrreich.

    Stolperer eins: curl http://localhost:3000/health antwortete mit Connection reset. Der Container lief, die Logs waren makellos, SAP-Preflight grün. Des Rätsels Lösung stand in der Ausgabe von docker ps: Das Port-Mapping zeigte auf 3000->8000 statt 3000->8080. Eine vertippte Ziffer, und die Requests liefen ins Leere. Der Debug-Trick, den ich mir gemerkt habe: docker compose config zeigt, was Compose wirklich geparst hat, nicht was man zu editieren glaubt. In meinem Fall bearbeitete ich nämlich zwischenzeitlich die falsche Datei, denn Compose bevorzugt compose.yaml vor docker-compose.yml, wenn beide herumliegen.

    Stolperer zwei: Claude Desktop unter Windows. Die Brücke mcp-remote (nötig, weil Desktop remote nur über einen lokalen Stellvertreterprozess spricht) hat gleich zwei Windows-Eigenheiten. npx lässt sich nicht direkt starten, es braucht die cmd /c-Klammer. Und Leerzeichen in Header-Argumenten werden beim Durchreichen zerlegt, der Authorization-Header kam kaputt an. Der Workaround: Header ohne Leerzeichen schreiben und den Wert über eine Umgebungsvariable einschleusen.

    "arc1": {
      "command": "cmd",
      "args": [
        "/c", "npx", "-y", "mcp-remote",
        "https://192.0.2.10/mcp",
        "--header", "Authorization:${AUTH_HEADER}"
      ],
      "env": {
        "AUTH_HEADER": "Bearer <API_KEY>",
        "NODE_EXTRA_CA_CERTS": "C:\\certs\\ca-root.crt"
      }
    }

    Stolperer drei war fast schon lustig: Nach dem Umbau auf HTTPS meldete der Server einen 404 mit dem freundlichsten Fehlertext des Tages: „Not found. Use /mcp for MCP protocol.“ Beim Umschreiben der URL war schlicht der Pfad /mcp verloren gegangen. Manchmal sagt einem das System wortwörtlich, was fehlt. Man muss es nur lesen.

    Nix mit Klartext: TLS mit Caddy in drei Zeilen

    Bearer-Keys im Klartext durchs LAN? Nein. Als TLS-Terminierung kam Caddy davor, weil der Reverse Proxy dort inklusive eigener Zertifizierungsstelle drei Zeilen braucht:

    https://192.0.2.10 {
        tls internal
        reverse_proxy 127.0.0.1:3000
    }

    Der Klartext-Port wanderte gleichzeitig auf Loopback (127.0.0.1:3000:8080 im Compose), aus dem Netz ist nur noch Port 443 erreichbar. Zwei Fallen auf der Zielgeraden: Die Root-CA von Caddy liegt in einem versteckten Verzeichnis (.local, ein normales ls zeigt nichts, und sie entsteht überhaupt erst nach dem ersten TLS-Handshake). Und auf dem Windows-Client hilft der Import in den Windows-Zertifikatsspeicher exakt gar nichts, weil Node ihn ignoriert. Node will NODE_EXTRA_CA_CERTS mit dem Pfad zur Datei. Wer die Datei dann noch per PowerShell-Umleitung kopiert, bekommt UTF-16 und einen kryptischen OpenSSL-Fehler obendrauf. scp nehmen, fertig.

    Learnings

    • „Funktioniert“ und „ist sicher“ sind zwei Baustellen. Ein MCP-Endpoint ohne Client-Auth ist im LAN ein offenes Scheunentor mit SAP-Entwicklerrechten dahinter.
    • Zwei Auth-Hops sind der Schlüssel: persönliche Keys zum Gateway, technischer User zum SAP. Credentials gehören auf den Server, nicht auf Laptops.
    • docker compose config vor jedem Neustart. Es zeigt die geparste Wahrheit und entlarvt vertippte Ports und doppelte Compose-Dateien in Sekunden.
    • Node ignoriert den Windows-Zertifikatsspeicher. Interne CAs brauchen NODE_EXTRA_CA_CERTS, alles andere ist verschwendete Lebenszeit.
    • Fehlermeldungen wörtlich lesen. Der beste Hinweis des Tages stand im 404-Text des Servers selbst.

    Nächster Ausbauschritt, wenn das Setup wächst: pro Person eine eigene Server-Instanz mit eigenem SAP-User, dann stimmt auch das SAP-eigene Audit-Log auf Personenebene. Aber das ist eine Geschichte für einen eigenen Beitrag.

  • 17 Dollar für zweieinhalb Stunden: was die Messung über unsere Tokenkosten verraten hat (Teil 2)

    17 Dollar für zweieinhalb Stunden: was die Messung über unsere Tokenkosten verraten hat (Teil 2)

    In Teil 1 habe ich meine SAP-Chats aufgeräumt: zwei getrennte Profile für Entwicklung und Beratung, ein Kickoff-Block pro Chat, und die Erkenntnis, dass sich „erst Sonnet, bei Bedarf Opus“ bis zu einer Eskalationsquote von 60 Prozent rechnet.

    Blieb die Frage, ob das überhaupt wirkt. Und dahinter die unangenehmere: irgendwann kommen Abrechnungs- und Nachweissituationen, und dann willst du Zahlen haben. Also haben wir gemessen. Was dabei herauskam, hat die schöne Theorie aus Teil 1 gleich mal einsortiert.

    Der Einstieg kostet null Aufwand

    Beide Werkzeuge haben das eingebaut. In Claude Code zeigt /usage den Session-Block mit Input, Output, Cache-Read und Cache-Write je Modell:

    Total cost:            $0.55
    Total duration (API):  6m 20s
    Usage by model:
       claude-sonnet-4-6:  1.2k input, 5.3k output, 940.0k cache read, 50.0k cache write ($0.55)

    Darunter steht auf einem Abo-Plan zusätzlich, welcher Anteil der letzten Nutzung auf Skills, Subagenten, Plugins und einzelne MCP-Server entfiel, plus Warnhinweise wie „langer Kontext“ oder „Cache-Miss“, sobald einer davon zehn Prozent oder mehr ausmacht. Mit d und w schaltest du zwischen 24 Stunden und sieben Tagen um. Wichtig: die Zähler setzen zurück, sobald du den Chat leerst. Eine Session ist also genau das, was zwischen zwei Aufräumaktionen liegt, was praktischerweise exakt zur Regel „ein Thema, ein Chat“ passt.

    In Cowork gibt es dasselbe als Skill: /explain-usage erklärt für die laufende Session, wohin die Tokens geflossen sind, mit kleiner Grafik und in normaler Sprache statt in Zählerkolonnen. Für den Berateralltag ist das der Einstieg, weil dort niemand ein Terminal offen hat.

    Der unterschätzte Befehl

    Interessanter als beide ist aber /insights. Der beantwortet nicht „wie viel“, sondern „warum“. Er wertet die letzten Sessions auf der Maschine aus und schreibt einen HTML-Report:

    ~/.claude/usage-data/report.html      # der aktuelle Lauf
    # dazu eine Kopie mit Zeitstempel je Durchlauf, nichts wird überschrieben

    Ein Lauf nimmt bis zu 200 noch nicht ausgewertete Sessions und überspringt die sehr kurzen. Im Kopf des Reports steht dann so etwas wie 200 sessions (412 total), damit du siehst, was fehlt. Inhaltlich geht es um Arbeitsmuster und Reibungspunkte: wo Anfragen missverstanden wurden, wo Code hinterher nicht lief, wo du dich im Kreis gedreht hast. Genau das sind die Stellen, an denen Tokens verbrennen, ohne dass eine Zahl es dir verrät.

    Kleiner Haken mit Humorwert: der Lauf selbst verbraucht Tokens. Die Kostenanalyse kostet also Geld. Einmal im Monat statt stündlich, und das Ergebnis wegsichern.

    Wenn es genauer sein soll: die Rohdaten

    Die liegen lokal, ein JSONL-Transkript pro Session:

    ls ~/.claude/projects/
    # ein Eintrag je Arbeitsverzeichnis, Slashes werden zu Bindestrichen:
    # /home/dev/kunden/kunde-a  ->  -home-dev-kunden-kunde-a

    Der Dateiname ohne Endung ist die Session-ID. In jeder Antwortzeile steckt ein Usage-Objekt:

    {
      "input_tokens": 2,
      "cache_read_input_tokens": 0,
      "output_tokens": 1417,
      "output_tokens_details": { "thinking_tokens": 528 },
      "cache_creation": {
        "ephemeral_1h_input_tokens": 69202,
        "ephemeral_5m_input_tokens": 0
      }
    }

    Selber parsen musst du das zum Glück nicht. Es gibt ccusage, ein Open-Source-Werkzeug, das genau diese Verzeichnisse liest und ohne Installation läuft:

    npx ccusage session                        # eine Zeile je Session
    npx ccusage session --json                 # maschinenlesbar, mit Session-ID und Kosten
    npx ccusage session --breakdown            # nach Modell aufgeschlüsselt
    npx ccusage session --since 20260801 --until 20260831
    npx ccusage daily                          # Tages- und Monatssummen gibt es auch

    Der Kostenmodus ist wählbar: --mode calculate rechnet aus den Tokenzählern, --mode display zeigt nur vorberechnete Werte. Das Werkzeug ist unabhängig von Anthropic, für den unternehmensweiten Einsatz solltest du also selbst draufschauen, bevor du es allen ausrollst.

    Uns hat am Ende ein eigenes Python-Skript besser gepasst, weil wir eine Spalte mehr brauchten: das Arbeitsverzeichnis als Mandatszuordnung, damit die CSV direkt zum Projekt passt. Und das war auch der Moment, in dem die Zahlen unbequem wurden.

    Der Befund

    Eine ganz normale Arbeitssession, großes Modell, 69 Turns, gut zweieinhalb Stunden:

    TokenartMengeKostenanteil
    Input, ungecacht138ca. 0 $
    Output99.3672,48 $
    Cache-Read5,88 Mio.2,94 $
    Cache-Write (1 Std.)1,19 Mio.11,94 $
    Summe7,18 Mio.17,37 $

    Der klassische Input: 138 Tokens. Einhundertachtunddreißig. Praktisch bedeutungslos. Zwei Drittel der Kosten entstehen beim Cache-Schreiben.

    Das ergibt Sinn, wenn man die Multiplikatoren kennt. Ein Cache-Treffer kostet nur 0,1 mal Input, das Schreiben in den Cache dagegen 1,25 mal (fünf Minuten Haltbarkeit) oder 2 mal (eine Stunde). Jeder Cache-Miss nach einer längeren Pause schreibt den kompletten Kontext neu. Und weil der ganze Verlauf bei jedem Turn erneut mitgeht, wachsen die Inputkosten quadratisch mit der Turn-Zahl: ein Chat mit 40 Turns kostet grob das Vierfache von vier Chats mit je zehn.

    Damit hat sich unsere Prioritätenliste aus Teil 1 gedreht. Sessionhygiene schlägt Modellwahl. Ein Thema, ein Chat, dazwischen konsequent aufräumen: das ist der Hebel, der nichts kostet außer einer Gewohnheit, und er wirkt stärker als jeder Preisunterschied zwischen den Modellen. Die 60-Prozent-Regel bleibt richtig, sie ist nur nicht Platz eins.

    Irrweg: die Dollarzahl ist keine Rechnung

    Weil die Abrechnungsfrage der eigentliche Anlass war, haben wir weitergefragt. Und da liegt eine Falle, die man besser vor dem ersten Kundengespräch kennt.

    Der Betrag, den die Werkzeuge anzeigen, ist auf einem Abo-Plan eine lokal berechnete Schätzung zu Listenpreisen. Sie kennt weder eure Konditionen noch Rabatte, stammt nur von dieser einen Maschine, und als Rechnungsposten existiert sie schlicht nicht: das Kontingent hängt am Sitzplatz, nicht an der Session. Als Steuerungsgröße taugt sie, als Beleg nicht. Wer echte Belege braucht, muss auf API-Nutzung mit einem eigenen Bereich je Kunde wechseln, und zahlt dann pro Token statt pauschal.

    Irrweg: die Daten sind nach 30 Tagen weg

    Die Sache, die uns fast durchgerutscht wäre: die Transkripte werden nach 30 Tagen automatisch gelöscht. Standardwert, unspektakulär dokumentiert, brutal in der Wirkung. Wer im Quartalsgespräch nach Belegen sucht, findet ein leeres Verzeichnis. Wenn diese Daten eure Nachweisgrundlage werden sollen, braucht ihr einen monatlichen Export, bevor die Rohdaten verfallen. Ein Cronjob am Monatsanfang reicht.

    Zweiter Punkt, im Kundenkontext ernst zu nehmen: die Transkripte sind Klartext und unverschlüsselt. Alles, was durch ein Werkzeug lief, steht darin, inklusive Dateiinhalten und Kommandoausgaben. Das gehört vorher durch Betriebsvereinbarung und Vertraulichkeitsprüfung, nicht hinterher.

    Und dann die unbequeme Frage

    Am Ende stand die Rechnung, die ich eigentlich zuerst hätten machen sollen. Als Größenordnung werden rund 150 bis 250 Dollar pro Entwickler und Monat genannt. Gegen einen Beratertagessatz ist das der Gegenwert von etwa einer Stunde.

    Für einen Betrag in dieser Größe eine sessiongenaue Einzelabrechnung aufzubauen, ist ökonomischer Unfug. Ein Pauschalzuschlag ist billiger in der Erstellung und deutlich weniger angreifbar als eine Schätzung, die sich als Rechnung ausgibt. Die Messung brauche ich trotzdem, aber zum Steuern, nicht zum Abrechnen.

    Learnings

    • Drei Befehle, drei Fragen. /usage und /explain-usage sagen dir, wie viel. /insights sagt dir, warum. Skripte brauchst du erst, wenn du auf Mandate umlegen willst.
    • Cache-Write ist der größte Einzelposten. In unserer Messung zwei Drittel der Kosten. Der klassische Input ist Rauschen.
    • Sessionhygiene vor Modellwahl. Der Kontext wächst quadratisch, das schlägt jeden Preisunterschied zwischen den Modellen.
    • Die angezeigte Zahl ist eine Schätzung, keine Rechnung. Auf einem Abo-Plan gibt es keinen Rechnungsposten pro Session.
    • 30 Tage Aufbewahrung. Wer Nachweise braucht, exportiert monatlich, sonst ist die Grundlage weg, bevor jemand danach fragt.
    • Erst prüfen, ob sich der Nachweisaufwand lohnt. Manchmal ist die richtige Antwort auf „wie rechnen wir das ab“ schlicht: gar nicht einzeln.
  • Immer Fable, immer volle Kanne: zwei Profile und eine Rechnung, die uns überrascht hat (Teil 1)

    Im SAP-Umfeld gibt es bei eigentlich nur zwei Modi. Entweder ich entwickle und mache technische Analysen (ABAP Cloud, RAP, CDS, ab und zu BTP), oder ich bin als Berater quer durch den Modulgarten unterwegs, heute FI, morgen MM, übermorgen die Frage, warum die Kontenfindung anders bucht als gedacht.

    In beiden Fällen bin ich bisher gleich vorgegangen: Chat auf, größtes Modell, drauflos. Ohne Persona, ohne Vorgaben, ohne alles. Das ist wunderbar bequem und produziert erstaunlich lange Wartezeiten. Irgendwann kam die Frage, ob das eigentlich sein muss.

    Irrweg Nummer eins: ein Profil für beides

    Der erste Reflex war, eine einzige gute Persona zu bauen, die beide Rollen abdeckt. Also so etwas wie „du bist SAP-Experte für Entwicklung und Beratung, richte dich danach, was ich gerade brauche“.

    Funktioniert schlecht. Ein solcher Vorspann muss beide Welten beschreiben, und du schleppst in jedem Entwicklungs-Chat die Beratungsanweisungen mit und umgekehrt. Schlimmer noch: das Modell muss in jedem Turn selbst entscheiden, in welchem Modus es gerade ist, und liegt dabei zuverlässig manchmal daneben. Antworten in der falschen Flughöhe waren bei uns ohnehin das häufigste Ärgernis, eine Umschalt-Persona macht das nicht besser.

    Die Lösung war unspektakulär: zwei getrennte Projekte, eins für Entwicklung, eins für Beratung, jeweils mit einer festen Persona in den Instructions. Kein Umschalten, kein Ballast.

    Was in so eine Persona gehört

    Die Entwickler-Variante regelt vier Dinge: Rolle und Haltung (Clean Core, Erweiterung vor Modifikation), die Antwortstruktur (Einordnung, Lösung, Transport- und Upgradefolgen), die Arbeitsweise (nur geänderte Codeabschnitte zeigen, bei Fehlern erst Hypothese plus einen Prüfschritt in ST22 oder ST05, unsichere technische Namen als „zu verifizieren in ADT“ markieren statt zu raten) und ein Ausgabebudget.

    Die Berater-Variante sieht anders aus, weil die Arbeit anders aussieht. Dort ist die Antwortstruktur: Prozesskontext, Standardweg im Sinne von Fit-to-Standard, konkreter Customizing-Pfad, Delta und Aufwand, offene Punkte für den Kunden. Dazu die Anweisung, die modulübergreifende Wirkung immer mitzuprüfen, also Bewertungsklasse und Kontenfindung von MM nach FI, Erlöskontenfindung von SD nach FI, die Ergebnisrechnung in CO-PA.

    Ein Detail, das mehr gebracht hat als erwartet: die Anweisung, unsichere technische Namen zu kennzeichnen, statt plausibel klingende zu erfinden. Ein erfundener BAdI-Name kostet dich zwanzig Minuten im System, bis du merkst, dass es ihn nicht gibt.

    Der Kickoff-Block

    Pro Chat kommt ein sechszeiliger Block obendrauf, ausgefüllt in etwa 30 Sekunden:

    ROLLE:   SAP-Entwickler, Schwerpunkt RAP
    SYSTEM:  S/4HANA 2023 On-Prem, Developer Extensibility
    ZIEL:    Determination ergänzen, die bei Anlage die Kostenstelle ableitet
    GEGEBEN: BDEF, CDS View, Handler-Klasse (angehängt)
    NICHT:   Draft-Handling, UI, Berechtigungen
    OUTPUT:  Diff plus drei ABAP-Unit-Testfälle, max. 80 Zeilen

    Für die Beratungsseite hat der Block zwei andere Zeilen, die den Unterschied machen: ADRESSAT (Fachbereich, IT oder Lenkungskreis) und ANNAHMEN (was das Modell ungefragt voraussetzen darf). Das erste steuert Ton und Tiefe und erspart die Runde „bitte nochmal für den Lenkungskreis“, das zweite ersetzt die Rückfrageschleife.

    Die wirksamsten Zeilen sind aber in beiden Fällen NICHT und OUTPUT. Die erste verhindert ungefragte Zusatzarbeit, die zweite deckelt die Ausgabeseite. Zusammen etwa 60 Tokens Aufwand.

    Irrweg Nummer zwei: „Sonnet lohnt sich nicht, weil ich es dann zweimal mache“

    Dann die Modellfrage. Die Listenpreise pro Million Tokens sehen so aus:

    ModellInputOutput
    Opus 55 $25 $
    Sonnet 52 $10 $
    Haiku 4.51 $5 $

    Das Verhältnis ist auf beiden Seiten gleich: Opus kostet das 2,5-fache von Sonnet und das 5-fache von Haiku. Wer grundsätzlich das größte Modell fährt, zahlt für jede Formatierungsaufgabe den fünffachen Preis.

    Der Einwand kam sofort, und zwar von mir selbst: wenn Sonnet nicht reicht, mache ich die Arbeit zweimal, und dann war nichts gespart. Klingt plausibel, hält aber der Rechnung nicht stand.

    Bei „Sonnet zuerst, bei Bedarf Eskalation auf Opus“ kostet eine Aufgabe 1 + p × 2,5 Einheiten, wobei p der Anteil der eskalierten Fälle ist. „Immer Opus“ kostet 2,5. Gleichstand liegt bei p = 0,6.

    Solange also weniger als 60 Prozent der Aufgaben hochgestuft werden müssen, ist Sonnet zuerst billiger, und zwar selbst dann, wenn die Eskalation die komplette Arbeit wiederholt. Im SAP-Alltag liegt der Wert deutlich darunter, weil der Großteil nachschlagen, formulieren oder nach klarer Spec umsetzen ist. Was tatsächlich sofort auf das große Modell gehört: modulübergreifende Konzeptarbeit, Root-Cause-Analyse ohne klare Fehlermeldung, Migrations- und Architekturentscheidungen. Also alles, wo ein Fehler später Tage kostet.

    Und was ich bewusst nicht optimiert habe

    Der naheliegende nächste Gedanke war, die Persona möglichst knapp zu halten, um Tokens zu sparen. Das ist Unsinn, und zwar aus einem Grund, der uns erst später richtig klar wurde: stabile Vorspänne werden gecacht und dabei mit einem Zehntel des normalen Preises abgerechnet. Eine gut ausformulierte Persona kostet praktisch nichts, solange du sie nicht ständig umschreibst.

    Genau das ist übrigens die häufigste Selbstsabotage: Instructions mitten in einer Arbeitssitzung nachschärfen. Jede Änderung wirft den Cache weg. Sammle die Änderungen und zieh sie gebündelt nach.

    Learnings

    • Zwei Rollen, zwei Projekte. Eine Umschalt-Persona schleppt in jedem Chat die jeweils andere Hälfte mit und rät trotzdem gelegentlich falsch.
    • NICHT und OUTPUT sind die wirksamsten Zeilen im Prompt. Sie kosten 60 Tokens und sparen Nachfragerunden.
    • Sonnet zuerst rechnet sich bis zu 60 Prozent Eskalationsquote. Selbst wenn die Eskalation die ganze Arbeit wiederholt.
    • Unsichere technische Namen kennzeichnen lassen. Ein erfundener BAdI kostet mehr Zeit als jeder Tokenpreis.
    • An der Persona zu sparen ist die falsche Baustelle. Sie ist gecacht und damit fast gratis.

    Damit war ich zufrieden. Bis ich angefangen haben zu messen, wo die Tokens tatsächlich hingehen. Das Ergebnis hat meine Prioritätenliste umgeworfen, und die Modellwahl war plötzlich nicht mehr der Hauptschuldige. Darum geht es in Teil 2.

  • Der unkaputtbare Spotify-Pi: HiFiBerry, Raspotify und ein Dateisystem, das nie wieder stirbt

    Es begann mit Zeichensalat auf der Konsole und endete mit einem Kernel Panic: VFS: unable to mount root fs on unknown-block(179,2). Übersetzt: Die SD-Karte meines Spotify-Raspberrys hatte sich nach Jahren Dauerbetrieb verabschiedet. Der Pi hängt bei uns an der Stereoanlage im Esszimmer und macht daraus per Spotify Connect ein Streaming-Ziel für die ganze Familie. Und natürlich wusste ich nicht mehr, was ich damals wie konfiguriert hatte.

    Also Neuaufbau. Diesmal aber richtig: dokumentiert, reproduzierbar, und mit einer Betriebsart, die den nächsten SD-Karten-Tod technisch ausschließt. Hier ist das komplette Rezept.

    Die Hardware: Pi, DAC und die SD-Karten-Frage

    Das Setup ist bewusst simpel: ein Raspberry Pi, darauf gesteckt ein HiFiBerry DAC+. Der DAC ist der entscheidende Baustein, denn der analoge Audioausgang des Pi ist klanglich eine Zumutung. Der HiFiBerry sitzt direkt auf den GPIO-Pins, holt sich das Signal digital per I2S ab und liefert sauberes Cinch-Stereo an den Verstärker. Kostenpunkt um die 30 Euro, klanglicher Unterschied: Welten.

    Spannender ist die SD-Karten-Frage, denn die war ja gerade meine Todesursache. Zwei Erkenntnisse aus der Recherche:

    Erstens: Die Preise für Endurance-Karten (die Dauerbetriebs-Klasse für Dashcams) haben sich durch die explodierte NAND-Nachfrage fast verdreifacht. Eine SanDisk Max Endurance 32 GB kostet aktuell um die 33 Euro, vor gut einem Jahr war sie für 12 zu haben.

    Zweitens, und das ist die eigentliche Pointe: Man braucht sie streng genommen gar nicht. Denn der wirksamste Schutz gegen SD-Verschleiß ist nicht bessere Hardware, sondern eine Betriebsart, bei der die Karte im laufenden Betrieb überhaupt nicht mehr beschrieben wird. Dazu unten mehr. Ich habe mich trotzdem für die Max Endurance entschieden, nach dem Motto: Wir machen das jetzt einmal ordentlich. Gürtel plus Hosenträger. Wer sparen will, nimmt eine normale Markenkarte und verlässt sich auf den Read-only-Trick.

    Ein Einkaufshinweis noch, der bei SD-Karten wichtiger ist als bei fast jedem anderen Produkt: nur direkt beim Händler oder bei „Versand durch Amazon“ kaufen, nicht bei Marketplace-Drittanbietern. Gefälschte Markenkarten sind ein Massenphänomen, und eine Fälschung stirbt genau so, wie meine alte Karte gestorben ist.

    Installation: 10 Minuten mit dem Imager

    Betriebssystem ist Raspberry Pi OS Lite (64-bit) auf Trixie-Basis (Debian 13). Lite deshalb, weil eine headless Audio-Appliance keinen Desktop braucht: weniger Platz, weniger RAM, weniger Angriffsfläche.

    Der Raspberry Pi Imager nimmt einem dabei fast alles ab. Vor dem Schreiben unbedingt „Einstellungen bearbeiten“ öffnen und dort Hostname, SSH mit Public Key und die WLAN-Zugangsdaten hinterlegen. Dann bootet der Pi direkt ins Netz und ist per SSH erreichbar, ohne dass je ein Monitor angeschlossen war.

    Nach dem ersten Boot das übliche Ritual:

    sudo apt update && sudo apt full-upgrade -y
    sudo reboot

    Den DAC aktivieren: drei Zeilen config.txt

    Der HiFiBerry wird nicht per Software installiert, sondern per Device-Tree-Overlay aktiviert. Die Datei liegt inzwischen unter /boot/firmware/config.txt (nicht mehr /boot/config.txt, viele ältere Anleitungen zeigen den veralteten Pfad). Drei Änderungen:

    #dtparam=audio=on
    dtoverlay=vc4-kms-v3d,noaudio
    dtoverlay=hifiberry-dacplus

    Zeile 1 schaltet den Onboard-Sound ab, Zeile 2 verhindert, dass HDMI-Audio dazwischenfunkt, Zeile 3 aktiviert den DAC+. Nach einem Reboot muss aplay -l eine Karte namens sndrpihifiberry zeigen. Falls nicht: Bei neueren Kerneln heißt das Overlay für die Standard-Variante hifiberry-dacplus-std, das war bei mir der Stolperstein-Kandidat Nummer eins.

    Und wenn der Sound läuft, aber verdächtig leise ist: In alsamixer heißt der relevante Regler beim DAC+ „Digital“, und der steht nach frischer Installation gern nicht auf Maximum.

    Raspotify: ein Einzeiler und vier Parameter

    Raspotify macht aus dem Pi ein Spotify-Connect-Gerät (Premium-Account vorausgesetzt). Installation:

    curl -sL https://dtcooper.github.io/raspotify/install.sh | sh

    Die Konfiguration lebt in /etc/raspotify/conf. Meine Werte:

    LIBRESPOT_NAME="HIFIberry"
    LIBRESPOT_BITRATE="320"
    LIBRESPOT_INITIAL_VOLUME="100"
    LIBRESPOT_MIXER="alsa"

    Der Name ist das, was in der Spotify-App als Wiedergabegerät auftaucht. Die Bitrate darf bei Premium auf 320 kbit/s. Die Initial-Lautstärke gilt für jede neue Connect-Session. Und MIXER="alsa" übergibt die Lautstärkeregelung an den Hardware-Regler des DAC-Chips statt sie in Software zu rechnen, das ist die klanglich sauberere Variante.

    Zwei Fallstricke aus der Praxis: Der Parameter LIBRESPOT_AUTOPLAY kennt nur "on", "off" oder gar nicht gesetzt (dann gilt die Konto-Einstellung). Ihn leer zu setzen ("") verursacht Startprobleme. Und wer die feste Ausgabe an einen Vollverstärker bevorzugt, setzt LIBRESPOT_VOLUME_CTRL="fixed", regelt dann aber ausschließlich am Verstärker.

    Nach sudo systemctl restart raspotify taucht das Gerät in der Spotify-App auf. Probetitel, Gänsehaut, weiter.

    Der WLAN-Kniff: den Pi am richtigen Access Point festnageln

    In einem Mesh-Netz mit mehreren Repeatern gibt es ein Phänomen namens Sticky Client: Der WLAN-Chip des Pi versteht die Steering-Standards (802.11k/v) nicht, klemmt sich beim Boot an das erstbeste Signal und bleibt kleben. Bei mir hing der Pi dadurch an der weit entfernten FRITZ!Box statt am Repeater drei Meter daneben.

    Die Lösung: die BSSID (also die Funk-MAC des gewünschten Access Points) fest eintragen. Unter Trixie mit NetworkManager sind das drei Befehle:

    nmcli -f SSID,BSSID,CHAN,SIGNAL dev wifi list
    nmcli connection modify "MeinWLAN" 802-11-wireless.bssid XX:XX:XX:XX:XX:XX
    nmcli connection up "MeinWLAN"

    Der erste zeigt alle Funkzellen mit Signalstärke. Die richtige ist die mit der eigenen SSID und dem stärksten Signal am Standort (bei 5 GHz: Kanal 36 aufwärts). Wichtig: nur eine BSSID pinnen, die der Pi im Scan auch wirklich sieht. Ich habe das auf die harte Tour gelernt und mich mit einer vom Pi aus unsichtbaren Zelle selbst ausgesperrt.

    Der bewusste Trade-off: Fällt genau dieser Access Point aus, weicht der Pi nicht mehr auf andere aus. Für ein stationäres Gerät neben einem stationären Repeater ist das verschmerzbar.

    Das Finale: Read-only-Betrieb, die Lebensversicherung der SD-Karte

    Jetzt kommt der Baustein, der den nächsten Kernel Panic technisch ausschließt: das Overlay File System. Dabei wird das Root-Dateisystem read-only gemountet und alle Schreibzugriffe landen in einem RAM-Overlay, das beim Neustart verfällt. Die SD-Karte wird im Betrieb schlicht nie wieder beschrieben. Für eine Appliance, die nichts speichern muss, die perfekte Betriebsart.

    Aktiviert wird es in sudo raspi-config unter Advanced Options → Overlay File System. Zwei Fragen, zweimal Ja: Overlay aktivieren und die Boot-Partition schreibschützen. Reboot, Kontrolle mit mount | grep overlay, fertig.

    Der einzige Preis dafür ist ein Wartungsritual: Für jede künftige Änderung (Config anpassen, Updates einspielen) muss das Overlay kurz deaktiviert werden, Reboot, ändern, wieder aktivieren, Reboot. Klingt umständlich, sind aber fünf Minuten. Deshalb der wichtigste Tipp zum Schluss: Erst alle Einstellungen final machen, dann versiegeln. Wer bei aktivem Overlay konfiguriert, wundert sich nach dem nächsten Neustart, wo die Änderungen geblieben sind. Ohne Fehlermeldung.

    Die Learnings in Kurzform

    1. SD-Karten sind Verschleißteile. Wer einen Pi im Dauerbetrieb hat, sollte den Kartentod einplanen, oder ihn per Read-only-Betrieb abschaffen.
    2. Overlay File System schlägt Endurance-Karte. Die teure Karte lindert das Symptom, das Read-only-Dateisystem beseitigt die Ursache. Beides zusammen ist Gürtel plus Hosenträger.
    3. Dokumentieren, bevor es brennt. Der halbe Schmerz beim Neuaufbau war das Rekonstruieren der alten Konfiguration. Dieser Beitrag ist mein Runbook in Blogform.
    4. Sticky Clients per BSSID festnageln. Aber nur auf Zellen, die das Gerät auch sehen kann. Vorher scannen, dann pinnen.
    5. Config-Reihenfolge beachten: erst DAC, dann Raspotify, dann WLAN, ganz zum Schluss versiegeln.

    Der Pi spielt inzwischen wieder, hängt am richtigen Access Point und übersteht den nächsten Stromausfall, Kartenfehler oder Bastelunfall mit einem Schulterzucken. Und falls doch mal etwas stirbt: Neuaufbau nach diesem Rezept, 30 Minuten.

  • Von 40 auf 211 Mbit für 0 Euro: WLAN-Tuning ist Geometrie, keine Hardware-Frage

    Von 40 auf 211 Mbit für 0 Euro: WLAN-Tuning ist Geometrie, keine Hardware-Frage

    Diese Geschichte beginnt mit einem Totalschaden: Meine FRITZ!Box 5690 Pro hat sich mit einem letzten Seufzer verabschiedet. Irreparabel. Was folgte, war erst Ärger, dann eine neue 7690, und am Ende die überraschendste Erkenntnis meiner Heimnetz-Karriere: Das größte WLAN-Upgrade in diesem Haus hat keinen Cent gekostet.

    Aber der Reihe nach.

    Die Ausgangslage: ein Haus aus den 60ern mit Ansage

    Unser Haus ist Baujahr 60er, und es bringt zwei Gegner mit, die jedem WLAN-Signal den Kampf ansagen. Erstens: tragende Poroton-Wände im Erdgeschoss, gleich zwei davon zwischen Router und Küche. Zweitens: eine Kaiserdecke zwischen Erdgeschoss und erstem Stock. Falls du den Begriff nicht kennst: Das ist eine Stahlsteindecke, Hohlziegel zwischen Stahlträgern. Für Funkwellen wirkt das Stahlraster fast wie ein Faradayscher Käfig, besonders auf 5 GHz.

    Die Besatzung: eine FRITZ!Box 7690 als Zentrale, ein FRITZ!Repeater 6000 im Erdgeschoss, ein 1750E im ersten Stock und ganz am Ende der Kette ein kleiner Repeater 600, zwei Räume weiter. Und genau dort, am Ende der Kette, kamen 40 Mbit an. Instabil. Videocall-Abbrüche inklusive.

    Irrweg Nummer 1: Powerline

    Mein erster Reflex vor Jahren war Powerline. Gleiche Phase, sollte also klappen. Das Ergebnis mit einem D-Link-Set war niederschmetternd. Woran es lag, weiß ich bis heute nicht genau: langer Leitungsweg über den Verteilerkasten, ein FI-Schalter dazwischen, Störer wie LED-Netzteile auf dem Stromkreis. Vermutlich von allem etwas. Merke: Powerline ist Lotterie, und alte Elektrik verkauft keine Gewinnlose.

    Irrweg Nummer 2: mehr Hardware kaufen wollen

    Nach dem Router-Tausch war mein Plan klar: Der schwächste Repeater fliegt raus, ein neuer Wi-Fi-7-Repeater kommt rein, Problem gelöst. Kostenpunkt: rund 140 Euro. Bevor ich bestellt habe, bin ich aber nochmal in die Analyse gegangen. Und die hat den Kauf komplett gestrichen.

    Erkenntnis 1: Funkwellen mögen keinen schrägen Einfall

    Der erste Aha-Moment: Es zählt nicht nur, WO ein Repeater steht, sondern in welchem WINKEL das Signal durch die Wand muss. Trifft der Funkweg schräg auf eine 24er-Poroton-Wand, wirkt sie effektiv wie 40+ Zentimeter Mauerwerk. Steht der Repeater so, dass das Signal möglichst senkrecht durch die Wand geht, gewinnst du schnell mal 6 bis 10 dB. Das ist der Unterschied zwischen „ein Balken, nichts geht“ und „läuft einfach“.

    Ich habe also nur den Repeater 6000 im Erdgeschoss umgestellt. Ergebnis: Küche und Esszimmer, vorher Funklöcher, liefen plötzlich stabil. Kosten: null.

    Erkenntnis 2: Das Mesh entscheidet nicht immer klug

    Der zweite Aha-Moment kam beim Blick in die Mesh-Übersicht der FRITZ!Box. Mein Repeater im ersten Stock verband sich nicht etwa mit dem 6000er direkt unter sich (kurzer, senkrechter Weg durch die Decke), sondern quer durchs halbe Haus direkt mit der FRITZ!Box. Das Mesh-Steering bevorzugt die Direktverbindung zur Basis, solange deren Signal irgendwie „ausreichend“ erscheint. Jeder zusätzliche Hop kostet schließlich Latenz. Was der Algorithmus dabei übersieht: Der 6000er ist ein Triband-Gerät mit eigenem Funkmodul für den Uplink. Die Kaskade über ihn ist in meinem Grundriss die klar bessere Route.

    Das Problem: Manuell umstellen kann man den Uplink in der Repeater-Oberfläche nicht mehr, seit die Geräte im Mesh laufen. Es gibt aber einen offiziellen Trick über die Connect/WPS-Tasten: Am Repeater, der umgehängt werden soll, die Taste rund 6 Sekunden drücken, bis die LED blinkt. Dann innerhalb von 2 Minuten am Zielgerät (bei mir: dem 6000er) die Connect-Taste kurz drücken. Die WPS-Kopplung bindet den Repeater an genau dieses Gerät, und die Funkstrecke wird sein Uplink.

    So habe ich die komplette Kette umsortiert: FRITZ!Box, darunter der 6000er, darunter der Repeater im ersten Stock, daran der kleine 600er am Ende. Jede Funkstrecke möglichst senkrecht durch das jeweilige Hindernis.

    Das Ergebnis

    Am Standort des letzten Repeaters, hinter zwei Poroton-Wänden und einer Stahlsteindecke, drei Funk-Hops von der Box entfernt: 211 Mbit statt instabiler 40. Stabil. Mehr als Faktor 5, ohne einen Cent auszugeben. Der geplante Repeater-Kauf ist gestrichen, beziehungsweise aufgeschoben bis zu dem Tag, an dem Glasfaser anliegt und die Kette wieder zum Engpass wird.

    Und der vielleicht wichtigste Effekt, den kein Datenblatt misst: Die Kinder haben aufgehört zu quengeln.

    Die Learnings in Kurzform

    Erst messen, dann kaufen.

    Die Mesh-Übersicht der FRITZ!Box zeigt dir, welcher Repeater woran hängt und mit welcher Datenrate. Das ist die Landkarte fürs Tuning.

    Geometrie schlägt Sendeleistung.

    Funkwege möglichst senkrecht durch Wände und Decken legen. Einen Repeater einen Meter zu verschieben, kann mehr bringen als ein neues Gerät.

    Dem Mesh-Steering auf die Finger schauen.

    Die automatische Uplink-Wahl ist nicht immer die beste. Mit dem WPS-Kopplungs-Trick lässt sich die Kette gezielt umsortieren.

    Kaskaden sind okay, wenn das Zwischenglied Triband ist.

    Ein Repeater mit dediziertem Backhaul-Funkmodul (wie der 6000er) reicht die Bandbreite fast verlustfrei weiter.

    Powerline in Altbau-Elektrik: Erwartungen tief hängen.

    Und wenn testen, dann nur mit Rückgaberecht.

    Im nächsten Beitrag geht es um die Frage, die vor diesem ganzen Umbau stand: Welche FRITZ!Box kauft man eigentlich, wenn DSL noch da ist, Glasfaser aber irgendwann kommt? Spoiler: Es ist nicht das teurere Modell.

  • Join the world of digitization!

    Join the world of digitization!

    It’s never been that easy.

    I have been involved with the wonders of information technology for 20+ years, including 15+ years in the SAP environment. And I still love it.

    Yes, SAP. For many, a completely chaotic, impenetrable jungle in a technical nightmare world. At the beginning of my journey, it looked no different to me; the software was not very user or administrator friendly, getting information was anything but easy, and the system’s sometimes jumbled error messages didn’t make it any better. If I had a curse jar in those days, I would be rich by now.

    I still see this today when customers or colleagues come into contact with process digitalization in general – or the SAP software in particular – for the first time. „I’ll never understand that!“ „Why can’t this just work?!“ „What is this bug again?“ „Why does such expensive software look so bad?“

    Well – you can argue about the SAP Gui. But apart from that, once you have understood how the system ticks inside, how the individual areas interact and how you can orchestrate the system across modules, systems and archtitectures, then the journey through the roaring SAP sea suddenly becomes an exciting journey full of unexpected possibilities. And by the way, this is valid for most cases when it comes to digitization!

    In this blog I would like to show individual parts of this journey, especially I would like to highlight how easy it is to get things up and running. Let me guide you through the cliffs. Join the ride!

    3 … 2 … 1 … Go digital!