fcloud-sdk 0.1.2__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/CHANGELOG.md +76 -0
  2. {fcloud_sdk-0.1.2/src/fcloud_sdk.egg-info → fcloud_sdk-0.2.0}/PKG-INFO +8 -4
  3. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/README.md +7 -3
  4. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/SKILL.md +122 -12
  5. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/pyproject.toml +2 -2
  6. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/SKILL.md +122 -12
  7. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/__init__.py +4 -0
  8. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/__main__.py +1 -0
  9. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/_direct_bridge.py +18 -1
  10. fcloud_sdk-0.2.0/src/fcloud/autoresearch/SETUP.md +67 -0
  11. fcloud_sdk-0.2.0/src/fcloud/autoresearch/program.template.md +159 -0
  12. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/__init__.py +2 -0
  13. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/attach.py +43 -9
  14. fcloud_sdk-0.2.0/src/fcloud/cli/autoresearch.py +36 -0
  15. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/common.py +63 -7
  16. fcloud_sdk-0.2.0/src/fcloud/cli/config_cmd.py +130 -0
  17. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/context.py +50 -9
  18. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/exec_cmd.py +42 -9
  19. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/help.py +6 -0
  20. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/interactive.py +30 -7
  21. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/job.py +29 -6
  22. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/main.py +52 -3
  23. fcloud_sdk-0.2.0/src/fcloud/cli/output.py +45 -0
  24. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/processes.py +189 -25
  25. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/run.py +42 -11
  26. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/sessions.py +124 -31
  27. fcloud_sdk-0.2.0/src/fcloud/cli/setup.py +101 -0
  28. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/sweep.py +432 -25
  29. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/sweep_harvest.py +3 -2
  30. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/sweep_watch.py +81 -20
  31. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli_args.py +15 -3
  32. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/client.py +34 -13
  33. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/config.py +90 -0
  34. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/errors.py +35 -0
  35. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/session.py +15 -3
  36. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/setup_cmd.py +8 -2
  37. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/shell.py +9 -0
  38. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/sweeps.py +54 -3
  39. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/types.py +6 -0
  40. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/v2_connect.py +94 -10
  41. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/volumes.py +23 -3
  42. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0/src/fcloud_sdk.egg-info}/PKG-INFO +8 -4
  43. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud_sdk.egg-info/SOURCES.txt +18 -0
  44. fcloud_sdk-0.2.0/tests/test_autoresearch.py +21 -0
  45. fcloud_sdk-0.2.0/tests/test_checkpoint_option.py +208 -0
  46. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_client.py +10 -9
  47. fcloud_sdk-0.2.0/tests/test_config_cmd.py +75 -0
  48. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_exec_attach_retry.py +24 -2
  49. fcloud_sdk-0.2.0/tests/test_first_run_login.py +77 -0
  50. fcloud_sdk-0.2.0/tests/test_kill_records_first.py +236 -0
  51. fcloud_sdk-0.2.0/tests/test_map_dag.py +40 -0
  52. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_map_default_image.py +3 -0
  53. fcloud_sdk-0.2.0/tests/test_map_window_flags.py +40 -0
  54. fcloud_sdk-0.2.0/tests/test_no_resume_gate.py +51 -0
  55. fcloud_sdk-0.2.0/tests/test_resume_volume_validation.py +86 -0
  56. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_run_wait_flags.py +2 -2
  57. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_secret_resolution.py +2 -2
  58. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_setup_cmd.py +6 -0
  59. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_shell_volumes.py +1 -1
  60. fcloud_sdk-0.2.0/tests/test_stop_refused_classification.py +95 -0
  61. fcloud_sdk-0.2.0/tests/test_sweep_boost.py +53 -0
  62. fcloud_sdk-0.2.0/tests/test_sweep_harvest.py +184 -0
  63. fcloud_sdk-0.2.0/tests/test_sweep_lifecycle_ux.py +523 -0
  64. fcloud_sdk-0.2.0/tests/test_sweep_retry_and_kill_truth.py +90 -0
  65. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_v2_connect.py +86 -2
  66. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_volume_download_delete.py +16 -1
  67. fcloud_sdk-0.2.0/tests/test_volume_preflight.py +61 -0
  68. fcloud_sdk-0.1.2/src/fcloud/cli/output.py +0 -17
  69. fcloud_sdk-0.1.2/src/fcloud/cli/setup.py +0 -44
  70. fcloud_sdk-0.1.2/tests/test_sweep_lifecycle_ux.py +0 -145
  71. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/CONTRIBUTING.md +0 -0
  72. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/LICENSE +0 -0
  73. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/MANIFEST.in +0 -0
  74. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/NOTICE +0 -0
  75. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/setup.cfg +0 -0
  76. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/_legacy_env.py +0 -0
  77. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/console.py +0 -0
  78. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/files.py +0 -0
  79. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/hardware.py +0 -0
  80. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/migration.py +0 -0
  81. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/mount.py +0 -0
  82. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/registry.py +0 -0
  83. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/ssh.py +0 -0
  84. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/volume.py +0 -0
  85. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/cli/wait.py +0 -0
  86. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/client_projects.py +0 -0
  87. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/client_sessions.py +0 -0
  88. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/client_volumes.py +0 -0
  89. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/direct.py +0 -0
  90. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/fileset.py +0 -0
  91. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/image.py +0 -0
  92. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/job.py +0 -0
  93. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/providers/__init__.py +0 -0
  94. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/providers/requests_http.py +0 -0
  95. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/py.typed +0 -0
  96. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/tunnel.py +0 -0
  97. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/version.py +0 -0
  98. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud/volume_wait.py +0 -0
  99. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud_sdk.egg-info/dependency_links.txt +0 -0
  100. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud_sdk.egg-info/entry_points.txt +0 -0
  101. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud_sdk.egg-info/requires.txt +0 -0
  102. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/fcloud_sdk.egg-info/top_level.txt +0 -0
  103. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/src/foom/__init__.py +0 -0
  104. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/conftest.py +0 -0
  105. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_cli_args.py +0 -0
  106. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_cli_dispatch.py +0 -0
  107. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_cli_guards.py +0 -0
  108. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_cli_help.py +0 -0
  109. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_client_contracts_c.py +0 -0
  110. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_client_host_frames_contract.py +0 -0
  111. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_client_http_contract.py +0 -0
  112. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_client_telemetry.py +0 -0
  113. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_config.py +0 -0
  114. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_default_image.py +0 -0
  115. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_direct_bridge.py +0 -0
  116. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_direct_fake_host.py +0 -0
  117. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_dotenv_precedence.py +0 -0
  118. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_download_volume_hint.py +0 -0
  119. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_emit_pid.py +0 -0
  120. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_fileset.py +0 -0
  121. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_job.py +0 -0
  122. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_legacy_shim.py +0 -0
  123. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_progress_narration.py +0 -0
  124. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_provider_env_default.py +0 -0
  125. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_queued_reason_render.py +0 -0
  126. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_rate_limit_ride.py +0 -0
  127. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_rebuild_respawn.py +0 -0
  128. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_ride_cap.py +0 -0
  129. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_run_migration.py +0 -0
  130. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_run_volume_collision.py +0 -0
  131. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_session_truth.py +0 -0
  132. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_sessions_live_statuses.py +0 -0
  133. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_sessions_point_lookup.py +0 -0
  134. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_shell_env.py +0 -0
  135. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_shell_interrupt.py +0 -0
  136. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_skus.py +0 -0
  137. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_sweep_cost_optin.py +0 -0
  138. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_sweep_status_view.py +0 -0
  139. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_sweeps_binding.py +0 -0
  140. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_tunnel.py +0 -0
  141. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_types.py +0 -0
  142. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_upload_s3_path.py +0 -0
  143. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_volume_cli_hardening.py +0 -0
  144. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_volume_resolution.py +0 -0
  145. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_volume_wait.py +0 -0
  146. {fcloud_sdk-0.1.2 → fcloud_sdk-0.2.0}/tests/test_wait.py +0 -0
