← Back to all posts

FORKING LIVE AGENT WORKSPACES · BLOG 1

Forking More Than a Process: How TClone Clones a Live Linux Workspace

fork() gives us the right memory trick for one process. TClone extends the idea to a running container full of processes, threads, mappings, and shared resources.

August 12, 2026 · 15 min read

Last week, we introduced the idea of forking live agent workspaces and explained why choosing the right system boundary—process, container, or microVM—is the first architectural decision.

Now we are beginning the first technical deep dive in that series. We will explain how TClone forks a complete Linux process tree, reconstructs it inside a sibling container, and lets the two live workspaces share memory efficiently through copy-on-write.

Short answer

TClone captures a consistent point in a running container, records its complete process and resource graph, and creates a CoW snapshot holder for each process. CRIU, the Linux checkpoint/restore tool, rebuilds that graph inside a sibling container, while a kernel-assisted memory path maps corresponding anonymous regions to the same physical pages. Source and branch resume as independent workspaces; later writes create private pages, and checkpoint serialization continues in the background.

A source container process tree being reconstructed with identical topology in a sibling branch, with both trees connected to the same shared memory pages and one written page splitting into private source and branch copies
TClone rebuilds the same process-tree topology in a sibling container. Both workspaces initially share physical memory pages; a write copies only the changed page for the writer.

A live coding workspace is closer to a small operating system than to one program. Codex may be attached to a tmux session. A shell has started a language server. A test runner has forked workers. A development server owns listening sockets. A browser or database may have its own helpers. These tasks share files, descriptors, sessions, memory objects, and namespace-local identities.

When an agent tries two implementation strategies from that exact moment, the branch must preserve the coordinated live workspace: its process hierarchy, in-memory execution state, open resources, namespaces, and filesystem view.

Why ordinary fork() stops too early

Linux fork() creates one child process from one calling process. Its performance model is excellent: parent and child initially point at the same physical pages, and the kernel copies page-table structures. A write later triggers a protection fault and allocates a private page for the writer.

Its scope is one calling process. A multithreaded caller produces a child containing only the calling thread, and every new process remains a child of its caller. A workspace containing tmux, Codex, shells, test workers, a language server, and a development server therefore requires a different operation: record the whole topology, then replay it inside a fresh PID namespace.

TClone recreates that hierarchy with the original namespace-local PIDs. From inside the branch, applications see the same parent-child and thread relationships. The host assigns independent PIDs to every restored task, keeping the source and its branches separate.

State Examples What restoration must preserve
Task identity PIDs, PPIDs, thread IDs, process groups, sessions The target graph must preserve relationships and namespace-visible identities.
Thread state Registers, stacks, TLS, signal masks, timers, robust futex and rseq state Ordinary fork() retains only the calling thread.
Address-space shape VMAs, protections, file offsets, anonymous mappings, shared mappings Virtual layout and sharing semantics must match before the saved execution state can resume.
Kernel resources Open files, pipes, Unix sockets, epoll objects, locks, credentials Each object needs an explicit policy: duplicate, share, transform, or reject.
Container boundary Mount, PID, network, IPC, UTS, user and cgroup namespaces The branch needs its own isolated sibling environment.
The core design tension

Linux already knows how to share pages cheaply, while CRIU knows how to describe and reconstruct process semantics. TClone joins those two ideas across sibling containers.

The five moves in a TClone fork

Five-stage TClone workflow: freeze the source, capture its process state and memory holders, create a sibling container, restore the process tree with shared memory, then resume both workspaces while checkpoint persistence continues
A TClone fork moves from one consistent source state to two runnable workspaces. Checkpoint persistence continues after source and branch resume.

TClone first freezes the source long enough to align three views of the branch point: the process graph, the address spaces, and the filesystem snapshot. CRIU walks descendants and threads through /proc, seizes them with ptrace, and records identities, namespaces, mappings, registers, open resources, signals, timers, and thread state. At the same time, an ordinary Linux CoW fork() creates one snapshot holder per process. Each holder preserves that process's point-in-time address space for background serialization after the source resumes.

Restoration starts inside fresh PID, mount, network, IPC, and UTS namespaces. A top-level restorer recreates the saved hierarchy with its namespace-local PIDs, redistributes shared resources, installs exact memory layouts, restores threads and execution state, and pairs each destination task with its source through a pidfd. TClone uses vma_cherrypick to attach supported anonymous mappings to their source pages. Once the tree is coherent, source and branch run independently while the holder daemon completes the checkpoint.

