Skip to content

Latest commit

 

History

History
350 lines (274 loc) · 10.4 KB

File metadata and controls

350 lines (274 loc) · 10.4 KB

DeltaGit Command Runtime

Purpose

DeltaGit should provide one provider-neutral command runtime for coding agents:

CoW workspace
+ process supervision
+ operation delta capture
+ rollback

This is a command runtime, not yet a container. It does not promise complete absolute-path virtualization without a virtual filesystem backend.

The runtime is the execution backend for per-operation workspace mode. The agent still invokes its native Bash or exec_command tool, but that tool runs only the small deltagit-run launcher. The original command is executed by the runtime inside the operation workspace.

Core boundary

Agent integrations only prepare an operation and rewrite the tool input. The runtime owns the actual operation lifecycle.

Codex / Claude Code / other agent
              │
              ▼
       PreToolUse adapter
              │
              ├── allocate operation
              ├── create CoW workspace
              └── rewrite command
                    │
                    ▼
       deltagit-run --operation <id>
                    │
                    ├── load the original command
                    ├── start the requested shell in view_root
                    ├── create process group / Job Object
                    ├── inherit stdin/stdout/stderr
                    ├── wait for root and descendants
                    ├── capture the final delta
                    └── seal, cancel, or rollback

The provider's session ID or task ID is transport-correlation metadata: it may route later input or output to the operation, but it must not determine when the DeltaGit operation ends.

Operation record

The smallest useful operation record contains:

operation_id
parent_version
base_root
view_root
original_command
original_cwd
environment
shell
owner_agent
tool_call_id
provider_session_id / task_id
deadline_ms
background
process_group_id / job_id
status

The original command is stored in the operation record. The hook should pass only the operation ID to deltagit-run; this avoids fragile shell quoting.

The runner should not expose provider-specific flags such as --yield-time-ms or --run-in-background. Those belong to the agent adapter and the native tool transport.

Parameter ownership

The adapters preserve the provider's input shape and rewrite only the command field. deltagit-run loads the rest from the operation record.

Input Owner DeltaGit behavior
command Agent adapter Store the exact original; replace with deltagit-run --operation <id>
workdir / effective cwd Adapter and runtime Store it and resolve the corresponding path inside the view
Claude timeout Provider and runtime Record as an operation deadline; enforce process-group cleanup
Claude run_in_background Provider transport Preserve it; the operation stays open after the tool returns
Codex yield_time_ms Provider transport Preserve it; it only controls how long the call waits
Codex max_output_tokens Provider output layer Preserve it; it does not affect the filesystem lifecycle
shell / tty Provider transport and runtime Preserve the requested shell and stream mode
write_stdin / task output Provider transport Continue or observe the existing operation; never create a new one
cancellation / task stop Provider and runtime Terminate the process group, drain children, then seal as cancelled

Keep these concepts separate:

hook timeout       → how long the PreToolUse adapter may run
command timeout    → how long the command may execute
poll/wait timeout  → how long the caller waits for output

In particular, yield_time_ms is never an operation deadline. A command that outlives the initial wait remains running and can continue through the provider's existing session or background-task mechanism.

Runtime packaging boundary

Do not split this into a second repository yet. Keep the first implementation inside DeltaGit so the operation protocol and workspace APIs can evolve together:

deltagit/
├── cmd/deltagit-run/
├── runtime/supervisor/
├── runtime/protocol/
└── runtime/adapters/
    ├── codex/
    └── claude/

The supervisor should be shell-neutral even if the first implementation only launches Bash. Later it can launch sh, zsh, PowerShell, or cmd without changing the agent adapters.

Split this into a separate deltagit-runtime repository only when it needs an independent release cycle or is consumed by another product. The repository should not be named bash-adapter: the adapters understand coding-agent hook protocols, while the supervisor understands processes and shells.

The ownership boundary is:

Agent adapter  → Claude/Codex hook payloads and tool IDs
Supervisor     → shells, stdio, deadlines, process trees, cancellation
DeltaGit       → views, deltas, checkpoints, rollback, sealing, branches

Lifecycle

prepared
    │
    ▼
running
    │
    ▼
draining
    │
    ▼