@@ -4,6 +4,82 @@ All notable changes to the `fcloud` Python SDK and CLI are recorded here.
4
4
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
5
5
  versions follow [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.2.0] - 2026-09-08
8
+
9
+ ### Added
10
+ - Per-session checkpoint/restore opt-out: `--no-checkpoint` on
11
+ `create`/`exec`/`run`/`shell` (SDK: `checkpoint=False` on
12
+ `Project.session`/`cold_session`, `Client.create_session`/
13
+ `create_cold_session`). A preempted opt-out session is rebuilt cold on any
14
+ available host (`/workspace` kept, running processes lost) instead of being
15
+ checkpointed and restored pinned to the region its checkpoint lives in.
16
+ Defaults resolve `--checkpoint`/`--no-checkpoint` → `FCLOUD_CHECKPOINT` →
17
+ `fcloud.json` `"checkpoint"` → the new `fcloud config set checkpoint on|off`
18
+ user default (`~/.fcloud/config.json`) → server default (on).
19
+ `SessionInfo.checkpoint` and `fcloud sessions --json` expose the policy; the
20
+ create fails loudly against a dispatcher too old to honour the opt-out.
21
+ - `--no-resume` on `exec`/`run`/`shell`: refuse to wake a cold (stopped)
22
+ session instead of transparently re-provisioning its hardware. The refusal
23
+ exits 1 with a typed `session_cold` error naming the session and the SKU a
24
+ resume would have provisioned. SDK: `Client.attach_session(...,
25
+ resume=False)` raises the new `fcloud.SessionColdError`.
26
+ - Pipelines: `fcloud map --after SWEEP[,SWEEP]` (SDK: `client.map(...,
27
+ after=handle_or_name, after_strict=…)`) holds a sweep in the new
28
+ `waiting` state until the named sweeps finish, then launches it — the
29
+ barrier is durable server-side, so a generate → train → eval chain
30
+ submits up front and the client can disconnect. An upstream failure
31
+ fails the downstream sweep (`upstream_failed:<name>`; re-arm with
32
+ `fcloud sweep retry <name> --all`); a partially-succeeded upstream
33
+ counts as finished unless `--after-strict`.
34
+ - `--volume NAME[:MOUNT][:ro]` (also fcloud.json `"read_only": true` or
35
+ the `name:/mount:ro` string form) attaches a volume read-only: the
36
+ session sees it, but its writes are discarded at close (no commit).
37
+ - `fcloud autoresearch init`: scaffolds a karpathy/autoresearch-style keep/discard
38
+ research loop (agent setup guide + `program.md` template) that runs experiments
39
+ on a persistent fcloud session.
40
+ The loop uploads `results.tsv` to the session each experiment; the dashboard's
41
+ session page then shows an Autoresearch tab (metric chart + results table).
42
+ - Skip-the-line boost: `fcloud sweep boost <name>` / pressing `s` in
43
+ `fcloud sweep status --watch` (offered while blocked on capacity) moves a
44
+ sweep to the fast claim lane; queued tasks re-lane immediately
45
+ (`SweepHandle.boost()` in the SDK). Freed machines alternate fast/free.
46
+ - `fcloud sweep status` explains WHY a sweep is waiting: a `why:` line under
47
+ `blocked on capacity` names the line (`3 session(s) ahead in the gpu_1x_l4
48
+ queue (1 fast-lane)`) and/or the platform (`a new machine is being
49
+ provisioned`, `no capacity ... (stockout)`, `cloud quota exhausted`, ...),
50
+ from the new `blocked.sku/queued_reason/queued_ahead/fast_ahead` status
51
+ fields; a `state: waiting` (pipeline) sweep names the upstream sweeps it
52
+ holds for. Older dispatchers omit the fields and status reads as before.
53
+ - Async sweep scheduling: `fcloud map --not-before WHEN` (park until), `--by WHEN`
54
+ (deadline; opportunistic idle-fill until `deadline - --runtime - pad`, then
55
+ normal launch) and `--runtime DUR` (per-task estimate, default 1h).
56
+ `fcloud sweep status` shows the window phase; a missed deadline marks the
57
+ sweep `deadline_missed` (webhook `job.deadline_missed`) but never cancels it.
58
+ - First-run login prompt: with no API key configured, the CLI offers to open
59
+ the dashboard login and store the key instead of failing with a bare error.
60
+ - CLI errors carry typed error codes and the request id; the client never
61
+ provisions hardware for a request the server would reject.
62
+
63
+ ### Changed
64
+ - Resuming a cold session now always prints one stderr line (`session <sid>
65
+ is cold — resuming (provisions <sku>); pass --no-resume to refuse`), even
66
+ when stderr is not a TTY or `FCLOUD_QUIET` is set — waking billed hardware
67
+ is a spend event, and non-TTY runs used to do it silently.
68
+ - Failed map task attempts now retry with a growing delay (45s × attempt,
69
+ capped at 10m) instead of relaunching immediately; preemption requeues
70
+ remain immediate and still don't consume the retry budget.
71
+
72
+ ### Fixed
73
+ - `fcloud map --volume name:/path` sent the custom mount path under a key
74
+ the server ignores, silently mounting the volume at its default path.
75
+ Custom mount paths now apply to sweep task sessions.
76
+
77
+ ## [0.1.3] - 2026-08-27
78
+
79
+ ### Changed
80
+ - `fcloud setup` installs the agent skill for all agents by default (Enter or
81
+ non-interactive stdin = `all`); `--agents none` opts out.
82
+
7
83
  ## [0.1.2] - 2026-08-27
