Forge - Scaling Engineering Beyond Engineers

Ephemeral Agent Sandboxes as an Organizational Force Multiplier

AuthorRichard Zhang

Overview

The people closest to a software problem are often not able to quickly act on it.

A customer success lead hears the same request over and over and realizes that adding a small feature would remove a recurring source of friction. A product manager finds an interaction that feels clunky and wrong. An on-call engineer sees an alert that appears connected to a recent release but is already consumed by a more urgent incident.

These people often have the context that matters most, of how the problem presents itself, who all it affects, and what a successful result should look like. What they may not have is a development environment, all the relevant tools properly configured, familiarity with the codebase, or an uninterrupted block of time to properly investigate and implement it.

So the work gets handed off as someone writes a Linear ticket, attaches screenshots, copies logs, explains the context, and waits. When an engineer eventually picks it up, they spend time reconstructing information someone else already had, asking follow-up questions, and going back and forth to try and understand the problem before they can begin addressing it.

We wanted a shorter path, and the goal was simple, to describe a piece of work in Slack and get back a concrete, reviewable result; an evidenced diagnosis/root-cause analysis, and when the task calls for code changes, a pull request and live application preview with the related changes. The requester should be able to verify the result, reply in the same thread, and keep iterating until ready for engineering review.

The work ranges from investigating customer issues across support tickets, logs, feature flags, analytics, and production state data to implementing product feedback, debugging a regression, fixing a failing test, or building the first version of a greenfield feature.

Forge is the internal agent platform we built at Junior to make that possible, and it has quickly become instrumental to how we tackle product problems, parallelize engineering work, and turn requests into working, reviewable software.

Initial Architecture: Coder + Susuwatari

Plenty of agents can search connected MCPs/APIs, edit code, and open pull requests. The important question for us was the back half of the loop and whether they could run the application and explore it the same way an engineer would, validating UI/UX before calling the change correct.

We needed to be able to run our complete development stack: web application, development server, local database, background services, and browser tooling. That way the agent can upload files, reproduce behavior, inspect application, server, workflow orchestration, and database state; make changes, and click around the real interface. It iterates against a live running system rather than reasoning from the code and tests alone.

That environment is the distinction; Forge provides an isolated, fully functioning development stack with durable workflow state, controlled access to organizational systems, and a preview that both the agent and requester can explore. The models are interchangeable; the managed runtime determines what it can investigate and verify.

Our first implementation, which we named Susuwatari, started as a side project extending remote development infrastructure we already had rather than being a properly planned platform project. In Miyazaki’s Spirited Away, the susuwatari (soot sprites) are small, fuzzy creatures that tirelessly carry lumps of coal through the boiler room to keep the bathhouse running.. We thought the name seemed appropriate for a system of workers quietly handling tasks in the background.

We were already operating remote development environments using Coder workspaces running on Google Compute Engine VMs. They provided most of the components we appeared to need: repository access, familiar development tooling, persistent disks, background services, and routable ports, so reusing that platform seemed a rational quick approach. We could provision a workspace, run a relay process inside it, send the agent a task, and expose whatever application it started.

That initial version very quickly proved the idea was valuable, but with each PoC and novel feature demo it also meant the prototype began accumulating responsibilities before we had stepped back to define a durable execution model, explicit lifecycle ownership, or the failure semantics of a long-running agent task.

There was no single point at which we designed Susuwatari as a complete system, as each new capability addressed the next immediate constraint and was attached wherever it fit most easily. Scheduling assumed the existing workspace lifecycle. Warm pools assumed the scheduler’s state. Recovery logic assumed the behavior of both. Preview routing, follow-up tasks, and persistence were then layered on top.

Each decision was reasonable in isolation. Together, they formed a house of cards and we eventually assembled a messy collection of independently stateful systems connected through implicit assumptions about which process was alive, which component owned the next transition, and which representation of the workspace was authoritative.

Susuwatari increasingly asked long-lived workspaces to behave like disposable sandboxes, while also asking a collection of services around it to behave like a workflow engine. That tension eventually became the architecture.