How cross-container copy-on-write memory works

TClone gives each restored process direct access to its corresponding source process's anonymous pages across sibling container boundaries. A privileged kernel interface connects the separately reconstructed address spaces to Linux's existing CoW machinery.

The restoration policy follows each mapping's semantics:

Memory class TClone treatment Reason
Anonymous memory Cross-container CoW between corresponding source and destination processes Heaps, stacks, and private application state often remain mostly unchanged across short speculative branches.
File-backed memory Rebind mappings to the branch's filesystem view; retain CoW for already-private pages Each branch needs an isolated file and page-cache view for later writes.
Shared memory Reconstruct POSIX shared segments independently inside the branch; keep rapidly rewritten GUI buffers branch-local Branch-local segments preserve isolation, and rapidly changing framebuffers offer little useful reuse.

TClone adds a privileged kernel interface called vma_cherrypick. During restore, the child identifies the corresponding source task through a validated pidfd. For each supported virtual memory area, the kernel module:

  1. locates the source VMA at the expected virtual address;
  2. duplicates the VMA metadata and policy into the target address space;
  3. links the destination region into the source's anonymous reverse-mapping, or anon_vma, chain;
  4. installs the source page-table entries into the destination as write-protected mappings to the same physical pages; and
  5. lets the kernel's ordinary CoW fault path allocate a private page on the first write by either side.
Before-and-after copy-on-write diagram: source and branch virtual pages initially map to one shared physical page; when the branch writes, it receives a private copy while the source keeps the original page
Source and branch initially map the same physical page read-only. A branch write creates one private page; unchanged pages remain shared.

Both address spaces initially refer to the same physical pages. Linux write-protects those mappings, so the first write triggers the normal CoW fault path: the writer receives a private copy and the other workspace keeps the original. Repeating the same mapping operation gives multiple branches access to the frozen source pages.

Memory consumption follows divergence. Branches that rewrite most of their heaps eventually own mostly separate pages. File-backed and shared mappings use their own restoration policies to preserve branch isolation, making memory treatment a per-class and per-VMA decision.

The cost CoW changes

Unchanged page contents stay shared. Fork latency still includes VMA discovery, page-table setup, per-task reconstruction, and protection changes. Later writes add fault handling and memory for divergent pages.

Evaluation results

We ran two independent same-host experiments on the latest integrated TClone stack. The first varied exact source process count from 1 to 200. The second fixed the source at ten processes and varied touched anonymous memory from 0 to 2 GiB. Each point below is the median of five successful forks.

Two line charts showing direct TClone median latency increasing from 463.8 milliseconds at one process to 1070.1 milliseconds at 200 processes, and from 531.4 milliseconds at zero requested dirty memory to 897.1 milliseconds at 2 GiB with ten processes
These separate experiments isolate two scaling dimensions. Together they show the costs of semantic state and memory bookkeeping while page contents remain shared.

The process curve rose from 463.8 milliseconds at one process to 1.070 seconds at 200. A linear fit to these latest medians was approximately 474.6 ms + 2.97 ms/process, reflecting the work required to capture and rebuild a larger Linux task graph.

The dirty-memory curve rose from 531.4 milliseconds with zero requested allocation to 897.1 milliseconds at 2 GiB. This end-to-end result includes mapping discovery, snapshot-holder preparation, page-table sharing, destination restore, and outer runtime work. Page contents remain shared across source and branch.

The memory accounting makes that distinction visible. At the 2-GiB point, the child reported about 2,049 MiB RSS, about 1,024 MiB proportional set size, and about 2,048.5 MiB shared-dirty memory. RSS counts every resident page mapped into the child; PSS divides shared pages across their mappings. A PSS near half the RSS is direct evidence that source and child still shared almost the complete payload after fork.

Where the time goes in one real workspace

A four-process Webtop-class source measured about 665 milliseconds at the client-visible TClone boundary. A representative breakdown attributed roughly 178 milliseconds to Phase A and 211 milliseconds to Phase B. Outside CRIU, important fixed costs included freeze confirmation, source syncfs, filesystem setup, network publication, and final cgroup migration.