8
84
 
9
85
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fcloud-sdk
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: Python SDK and CLI for the fcloud GPU compute platform
5
5
  Author: fcloud
6
6
  License-Expression: Apache-2.0
@@ -51,13 +51,15 @@ pip install fcloud-sdk
51
51
 
52
52
  Create an account on https://fcloud-home.vercel.app.
53
53
  Enter a credit card, and copy your API key.
54
+ Or just run any `fcloud` command (or `fcloud login`): a fresh install
55
+ walks you through sign-up and saves the key you paste.
54
56
 
55
57
  ```bash
56
58
  fcloud setup # prompts for the key; optionally installs the agent skill
57
59
  fcloud health # verify connectivity
58
60
  ```
59
61
 
60
- `fcloud setup --agents all` also installs the fcloud skill file for supported
62
+ `fcloud setup` also installs (by default) the fcloud skill file for supported
61
63
  coding agents (Cursor, Claude Code, Codex) so an agent can drive fcloud for you.
62
64
  Use `--agents none` to skip that.
63
65
 
@@ -201,16 +203,18 @@ Other environment switches:
201
203
  | `FCLOUD_QUIET=1` | Suppress "still waiting" progress lines while a host is provisioned |
202
204
  | `FCLOUD_QUEUE_TIMEOUT=<seconds>` | How long to wait for capacity before giving up (default 1200) |
203
205
  | `FCLOUD_MIGRATE_RESTART=never` | Don't automatically re-run a command after a host rebuild (default `auto`) |
206
+ | `FCLOUD_CHECKPOINT=off` | Default checkpoint/restore policy for new sessions. `off`: a preempted session rebuilds cold on any available host (`/workspace` kept, processes lost) instead of restoring pinned to its checkpoint's region. Per-session: `--checkpoint`/`--no-checkpoint`; per-user: `fcloud config set checkpoint off`; per-project: `fcloud.json` `"checkpoint": false` |
204
207
  | `FCLOUD_INSECURE_HTTP=1` | Allow a plaintext `http://` API URL to a non-loopback host (refused by default — the API key would travel unencrypted). Loopback URLs never need this |
205
208
  | `FCLOUD_TELEMETRY=0` | Disable all client telemetry. When enabled (the default), the client reports failures the backend cannot otherwise see — an uncaught CLI error, a queue-wait timeout, exhausted connect retries — as a fixed-allowlist payload (session id, event type, error class, truncated message, SKU/timing fields); never file contents, paths from OS errors, or credentials |
206
209
 
207
210
  A `fcloud.json` at the project root can set defaults (image build steps, default
208
- volumes). Note that fcloud will run the build steps it finds there, so treat a
211
+ volumes, checkpoint policy); `fcloud config` stores per-user defaults in
212
+ `~/.fcloud/config.json`. Note that fcloud will run the build steps it finds there, so treat a
209
213
  cloned repo's `fcloud.json` the way you would its Dockerfile.
210
214
 
211
215
  ## Agent skill
212
216
 
213
- `fcloud setup --agents all` installs `SKILL.md` for supported coding agents. It
217
+ `fcloud setup` installs `SKILL.md` by default (`--agents none` to skip) for supported coding agents. It
214
218
  is the long-form, agent-oriented guide: workflow patterns, monitoring loops,
215
219
  and anti-patterns.
216
220
 
@@ -17,13 +17,15 @@ pip install fcloud-sdk
17
17
 
18
18
  Create an account on https://fcloud-home.vercel.app.
19
19
  Enter a credit card, and copy your API key.
20
+ Or just run any `fcloud` command (or `fcloud login`): a fresh install
21
+ walks you through sign-up and saves the key you paste.
20
22
 
21
23
  ```bash
22
24
  fcloud setup # prompts for the key; optionally installs the agent skill
23
25
  fcloud health # verify connectivity
24
26
  ```
25
27
 
26
- `fcloud setup --agents all` also installs the fcloud skill file for supported
28
+ `fcloud setup` also installs (by default) the fcloud skill file for supported
27
29
  coding agents (Cursor, Claude Code, Codex) so an agent can drive fcloud for you.
28
30
  Use `--agents none` to skip that.
29
31
 
@@ -167,16 +169,18 @@ Other environment switches:
167
169
  | `FCLOUD_QUIET=1` | Suppress "still waiting" progress lines while a host is provisioned |