One of the most visible problems was latency. A cold Coder workspace on GCE could easily take five minutes or more to become useful. Provisioning the VM was only the beginning: the OS had to boot, the Coder agent had to connect, startup scripts had to run, repos and credentials had to be prepared, heavy background services had to start, the agent relay had to become healthy, and proxy routes had to be restored. A workspace could be technically “running” well before it was capable of accepting a task.

The obvious response was to maintain a warm pool of pre-provisioned running workspaces, which improved the best case, but traded that latency for cost. Keeping enough machines ready meant paying for idle capacity, while keeping too few meant the pool disappeared as soon as several requests arrived together. The pool also had to be replenished, refreshed after image changes, reconciled when its view diverged from Coder or GCE, and prevented from handing out stale or partially initialized workspaces.

We tried to reduce that cost by suspending warm machines when they were not in use. Unfortunately, suspension was itself slow. A workspace could not safely return to the reserve pool until GCE had preserved its state and the surrounding control planes agreed that it was suspended. Resume was also slow and highly variable: GCE had to restore the VM, Coder had to reconnect to its agent, workspace-local processes had to recover, the relay had to pass its health checks, and routes had to be registered before work could continue.

We ended up with three awkward operating modes:

  • Cold workspaces were reproducible but took far too long to start.
  • Running warm workspaces were fast but expensive to keep idle.
  • Suspended workspaces cost less, but were still slow to suspend and resume while carrying substantially more state.

The problem was that Susuwatari’s workspace had become both the execution environment and the implicit record of workflow progress. What began as a startup-latency problem had become a cost problem and, eventually, a state-management problem.

By the end, a single agent task existed simultaneously across several systems:

A single Susuwatari agent task spread across Coder, GCE, Firestore, and Cloud Run, with no durable owner of workflow progress
Open full size ↗

No individual system was necessarily wrong, but their combined view could still be incoherent. Coder could report that a workspace build was running while GCE reported that the underlying VM was suspended. Firestore could mark a warm workspace as ready while an older Cloud Run revision was still suspending it. A startup marker could remain running after the shell process responsible for updating it had crashed.

There was no durable authority that owned the task’s progress, resolving these contradictions, or determining the next safe action. It wasn’t necessarily just that full VMs were too slow or that we needed a better database approach. Fragility came from composing independently stateful systems without a durable execution model binding them together.

At some point, we decided that continuing to patch the system would have meant building our own workflow engine indirectly. We decided to make that responsibility explicit instead.

Forge Architecture: Temporal + E2B

When we decided to properly rebuild Susuwatari from first principles, we began with a conceptual inversion, that investigations are durable, and that the sandbox is replaceable.

An investigation represents the entire complete human-agent collaboration, can contain multiple turns, and might live for hours or days. A sandbox is simply a resource acquired by that investigation. Forge normally resumes the same sandbox and model session across turns, but if the sandbox disappears, the investigation still exists and can decide how to recover.

Forge architecture: Slack, GitHub, and scheduled triggers drive a Temporal workflow that manages an E2B sandbox running the development stack, with tool calls routed through the MCP and API gateway
Open full size ↗

Temporal was the direct response to Susuwatari’s state management problem. Forge tasks routinely outlive the processes executing them: a task may run for hours, wait days for a reply in Slack, span multiple deployments, and encounter failures across several external systems. Temporal records that lifecycle as durable workflow history, making the workflow, not a process, VM, or collection of status fields, the authority. If a worker disappears, another can replay the history and continue from the last recorded transition.

The compute layer presented the other half of the problem. Coder’s lifecycle made sense for a human development environment where you’d provision a workspace infrequently, personalize it over time, and keep it around long enough to amortize its long startup cost.

Forge had the opposite requirements. Every investigation might need its own isolated environment, demand could arrive in bursts, and startup latency was directly visible to the requester. The environment needed to start from a known baseline, become verifiably ready, pause while awaiting feedback, resume with its working context intact, and be destroyed promptly when the task ended. We needed task-scoped compute with a lifecycle designed to be controlled programmatically, not a long-lived human workspace retrofitted to behave that way.

