Der vorletzte Post in diesem Blog hat sich mit der Sicherheit von Werkzeugen zur agentischen Softwareentwicklung beschäftigt. In diesem Post geht es hingegen um erste, eigene Erfahrungen mit so einem Tool, ganz konkret Google Antigravity. Ich spreche dabei für mich nicht von Vibe Coding, da mir weiterhin der Blick in den Quellcode wichtig ist und das technische Verständnis des Produkts, welches sich erzeuge.
Warum Antigravity
Die Wahl war zuerst eher zufällig, ein Zusammentreffen von Meldungen zu diesem noch relativ neuen Angebot, Gelegenheit etwas neues auszuprobieren und meiner alten Affinität zu Google Produkten.
Nach meinen ersten Erfahrungen damit würde ich die Entscheidung im Nachhinein so begründen:
- Antigravity setzt auf Visual Studio Code auf, und damit habe ich schon Erfahrungen
- Die duale Sicht auf die Softwareentwicklung gefällt mir: Einmal die klassische VS Code Oberfläche, die mir als Softwareentwickler den gewohnten, direkten Umgang mit dem Code erlaubt. Dazu aber die Parallelwelt der komplett auf den agentischen Ansatz fokussierten ‚Agent Manager‘ Sicht
Nachteilig ist heute, dass es zumindest nach meinem Stand nicht möglich ist selbst gehostete Modelle zu verwenden:

Der Start: Was erlaube ich den Agenten?
Bei den ersten Schritten mit Antigravity war die Erinnerung an die ganzen Sicherheitsaspekte noch sehr frisch, und ich habe jede Interaktion mit dem System, selbst ein einfaches ls -ltr explizit erlaubt. Aber das ermüdet einen schnell und wenn die Agenten richtig nützlich sein sollen, dann müssen sie auch in Teilen eigenständig voranschreiten können.
Hier kann man sich in den zahlreichen Einstellungen von Antigravity und dem VS Code Unterbau etwas verlieren, ich habe es dann meist bei dem Default gelassen.
Das erste Projekt: Generieren einer komplexen Ordnerstruktur
Für eine anstehende Aufgabe brauche ich eine Möglichkeit beliebig große Ordnerstrukturen mit einem spezifischem Inhalt zu generieren. Ein Kommandozeilentool dafür zu basteln ist ein schönes, in sich abgeschlossenes Projekt. Und dabei nicht völlig trivial. Die Ordnerstruktur ist grundsätzlich so:
/Wurzelverzeichnis
/12345
/Textdatei.txt
/PDF-Datei.pdf
/12346
/Textdatei.txt
/PDF-Datei.pdf
/12347
/Textdatei.txt
/PDF-Datei.pdf
Ich möchte bei der Generierung das vorgeben können:
- Wie soll das Wurzelverzeichnis heißen
- Wie viele Unterverzeichnisse sollen erzeugt werden
- Wie groß sollen die PDF Dateien sein
Dabei möchte ich in den Textdateien einen zufälligen Inhalt aus einer vorgegebenen Liste eintragen und die PDF-Dateien sollen etwas in der Größe variieren und den Namen des Verzeichnisses in einer lesbaren Form enthalten, damit später die korrekte Verarbeitung leicht geprüft werden kann.
Es soll Java 25 verwendet werden und Maven für den Build, damit ich das Produkt auch nachvollziehen und ggf. selbst anpassen kann.
Schneller Erfolg nach Startproblemen
Leider habe ich mir das grundsätzliche Prompt nicht gemerkt und es ist mir später verloren gegangen, nachdem ich das Verzeichnis des Projekts verschoben habe. Offenbar merkt sich Antigravity seine Chats mit Bezug zu dem Verzeichnis, denn nach der Änderungen wurden sie nicht mehr angezeigt.
Aber auf Basis des Prompts, welches die Architektur festlegte, erzeugt Antigravity in dem leeren Verzeichnis schnell die grundlegenden Strukturen und den Maven Build. Das Startproblem kam dann vom VS Code Teil: Hier wurden Add-ons installiert, die nicht mit Java 25 umgehen konnten und das herauszufinden hat dann fast mehr Zeit gekostet, als das Projekt abzuschließen.
Im Endeffekt habe ich dann zwei Java Dateien erhalten, die meine Anforderungen perfekt erfüllen. Einmal UcanDummyGenerator.java, welches dann auf der Kommandozeile aufgerufen wird:
package org.unibi.us;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
/**
* UcanDummyGenerator - A tool for creating complex folder structures for
* student tests.
*/
public class UcanDummyGenerator {
private static final String[] GRADES = {"1,0", "1,3", "2,0", "2,3", "3,0", "3,3", "4,0", "5,0"};
private static final Random RANDOM = new Random();
public static void main(String[] args) {
if (args.length != 3) {
System.err.println("Usage: UcanDummyGenerator <number_of_subfolders> <target_location> <sizeGoalKB>");
System.exit(1);
}
int numSubfolders;
int sizeGoalKB;
try {
numSubfolders = Integer.parseInt(args[0]);
sizeGoalKB = Integer.parseInt(args[2]);
} catch (NumberFormatException e) {
System.err.println("Error: The first and third arguments must be integers (number of subfolders and size in KB).");
System.exit(1);
return;
}
String targetName = args[1];
Path targetPath = Paths.get("outputs", targetName);
if (Files.exists(targetPath)) {
System.err.println("Error: Target location '" + targetPath + "' already exists. Execution denied.");
System.exit(1);
}
try {
Files.createDirectories(targetPath);
System.out.println("Created target directory: " + targetPath.toAbsolutePath());
Set<Integer> matriculationNumbers = new HashSet<>();
while (matriculationNumbers.size() < numSubfolders) {
// Generate a random 7-digit number (1000000 to 9999999)
matriculationNumbers.add(1000000 + RANDOM.nextInt(9000000));
}
for (Integer matriculationNumber : matriculationNumbers) {
Path subfolderPath = targetPath.resolve(String.valueOf(matriculationNumber));
Files.createDirectory(subfolderPath);
// Create note.txt
Path noteFile = subfolderPath.resolve("note.txt");
String grade = GRADES[RANDOM.nextInt(GRADES.length)];
Files.writeString(noteFile, grade);
// Create klausur.pdf with matriculation number as content
Path pdfFile = subfolderPath.resolve("klausur.pdf");
PDFGenerator.generate("Klausur für " + String.valueOf(matriculationNumber), pdfFile.toString(), sizeGoalKB);
}
System.out.println("Successfully generated " + numSubfolders + " subfolders in '" + targetPath + "'.");
} catch (IOException e) {
System.err.println("An error occurred during directory or file creation: " + e.getMessage());
e.printStackTrace();
System.exit(1);
}
}
}
Und dann noch PDFGenerator.java für die Erzeugung der PDF Dateien:
package org.unibi.us;
import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
/**
* PDFGenerator - A utility class for generating PDF files from plain text.
*/
public class PDFGenerator {
/**
* Generates a PDF file with the given content and saves it to the specified
* path. Optionally pads the file with random bytes to reach a target size.
*
* @param content The plain text content to be included in the PDF.
* @param filePath The path where the PDF will be stored.
* @param sizeGoalKB The target size in kilobytes. The result will be +/-
* 10% of this value. Pass 0 or less for no padding.
* @throws IOException If an I/O error occurs during PDF creation or saving.
*/
public static void generate(String content, String filePath, int sizeGoalKB) throws IOException {
java.util.Random random = new java.util.Random();
try (PDDocument document = new PDDocument()) {
PDPage page = new PDPage();
document.addPage(page);
try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
contentStream.beginText();
// Using standard Helvetica font for simplicity
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12);
contentStream.newLineAtOffset(50, 750); // Start near the top-left corner
// PDFBox doesn't automatically handle newlines in showText,
// so we split by newline and handle each line.
String[] lines = content.split("\\r?\\n");
for (String line : lines) {
contentStream.showText(line);
contentStream.newLineAtOffset(0, -15); // Move down for the next line
}
contentStream.endText();
}
if (sizeGoalKB > 0) {
// Calculate target bytes with +/- 10% random factor
double factor = 0.9 + (random.nextDouble() * 0.2);
long targetBytes = (long) (sizeGoalKB * 1024 * factor);
// Save to a temporary buffer to see current size
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
document.save(baos);
long currentSize = baos.size();
if (currentSize < targetBytes) {
long paddingNeeded = targetBytes - currentSize;
// Add a dummy stream with random bytes to the document
byte[] padding = new byte[(int) Math.min(paddingNeeded, Integer.MAX_VALUE)];
random.nextBytes(padding);
org.apache.pdfbox.cos.COSStream dummyStream = document.getDocument().createCOSStream();
try (java.io.OutputStream os = dummyStream.createOutputStream()) {
os.write(padding);
}
// We need to reference this stream somewhere so it's saved.
// Adding it to a custom entry in the document catalog works well for "bloating".
document.getDocumentCatalog().getCOSObject().setItem(
org.apache.pdfbox.cos.COSName.getPDFName("Padding"),
dummyStream
);
}
}
document.save(filePath);
}
}
}
Der Code ist so weit sauber strukturiert, fängt Fehlerfälle ab wie fehlende Parameter und hat zahlreiche Kommentierungen. Bei den Kommentierungen zeigt sich, dass Antigravity auf Englisch ‚denkt‘, selbst wenn man problemlos auf Deutsch mit dem Tool sprechen kann.
Eine Erweiterung der Funktion geht dann einfach per Prompt. Zum Beispiel so:
Bitte ergänze in dem PDF noch die Note, die für die Textdatei gewählt wurde. Die Note soll unterhalb der Überschrift mit der Matrikelnummer in einer separaten Zeile stehen und das Präfix 'Benotung: ' erhalten
Dann legt Antigravity bei einfacheren Aufgaben direkt los:

Man sieht hier, wie der Build ausgeführt wird, um die syntaktische Korrektheit des erzeugen Codes direkt zu validieren. Bei komplexeren Aufgaben erhält man eine Planungsdarstellung, in der Antigravity seine Überlegungen für die Umsetzung präsentiert. Hier kann man dann kommentierend eingreifen und so das Vorgehen beeinflussen. Oder auch komplett ablehnen. Ein Beispiel dafür zeige ich gleich im Kontext des nächsten Projekts, welches ich mit Antigravity angegangen bin:
Baue mir einen Proxy für die OpenAI API
Beim zweiten Projekt habe ich das initiale Prompt nicht verloren, und das war dieses:
Ich möchte meinen Nutzern den Zugriff auf die OpenAI API bzw. kompatible APIs ermöglichen. Aber um Logging und Abrechungen machen zu können sollen die Zugriffe durch einen eigenen Proxy geführt werden. Das sind die Eckpunkte für die technische Umsetzung:
- Die Implementierung soll in Java erfolgen, mindestens Java Version 17
- Für die Erstellung der API Endpunkte soll Jersey verwendet werden
- Lass uns in der Planung festlegen, welche der OpenAI Endpunkte ich durchreichen will, und welche nicht
- Meine Proxy API soll ansonsten komplett kompatibel sein zur OpenAI API
- Ich brauche ein Logging der Zugriffe, bei der die verwendeten Tokens etc. ausgegeben werden
- Ich möchte in der Lage sein nicht nur Richtung OpenAI API Anfragen weiterzuleiten, sondern auch an andere OpenAI-kompatible APIs. Der API Endpunkt und der API Key sollen daher konfigurierbar sein
- Für die Ansprache der OpenaAI API soll das langchain4j Paket verwendet werden
- Ich möchte in späteren Schritten eine Testmöglichkeit aufbauen mit einer einfachen Chatoberfläche
Hier gibt es nun einen detaillierten Vorschlag, wie das Thema angegangen werden soll, und der beginnt so:

Wieder ist schnell eine erste Version lauffähig und Antigravity hat sich auch ein eigenes Testscript erzeugt, um die Funktionsweise seiner Programmierung jederzeit validieren zu können:
#!/bin/bash
java -cp target/openai-proxy-1.0-SNAPSHOT.jar org.unibi.us.proxy.Main &
SERVER_PID=$!
sleep 5
curl -X POST http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-mini",
"messages": [{"role": "user", "content": "Say hello and good bye!"}]
}'
kill $SERVER_PID
Man sieht in späteren Schritten oft, dass Antigravity es selbst verwendet. Und für eigene, manuelle Tests ist es auch nützlich. Aber spannender ist dann etwas, was ich sonst auf Grund des Aufwands wohl nicht gemacht hätte: Ich lasse mir von Antigravity ein kleines Chatinterface bauen, welches mir Tests des Proxys im Webbrowser erlaubt. Und das sieht am Ende so aus:

Wer hier ein Déjà-vu hat und sich an den Header des eKVVs erinnert fühlt: Ich habe Antigravity einen Screenshot gegeben und aufgefordert das Design der Seite zu übernehmen. Wie man sieht hat das nur so halb geklappt, aber es zeigt wie verblüffend einfach es sein kann Designs zu übernehmen.
Was man hier auch sieht: Ich habe mir verschiedene Einstellungen in die Oberfläche einbauen lassen, damit ich das Durchschleifen an die API testen kann. Diese Mühe hätte ich mir vermutlich nicht gemacht, wenn ich den Proxy als manuell programmiertes Projekt implementiert hätte.
Mein Fazit aus den ersten beiden Projekten
Nach den ersten beiden kleinen Projekten – und faktisch noch einem dritten, bei dem ich in Antigravity Folien für eine Präsentation mit dem Slidev Framework entwickelt habe – möchte ich meine Erfahrungen so zusammenfassen:
Stand alone Projekte nur noch so
Auch wenn ich schon lange Software entwickle, und mir das Spaß macht: Eigentlich geht es mir meist mehr um die Realisierung von Ideen, als darum dabei neue Techniken zu erlernen, oder um das Coden um des Codens willen.
Mit einem Tool wie Antigravity lassen sich viele Hürden, die man sonst bei neuen Projekten immer wieder überwinden muss, auf die KI abwälzen. Und man kann sich selbst mehr auf das Produkt fokussieren und seine Ideen dazu, wie man es weiterentwickeln kann.
Durch den gleichberechtigten Blick in den erzeugten Code kann ich nachverfolgen, was passiert, und gleichzeitig noch etwas lernen.
Noch nicht so richtig klar ist mir nach diesen begrenzten Einsätzen, wie man mit so einem Tool in umfangreichen Codebasen arbeiten kann, ohne das die Kosten explodieren oder man die Sorge haben muss, dass das notwendige Codeverständnis auf Grund des begrenzten Kontextes nicht mehr ausreicht.
Sofort git einrichten
Ich habe in den Projekten schnell git eingerichtet, auch wenn das inhaltich vielleicht gar nicht so notwendig erscheint. Aber es macht nochmal schneller sichtbar, was die Agenten im letzten Schritt geändert haben und die Sache so besser kontrollierbar. Und wenn es etwas schief gehen sollte, dann kann man wieder zurück (zumindest so lange nicht das komplette Verzeichnis ausradiert wird).
Management- und Organisationsqualitäten sind plötzlich wichtig
Die agentische Softwareentwicklung fordert etwas andere Fähigkeiten, als das einsame vor sich hin programmieren:
- Die Fähigkeit das angestrebte Ergebnis zu beschreiben ist die Grundvoraussetzung vor eine erfolgreiche Entwicklung
- Die eigene Rolle verschiebt sich hin zu einem Auftraggeber oder ‚Vorgesetzten‘ einer kleiner Heerschar von Agenten
- Diesen muss man die Arbeit in angemessenen Häppchen geben, damit das Ergebnise in die richtige Richtung geht, verständlich und kontrollierbar bleibt
- Man kann mehrere Agenten parallel losschicken bzw. das sollte man, da die Arbeiten nicht instantan erledigt werden. Hier kann man durchaus etwas in Stress geraten, wenn sich alle paar Minuten einer der Agenten meldet und den Abschluss seiner Arbeit meldet
Ich würde trotzdem sagen, dass ein Verständnis der Softwareentwicklung und des generierten Codes weiterhin wichtig ist, zumindest die Qualität des Produkts eine gewissen Relevanz hat. Auch sieht man, dass Antigravity nicht immer von selbst hinter sich aufräumt und manchmal Artefakte von Tests und Irrwegen erhalten bleiben, die es nicht braucht.
Agenten leben in der Vergangenheit
Die Agenten werden von Sprachmodellen betrieben, deren Trainingsdaten nie aktuell sein können. Auch wenn sie Webrecherchen durchführen können, so schützt dies nicht davor bei veralteten Softwareständen zu landen.
Mir ist es im Proxy Projekt zum Beispiel erst spät aufgefallen, dass Langchain4j in einer 0er Version verwendet wurde, während die aktuelle Version bereits 1.12 ist.
Agenten wollen manchmal zu viel
Im Proxy Projekt hat sich Antigravity sehr in dem Versuch verhakt, die statische HTML Datei mit dem Chatinterface über den gleichen Grizzly Server auszuliefern, der auch die API Endpunkte bereitstellte. Das hat lange nicht funktioniert und viele Runden, in denen Antigravity um sich selbst gekreist ist. Und am Ende bei einer nicht wirklich eleganten Lösung ankam, die man nur für einen Test dulden konnte.
Hier hätte ich vielleicht früher eingreifen und vorgeben sollen, dass ein zweiter Server gestartet wird. Oder die HTML Datei gleich direkt aus dem Dateisystem aufrufen sollen.
Kosten laufen schnell aus dem Ruder
Diese zahlreichen Versuche das Problem mit den sich in die Quere kommenden Webrequests zu lösen waren wohl auch ein Grund, warum mein Guthaben an AI Credits dann schnell aufgebraucht war. Da ich das Projekt nicht so lange unterbrechen wollte, bis ich einige Tag später wieder ein paar Credits von Google geschenkt bekomme, habe ich dann kurzfristig ein Abo abgeschlossen.
Die Oberfläche weist einen nicht wirklich offensiv darauf hin, wie schnell man seine Credits verbraucht, oder wie aufwändig einzelne Aufgaben sind.
Autocompletion auf Steroiden
Fazinierend finde ich die Vorschläge für automatische Vervollständigungen. In beiden Projekten habe ich nachträglich eine README.md angelegt und teilweise wird einem ein kompletter Paragraph mit sinnvollem Text zum Projekt vorgeschlagen, den man einfach nur bestätigen muss.
Auch Commit Messages, Inhalte von bekannten Dateiformaten wie .gitignore und selbst Inhalte für meine Slidev-Präsentation zu BIKI und großen Sprachmodellen kommen wie von Geisterhand. Manchmal allerdings auch etwas zu aufdringlich.
Alles in einer Umgebung
Für die Entwicklung muss man eigentlich Antigravity nicht mehr verlassen, so lange die Credits nicht aufgebraucht sind: Ob man nun erst ein Konzept erstellt, sich bei der Frage der richtigen Technologie berät, einen Stacktrace verstehen will, immer kann man mit der KI sprechen und bleibt dabei im gleichen Kontext.
Kommt die Zeit der Eigenentwicklungen zurück?
Ich bin traditionell eher auf der Seite derer, die Eigenentwicklungen in Organisationen beführworten. Wie könnte das auch angesichts des BIS Projekts anders sein. Trotzdem war immer klar, dass sich komplexe Entwicklungen, insbesondere in Bereichen, die starken Regulierungen unterworfen sind, manchmal so nicht in wirtschaftlicher Weise umsetzen lassen. Und es braucht auch immer Leute, mit denen man dies tun kann.
Die agentische Softwareentwicklung verschiebt aber die Grenze sehr, bis zu der eine Eigenentwicklung vernünftig sein kann für eine Organisation, und das aus mehreren Gründen:
- Durch Vibe Coding kann es Fachabteilungen möglich werden sehr realistische Prototypen zu erstellen, die genau ihren Anforderung entsprechen. Sie müssen sich hier nicht mehr unbedingt von Software Anbietern beraten und damit in die Richtung von Standardprodukten drängen lassen.
- Auch für die IT Abteilungen kann sich die Zeit von der Idee zur produktiven Umsetzung drastisch verkürzen, insbesondere wenn agentische Entwicklung mit CI/CD Umgebungen verbunden ist, die eine rasche Auslieferung neuer Stände ermöglichen.
- Eine passgenaue Eigenentwicklung kann damit möglicherweise schneller zur Verfügung stehen, als eine komplexe Standardsoftware, die verstanden, angepasst und eingeführt werden muss.
- Damit kann einer der großen Vorteile einer Eigenentwicklung, nämlich die Möglichkeit sich iterativ einer komplexen Prozesslage zu nähern, wieder genutzt werden.
Hier steht denke ich ein Umdenken an, damit die neuen Chancen durch die KI realisiert werden können. Dazu muss das Denken in den Bahnen der in den letzten Jahrzehnten etablierten ‚Sicherheiten‘ bei der Wahl des richtigen Softwareprodukts aufhören.