quiescing
    │
    ├── sealed
    ├── cancelled
    ├── failed
    └── escaped

PostToolUse is optional telemetry. It is not the lifecycle authority. A command is complete only after its process group or Job Object has drained and the workspace has reached a short filesystem-quiescence window.

This handles commands such as:

long-running-command &
exit 0

The shell may exit first, but the operation remains active until the child processes finish.

Agent adapters

Codex

PreToolUse(Bash)
    └── rewrite command to deltagit-run

The adapter stores the complete original exec_command input, including workdir, yield_time_ms, output limits, and terminal mode. It returns the same input with only command changed. Codex continues to own exec_command, session_id, write_stdin, and output streaming. write_stdin does not create another operation and does not need a separate DeltaGit adapter.

Claude Code

PreToolUse(Bash)
    └── rewrite command to deltagit-run

Claude Code's hook can replace the Bash input through updatedInput. Preserve the original timeout, run_in_background, and other tool fields. Claude Code continues to own its task IDs, output retrieval, and task cancellation. The operation remains open until the supervisor observes process-group completion, not merely until the hook or tool call returns.

The same operation allocator and deltagit-run binary should serve both providers. Only the hook configuration and provider-specific event parsing belong in the adapters.

References:

Path policy

Do not perform general string replacement on arbitrary Bash commands.

Relative paths

The supervisor changes the process working directory:

base_root = /Users/yifanxu/project
view_root = /tmp/deltagit/op-123/workspace

This makes ordinary relative paths resolve inside the operation workspace:

./src/index.ts

Structured file tools

For Read, Edit, and Write, an adapter can safely map structured paths:

/Users/yifanxu/project/src/a.ts
        │
        ▼
/tmp/deltagit/op-123/workspace/src/a.ts

Arbitrary absolute paths

An opaque Bash string may contain paths inside variables, substitutions, here-documents, nested scripts, or binaries. Reliable translation requires a virtual filesystem layer such as FUSE, FSKit, OverlayFS, or an equivalent OS mechanism.

Therefore the first version guarantees:

CoW + changed cwd       → relative-path isolation
structured path mapping  → direct file-tool isolation
virtual filesystem       → full absolute-path transparency

Direct tools and Bash

Keep the first version simple:

Edit / Write → atomic operation on the current persistent workspace
Bash         → ephemeral CoW operation supervised by deltagit-run

An Edit or Write should not automatically join a currently running Bash operation. Joining live operations would require a shared virtual view and a more complex concurrency model. Add structured path routing later if that behavior becomes necessary.

Workspace backends

Use native CoW or the simplest available fallback:

macOS   → APFS clone/reflink backend
Linux   → reflink backend, then copy fallback
Windows → native CoW backend where available, then copy fallback

FUSE and FSKit are optional materialization backends, not requirements of the operation model or the command supervisor.

Recovery

Write the operation record before starting the child process. A recovery command should later be able to inspect orphaned operations and determine whether to:

reattach
finalize
mark cancelled
mark abandoned
retain for manual recovery

Recovery is more important than adding an early UI layer.

Implementation order

  1. Implement deltagit-run with the operation state machine.
  2. Add the macOS native CoW workspace backend.
  3. Capture created, modified, and removed files.
  4. Verify long-running commands and write_stdin.
  5. Verify background children, cancellation, and rollback.
  6. Add the Codex PreToolUse(Bash) adapter.
  7. Add the Claude Code PreToolUse(Bash) adapter.
  8. Add structured path mapping for Read, Edit, and Write.
  9. Add Linux and Windows workspace backends.
  10. Convert sealed operations into persistent branch commits and Git-compatible history.

First test matrix

short Bash command
file creation
file modification
file deletion
long-running Bash
write_stdin input
background child process
cancellation
rollback
two parallel operations
npm install
absolute-path bypass demonstration

Explicitly out of scope for v1

general Bash string rewriting
FUSE/FSKit as a mandatory dependency
provider-specific session managers
PostToolUse-based sealing
automatic merging of Edit/Write into a live Bash operation
full absolute-path transparency

The first milestone is complete when two agents can run independent Bash operations in cheap CoW workspaces, receive normal command I/O, and safely seal or rollback their operation without DeltaGit knowing which provider owns the surrounding tool session.