An agentic development environment.
It's open source and works with the models you already use. Start an agent on one machine, then pick up the same session from another.
I ran 100 uploads on a connection that drops one request in five. Failed uploads fell from 23 to 1.
The one failure was a file the server rejected, so a retry could not help.
Upload speed now sits in the header next to the file name, and it hides when the queue is empty.
On it. I will post here when there is something to look at.
Reproduced it and found the cause. The fix is ready for review.
Added exponential backoff to the upload queue. A failed chunk retries up to five times, and the progress bar shows the attempt. Tests cover the timeout and the 503 path.
A failed chunk used to be dropped. The queue now asks the backoff policy for a delay and schedules the retry, so every retry goes through one place.
After five failed attempts the chunk fails for good and the upload stops with a clear error, so a dead connection never retries forever.
The queue change is in src/upload/queue.ts, and src/upload/backoff.ts holds the delays.
The queue writes each chunk's state to disk. I'm wiring the restart path now.
On start the queue reads the saved state and sends only the chunks that never finished, so a 2 GB file does not start over.
The strategy says to go to market, so I added a function for that.
1export async function goToMarket() {2await ship();3await tellDenis();4}
One test passes. The market hasn't replied yet.
The test read the file before the watcher had flushed it. It now waits for the flush event, and 200 runs in a row pass.
1test('reads the synced file', async () => {2await writeFile(path, 'hello');3await watcher.flushed(path);4const text = await readFile(path);5expect(text).toBe('hello');6});
The wait lives in the test helper, so the other sync tests get the fix too.
The new page is ready. I need to run the settings migration before removing the tabs.
| Old tab | New section |
|---|---|
| General | Workspace |
| Appearance | Workspace |
| Accounts | Profile |
The migration ran on 212 settings without errors and the old tabs are gone. All settings now live on one page.
Stopped here. The old tabs stay until you are ready to run the migration.
Two findings. The session cookie has no expiry, and the redirect after login accepts any URL. The rest looks fine.
Done. The fixes you chose are in, each with a test, and anything you put off stays as a comment on the pull request.
Understood. Nothing changed, and the findings stay as comments on the pull request.
I tried the new command on a clean machine and it works.
1bun add -g uploader2uploader --help
The old curl command still works too, so it can stay as a fallback.
Done. The new command is where you asked, and the old one stays as a fallback.
Most of the time goes into reading every file for each search. An index cuts it down, and small repos barely change.
The index updates when a file changes, so it never needs a full rebuild.
The tree now loads from a cache on disk and refreshes in the background. Nothing changes for the user except the wait.
- Cache hits
- 94%
- Size on disk
- 2.1 MB
- Tree load time
- 40 ms
The cache clears itself when the branch changes.
Found it. An empty file parses to null, and the code reads a field from it straight away.
1export function loadConfig(path: string) {2const text = readFileSync(path, 'utf8');3if (text.trim() === '') return defaults;4return { ...defaults, ...parse(text) };5}
Now an empty file counts as the default config, and a broken one shows a clear error.
Five new issues. Three are real bugs, and one of them can wait for the next release.
| Issue | Kind |
|---|---|
| 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 |
I labelled them all and linked the duplicate to the older report.
What are we working on in uploader?
Retry failed uploads
Added exponential backoff to the upload queue. A failed chunk retries up to five times, and the progress bar shows the attempt. Tests cover the timeout and the 503 path.
A failed chunk used to be dropped. The queue now asks the backoff policy for a delay and schedules the retry, so every retry goes through one place.
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}
Runs on your machine. Reach it from anywhere.
The desktop app runs agents on your computer and connects to your servers. Pair a phone once with a QR code and it reaches them over Private Relay, end-to-end encrypted, with no open port.
What are we working on in uploader?
What are we working on in uploader?
What are we working on in uploader?
No sessions found.
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); });
One task, several models.
Send one prompt to several models at once. Each run can get its own worktree and branch. Keep the result you like best or fuse the answers into one.
Read a large diff as a walkthrough.
The walkthrough puts a big diff in the order it makes sense, explains each step in a sentence and marks the key change, so you know what to read first.
Your tickets and tools, one click away.
Start a session from a GitHub issue, a pull request or a Linear ticket, and the agent gets the whole thread. The integrations catalog has more than 160 services, Figma and Sentry among them, and each installs with one click.
Give it a goal or a schedule.
Set a goal and the agent keeps working, turn after turn, until a separate check says it's done or stuck. Scheduled tasks start a session on their own, every morning or once next Tuesday.
From people who use it.
Posts we did not ask for.
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.
Install it on the machine you already have.
The desktop app on macOS, Windows or Linux, or one command on a server. Then open it from wherever you are.
OpenChamber collects nothing about your work. The code is MIT-licensed, so anyone can check.
Activity
Can the delays go into config? We may want longer ones on mobile.
Good idea. The delays are one array now, so this can be a follow-up.