|
1 | 1 | # Remote workspace transport |
2 | 2 |
|
3 | | -This document defines how BitFun turns SSH hosts and Docker containers into one |
4 | | -workspace runtime without leaking transport-specific behavior into Agent, |
5 | | -search, terminal, or file-service callers. |
| 3 | +This document defines the transport boundary for SSH and Docker workspaces. |
| 4 | +The Agent Runtime stays on the BitFun host. Local and remote workspaces share |
| 5 | +file-tool algorithms through Session-bound IO providers; search retains native |
| 6 | +acceleration with shared matching and reduction. The convergence section below |
| 7 | +describes this boundary and the remaining capability limits. |
6 | 8 |
|
7 | 9 | ## Goals |
8 | 10 |
|
@@ -115,11 +117,15 @@ local Docker uses the existing local PTY service with `docker exec -it`. |
115 | 117 | SSH workspaces continue to use SFTP. Docker workspaces use binary stdio streams, |
116 | 118 | not text or base64 envelopes. |
117 | 119 |
|
118 | | -Reads stream chunks and report real byte progress. Writes stream to a unique |
119 | | -temporary file in the destination directory and rename it only after the input |
120 | | -has completed successfully. Cancellation kills the process and the shell trap |
121 | | -removes the temporary file, so an interrupted upload does not replace a valid |
122 | | -destination with partial content. |
| 120 | +Reads stream chunks and report real byte progress. File transfers stream to a |
| 121 | +unique temporary file in the destination directory and rename it only after the |
| 122 | +input has completed successfully. Cancellation kills the process and the shell |
| 123 | +trap removes the temporary file, so an interrupted upload does not replace a |
| 124 | +valid destination with partial content. Workspace tool writes retain ordinary |
| 125 | +filesystem semantics instead: after validating the staged bytes, they write |
| 126 | +through the destination to preserve existing links and permissions. The final |
| 127 | +write is not an atomic transaction against other writers; interruption during |
| 128 | +that phase can have a partial or unknown outcome. |
123 | 129 |
|
124 | 130 | Directory and stat records use NUL-separated fields. File names containing |
125 | 131 | newlines or the delimiters used by older implementations remain round-trippable. |
@@ -203,3 +209,216 @@ Contract tests cover legacy Agent/profile deserialization, defaulted connection |
203 | 209 | options, remote-workspace retention, stdio round trips, cancellation, and |
204 | 210 | delimiter-safe Docker metadata parsing. A Docker-backed ignored integration test |
205 | 211 | is available through `BITFUN_TEST_DOCKER_CONTAINER`. |
| 212 | + |
| 213 | +## Agent Runtime convergence |
| 214 | + |
| 215 | +Decision: keep remote workspaces lightweight. Do not require BitFun CLI, |
| 216 | +a remote Agent daemon, a shared service, or a new remote installation. The |
| 217 | +BitFun host keeps the existing Agent Runtime, model credentials, Session and |
| 218 | +permission ownership. Only workspace filesystem and process IO crosses SSH. |
| 219 | +Read, Write, Edit, Delete and LS use the bound filesystem provider. Grep and |
| 220 | +Glob share matching/result algorithms while retaining native acceleration. |
| 221 | +Snapshot file IO uses that same boundary; complete remote Session Undo remains |
| 222 | +gated because individual recorded operations do not prove historical coverage. |
| 223 | + |
| 224 | +### One Runtime, one IO boundary |
| 225 | + |
| 226 | +```mermaid |
| 227 | +flowchart TB |
| 228 | + Surface["Driving surface"] --> Runtime["Existing Agent Runtime on BitFun host"] |
| 229 | + Runtime --> Owners["Session · tools · permission · hooks · snapshots"] |
| 230 | + Owners --> IO["Session-bound workspace IO"] |
| 231 | + IO --> Local["Local filesystem and process provider"] |
| 232 | + IO --> Remote["SSH / SFTP / Docker provider"] |
| 233 | + Remote --> Target["Ordinary target files and processes"] |
| 234 | +``` |
| 235 | + |
| 236 | +Runtime ownership and execution location are different concerns. Sharing the |
| 237 | +Runtime does not require deploying it beside the workspace. Session lifecycle, |
| 238 | +read-before-write checks, edit matching, result rendering, snapshot history and |
| 239 | +revert transitions have one implementation on the BitFun host. Providers only |
| 240 | +perform typed filesystem/process operations; they do not implement Read, Edit, |
| 241 | +Grep or fork as separate product features. |
| 242 | + |
| 243 | +Target installation, process supervision, a multi-user daemon, model proxying |
| 244 | +and Session migration are not prerequisites for this SSH workspace feature. |
| 245 | +Existing App Server, Shared TUI IPC, Peer Device and Detached Dispatch contracts |
| 246 | +are unchanged. |
| 247 | + |
| 248 | +### Shared owners and provider contracts |
| 249 | + |
| 250 | +| Responsibility | Shared owner | Provider boundary | |
| 251 | +|---|---|---| |
| 252 | +| Agent loop, context, permissions, fork, Session persistence | Existing Runtime / Coordinator / SessionManager | None; preserve existing host ownership | |
| 253 | +| Read windows, tail, text encoding and output budgets | `tool-execution` reading algorithm | Open a byte stream; metadata when needed | |
| 254 | +| Edit/Write matching, freshness and result construction | Existing tool pipeline and read-state owner | Read bytes, inspect metadata, write bytes | |
| 255 | +| LS/Glob filtering, order and presentation | Shared listing and search helpers | Enumerate typed entries with metadata | |
| 256 | +| Grep pattern/type/ignore policy and result reduction | Shared search helpers | Native scanning, remote bytes, optional compatible search accelerator | |
| 257 | +| Snapshot hashes, compression, history and revert phases | Existing Snapshot owners on the BitFun host | Read/restore/remove actual workspace files | |
| 258 | +| Exec lifecycle and output | Existing execution owner | Local process or existing SSH process transport | |
| 259 | +| Hook contract, permission decisions and source trust | Existing hook owner | Explicitly selected execution domain and process provider | |
| 260 | + |
| 261 | +`WorkspaceFileSystem` remains the typed boundary. It must distinguish missing |
| 262 | +paths from permission/transport errors, preserve byte content and symlink |
| 263 | +semantics, expose real metadata, and provide streaming reads and the mutation |
| 264 | +operations actually consumed by tools and snapshots. File length or timestamp |
| 265 | +not supplied by a backend is unknown, not zero. Remote modification times come |
| 266 | +from the target, not the controller clock. |
| 267 | + |
| 268 | +Local and SFTP readers can seek. A Docker stream may reject seek explicitly |
| 269 | +while supporting the shared forward-only parser and tail ring buffer; it must |
| 270 | +not implement seek by silently downloading an entire file to a temporary local |
| 271 | +copy. Cancellation drops/cancels the transport, and successful EOF must include |
| 272 | +successful command completion. Output and memory bounds do not imply a bound |
| 273 | +on network transfer. |
| 274 | + |
| 275 | +The path resolver and Runtime context select the provider once from the |
| 276 | +Session's verified workspace binding. A remote request with an unavailable |
| 277 | +provider fails; it never acquires a local provider as a fallback. Explicit |
| 278 | +`bitfun://` artifacts remain host-owned and use local storage even in a remote |
| 279 | +Session. Tools must not consult whichever workspace is currently selected in |
| 280 | +the UI. POSIX remote paths must not acquire controller OS path semantics. |
| 281 | + |
| 282 | +### Search and large-file performance |
| 283 | + |
| 284 | +Preserve the local native parallel scanner and its ignore behavior. Do not |
| 285 | +route all local search through a POSIX shell just to make the call sites look |
| 286 | +the same. Share pattern/type expansion, matching semantics, sorting, pagination |
| 287 | +and output construction; retain provider-specific data access optimizations. |
| 288 | + |
| 289 | +An already available compatible target search executable can reduce network |
| 290 | +traffic. It is an optional accelerator, not a requirement to install BitFun. |
| 291 | +Its results must satisfy the same contract, including file type definitions, |
| 292 | +ignore rules, Unicode, filenames containing newlines, context and truncation. |
| 293 | +Do not equate different system `rg` type catalogs or approximate a Rust regex |
| 294 | +with system grep and report an empty result as success. |
| 295 | + |
| 296 | +The built-in `Grep` API accepts structured search arguments; it is separate from |
| 297 | +model-authored `ExecCommand` shell text. ExecCommand preserves the submitted |
| 298 | +command and reports the target environment's actual result. The model can then |
| 299 | +choose another available command; the runtime does not silently rewrite it. |
| 300 | + |
| 301 | +Current target prefilters accept nonempty case-sensitive literals or unions of |
| 302 | +literal alternatives. They never compile the full user regex in a second engine: |
| 303 | +even an installed `rg` can have different Unicode tables. Other expressions use |
| 304 | +the shared scanner. A behavior-probed `rg` returns framed NUL-delimited candidate |
| 305 | +paths. If unavailable, a behavior-probed system `grep -a -F -q` checks authorized |
| 306 | +regular files in batches of at most 128 paths / 16 KiB of command text. These are |
| 307 | +transport batch sizes, not search limits; oversized arguments use file streams. |
| 308 | +Grep additionally retains UTF-16 BOM files so native BOM decoding cannot create |
| 309 | +a match that the byte prefilter excluded. Per-file statuses and a completion |
| 310 | +marker distinguish no match from errors, startup banners and truncated output. |
| 311 | +Only the shared Rust matcher computes results, counts, context and pagination. |
| 312 | +This fallback saves file transfer but still starts one grep process per file; |
| 313 | +it is not a claim of equal performance on small-file trees or slow connections. |
| 314 | + |
| 315 | +Without a compatible accelerator, identical matching may require reading |
| 316 | +remote file bytes into the shared scanner. That preserves semantics but costs |
| 317 | +bandwidth and SSH round trips. Exact total line counts and complete content |
| 318 | +hashes also require examining complete input. Make that cost observable, |
| 319 | +support cancellation/backpressure, and measure it with large-file and slow-link |
| 320 | +fixtures. Do not hide it behind arbitrary file-count, depth or size cutoffs. |
| 321 | +An existing optimized path must not be replaced until the shared path has |
| 322 | +behavior and performance evidence; incomplete migrations remain explicit. |
| 323 | + |
| 324 | +The current WorkspaceFS scanner applies `.gitignore`/`.ignore` inside the |
| 325 | +requested search scope; it does not import parent, global Git or |
| 326 | +`.git/info/exclude` rules and reports that boundary in its result. File symlinks |
| 327 | +can be read, but directory-link roots and hard-link restrictions without a |
| 328 | +provider identity proof fail explicitly. Content/count searches scan candidates |
| 329 | +to compute accurate totals; multiline matching uses the native searcher's |
| 330 | +buffering. The remote query retains its cancellable 30-second deadline. |
| 331 | +Output retention is bounded by pagination independently of exact match counting; |
| 332 | +one extra retained line preserves the truncation indicator. Search results expose |
| 333 | +the selected backend, scanned-file count and stream bytes. Cancellation covers |
| 334 | +metadata, enumeration and opening as well as reading. Ordinary SSH command tasks |
| 335 | +retain transport ownership after caller drop to interrupt, drain and close their |
| 336 | +channel without cancelling sibling commands. Callers return after a bounded |
| 337 | +cleanup grace period; the transport owner retains a pending channel-open request |
| 338 | +until confirmation or disconnect, then closes a late channel without executing |
| 339 | +the cancelled command. A request that never receives confirmation cannot be |
| 340 | +individually closed with the current SSH library, so its owner remains until the |
| 341 | +transport ends rather than disconnecting the shared connection. |
| 342 | +These are current limits, not evidence of complete backend parity. |
| 343 | + |
| 344 | +### Snapshot storage and safe mutations |
| 345 | + |
| 346 | +Keep snapshot blobs, metadata, operation history and revert state in the |
| 347 | +existing local workspace runtime or remote-workspace mirror directories. |
| 348 | +Only the current workspace file bytes and metadata use the selected filesystem |
| 349 | +provider. Hashing, diff calculation, operation history and the staged revert |
| 350 | +state machine remain shared. No remote database, Session service, credential |
| 351 | +copy or model-network migration is introduced. |
| 352 | + |
| 353 | +Snapshot manager caches, locks and persistence scope must identify the exact |
| 354 | +workspace binding, including the SSH connection. A POSIX path alone cannot |
| 355 | +identify a workspace when two users/hosts expose the same path. Existing data |
| 356 | +must remain readable; new isolation must not delete or reinterpret old records. |
| 357 | +Historical remote edits without snapshots cannot acquire a fabricated baseline |
| 358 | +from current file contents. Enable rollback only when its real evidence exists. |
| 359 | + |
| 360 | +New remote snapshot directories are scoped by the complete Session connection |
| 361 | +identity inside the existing local mirror. Unattributed legacy records remain |
| 362 | +untouched and are not treated as proven history for that connection. Only |
| 363 | +successful snapshot completion adds `snapshot_recorded: true` to the persisted |
| 364 | +tool result. Operation cards use that fact to query immutable summary/diff |
| 365 | +history with the same Session scope, including while disconnected. This does |
| 366 | +not enable current-file comparison, full-Session rollback or Session-wide |
| 367 | +snapshot refresh for older remote history. |
| 368 | + |
| 369 | +Prepare tracking, invoke the tool once, then complete tracking. A bookkeeping |
| 370 | +failure after a mutation cannot cause the wrapper to invoke the tool again. |
| 371 | +Retain the real tool result and an explicit snapshot warning. A transport loss |
| 372 | +after submitting a mutation can have an unknown outcome; it must not be |
| 373 | +reported as definitely unapplied or automatically replayed. |
| 374 | + |
| 375 | +Local/remote read-before-write checks must use the same algorithm, but neither |
| 376 | +ordinary local filesystem writes nor SFTP provide a universal compare-and-swap |
| 377 | +against arbitrary external editors. Recheck freshness and preserve conflict |
| 378 | +errors. Do not describe an in-process lock, preflight timestamp or rename as a |
| 379 | +cross-user transaction guarantee. |
| 380 | + |
| 381 | +### Multiple users and capability differences |
| 382 | + |
| 383 | +Each BitFun host retains its own Session, model credentials, permissions and |
| 384 | +connection state. SSH authenticates an ordinary target OS user; no shared |
| 385 | +BitFun daemon or global target configuration is introduced. Connection routing |
| 386 | +and local mirrors must not mix profiles or identities. If users intentionally |
| 387 | +share a target OS account or directory, normal filesystem permissions and |
| 388 | +concurrent-edit conflicts still apply; BitFun cannot manufacture isolation |
| 389 | +between identical OS credentials. |
| 390 | + |
| 391 | +Discover capabilities at the provider/assembly boundary, not with scattered |
| 392 | +`is_remote` gates in individual tools. Missing target programs, an unavailable |
| 393 | +interactive desktop, network loss and platform-specific process behavior can |
| 394 | +require explicit differences. A local hook executable or MCP command is not |
| 395 | +silently relocated to the target. Keep its execution domain and source trust |
| 396 | +explicit; reuse the existing engine and approval mechanism. |
| 397 | + |
| 398 | +Remote workspaces do not acquire Detached Dispatch semantics through this |
| 399 | +change: closing the BitFun host does not promise a durable remote Agent run. |
| 400 | +Keep existing cancellation/reconnection behavior and report unknown command |
| 401 | +outcomes honestly. |
| 402 | + |
| 403 | +### Completion evidence |
| 404 | + |
| 405 | +Run the same real tool and snapshot fixtures with local and SSH-backed IO, |
| 406 | +comparing results, file bytes, error categories, ordering, read-state, fork, |
| 407 | +rollback and interaction behavior. Test direct SSH, ProxyJump, local Docker |
| 408 | +and remote Docker separately. Local fake-provider tests are useful owner |
| 409 | +coverage but are not evidence of actual SSH behavior. |
| 410 | + |
| 411 | +Include binary/invalid UTF-8 input, CRLF, empty and very large files, tail and |
| 412 | +pagination, Unicode/newline names, symlinks, permission errors, offline targets, |
| 413 | +external changes, same-path different identities, snapshot start/completion |
| 414 | +failure and cancellation during transfer. Controller sentinel files must stay |
| 415 | +unread and unchanged for remote workspace requests. Preserve old profile and |
| 416 | +Session payload round trips. |
| 417 | + |
| 418 | +The structural gate removes duplicate tool algorithms and routes workspace IO |
| 419 | +through the bound provider. Raw filesystem/process calls that remain must have |
| 420 | +an explicit owner: local Session/artifact storage, a concrete provider, or a |
| 421 | +measured compatible accelerator. Moving a remote shell builder into a wrapper |
| 422 | +without sharing its semantics does not satisfy the gate. Remote Control, Peer |
| 423 | +Device and Detached Dispatch remain separate scenarios requiring their own |
| 424 | +regression evidence. |
0 commit comments