168
170
  | `FCLOUD_QUEUE_TIMEOUT=<seconds>` | How long to wait for capacity before giving up (default 1200) |
169
171
  | `FCLOUD_MIGRATE_RESTART=never` | Don't automatically re-run a command after a host rebuild (default `auto`) |
172
+ | `FCLOUD_CHECKPOINT=off` | Default checkpoint/restore policy for new sessions. `off`: a preempted session rebuilds cold on any available host (`/workspace` kept, processes lost) instead of restoring pinned to its checkpoint's region. Per-session: `--checkpoint`/`--no-checkpoint`; per-user: `fcloud config set checkpoint off`; per-project: `fcloud.json` `"checkpoint": false` |
170
173
  | `FCLOUD_INSECURE_HTTP=1` | Allow a plaintext `http://` API URL to a non-loopback host (refused by default — the API key would travel unencrypted). Loopback URLs never need this |
171
174
  | `FCLOUD_TELEMETRY=0` | Disable all client telemetry. When enabled (the default), the client reports failures the backend cannot otherwise see — an uncaught CLI error, a queue-wait timeout, exhausted connect retries — as a fixed-allowlist payload (session id, event type, error class, truncated message, SKU/timing fields); never file contents, paths from OS errors, or credentials |
172
175
 
173
176
  A `fcloud.json` at the project root can set defaults (image build steps, default
174
- volumes). Note that fcloud will run the build steps it finds there, so treat a
177
+ volumes, checkpoint policy); `fcloud config` stores per-user defaults in
178
+ `~/.fcloud/config.json`. Note that fcloud will run the build steps it finds there, so treat a
175
179
  cloned repo's `fcloud.json` the way you would its Dockerfile.
176
180
 
177
181
  ## Agent skill
178
182
 
179
- `fcloud setup --agents all` installs `SKILL.md` for supported coding agents. It
183
+ `fcloud setup` installs `SKILL.md` by default (`--agents none` to skip) for supported coding agents. It
180
184
  is the long-form, agent-oriented guide: workflow patterns, monitoring loops,
181
185
  and anti-patterns.
182
186
 
@@ -336,6 +336,39 @@ once**, keep one client attached per box for its whole run — e.g. N parallel
336
336
  creating N sessions and attending to them one at a time, which lets the idle
337
337
  ones lose their hosts.
338
338
 
339
+ ### Checkpoint/restore on preemption (and how to turn it off)
340
+
341
+ Sessions run on spot capacity. By default, when a host is reclaimed fcloud
342
+ **checkpoints** the live sandbox (GPU state included) and **restores** it on a
343
+ fresh box, so running processes continue. The restore is pinned to the region
344
+ the checkpoint lives in (cross-region transfer of a multi-GB image is not free),
345
+ so a restore can wait in `awaiting_restore_capacity` until that region has spot
346
+ capacity again.
347
+
348
+ If you would rather get **any** available host quickly and re-run your command
349
+ yourself, turn checkpoint/restore off for the session. A preempted
350
+ `--no-checkpoint` session is rebuilt cold on any host in any region:
351
+ `/workspace` is preserved (synced before the box dies), running processes are
352
+ lost, and the session carries `state_loss_reason=spot_preempt_checkpoint_disabled`
353
+ in `fcloud sessions --json`. `fcloud run`/`exec` re-run the command after a
354
+ rebuild (`FCLOUD_MIGRATE_RESTART`), so a resumable script (one that reloads its
355
+ own checkpoints from `/workspace` or a volume) is the natural pairing.
356
+
357
+ ```bash
358
+ fcloud run train.py --sku gpu_1x_h100 --no-checkpoint # this session only
359
+ fcloud create --sku gpu_1x_l4 --no-checkpoint # for the session's life
360
+ fcloud config set checkpoint off # my default, every project
361
+ fcloud config get checkpoint # -> off
362
+ fcloud config unset checkpoint # back to on
363
+ ```
364
+
365
+ Precedence, strongest first: `--checkpoint`/`--no-checkpoint` (or SDK
366
+ `checkpoint=True/False`) → `FCLOUD_CHECKPOINT=on|off` → `fcloud.json`
367
+ `"checkpoint": false` → `fcloud config set checkpoint` → server default (on).
368
+ The policy is fixed when the session is **created**; `--on <sid>` keeps the
369
+ session's existing policy. Jobs (`fcloud job`, `fcloud map`) never checkpoint.
370
+ `fcloud sessions` marks opted-out sessions `[no-checkpoint]`.
371
+
339
372
  ### File Transfer
340
373
 
341
374
  | Command | Description | Needs active session? |
@@ -470,9 +503,12 @@ detach or session close.
470
503
  Volumes are attached
471
504
  **after** the session is online, so a volume-mounted command queues and
472
505
  provisions a host like any other session, and `--volume` also works against an
