Eine agentische Entwicklungsumgebung.
Open Source und läuft mit den Modellen, die du ohnehin nutzt. Starte einen Agenten auf einer Maschine und mach mit derselben Session auf einer anderen weiter.
Ich habe 100 Uploads über eine Verbindung laufen lassen, die jede fünfte Anfrage verliert. Fehlgeschlagene Uploads sanken von 23 auf 1.
Der eine Fehlschlag war eine Datei, die der Server abgelehnt hat, da hilft auch kein neuer Versuch.
Das Upload-Tempo steht jetzt im Header neben dem Dateinamen und verschwindet, wenn die Warteschlange leer ist.
Bin dran. Ich melde mich hier, sobald es etwas zu sehen gibt.
Nachgestellt und die Ursache gefunden. Der Fix ist bereit für das Review.
Exponentielles Backoff zur Upload-Warteschlange hinzugefügt. Ein fehlgeschlagener Chunk wird bis zu fünfmal wiederholt, und der Fortschrittsbalken zeigt den Versuch. Tests decken den Timeout und den 503-Fall ab.
Früher wurde ein fehlgeschlagener Chunk verworfen. Jetzt fragt die Warteschlange die Backoff-Richtlinie nach einer Wartezeit und plant den neuen Versuch, sodass jeder Retry über eine Stelle läuft.
Nach fünf Fehlversuchen gilt der Chunk als gescheitert und der Upload stoppt mit einer klaren Meldung, damit eine tote Verbindung nicht endlos wiederholt wird.
Die Änderung an der Queue steckt in src/upload/queue.ts, die Wartezeiten stehen in src/upload/backoff.ts.
Die Warteschlange schreibt den Status jedes Chunks auf die Festplatte. Jetzt baue ich den Weg nach dem Neustart ein.
Beim Start liest die Warteschlange den gespeicherten Status und sendet nur unfertige Chunks, eine 2-GB-Datei beginnt also nicht von vorn.
Die Strategie sagt, wir sollen auf den Markt gehen. Also habe ich dafür eine Funktion angelegt.
1export async function goToMarket() {2await ship();3await tellDenis();4}
Ein Test läuft durch. Der Markt hat noch nicht geantwortet.
Der Test las die Datei, bevor der Watcher sie geschrieben hatte. Jetzt wartet er auf das Flush-Ereignis, und 200 Läufe in Folge sind grün.
1test('reads the synced file', async () => {2await writeFile(path, 'hello');3await watcher.flushed(path);4const text = await readFile(path);5expect(text).toBe('hello');6});
Das Warten steckt im Test-Helper, also profitieren auch die anderen Sync-Tests.
Die neue Seite ist fertig. Bevor ich die Tabs entferne, muss ich die Migration der Einstellungen ausführen.
| Alter Tab | Neuer Bereich |
|---|---|
| Allgemein | Arbeitsbereich |
| Darstellung | Arbeitsbereich |
| Konten | Profil |
Die Migration lief über 212 Einstellungen ohne Fehler, die alten Tabs sind weg. Alle Einstellungen liegen jetzt auf einer Seite.
Hier gestoppt. Die alten Tabs bleiben, bis du die Migration ausführen willst.
Zwei Befunde. Das Session-Cookie läuft nie ab, und die Weiterleitung nach dem Login akzeptiert jede URL. Der Rest sieht gut aus.
Erledigt. Die gewählten Fixes sind drin, jeder mit Test, und alles Verschobene bleibt als Kommentar im Pull Request.
Verstanden. Nichts wurde geändert, die Befunde bleiben als Kommentare im Pull Request.
Ich habe den neuen Befehl auf einem frischen Rechner getestet, er funktioniert.
1bun add -g uploader2uploader --help
Der alte curl-Befehl funktioniert auch noch und kann als Ausweichweg bleiben.
Erledigt. Der neue Befehl steht dort, wo du ihn wolltest, der alte bleibt als Ausweichweg.
Die meiste Zeit geht dafür drauf, bei jeder Suche alle Dateien zu lesen. Ein Index verkürzt das, kleine Repos ändern sich kaum.
Der Index aktualisiert sich bei jeder Dateiänderung und braucht keinen vollen Neuaufbau.
Der Baum lädt jetzt aus einem Cache auf der Festplatte und aktualisiert sich im Hintergrund. Für Nutzer ändert sich nur die Wartezeit.
- Cache-Treffer
- 94%
- Größe auf der Festplatte
- 2.1 MB
- Ladezeit des Baums
- 40 ms
Der Cache leert sich, wenn die Branch wechselt.
Gefunden. Eine leere Datei ergibt null, und der Code liest sofort ein Feld daraus.
1export function loadConfig(path: string) {2const text = readFileSync(path, 'utf8');3if (text.trim() === '') return defaults;4return { ...defaults, ...parse(text) };5}
Jetzt gilt eine leere Datei als Standardkonfiguration, eine kaputte zeigt einen klaren Fehler.
Fünf neue Issues. Drei sind echte Bugs, einer davon kann bis zum nächsten Release warten.
| Issue | Art |
|---|---|
| Upload stalls at 99% on Safari | bug |
| Crash on a file named final_final_v2_🦆.mov | bug |
| Progress bar jumps back after resume | bug |
| Drag a folder into the upload list | feature |
| Is there a size limit? Asking for a 400 GB log file | question |
Ich habe alle gelabelt und das Duplikat mit der älteren Meldung verknüpft.
Woran arbeiten wir in uploader?
Fehlgeschlagene Uploads wiederholen
Exponentielles Backoff zur Upload-Warteschlange hinzugefügt. Ein fehlgeschlagener Chunk wird bis zu fünfmal wiederholt, und der Fortschrittsbalken zeigt den Versuch. Tests decken den Timeout und den 503-Fall ab.
Früher wurde ein fehlgeschlagener Chunk verworfen. Jetzt fragt die Warteschlange die Backoff-Richtlinie nach einer Wartezeit und plant den neuen Versuch, sodass jeder Retry über eine Stelle läuft.
const result = await sendChunk(chunk); if (!result.ok) return fail(chunk); if (!result.ok) { const delay = backoff.next(chunk.attempt); if (delay === null) return fail(chunk); return schedule(chunk, delay); } progress.advance(chunk.size); const DELAYS = [500, 1000, 2000, 4000, 8000]; export const backoff = { next(attempt: number) { return DELAYS[attempt] ?? null; } }; 1import { backoff } from './backoff';2import { sendChunk } from './client';3import { progress } from './progress';4import type { Chunk, UploadResult } from './types';56// TODO: remove before launch (added in 2023)7const MAX_PARALLEL = 4;8const queue: Chunk[] = [];9let running = 0;1011export function enqueue(chunks: Chunk[]) {12queue.push(...chunks);13void drain();14}1516async function drain() {17while (running < MAX_PARALLEL && queue.length > 0) {18const chunk = queue.shift();19if (!chunk) return;20running += 1;21await upload(chunk);22running -= 1;23}24}2526export async function upload(chunk: Chunk): Promise<UploadResult> {27const result = await sendChunk(chunk);28if (!result.ok) {29const delay = backoff.next(chunk.attempt);30if (delay === null) return fail(chunk);31return schedule(chunk, delay);32}33progress.advance(chunk.size);34return done(chunk);35}3637function schedule(chunk: Chunk, delay: number) {38chunk.attempt += 1;39progress.retrying(chunk);40setTimeout(() => enqueue([chunk]), delay);41}
Läuft auf deiner Maschine. Erreichbar von überall.
Die Desktop-App führt Agenten auf deinem Computer aus und verbindet sich mit deinen Servern. Ein Handy koppelst du einmal per QR-Code, danach erreicht es sie über Private Relay, Ende-zu-Ende-verschlüsselt und ohne offenen Port.
Woran arbeiten wir in uploader?
Woran arbeiten wir in uploader?
Woran arbeiten wir in uploader?
Keine Sitzungen gefunden.
const result = await sendChunk(chunk); if (!result.ok) { const delay = backoff.next(chunk.attempt); if (delay === null) return fail(chunk); return schedule(chunk, delay); }
const DELAYS = [500, 1000, 2000, 4000, 8000]; export const backoff = { next(attempt: number) { return DELAYS[attempt] ?? null; } };
test('retries a chunk after a 503', async () => { server.respondWith(503); await upload(chunk); clock.tick(500); expect(server.calls).toBe(2); });
Eine Aufgabe, mehrere Modelle.
Schick denselben Prompt gleichzeitig an mehrere Modelle. Jeder Lauf kann einen eigenen Worktree und Branch bekommen. Behalte das Ergebnis, das dir am besten gefällt, oder führe die Antworten zu einer zusammen.
Lies einen großen Diff als Rundgang.
Der Walkthrough bringt einen großen Diff in die Reihenfolge, in der er Sinn ergibt, erklärt jeden Schritt in einem Satz und markiert die Kernänderung, damit du weißt, was du zuerst lesen solltest.
Deine Tickets und Tools, einen Klick entfernt.
Starte eine Session aus einem GitHub-Issue, einem Pull Request oder einem Linear-Ticket, und der Agent bekommt den ganzen Thread. Der Integrationskatalog hat mehr als 160 Dienste, darunter Figma und Sentry, und jeder ist mit einem Klick installiert.
Gib ihm ein Ziel oder einen Zeitplan.
Setz ein Ziel, und der Agent arbeitet Zug um Zug weiter, bis eine separate Prüfung sagt, dass er fertig ist oder feststeckt. Geplante Aufgaben starten eine Session von selbst, jeden Morgen oder einmal am nächsten Dienstag.
Von Leuten, die es benutzen.
Beiträge, um die wir nicht gebeten haben.
I've had a deep experience with openchamber. I think my choice was right. This thing is really useful, smooth and silky. Go try it out quick, I strongly recommend it, no more messing around blindly.
@realchendahuang Was looking for a way to run agents from my phone, remotely on my server, other than tmux. Found OpenChamber. It is INSANELY good, highly recommended.
@NihilSineTheo Seriously, I've spent almost the whole week trying to find a tool that made sense to orchestrate agents and, as a dev (actually, a devops), openchamber is the only one that felt natural and logical.
@1un4rPunk I just want to say the app itself is phenomenal. The level of polish is just unreal. I saw OpenChamber back when it was a small project, I think maybe a couple months ago, and now it is unrecognizable. Truly amazing work. The latest release with its streaming feature is everything I could have possibly asked for and more. It is gorgeous and smooth.
Installiere es auf der Maschine, die du schon hast.
Die Desktop-App für macOS, Windows oder Linux oder ein Befehl auf einem Server. Danach öffnest du es, von wo du gerade bist.
OpenChamber sammelt nichts über deine Arbeit. Der Code steht unter MIT-Lizenz, also kann jeder es prüfen.
Aktivität
Können die Wartezeiten in die Konfiguration? Mobil wollen wir vielleicht längere.
Gute Idee. Die Wartezeiten sind jetzt ein Array, das geht also in einem Folge-PR.