Among the providers we evaluated, E2B was the only one that we found could quickly and reliably preserve both in-memory process state and disk state across pause and resume. We start E2B sandboxes from a template, run our complete development stack, and expose the ports required for application previews. When Forge is waiting for feedback, it can pause the sandbox to avoid paying for active compute and later resume the same repository, services, development servers, and agent session.

We did not need stateless compute, we needed state with an owner, where the sandbox keeps the task’s working state, the codebase, running processes, and agent session; while Temporal records its durable progress. That makes the sandbox a resource managed by the workflow rather than the implicit state machine on which the workflow depends.

We also wanted to ensure that the agent does not manage that lifecycle either. A thin relay inside each sandbox starts and resumes Claude Code through the Claude Agent SDK, streams events back to Forge, and provides a stable interface for session management, cancellation, and status. Forge handles the predictable mechanics around it: provisioning compute, delivering credentials, starting turns, retrying infrastructure failures, publishing results, pausing for feedback, and eventually tearing everything down.

This leaves the model to handle the work that actually benefits from judgment: exploring code, forming hypotheses, making changes, and interpreting results.

That boundary is deliberate. Asking the model to control every transition would make predictable infrastructure operations probabilistic. Encoding the investigation itself as a fixed workflow would remove the flexibility that makes the agent useful. Forge puts deterministic orchestration around the agentic loop and uses each where it is strongest.

MCP, API, and Inference Proxy Gateways: (Effectively) Credentialless Sandboxes

Remote development infrastructure is helpful, but an isolated sandbox is a lot more useful if it can interact with the rest of the organization. It may need to inspect an error in Datadog, read a Linear issue, check a feature flag, query analytics, view a support ticket, or examine production database data.

The naive solution is to inject the corresponding credentials into the sandbox, but agents consume untrusted input from repos, Slack messages, issue descriptions, logs, and any number of other tools it can access. If an organization-wide, long-lived API key exists in the environment, a compromised turn can print it, exfiltrate it, or use its entire permission surface. Revoking access for one sandbox then requires rotating the credential for every consumer.

Forge instead gives the sandbox constrained capabilities for accessing systems whose underlying credentials remain elsewhere. There are two main paths: MCP and API calls pass through the Forge gateway, while model inference passes through a separate routing layer.

Gateway architecture: the sandbox holds only short-lived scoped tokens while proxy and facade integrations keep upstream credentials at the gateway
Open full size ↗

The sandbox is not literally credential-free. It holds tokens that let it act through these intermediaries. The important distinction is that those tokens are tied to a particular turn or sandbox, have a bounded permission or spending surface, and can be revoked without rotating the organization’s upstream credentials.

At the start of each agent turn, Forge mints a random gateway token and stores only its SHA-256 hash. The corresponding record binds it to an investigation, a sandbox, a set of integration scopes, and an expiration time. Its default lifetime is one hour: long enough for a turn, but short enough that a leaked token does not survive indefinitely. Teardown revokes any remaining tokens for the investigation.

The gateway derives identity from the token rather than accepting an investigation identifier from the sandbox. An injected agent cannot name another investigation and inherit its access. Every request resolves the token server-side, checks its expiration and revocation state, and verifies that it includes the requested integration.

Enabling the gateway also removes the corresponding raw credentials from the sandbox. These happen as one switch: if token minting fails, the sandbox receives no integration access for that turn rather than silently falling back to the upstream key. Failing closed was part of the cutover contract, not a hardening step we planned to add later.

Proxy and Facade Integrations

We structured the gateway to expose external systems in two ways.

Proxy integrations front existing HTTP MCP servers, like with Datadog and Linear for example. Forge forwards the JSON-RPC messages, injects the upstream credential, filters tools/list, blocks disallowed tools/call requests, and carries the upstream MCP session identifier across calls. This lets upstream read tools evolve without requiring us to reimplement their schemas.

Facade integrations are used when an upstream system has no usable HTTP MCP server, requires an interactive OAuth flow that a headless sandbox cannot complete, or exposes far more capability than we want the agent to have. Forge implements a small MCP surface at the gateway and translates its tools into conventional API calls.