473
- already-running session (`--on <sid>`). Mounted volumes are active in at most
474
- one session, and browsing/import/delete returns a conflict while a volume is
475
- attached.
506
+ already-running session (`--on <sid>`). Any number of sessions may attach the
507
+ same volume concurrently — commits merge per path, last writer wins per file
508
+ (so key parallel writers' outputs by distinct paths). Only `delete` and
509
+ `import` conflict while a volume is attached; browsing works any time. Append
510
+ `:ro` (`--volume data:/vol:ro`) to attach read-only: the session sees the
511
+ volume but its writes are discarded at close (no commit).
476
512
 
477
513
  Declare volumes once in `fcloud.json` to skip `--volume` on every command (see
478
514
  [Project Config](#project-config-fcloudjson)):
@@ -678,6 +714,18 @@ without it the cap is **8**, and submitting more tasks than that runs them
678
714
  in waves. Submit says so out loud, and the cap is changeable on a live
679
715
  sweep: `fcloud sweep set <name> --max-parallel N`.
680
716
 
717
+ **Scheduling (async).** `--not-before WHEN` parks the sweep until then;
718
+ `--by WHEN` sets a deadline: the platform runs tasks opportunistically on
719
+ already-idle hosts (no capacity is provisioned early) and switches to
720
+ normal launching at `deadline - --runtime - a provisioning pad`, so the
721
+ sweep finishes by the deadline without paying for eager scale-up.
722
+ `--runtime DUR` is the per-task runtime estimate (default 1h). WHEN is a
723
+ duration from now (`30m`, `8h`, `2d`) or an ISO time (`2026-09-01T02:00`,
724
+ local). A missed deadline never cancels the sweep — status shows
725
+ `deadline_missed` and the webhook fires `job.deadline_missed`. While
726
+ waiting, `fcloud sweep status` prints the window phase and when launches
727
+ begin.
728
+
681
729
  **Canary.** Task 0 runs first and gates the rest: a broken sweep costs one
682
730
  task, not N. `fcloud map` blocks until the canary passes, then detaches (Ctrl-C
683
731
  detaches the watch — it does NOT cancel). `--no-canary` / `--no-wait` opt out.
@@ -687,11 +735,53 @@ the rest — cheaper than `--no-canary`, which risks N bad tasks to save one.
687
735
  Failures don't consume the retry budget when caused by spot preemption — the
688
736
  task just requeues.
689
737
 
738
+ **Skip the line.** When tasks are blocked on capacity, `fcloud sweep boost
739
+ <name>` (or pressing `s` in `sweep status --watch`, offered only while a
740
+ line exists) moves the sweep to the fast claim lane: queued tasks skip
741
+ ahead of free-lane work, and freed machines alternate fast/free so nobody
742
+ is starved. SDK: `handle.boost()`. Unpriced for now.
743
+
690
744
  **Waiting for capacity is not failure.** A task with no host yet keeps its
691
745
  queued session, charges no retry, and is reported as blocked:
692
- `fcloud sweep status` prints `blocked on capacity: N task(s), oldest 6m12s`.
693
- That is the fleet scaling up, not your sweep breaking — cancel if the wait
694
- is longer than the work is worth.
746
+ `fcloud sweep status` prints `blocked on capacity: N task(s), oldest 6m12s`
747
+ followed by a `why:` line saying what the wait actually is — a line
748
+ (`3 session(s) ahead in the gpu_1x_l4 queue (1 fast-lane)`), the platform
749
+ (`a new machine is being provisioned`, `waiting for a busy machine to free
750
+ up`), or bad news worth acting on (`no capacity ... (stockout) — consider
751
+ another SKU`, `cloud quota exhausted`). Use it to decide: a line or a
752
+ provision resolves itself (or `boost` past the free lane); a stockout or
753
+ quota wall means pick another SKU or cancel. A `state: waiting` sweep is a
754
+ pipeline hold, and status names the upstream sweeps it waits on.
755
+
756
+ **Retry backoff.** A charged failure re-queues with a growing delay (45s ×
757
+ attempt, capped at 10m) so a deterministic crasher doesn't burn its budget
758
+ in seconds; preemption/lost-host requeues re-run immediately and never
759
+ consume the budget.
760
+
761
+ **Pipelines (`--after`).** `--after SWEEP[,SWEEP]` holds a sweep in
762
+ `waiting` until the named sweeps finish, then launches it — a durable
763
+ server-side barrier, so a whole generate → train → eval pipeline submits up
764
+ front and the client can disconnect:
765
+
766
+ ```bash
767
+ fcloud map --name gen --volume run42:/vol \
768
+ -- python3 gen.py --out /vol/data/shard-{i}.jsonl ::: {0..63}
769
+ fcloud map --name train --sku gpu_8x_h100 --volume run42:/vol --after gen \
770
+ -- python3 train.py --data /vol/data --ckpt /vol/ckpt
771
+ fcloud map --name eval --volume run42:/vol --after train \
772
+ -- python3 eval.py --ckpt /vol/ckpt --suite {} ::: mmlu,gsm8k,mbpp
773
+ ```
774
+
775
+ Data flows between stages through the shared volume: map tasks write
776
+ disjoint task-keyed paths, the next stage mounts the same volume and sees
777
+ the union (the gate waits for the upstream's volume commits to land before
778
+ releasing). An upstream failure fails the downstream sweep with
779
+ `upstream_failed:<name>` instead of running it on missing inputs; after
780
+ fixing and re-running the upstream, `fcloud sweep retry <name> --all`
781
+ re-arms the downstream. A `succeeded_partial` upstream counts as finished
782
+ unless `--after-strict`. Edges bind at submit, so upstreams must be
783
+ submitted first (which also makes cycles impossible). SDK:
784
+ `client.map(..., after=gen_handle)` (or a name, or a list; `after_strict=True`).
695
785
 
696
786
  **Tracking & recovery:**
697
787
  ```bash
@@ -699,15 +789,16 @@ fcloud sweeps # all sweeps: state + done/run/fail count
699
789
  fcloud sweep status <name> # counts + failures clustered by error, with exemplar task
700
790
  fcloud sweep status <name> --watch # same, repainted until terminal (--interval S)
701
791
  fcloud sweep harvest <name> '*.json' ./out # every task's outputs → out/task-<i>/…
702
- fcloud sweep logs <name> [--task N] # durable output (defaults to the exemplar failure; falls back to earlier attempts)
792
+ fcloud sweep logs <name> [--task N] [--attempt K] # durable output (defaults to the exemplar failure, falling back to earlier attempts; --attempt 1 = most recent attempt, explicitly)
703
793
  fcloud sweep set <name> --max-parallel N # change the fan-out cap on a live sweep
704
- fcloud sweep retry <name> [--all] # re-run only failed tasks (fix code first: resubmit is idempotent)
794
+ fcloud sweep retry <name> [--all] # re-run failed tasks (fix code first: resubmit is idempotent); --all adds canceled + WEDGED in-flight tasks (lost host / dead placement) and never restarts healthy running work
705
795
  fcloud sweep cancel <name> [--remaining] # stop; --remaining keeps running tasks (partial success)
706
- fcloud sweep wait <name> # block until terminal; exit 0 on success
796
+ fcloud sweep wait <name> [--poll S] [--timeout S] # block until terminal; exit 0 = success, 124 = still running at timeout
707
797
  ```
708
- `--webhook URL` (or account default) POSTs the status document on canary
709
- pass/fail and completion — agents should submit with `--no-wait --json` and
710
- wake on the webhook instead of polling.
798
+ `--webhook URL` POSTs the status document on canary pass/fail and
799
+ completion — agents should submit with `--no-wait --json` and wake on the
800
+ webhook instead of polling. There is no account-default webhook: without
801
+ `--webhook`, nothing is POSTed.
711
802
 
712
803
  **The status JSON is a versioned public contract** (`schema_version: 1`),
713
804
  specified in `docs/sweep-status-schema.md`: `counts` is an open map keyed by
@@ -1086,6 +1177,7 @@ flags merge with (and re-map by name) the `volumes` defaults.
1086
1177
  | ------------ | ------------------------------------------ |
1087
1178
  | `sku` | Default hardware SKU for `exec` and `run` |
1088
1179
  | `volumes` | Volumes auto-attached to every `exec`/`run` session. Each entry is `"name"`, `"name:/mount"`, or `{"name": ..., "mount": ...}` |
1180
+ | `checkpoint` | `false` turns checkpoint/restore off for sessions created from this project (preemption rebuilds cold on any host). Default `true`. See *Checkpoint/restore on preemption* |
1089
1181
  | `image.base` | Base image (default: `python:3.11-slim`) |
1090
1182
  | `image.apt` | Packages to `apt-get install` |
1091
1183
  | `image.pip` | Packages to `pip install` |
@@ -1166,6 +1258,24 @@ CLI commands or explicitly via `client.project("name", image=...)`.
1166
1258
  remains pinned after a hard kill, recover with `fcloud stop <sid>` and then use the
1167
1259
  session again.
1168
1260
 
1261
+ ## Autonomous research loops (autoresearch)
1262
+
1263
+ To run an overnight keep/discard experiment loop on the user's code — after
1264
+ [karpathy/autoresearch](https://github.com/karpathy/autoresearch), with the GPU on
1265
+ fcloud instead of under the desk:
1266
+
1267
+ ```bash
1268
+ fcloud autoresearch init # writes autoresearch/SETUP.md + program.template.md
1269
+ ```
1270
+
1271
+ Then read `autoresearch/SETUP.md` and follow it. It walks you through deciding the
1272
+ metric, the one mutable file vs the frozen eval, and the fixed time budget with the
1273
+ user; you write `program.md`, run the baseline on a persistent session, and hand
1274
+ off to the loop, which only ever re-uploads the mutable file and greps the metric.
1275
+ Progress is visible on the session's page in the fcloud dashboard (Autoresearch
1276
+ tab: metric chart + results table) once the loop's `results.tsv` upload syncs;
1277
+ point the user there when the loop starts.
1278
+
1169
1279
  ## Anti-Patterns
1170
1280
 
1171
1281
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fcloud-sdk"
7
- version = "0.1.2"
7
+ version = "0.2.0"
8
8
  description = "Python SDK and CLI for the fcloud GPU compute platform"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -45,7 +45,7 @@ where = ["src"]
45
45
  # editable copy at the package root) so `fcloud setup` can find it via
46
46
  # importlib.resources under any install scheme, including `pip install --user`.
47
47
  [tool.setuptools.package-data]
48
- fcloud = ["py.typed", "SKILL.md"]
48
+ fcloud = ["py.typed", "SKILL.md", "autoresearch/*.md"]
49
49
 
50
50
  [project.optional-dependencies]
51
51
  dev = [
@@ -336,6 +336,39 @@ once**, keep one client attached per box for its whole run — e.g. N parallel
336
336
  creating N sessions and attending to them one at a time, which lets the idle
337
337
  ones lose their hosts.
338
338
 
339
+ ### Checkpoint/restore on preemption (and how to turn it off)
340
+
341
+ Sessions run on spot capacity. By default, when a host is reclaimed fcloud
342
+ **checkpoints** the live sandbox (GPU state included) and **restores** it on a
343
+ fresh box, so running processes continue. The restore is pinned to the region
344
+ the checkpoint lives in (cross-region transfer of a multi-GB image is not free),
345
+ so a restore can wait in `awaiting_restore_capacity` until that region has spot
346
+ capacity again.
347
+
348
+ If you would rather get **any** available host quickly and re-run your command
349
+ yourself, turn checkpoint/restore off for the session. A preempted
350
+ `--no-checkpoint` session is rebuilt cold on any host in any region:
351
+ `/workspace` is preserved (synced before the box dies), running processes are
352
+ lost, and the session carries `state_loss_reason=spot_preempt_checkpoint_disabled`
353
+ in `fcloud sessions --json`. `fcloud run`/`exec` re-run the command after a
354
+ rebuild (`FCLOUD_MIGRATE_RESTART`), so a resumable script (one that reloads its
355
+ own checkpoints from `/workspace` or a volume) is the natural pairing.
356
+
357
+ ```bash
358
+ fcloud run train.py --sku gpu_1x_h100 --no-checkpoint # this session only
359
+ fcloud create --sku gpu_1x_l4 --no-checkpoint # for the session's life
360
+ fcloud config set checkpoint off # my default, every project
361
+ fcloud config get checkpoint # -> off
362
+ fcloud config unset checkpoint # back to on
363
+ ```
364
+
365
+ Precedence, strongest first: `--checkpoint`/`--no-checkpoint` (or SDK
366
+ `checkpoint=True/False`) → `FCLOUD_CHECKPOINT=on|off` → `fcloud.json`
367
+ `"checkpoint": false` → `fcloud config set checkpoint` → server default (on).
368
+ The policy is fixed when the session is **created**; `--on <sid>` keeps the
369
+ session's existing policy. Jobs (`fcloud job`, `fcloud map`) never checkpoint.
370
+ `fcloud sessions` marks opted-out sessions `[no-checkpoint]`.
371
+
339
372
  ### File Transfer
340
373
 
341
374
  | Command | Description | Needs active session? |
@@ -470,9 +503,12 @@ detach or session close.
470
503
  Volumes are attached
471
504
  **after** the session is online, so a volume-mounted command queues and
472
505
  provisions a host like any other session, and `--volume` also works against an
473
- already-running session (`--on <sid>`). Mounted volumes are active in at most
474
- one session, and browsing/import/delete returns a conflict while a volume is
475
- attached.
506
+ already-running session (`--on <sid>`). Any number of sessions may attach the
507
+ same volume concurrently — commits merge per path, last writer wins per file
508
+ (so key parallel writers' outputs by distinct paths). Only `delete` and
509
+ `import` conflict while a volume is attached; browsing works any time. Append
510
+ `:ro` (`--volume data:/vol:ro`) to attach read-only: the session sees the
511
+ volume but its writes are discarded at close (no commit).
476
512
 
477
513
  Declare volumes once in `fcloud.json` to skip `--volume` on every command (see
478
514
  [Project Config](#project-config-fcloudjson)):
@@ -678,6 +714,18 @@ without it the cap is **8**, and submitting more tasks than that runs them
678
714
  in waves. Submit says so out loud, and the cap is changeable on a live
679
715
  sweep: `fcloud sweep set <name> --max-parallel N`.
680
716
 
717
+ **Scheduling (async).** `--not-before WHEN` parks the sweep until then;
718
+ `--by WHEN` sets a deadline: the platform runs tasks opportunistically on
719
+ already-idle hosts (no capacity is provisioned early) and switches to
720
+ normal launching at `deadline - --runtime - a provisioning pad`, so the
721
+ sweep finishes by the deadline without paying for eager scale-up.
722
+ `--runtime DUR` is the per-task runtime estimate (default 1h). WHEN is a
723
+ duration from now (`30m`, `8h`, `2d`) or an ISO time (`2026-09-01T02:00`,
724
+ local). A missed deadline never cancels the sweep — status shows
725
+ `deadline_missed` and the webhook fires `job.deadline_missed`. While
726
+ waiting, `fcloud sweep status` prints the window phase and when launches
727
+ begin.
728
+
681
729
  **Canary.** Task 0 runs first and gates the rest: a broken sweep costs one
682
730
  task, not N. `fcloud map` blocks until the canary passes, then detaches (Ctrl-C
683
731
  detaches the watch — it does NOT cancel). `--no-canary` / `--no-wait` opt out.
@@ -687,11 +735,53 @@ the rest — cheaper than `--no-canary`, which risks N bad tasks to save one.
687
735
  Failures don't consume the retry budget when caused by spot preemption — the
688
736
  task just requeues.
689
737
 
738
+ **Skip the line.** When tasks are blocked on capacity, `fcloud sweep boost
739
+ <name>` (or pressing `s` in `sweep status --watch`, offered only while a
740
+ line exists) moves the sweep to the fast claim lane: queued tasks skip
741
+ ahead of free-lane work, and freed machines alternate fast/free so nobody
742
+ is starved. SDK: `handle.boost()`. Unpriced for now.
743
+
690
744
  **Waiting for capacity is not failure.** A task with no host yet keeps its
691
745
  queued session, charges no retry, and is reported as blocked:
692
- `fcloud sweep status` prints `blocked on capacity: N task(s), oldest 6m12s`.
693
- That is the fleet scaling up, not your sweep breaking — cancel if the wait
694
- is longer than the work is worth.
746
+ `fcloud sweep status` prints `blocked on capacity: N task(s), oldest 6m12s`
747
+ followed by a `why:` line saying what the wait actually is — a line
748
+ (`3 session(s) ahead in the gpu_1x_l4 queue (1 fast-lane)`), the platform
749
+ (`a new machine is being provisioned`, `waiting for a busy machine to free
750
+ up`), or bad news worth acting on (`no capacity ... (stockout) — consider
751
+ another SKU`, `cloud quota exhausted`). Use it to decide: a line or a
752
+ provision resolves itself (or `boost` past the free lane); a stockout or
753
+ quota wall means pick another SKU or cancel. A `state: waiting` sweep is a
754
+ pipeline hold, and status names the upstream sweeps it waits on.
755
+
756
+ **Retry backoff.** A charged failure re-queues with a growing delay (45s ×
757
+ attempt, capped at 10m) so a deterministic crasher doesn't burn its budget
758
+ in seconds; preemption/lost-host requeues re-run immediately and never
759
+ consume the budget.
760
+
761
+ **Pipelines (`--after`).** `--after SWEEP[,SWEEP]` holds a sweep in
762
+ `waiting` until the named sweeps finish, then launches it — a durable
763
+ server-side barrier, so a whole generate → train → eval pipeline submits up
764
+ front and the client can disconnect:
765
+
766
+ ```bash
767
+ fcloud map --name gen --volume run42:/vol \
768
+ -- python3 gen.py --out /vol/data/shard-{i}.jsonl ::: {0..63}
769
+ fcloud map --name train --sku gpu_8x_h100 --volume run42:/vol --after gen \
770
+ -- python3 train.py --data /vol/data --ckpt /vol/ckpt
771
+ fcloud map --name eval --volume run42:/vol --after train \
772
+ -- python3 eval.py --ckpt /vol/ckpt --suite {} ::: mmlu,gsm8k,mbpp
773
+ ```
774
+
775
+ Data flows between stages through the shared volume: map tasks write
776
+ disjoint task-keyed paths, the next stage mounts the same volume and sees
777
+ the union (the gate waits for the upstream's volume commits to land before
778
+ releasing). An upstream failure fails the downstream sweep with
779
+ `upstream_failed:<name>` instead of running it on missing inputs; after
780
+ fixing and re-running the upstream, `fcloud sweep retry <name> --all`
781
+ re-arms the downstream. A `succeeded_partial` upstream counts as finished
782
+ unless `--after-strict`. Edges bind at submit, so upstreams must be
783
+ submitted first (which also makes cycles impossible). SDK:
784
+ `client.map(..., after=gen_handle)` (or a name, or a list; `after_strict=True`).
695
785
 
696
786
  **Tracking & recovery:**
697
787
  ```bash
@@ -699,15 +789,16 @@ fcloud sweeps # all sweeps: state + done/run/fail count
699
789
  fcloud sweep status <name> # counts + failures clustered by error, with exemplar task
700
790
  fcloud sweep status <name> --watch # same, repainted until terminal (--interval S)
701
791
  fcloud sweep harvest <name> '*.json' ./out # every task's outputs → out/task-<i>/…
702
- fcloud sweep logs <name> [--task N] # durable output (defaults to the exemplar failure; falls back to earlier attempts)
792
+ fcloud sweep logs <name> [--task N] [--attempt K] # durable output (defaults to the exemplar failure, falling back to earlier attempts; --attempt 1 = most recent attempt, explicitly)
703
793
  fcloud sweep set <name> --max-parallel N # change the fan-out cap on a live sweep
704
- fcloud sweep retry <name> [--all] # re-run only failed tasks (fix code first: resubmit is idempotent)
794
+ fcloud sweep retry <name> [--all] # re-run failed tasks (fix code first: resubmit is idempotent); --all adds canceled + WEDGED in-flight tasks (lost host / dead placement) and never restarts healthy running work
705
795
  fcloud sweep cancel <name> [--remaining] # stop; --remaining keeps running tasks (partial success)
706
- fcloud sweep wait <name> # block until terminal; exit 0 on success
796
+ fcloud sweep wait <name> [--poll S] [--timeout S] # block until terminal; exit 0 = success, 124 = still running at timeout
707
797
  ```
708
- `--webhook URL` (or account default) POSTs the status document on canary
709
- pass/fail and completion — agents should submit with `--no-wait --json` and
710
- wake on the webhook instead of polling.
798
+ `--webhook URL` POSTs the status document on canary pass/fail and
799
+ completion — agents should submit with `--no-wait --json` and wake on the
800
+ webhook instead of polling. There is no account-default webhook: without
801
+ `--webhook`, nothing is POSTed.
711
802
 
712
803
  **The status JSON is a versioned public contract** (`schema_version: 1`),
713
804
  specified in `docs/sweep-status-schema.md`: `counts` is an open map keyed by
@@ -1086,6 +1177,7 @@ flags merge with (and re-map by name) the `volumes` defaults.
1086
1177
  | ------------ | ------------------------------------------ |
1087
1178
  | `sku` | Default hardware SKU for `exec` and `run` |
1088
1179
  | `volumes` | Volumes auto-attached to every `exec`/`run` session. Each entry is `"name"`, `"name:/mount"`, or `{"name": ..., "mount": ...}` |
1180
+ | `checkpoint` | `false` turns checkpoint/restore off for sessions created from this project (preemption rebuilds cold on any host). Default `true`. See *Checkpoint/restore on preemption* |
1089
1181
  | `image.base` | Base image (default: `python:3.11-slim`) |
1090
1182
  | `image.apt` | Packages to `apt-get install` |
1091
1183
  | `image.pip` | Packages to `pip install` |
@@ -1166,6 +1258,24 @@ CLI commands or explicitly via `client.project("name", image=...)`.
1166
1258
  remains pinned after a hard kill, recover with `fcloud stop <sid>` and then use the
1167
1259
  session again.
1168
1260
 
1261
+ ## Autonomous research loops (autoresearch)
1262
+
1263
+ To run an overnight keep/discard experiment loop on the user's code — after
1264
+ [karpathy/autoresearch](https://github.com/karpathy/autoresearch), with the GPU on
1265
+ fcloud instead of under the desk:
1266
+
1267
+ ```bash
1268
+ fcloud autoresearch init # writes autoresearch/SETUP.md + program.template.md
1269
+ ```
1270
+
1271
+ Then read `autoresearch/SETUP.md` and follow it. It walks you through deciding the
1272
+ metric, the one mutable file vs the frozen eval, and the fixed time budget with the
1273
+ user; you write `program.md`, run the baseline on a persistent session, and hand
1274
+ off to the loop, which only ever re-uploads the mutable file and greps the metric.
1275
+ Progress is visible on the session's page in the fcloud dashboard (Autoresearch
1276
+ tab: metric chart + results table) once the loop's `results.tsv` upload syncs;
1277
+ point the user there when the loop starts.
1278
+
1169
1279
  ## Anti-Patterns
1170
1280
 
1171
1281
 
@@ -22,6 +22,7 @@ import fcloud._legacy_env # noqa: F401 — must run before any env read below
22
22
  from fcloud.client import (
23
23
  Client,
24
24
  FcloudError,
25
+ NoApiKeyError,
25
26
  PaymentOverdueError,
26
27
  SessionNotFoundError,
27
28
  )
@@ -31,6 +32,7 @@ from fcloud.errors import (
31
32
  FcloudRuntimeError,
32
33
  FoomError,
33
34
  RateLimitedError,
35
+ SessionColdError,
34
36
  VolumeHydrationError,
35
37
  )
36
38
  from fcloud.image import Image
@@ -75,8 +77,10 @@ __all__ = [
75
77
  "FcloudError",
76
78
  "FcloudRuntimeError",
77
79
  "MapSpecError",
80
+ "NoApiKeyError",
78
81
  "PaymentOverdueError",
79
82
  "RateLimitedError",
83
+ "SessionColdError",
80
84
  "SessionNotFoundError",
81
85
  "VolumeConflictError",
82
86
  "VolumeHydrationError",
@@ -36,6 +36,7 @@ from fcloud.cli.common import ( # noqa: F401
36
36
  _shape_exec_command,
37
37
  _split_trailing_args,
38
38
  )
39
+ from fcloud.cli.config_cmd import cmd_config # noqa: F401
39
40
  from fcloud.cli.console import ( # noqa: F401
40
41
  _exec_result,
41
42
  _live_console_sink,