Inside Phase A, the small four-process tree spent about 23 milliseconds on setup and discovery, 58 milliseconds in the per-task loop, and 96 milliseconds in global finalization such as mount and namespace state. At 100 processes, the relationship reversed: the serial task loop dominated, led by core/thread serialization, page handoff, parasite infection, VMA collection, MM/FS state, and file-image work.

Inside Phase B, parent setup took about 1 millisecond. The restore child then spent more than 200 milliseconds reconstructing namespaces and processes. Further gains depend on reducing that work or introducing more safe parallelism.

Detailed vertical Gantt chart of a TClone fork showing pre-CRIU setup, CRIU Phase A, CRIU Phase B, runtime readiness, and publication with nested phase buckets
Detailed instrumentation of the representative Webtop path. Nested rows break down their parent intervals and are already included in the total.

Why asynchronous checkpointing changes the contract

A traditional checkpoint completes when enough state has been serialized for a later restore. A fast local fork reaches an earlier milestone when the sibling becomes runnable. Durable images, stable-storage flushes, and remote replication may complete afterward.

The snapshot holders make this separation concrete. While the source is frozen, TClone creates one holder per source process through ordinary CoW fork(). Each holder preserves the branch point after the live source resumes and dirties its pages. TClone reparents the holders to a background dump daemon, which serializes their stable address-space views and reclaims them when complete. Source and branch can run during that durable writeout.

Successive durable checkpoints can also chain through dirty-page tracking: the new checkpoint records pages modified since its parent image and refers back to unchanged pages. This reduces serialized data without changing the online branch rule: snapshot-holder creation is synchronous, page-content persistence is asynchronous.

This design creates lifecycle obligations. Holders, pidfds, the dump daemon, snapshot layers, and cleanup must remain valid until the selected durability milestone is satisfied. The API therefore needs a precise contract for local usability and durability.

The same distinction will matter in Blog 2. A Btrfs snapshot can clone the filesystem namespace quickly. Dirty-data writeback and host page-cache duplication follow their own cost model, making memory CoW one layer of the complete workspace branch.

What TClone optimizes—and what it accepts

TClone chooses a deliberately asymmetric design:

The trade suits live coding and computer-use workspaces that branch frequently, carry large warm working sets, and benefit from host-visible Linux processes. Strong tenant isolation, guest-kernel compatibility, and extremely process-heavy workloads may favor a microVM boundary.

The durable mental model

TClone combines CRIU's semantic reconstruction with fork-like page sharing and filesystem versioning. This combination produces a locally runnable sibling before background checkpoint persistence reaches its durability milestone.

Explore the runtime behind live coding-agent workspace fork and rollback.

The open-source os4agent repository contains the patched Linux kernel, CRIU, crun, conmon, and Podman components used by TClone.

View GenseeAI/os4agent on GitHub

Frequently asked questions

Why does TClone reconstruct the process tree?

fork() duplicates one calling process as a child of its source and keeps only the calling thread from a multithreaded process. TClone records the complete hierarchy and replays it in a fresh sibling namespace, preserving parent-child and thread relationships.

Does TClone copy all source-container memory during fork?

For supported anonymous mappings, the destination receives the source's VMA structure and write-protected page-table mappings through a kernel-assisted CoW path. Page contents stay shared until a write occurs. Ordinary-fork snapshot holders preserve the point-in-time source view for background serialization, while the online path handles metadata, holders, page tables, and restoration.

What are Phase A and Phase B?

Phase A turns the frozen source tree into a restoration plan: tasks, threads, namespaces, mappings, files, signals, timers, and topology. Phase B turns that plan into a runnable sibling by creating namespaces and tasks, restoring resources and memory layouts, recreating threads, and coordinating readiness.

Why does latency still grow with dirty memory?

A larger resident set creates more mapping, snapshot-holder, page-table, and restore work across the complete return path. In our ten-process end-to-end experiment, latency rose by 365.7 milliseconds from zero requested allocation to 2 GiB while source and branch continued to share the payload pages.

What usually predicts TClone cost?

Process count, VMA topology, open-resource complexity, resident-page count, and outer filesystem or publication barriers. Logical container image size alone is a poor predictor. The shape and ownership of live state matter more than one capacity number.

Further reading

Explore the open-source GenseeAI/os4agent runtime, CRIU's checkpoint/restore documentation, and the Linux fork(2) manual page.