We use facades for systems including LaunchDarkly, Pylon, and our production database. A facade is more than a protocol adapter; it determines which operations exist at all. Our feature-flag facade can expose list_flags and get_flag without implementing a mutation.

Production database access shows how the layers compose. The connection string remains in the gateway, which connects to a physical read replica using a narrowly scoped Postgres role. Each query runs inside a read-only transaction with a statement timeout and bounded response size. We do not ask the agent to choose a safe query when the infrastructure can make unsafe operations unavailable.

Model Inference

The coding agent itself presents the same credential problem. Claude Code needs access to a model provider, and injecting our organization-wide Anthropic API key into every sandbox would expose one of the most valuable credentials in the system.

In our normal production path, Forge routes inference through OpenRouter’s Anthropic-compatible endpoint using our Anthropic BYOK configuration. The underlying Anthropic credential remains at the routing layer. When Forge creates a sandbox, it mints a separate OpenRouter key for that sandbox with a hard spending limit. The raw key is written into the sandbox but never persisted by Forge; only its non-secret hash is stored for usage reconciliation and revocation. The higher-privilege provisioning key never leaves the worker.

The key is deleted when the sandbox is destroyed, and an orphan reaper handles missed cleanup. Compromising a sandbox could expose that sandbox’s remaining inference allowance, but it would not reveal the underlying Anthropic credential or grant unrestricted inference access across the fleet.

Centralizing this boundary also lets Forge vary tool access based on the selected model. If a provider does not offer the data-retention guarantees we require, Forge omits every MCP server capable of retrieving customer or production data while leaving local repository tools available. This is an auditable change to the tool set rather than a prompt asking the model not to access something.

Edge Proxy Architecture: Application Previews

The back half of the Forge loop, the application preview, is an integral part of what makes the whole platform effective. A pull request shows what code changed, and tests provide evidence that it works, but a running application lets the agent and requester reproduce the original behavior, exercise the change, and decide whether it is actually correct.

The agent can drive that same application through browser tooling, navigate the interface, upload files, reproduce behavior, and capture screenshots as it works. Forge posts those screenshots back into the Slack thread, giving the requester immediate visual evidence of what changed.

E2B already exposes sandbox ports over HTTPS, so putting a proxy in front of them initially looked like a purely cosmetic improvement; we wanted to send people a custom URL rather than an infrastructure provider’s hostname. That vanity domain quickly became a useful control point for network access, private sandbox ingress, pause and resume behavior, WebSockets, local authentication, and all the other things required to make an ephemeral development environment feel like an ordinary application.

Browser-facing preview traffic passes through a Google Cloud load balancer and Cloud Armor policy that restricts access to our office and VPN egress addresses. The sandbox itself is also created with public traffic disabled so E2B rejects requests to its raw hostname unless they include a private traffic token specific to that sandbox, which Forge Edge resolves and injects when proxying the request. Cloud Armor restricts who can reach the browser-facing edge, while the E2B token restricts who can reach the sandbox behind it. Knowing the sandbox identifier or raw E2B hostname is not enough to bypass the Forge domain.

Each preview hostname encodes the upstream port and E2B sandbox identifier:

https://<port>-<sandbox-id>.<preview-domain>

Forge Edge reconstructs the corresponding E2B hostname directly from the request, and route resolution is a pure hostname parse. There is no per-preview proxy configuration to create, synchronize, or clean up, and the same scheme works for any port a repository exposes. The only stateful lookup on the request path resolves the private-ingress token, which is cached at the edge. The proxy rewrites the upstream host expected by E2B while preserving the public Forge hostname in forwarding headers. To the browser, the result behaves like a normal HTTPS application on a Forge-owned origin.

Edge proxy request path: browser traffic passes a Cloud Armor policy and Forge Edge, which injects the private-ingress token before reaching the sandbox
Open full size ↗

A Preview Request Can Resume Compute

Since Forge pauses sandboxes while waiting for feedback, we needed to ensure that opening a preview link wakes the sandbox, so an investigation does not need to consume active compute merely because someone might return to it later.

