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.
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 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. |
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
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:
- locates the source VMA at the expected virtual address;
- duplicates the VMA metadata and policy into the target address space;
- links the destination region into the source's anonymous reverse-mapping, or
anon_vma, chain; - installs the source page-table entries into the destination as write-protected mappings to the same physical pages; and
- lets the kernel's ordinary CoW fault path allocate a private page on the first write by either side.
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.
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.
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.
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:
- Share bulk state: source and branch reuse physical memory pages and unchanged filesystem data.
- Reconstruct semantics: Linux still needs real tasks, threads, namespaces, mappings, and supported kernel objects in the sibling.
- Move persistence later: local usability can precede durable checkpoint completion when the caller requests that contract.
- Keep divergence private: later writes allocate branch-specific memory and filesystem state.
- Pay for visibility: using the host kernel enables process-level inspection and policy through a custom, privileged runtime and a carefully maintained kernel interface.
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.
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 GitHubFrequently 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.