apfel von der Kommandozeile
Artikel 1 · Serie: Ein lokaler Coding-Agent mit apfel
Bevor wir einen Agenten bauen, lernen wir das Werkzeug kennen, auf dem er sitzt. apfel ist eine kleine Swift-CLI, die Apples Foundation Model auf macOS 26 als Kommandozeile, als OpenAI-kompatiblen HTTP-Server und als interaktiven Chat zugänglich macht. Wir installieren apfel über Homebrew, gehen die drei Modi der Reihe nach durch, Prompt, Serve, Chat und schreiben dabei ein paar kleine Shell-Skripte, die später als Bausteine für den Agenten dienen. Am Ende haben wir ein Demo-Repo auf Codeberg mit Tag v0.1 und einen ersten qualitativen Eindruck davon, was das kleine on-device-Modell trägt und wo es wackelt.
Voraussetzungen verifizieren
apfel setzt eine sehr konkrete Plattform voraus. Wir checken sie kurz, bevor wir installieren.
sw_vers # macOS 26.3 (Tahoe) oder neuer
uname -m # arm64
xcodebuild -version # Xcode 26.4 oder neuer (optional)
Die Pflichtangaben: ein Apple-Silicon-Mac (M1 oder neuer), macOS 26.3 (Tahoe) und ein in den Systemeinstellungen aktiviertes Apple Intelligence. Ohne aktives Apple Intelligence quittiert apfel jeden Aufruf mit Exit-Code 5 („Model unavailable"). Xcode 26.4+ braucht es nur, wenn wir später die HEAD-Variante von apfel selbst bauen wollen; für die Homebrew-Bottle ist es nicht nötig.
| Komponente | Stand v0.1 | Wofür |
|---|---|---|
| Hardware | Apple Silicon (M1+) | arm64-Architektur, Neural Engine |
| macOS | 26.3 (Tahoe) | Foundation-Models-Framework |
| Apple Intelligence | aktiv | Modell wird sonst nicht freigeschaltet |
| Homebrew | aktuell | Installations-Paketmanager |
| Xcode (optional) | 26.4 | Source-Build von apfel |
Installation
brew install apfel
apfel --version # apfel v1.5.1
apfel --release # detaillierte Release- und Build-Info
apfel --help # alle Modi und Flags auf einen Blick
Die Homebrew-Formel zieht die aktuelle Bottle. Stand des Tags v0.1 ist apfel 1.5.1; die Version steht im Frontmatter dieses Artikels und in docs/setup.md des Demo-Repos. Bei späteren Versionssprüngen in der Reihe benennen wir den Sprung explizit im jeweiligen Artikel.
apfel --help ist die wichtigste erste Lektüre. Es zeigt die drei Modi — Prompt, --serve, --chat — als Hauptverwendungen und listet die Flags mit Erklärungen. Die USAGE-Zeile ist verbindlich:
USAGE:
apfel [OPTIONS] <prompt> Send a single prompt
apfel -f <file> <prompt> Attach file content to prompt
apfel --chat Interactive conversation
apfel --serve Start OpenAI-compatible HTTP server
Flags stehen vor dem positionalen Prompt-Argument. apfel -s "..." "..." ist richtig, apfel "..." -s "..." lässt das, was nicht passt, stillschweigend fallen. Das klingt trivial; in der Sektion „Stolpersteine aus dem Bau" kommt es zurück.
Prompt-Modus
Der Prompt-Modus ist eine einzelne, in sich abgeschlossene Anfrage. Wir übergeben einen String, apfel schickt ihn an das lokale Modell und schreibt die Antwort nach stdout.
apfel "Was ist eine Closure in Swift, in zwei Sätzen?"
Das ist der Baustein, auf dem alles andere aufsitzt. Der Agent-Loop, den wir später bauen, ist im Kern eine Schleife aus solchen Aufrufen, mit zusätzlichem Kontext, Tool-Calling und Bestätigungs-Gates.
Ein erstes brauchbares Beispiel statt Hello-World ist ein Commit-Message-Vorschlag aus dem gerade gestagten Diff:
git diff --cached | apfel \
-s "Du schreibst Conventional Commits in einer Zeile, max. 60 Zeichen, kleingeschrieben außer Eigennamen, kein Punkt am Ende." \
"Schreibe eine passende Message für diesen Diff."
stdin liefert den Diff, der positionale Prompt steuert das Verhalten, -s setzt die Rolle. Drei Mechaniken in einer Zeile, und das ist im Kern, was wir später als Tool-Aufruf im Agenten kapseln werden.
JSON-Output und das Skript-Pattern
apfels Skripting-Pattern ist eingebaut: -o json schaltet von Klartext-Antwort auf strukturiertes JSON um. Damit lassen sich Antworten sauber durch jq pipen.
apfel -o json "Erkläre Higher-Order Functions in einem Satz." | jq -r '.content'
Genau dieses Pattern lebt im Demo-Repo als examples/cli/04-json-pipe.sh. Es ist drei Zeilen lang und zeigt, wie aus einem apfel-Aufruf ein UNIX-Werkzeug wird, das sich in Pipes einreiht.
Für Skripte ist es nützlich, dass apfel klare Exit-Codes liefert:
| Code | Bedeutung |
|---|---|
0 | Success |
1 | Runtime error |
2 | Usage error (falsche Flags) |
3 | Guardrail blocked (Content-Policy) |
4 | Context overflow (Input zu lang) |
5 | Model unavailable (Apple Intelligence nicht aktiv) |
6 | Rate limited / busy |
Ein Skript, das apfel aufruft, kann auf 5 reagieren und den Nutzer auf Apple Intelligence hinweisen, auf 4 mit kürzerem Kontext nachfassen, auf 3 die Guardrail dokumentieren. Das ist mehr Struktur, als viele Cloud-CLIs mitbringen.
Das Foundation Model antwortet nicht deterministisch. Für reproduzierbare Smoke-Tests gibt es --seed <n>. Wenn wir später im Agenten Tests gegen Modell-Verhalten schreiben, ist --seed der Anker.
System-Prompt und Datei-Input
-s "<rolle>" setzt einen System-Prompt, der die Persona oder das Format vorgibt. Datei-Inhalt geht auf zwei dokumentierten Wegen ins Modell, beide stehen in apfel --help.
Variante A: -f als apfel-natives Flag.
apfel -f notes.md "Fasse den folgenden Inhalt in drei Sätzen zusammen."
apfel -f a.txt -f b.txt "Vergleiche diese beiden Dateien."
-f ist repeatable; mehrere Dateien werden in einer Anfrage angehängt.
Variante B: stdin (Pipe oder Input-Redirect).
apfel "Fasse den folgenden Inhalt in drei Sätzen zusammen." < notes.md
cat notes.md | apfel "Fasse den folgenden Inhalt in drei Sätzen zusammen."
Beides funktioniert. Die Demo-Skripte im Repo nutzen stdin-Redirect (< file), weil er ohne externe Abhängigkeit auskommt und sich gut mit anderen UNIX-Werkzeugen in Pipes verkettet. -f ist im Multi-File-Fall die kompaktere Form.
Ein Punkt, den das kleine on-device-Modell uns gleich beibringt: bei Code-Aufgaben muss der System-Prompt den vorgegebenen Input explizit in den Fokus rücken, sonst erfindet das Modell gerne einen anderen Code und erklärt den. Was wirkt:
apfel \
-s "Du bist ein präziser Senior-Entwickler. Erkläre AUSSCHLIESSLICH den vorgegebenen Code. Erfinde keinen anderen Code, schreibe keine eigene Variante." \
"Erkläre, was dieser Code tut." \
< fibonacci.swift
Das Skript examples/cli/02-explain-code.sh macht genau das. Ohne den Anti-Halluzinations-Zusatz im System-Prompt war das Modell in unserem ersten Smoke-Test mehrfach kreativer als nötig.
Chat-Modus
apfel --chat öffnet eine interaktive Sitzung im Terminal. Das Modell hält Kontext über mehrere Runden, bis wir die Sitzung beenden oder das Kontextfenster überläuft.
apfel --chat -s "Du bist ein nüchterner Coding-Assistent. Antworte knapp und klar."
apfel bringt Strategien mit, wie der Kontext gemanagt wird, wenn die Sitzung lang wird:
newest-first(Default) — älteste Turns fliegen zuerst rausoldest-first— neuere Turns weichen, älteste bleibensliding-windowmit--context-max-turns <n>— feste Anzahl Turnssummarize— apfel komprimiert ältere Turns selbständigstrict— Fehler bei Overflow, kein automatisches Trimmen
--context-status schaltet eine Anzeige nach jedem Turn frei, die den Füllstand des Kontextfensters meldet. Das ist eines der nützlichsten Flags fürs Verstehen des on-device-Modells: wir sehen direkt, wann wir an die Grenze stoßen.
Im Demo-Repo liegt ein Thin-Wrapper-Skript examples/cli/07-chat-session.sh, das den System-Prompt auf einen kurz-und-klar-Stil setzt und die Default-Context-Strategy behält. Der Chat-Modus ist die Sandkasten-Umgebung, in der wir die Plan-Act-Observe-Mechanik des Agenten später am direktesten nachvollziehen können.
Vorgeschmack Serve-Modus
apfel --serve startet einen HTTP-Server auf 127.0.0.1:11434 mit einer OpenAI-kompatiblen API.
apfel --serve
# Server lauscht auf http://localhost:11434/v1
Aus einem anderen Terminal:
curl -s http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "apple-foundationmodel",
"messages": [{"role": "user", "content": "Erkläre Higher-Order Functions in einem Satz."}]
}' | jq -r '.choices[0].message.content'
Das ist die Brücke, an der unser Swift-Agent in Artikel 3 andocken wird. /v1/chat/completions mit Streaming via SSE, /v1/models und /health sind die wichtigsten Endpoints. apfel beschreibt sich selbst als Drop-in-Ersatz für jede SDK, die einen OpenAI-Endpoint erwartet — das macht den Serve-Modus zum eigentlichen Türöffner für eigene Tools.
Wir steigen hier nur kurz ein. Artikel 2 nimmt sich Serve im Detail vor: alle Endpoints, Token-Auth, CORS, das, was hier nur als Liste in --help steht.
Was das Modell trägt, wo es wackelt
Nach dem Smoke-Test mit den Demo-Skripten sammeln sich erste qualitative Eindrücke. Das ist kein Eval, den machen wir in Artikel 6 mit Aufgaben-Kanon und Quellen-Labels pro Zahl. Aber es zeigt eine grobe Linie.
Das Modell trägt:
- Zusammenfassen mittlerer Textstücke (eine Notiz von wenigen Sätzen wird in drei Sätzen sauber gespiegelt)
- Übersetzung Deutsch ↔ Englisch in beide Richtungen, idiomatisch
- Code-Erklärung, wenn der System-Prompt auf den vorgegebenen Code fokussiert
- Diff-Review im 3-Punkte-Format, wenn die Aufgabe klar abgesteckt ist
- Konzept-Erklärungen zu Programmier-Themen (Higher-Order Functions, Rekursion, Closures)
Das Modell wackelt:
- Geographische und politische Faktenfragen auf Deutsch. „Was ist die Hauptstadt von Österreich?" liefert in unserer Messung eine Marketing-Reflex-Antwort über „Webseiten der zuständigen Behörden". Dieselbe Frage auf Englisch „What is the capital of Austria?" liefert sauber „Vienna". Die Guardrails greifen sprach-asymmetrisch¹.
- Generische „Nenne drei …"-Prompts triggern den Marketing-Reflex auch auf Englisch.
--permissivelockert die Filter, hilft hier aber nicht immer. - Code-Halluzination: ohne expliziten Fokus-Prompt erfindet das Modell bei Code-Aufgaben gerne eigenständig anderen Code, statt den vorgegebenen zu erklären.
¹ Eigenmessung 2026-06-02 mit apfel 1.5.1 auf macOS 26.3, dokumentiert im Buildlog der Reihe.
Das Modell ist klein (Apple Machine Learning Research nennt für die On-Device-Variante der ersten Apple-Intelligence-Generation rund drei Milliarden Parameter²) und stochastisch: gleicher Aufruf, mal saubere Antwort, mal Deflection. Für reproduzierbare Tests setzen wir --seed. Für Aufgaben, die zuverlässig laufen sollen, lohnt sich ein Anti-Halluzinations-Prompt mit klarem Fokus auf den Input.
² Apple Machine Learning Research (Juni 2024, aktualisiert Juli 2024): „Introducing Apple’s On-Device and Server Foundation Models", https://machinelearning.apple.com/research/introducing-apple-foundation-models.
Demo-Repo: apfel-coding-agent v0.1
Der Stand am Ende dieses Artikels ist auf Codeberg eingefroren als Tag v0.1: https://codeberg.org/rotecodefraktion/apfel-coding-agent/src/tag/v0.1. Wer mitziehen möchte, findet dort alles, was wir hier gemacht haben — die sieben Beispiel-Skripte, die Setup-Doku und die CLAUDE.md mit den Reihen-Konventionen.
Demo-Repo apfel-coding-agent v0.1 einrichten
Klonen und auf den Tag einsteigen:
git clone https://codeberg.org/rotecodefraktion/apfel-coding-agent.git
cd apfel-coding-agent
git checkout v0.1
chmod +x examples/cli/*.sh
Inhalt im Tag v0.1:
README.md— Reihen-Bezug und Quick-StartCLAUDE.md— Konventionen für Code-Sessions (Sprache, Stack, Pfadkonvention)LICENSE— MIT.gitignore— Swift-/macOS-Standard-Ignoresdocs/setup.md— Installation, USAGE-Regeln, Datei-Input-Varianten, Exit-Codes, sprach-asymmetrische Guardrails, Stand-Snapshotexamples/cli/— sieben kleine Skripte:01-summarize-notes.sh— Notiz zusammenfassen (stdin-Redirect)02-explain-code.sh— Code erklären mit Anti-Halluzinations-Prompt03-suggest-commit-message.sh— Conventional Commit ausgit diff --cached04-json-pipe.sh—-o json | jqals Skript-Pattern05-translate.sh— Übersetzen mit System-Prompt06-explain-diff.sh— Diff-Review im 3-Punkte-Format07-chat-session.sh— interaktive Chat-Session mit Default-System-Prompt
Erster Test, dass alles greift:
echo "The series builds a local coding agent in Swift on top of apfel." | apfel "Summarize this in one sentence."
Wenn eine kurze englische Zusammenfassung zurückkommt, ist die Installation durch.
Stolpersteine aus dem Bau
Drei Themen, die im ersten Anlauf Zeit gekostet haben, als Mitnahme für alle, die später mitmachen.
Argument-Reihenfolge. apfels USAGE schreibt apfel [OPTIONS] <prompt>. Flags müssen vor dem positionalen Prompt stehen. apfel "..." -f file.md ignoriert die Datei. apfel -f file.md "..." ist korrekt. Ein Lesefehler in der --help-Ausgabe, der den Skripten im ersten Wurf alle Datei-Inputs gekostet hat.
Sprach-Asymmetrie der Guardrails. Die Hauptstadt-von-Österreich-Frage auf Deutsch trifft einen restriktiveren Filter als dieselbe Frage auf Englisch. Für Smoke-Tests in einer deutschsprachigen Reihe heißt das: Faktenfragen auf Englisch stellen oder den System-Prompt auf Englisch halten. Im Demo-Repo nutzen alle Skripte englische System-Prompts; die User-Eingaben (Notizen, Code, Diffs) sind sprachneutral.
Code-Halluzination ohne Fokus. Wenn wir das Modell bitten, einen Code zu erklären, ohne im System-Prompt zu sagen „erkläre AUSSCHLIESSLICH den vorgegebenen Code", erfindet es gerne einen anderen Code und erklärt den souverän. Der Anti-Halluzinations-Zusatz in 02-explain-code.sh ist deshalb keine Stil-, sondern eine Korrektheits-Maßnahme.
Wie es weitergeht
Artikel 2 nimmt sich den Serve-Modus im Detail vor: alle drei Endpoints, das Chat-Completions-Schema mit Streaming via SSE, Token-Auth und CORS, der Aufbau eines OpenAI-Drop-in im Detail. Damit haben wir die Grundlage, an die unser Swift-Client in Artikel 3 andocken kann.
Vorheriger Artikel: Das Modell ist schon da — Prolog zum lokalen Coding-Agenten. Nächster Artikel: Der Serve-Modus und das OpenAI-Protokoll (Platzhalter — Link wird mit Publish von Artikel 2 final). Repo-Tag: v0.1.