The awkward part is that the first request initiates a resume but a deeply paused sandbox would often refuse the connection, time out, or return a transient 502, 503, or 504 while E2B restores its memory state. Forge Edge retries requests that it can safely replay, so the first page load may be slower, but it waits for the sandbox and application rather than immediately surfacing a gateway error.

Waking the application is not enough if parts of it remain broken. Next.js HMR and Supabase Realtime both depend on WebSockets, so Forge Edge implements a separate upgrade path over a TLS-wrapped socket. It rewrites the upstream host, injects the private-ingress token, and pipes the connection bidirectionally. It also removes the browser’s Origin header when the development server would otherwise reject the Forge preview origin as non-local.

We considered serving production builds instead of development servers. That would simplify startup and avoid mutable compiler caches, but it would also remove the rapid feedback loop from investigations that use the preview to iterate. Keeping development servers introduces its own operational problems. Pausing and resuming them also preserves compiler caches and partially compiled routes. Forge uses readiness probes, route warming, a watchdog, and targeted cache repair rather than treating “the process is running” as equivalent to “the application is ready.”

Making Local Services Behave as One Application

Our primary application runs a complete local Supabase stack inside each sandbox. The application is served from port 3000, while Supabase runs on port 54321. Sending the browser directly to a separate 54321-<sandbox-id> hostname would create a second origin, breaking the application’s assumptions about authentication and session state.

Instead, the application addresses Supabase through a same-origin /__supabase path. Forge Edge recognizes that prefix on the application host, forwards the request to port 54321 inside the same sandbox, and strips the prefix before it reaches Supabase. Authentication, Storage, and Realtime therefore remain behind the same browser-facing origin as the application.

Some protocols, including resumable uploads and OAuth callbacks, required additional narrowly scoped routing and response handling. From the browser’s perspective, however, the resulting preview behaves like a single ordinary application despite being assembled from several sandbox-local services.

Long-Term Organizational Impact

People across the organization are already generating code at a pace that would’ve been impossible up until very recently. Engineers ship far more and far faster with coding agents, owning not just implementation but increasingly deployment, infrastructure, debugging, and production system reliability operations. Product managers turn requirements directly into working prototypes, and designers materialize real interactions without waiting for engineering handoffs.

Forge takes that transition as a given. Our bet is that the next set of gains will come not only from generating code faster, but from changing how software work moves through an organization.

Most coding agents accelerate someone who is actively driving them. A person gathers context, selects the repository, prepares the environment, connects the necessary systems, supervises the session, and verifies the result. The implementation may happen faster, but the task remains coupled to that person’s attention and availability.

Forge targets a different constraint: how much work the organization can execute at once. An investigation begins where the context already exists, continues without someone sitting in front of it, and returns with evidence rather than code alone. It can inspect organizational systems, run the full development stack, exercise the application, and return a diagnosis, tests, screenshots, a pull request, and a live preview. Engineers still define the boundaries and decide what ships, but nobody has to act as the agent’s full-time operator.

And since Forge is multiplayer by default, because investigations live in Slack threads rather than private IDE or CLI sessions, any teammate with access to the conversation can contribute context, answer the agent’s questions, challenge an assumption, or request another iteration. As Forge works, it returns screenshots and live previews in the thread, giving the team something concrete to react to and steer with, turning review into an active conversation while the work is still happening. Support can add customer details, design can evaluate the interaction, and engineering can redirect the implementation. Status updates, screenshots, answers, and the live preview all return to the same thread. The investigation belongs to the team, not only to the person who started it.

That is why we built a workflow engine, isolated compute layer, credential gateways, and application-preview infrastructure instead of simply wrapping a model in a Slack bot. Much less infrastructure would be required to generate a diff. The additional machinery lets delegated work survive failures, access real systems under controlled conditions, and return with enough evidence for someone else to review it.

Forge now handles hundreds of investigations each week across engineering, product, and customer success. As a result, we move faster, more work progresses in parallel, and engineers can focus their attention where their judgment is best employed.

Interested in joining our technical team and working on similar, challenging problems? Apply on our careers page, we’re always looking for top talent across various markets: https://junior.ai/careers