fcloud-sdk 0.1.3__tar.gz → 0.2.1__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 (153) hide show
  1. fcloud_sdk-0.2.1/CHANGELOG.md +222 -0
  2. {fcloud_sdk-0.1.3/src/fcloud_sdk.egg-info → fcloud_sdk-0.2.1}/PKG-INFO +23 -8
  3. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/README.md +22 -7
  4. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/SKILL.md +129 -13
  5. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/pyproject.toml +2 -2
  6. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/SKILL.md +129 -13
  7. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/__init__.py +4 -0
  8. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/__main__.py +2 -0
  9. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/_direct_bridge.py +23 -1
  10. fcloud_sdk-0.2.1/src/fcloud/autoresearch/SETUP.md +67 -0
  11. fcloud_sdk-0.2.1/src/fcloud/autoresearch/program.template.md +159 -0
  12. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/__init__.py +2 -0
  13. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/attach.py +43 -9
  14. fcloud_sdk-0.2.1/src/fcloud/cli/autoresearch.py +36 -0
  15. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/common.py +63 -7
  16. fcloud_sdk-0.2.1/src/fcloud/cli/config_cmd.py +130 -0
  17. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/context.py +50 -9
  18. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/exec_cmd.py +42 -9
  19. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/help.py +8 -1
  20. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/interactive.py +30 -7
  21. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/job.py +88 -10
  22. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/main.py +52 -3
  23. fcloud_sdk-0.2.1/src/fcloud/cli/output.py +45 -0
  24. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/processes.py +223 -26
  25. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/run.py +42 -11
  26. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/sessions.py +246 -39
  27. fcloud_sdk-0.2.1/src/fcloud/cli/setup.py +100 -0
  28. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/sweep.py +432 -25
  29. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/sweep_harvest.py +13 -3
  30. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/sweep_watch.py +81 -20
  31. fcloud_sdk-0.2.1/src/fcloud/cli/usage.py +67 -0
  32. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/volume.py +5 -1
  33. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/wait.py +7 -0
  34. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli_args.py +16 -3
  35. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/client.py +58 -13
  36. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/config.py +90 -0
  37. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/errors.py +35 -0
  38. fcloud_sdk-0.2.1/src/fcloud/login.py +236 -0
  39. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/session.py +15 -3
  40. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/setup_cmd.py +18 -3
  41. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/shell.py +9 -0
  42. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/sweeps.py +54 -3
  43. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/types.py +6 -0
  44. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/v2_connect.py +87 -10
  45. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/volumes.py +23 -3
  46. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1/src/fcloud_sdk.egg-info}/PKG-INFO +23 -8
  47. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud_sdk.egg-info/SOURCES.txt +24 -0
  48. fcloud_sdk-0.2.1/tests/test_autoresearch.py +21 -0
  49. fcloud_sdk-0.2.1/tests/test_checkpoint_option.py +208 -0
  50. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_client.py +10 -9
  51. fcloud_sdk-0.2.1/tests/test_config_cmd.py +75 -0
  52. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_direct_bridge.py +23 -0
  53. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_exec_attach_retry.py +24 -2
  54. fcloud_sdk-0.2.1/tests/test_first_run_login.py +80 -0
  55. fcloud_sdk-0.2.1/tests/test_job_ls.py +99 -0
  56. fcloud_sdk-0.2.1/tests/test_kill_records_first.py +236 -0
  57. fcloud_sdk-0.2.1/tests/test_login.py +202 -0
  58. fcloud_sdk-0.2.1/tests/test_logs_unknown_pid.py +84 -0
  59. fcloud_sdk-0.2.1/tests/test_map_dag.py +40 -0
  60. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_map_default_image.py +3 -0
  61. fcloud_sdk-0.2.1/tests/test_map_window_flags.py +40 -0
  62. fcloud_sdk-0.2.1/tests/test_no_resume_gate.py +51 -0
  63. fcloud_sdk-0.2.1/tests/test_resume_volume_validation.py +86 -0
  64. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_run_wait_flags.py +2 -2
  65. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_secret_resolution.py +2 -2
  66. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_sessions_live_statuses.py +17 -0
  67. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_shell_volumes.py +1 -1
  68. fcloud_sdk-0.2.1/tests/test_spend.py +203 -0
  69. fcloud_sdk-0.2.1/tests/test_stop_refused_classification.py +95 -0
  70. fcloud_sdk-0.2.1/tests/test_sweep_boost.py +53 -0
  71. fcloud_sdk-0.2.1/tests/test_sweep_harvest.py +211 -0
  72. fcloud_sdk-0.2.1/tests/test_sweep_lifecycle_ux.py +523 -0
  73. fcloud_sdk-0.2.1/tests/test_sweep_retry_and_kill_truth.py +90 -0
  74. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_v2_connect.py +86 -2
  75. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_volume_cli_hardening.py +9 -0
  76. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_volume_download_delete.py +16 -1
  77. fcloud_sdk-0.2.1/tests/test_volume_preflight.py +61 -0
  78. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_wait.py +26 -0
  79. fcloud_sdk-0.1.3/CHANGELOG.md +0 -106
  80. fcloud_sdk-0.1.3/src/fcloud/cli/output.py +0 -17
  81. fcloud_sdk-0.1.3/src/fcloud/cli/setup.py +0 -44
  82. fcloud_sdk-0.1.3/tests/test_sweep_lifecycle_ux.py +0 -145
  83. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/CONTRIBUTING.md +0 -0
  84. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/LICENSE +0 -0
  85. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/MANIFEST.in +0 -0
  86. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/NOTICE +0 -0
  87. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/setup.cfg +0 -0
  88. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/_legacy_env.py +0 -0
  89. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/console.py +0 -0
  90. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/files.py +0 -0
  91. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/hardware.py +0 -0
  92. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/migration.py +0 -0
  93. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/mount.py +0 -0
  94. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/registry.py +0 -0
  95. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/cli/ssh.py +0 -0
  96. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/client_projects.py +0 -0
  97. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/client_sessions.py +0 -0
  98. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/client_volumes.py +0 -0
  99. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/direct.py +0 -0
  100. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/fileset.py +0 -0
  101. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/image.py +0 -0
  102. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/job.py +0 -0
  103. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/providers/__init__.py +0 -0
  104. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/providers/requests_http.py +0 -0
  105. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/py.typed +0 -0
  106. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/tunnel.py +0 -0
  107. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/version.py +0 -0
  108. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud/volume_wait.py +0 -0
  109. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud_sdk.egg-info/dependency_links.txt +0 -0
  110. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud_sdk.egg-info/entry_points.txt +0 -0
  111. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud_sdk.egg-info/requires.txt +0 -0
  112. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/fcloud_sdk.egg-info/top_level.txt +0 -0
  113. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/src/foom/__init__.py +0 -0
  114. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/conftest.py +0 -0
  115. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_cli_args.py +0 -0
  116. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_cli_dispatch.py +0 -0
  117. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_cli_guards.py +0 -0
  118. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_cli_help.py +0 -0
  119. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_client_contracts_c.py +0 -0
  120. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_client_host_frames_contract.py +0 -0
  121. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_client_http_contract.py +0 -0
  122. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_client_telemetry.py +0 -0
  123. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_config.py +0 -0
  124. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_default_image.py +0 -0
  125. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_direct_fake_host.py +0 -0
  126. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_dotenv_precedence.py +0 -0
  127. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_download_volume_hint.py +0 -0
  128. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_emit_pid.py +0 -0
  129. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_fileset.py +0 -0
  130. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_job.py +0 -0
  131. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_legacy_shim.py +0 -0
  132. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_progress_narration.py +0 -0
  133. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_provider_env_default.py +0 -0
  134. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_queued_reason_render.py +0 -0
  135. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_rate_limit_ride.py +0 -0
  136. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_rebuild_respawn.py +0 -0
  137. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_ride_cap.py +0 -0
  138. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_run_migration.py +0 -0
  139. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_run_volume_collision.py +0 -0
  140. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_session_truth.py +0 -0
  141. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_sessions_point_lookup.py +0 -0
  142. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_setup_cmd.py +0 -0
  143. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_shell_env.py +0 -0
  144. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_shell_interrupt.py +0 -0
  145. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_skus.py +0 -0
  146. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_sweep_cost_optin.py +0 -0
  147. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_sweep_status_view.py +0 -0
  148. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_sweeps_binding.py +0 -0
  149. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_tunnel.py +0 -0
  150. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_types.py +0 -0
  151. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_upload_s3_path.py +0 -0
  152. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_volume_resolution.py +0 -0
  153. {fcloud_sdk-0.1.3 → fcloud_sdk-0.2.1}/tests/test_volume_wait.py +0 -0
@@ -0,0 +1,222 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `fcloud` Python SDK and CLI are recorded here.
4
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
5
+ versions follow [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.2.1] - 2026-09-14
10
+
11
+ ### Added
12
+ - `fcloud login`: sign in through the browser (device authorization). Shows a
13
+ short code, opens the approval page, and saves a key minted for this
14
+ machine (named `cli:<hostname>`); then installs the fcloud skill for the
15
+ coding agents present on the machine (`--agents auto`, the default; also
16
+ `all`, a list, or `none`). `--no-browser` prints the URL and code instead
17
+ of opening a browser. A fresh install's first command starts the same
18
+ login, and `fcloud setup` with no key delegates to it.
19
+ - Spend visibility: `fcloud sessions` shows `$/HR` (the rate billed on the
20
+ session's open usage interval while a host is held, 0 when cold), `SPENT`
21
+ (compute charged so far) and `AGE` (time on its current host) per row plus
22
+ a "Spending now" footer; `--json` rows carry `usd_per_hour`, `spent_usd`
23
+ and `host_age_seconds` (SDK: `Client.session_cost`). New `fcloud usage [--since DUR|TS] [--until TS]`
24
+ reports what the account was charged over a window (default 30 days) by
25
+ resource and session (SDK: `Client.billing_usage`).
26
+
27
+ ### Changed
28
+ - `fcloud job ls` shows each job's COMMAND and EXIT code (from its process
29
+ index; `--json` rows carry `command` and `exit_code`), so rows are
30
+ tellable apart and failures visible without opening each job.
31
+
32
+ ### Fixed
33
+ - `fcloud logs <sid> <pid>` with a pid the dispatcher has no record of now
34
+ exits 1 (`process_not_found`) like `wait` and `kill`, instead of printing
35
+ an empty stream with exit 0 (and `status: ""` in `--json`) that a poller
36
+ read as a finished, silent process. A just-spawned pid gets a few seconds
37
+ to reach the durable index before it is called unknown.
38
+ - `fcloud wait` with a `--timeout` shorter than the unknown-pid grace window
39
+ exits 1 for an unknown pid, not 75 (retry), so scripted callers stop
40
+ retrying a typo forever.
41
+ - Cold starts of `run`/`exec`/`job run` no longer dump every host status
42
+ frame (`⟵ session_status: building (phase_change) setup_timing ...`);
43
+ the fresh-allocation and reconnect paths now use the same quiet printer
44
+ as warm attaches.
45
+ - `fcloud sweep harvest` no longer pulls the session's virtual `_logs/`
46
+ directory (`_logs/processes.json`) for a results glob like `*.json`; name
47
+ `_logs/` in the glob to fetch logs.
48
+ - `fcloud sessions` sizes the HOST and SKU columns to the data (27-char AWS
49
+ host ids overflowed the fixed width on every row).
50
+ - `fcloud volume list` shows each volume's last update time.
51
+ - SKILL.md documents `fcloud sessions --limit N`.
52
+
53
+ ## [0.2.0] - 2026-09-08
54
+
55
+ ### Added
56
+ - Per-session checkpoint/restore opt-out: `--no-checkpoint` on
57
+ `create`/`exec`/`run`/`shell` (SDK: `checkpoint=False` on
58
+ `Project.session`/`cold_session`, `Client.create_session`/
59
+ `create_cold_session`). A preempted opt-out session is rebuilt cold on any
60
+ available host (`/workspace` kept, running processes lost) instead of being
61
+ checkpointed and restored pinned to the region its checkpoint lives in.
62
+ Defaults resolve `--checkpoint`/`--no-checkpoint` → `FCLOUD_CHECKPOINT` →
63
+ `fcloud.json` `"checkpoint"` → the new `fcloud config set checkpoint on|off`
64
+ user default (`~/.fcloud/config.json`) → server default (on).
65
+ `SessionInfo.checkpoint` and `fcloud sessions --json` expose the policy; the
66
+ create fails loudly against a dispatcher too old to honour the opt-out.
67
+ - `--no-resume` on `exec`/`run`/`shell`: refuse to wake a cold (stopped)
68
+ session instead of transparently re-provisioning its hardware. The refusal
69
+ exits 1 with a typed `session_cold` error naming the session and the SKU a
70
+ resume would have provisioned. SDK: `Client.attach_session(...,
71
+ resume=False)` raises the new `fcloud.SessionColdError`.
72
+ - Pipelines: `fcloud map --after SWEEP[,SWEEP]` (SDK: `client.map(...,
73
+ after=handle_or_name, after_strict=…)`) holds a sweep in the new
74
+ `waiting` state until the named sweeps finish, then launches it — the
75
+ barrier is durable server-side, so a generate → train → eval chain
76
+ submits up front and the client can disconnect. An upstream failure
77
+ fails the downstream sweep (`upstream_failed:<name>`; re-arm with
78
+ `fcloud sweep retry <name> --all`); a partially-succeeded upstream
79
+ counts as finished unless `--after-strict`.
80
+ - `--volume NAME[:MOUNT][:ro]` (also fcloud.json `"read_only": true` or
81
+ the `name:/mount:ro` string form) attaches a volume read-only: the
82
+ session sees it, but its writes are discarded at close (no commit).
83
+ - `fcloud autoresearch init`: scaffolds a karpathy/autoresearch-style keep/discard
84
+ research loop (agent setup guide + `program.md` template) that runs experiments
85
+ on a persistent fcloud session.
86
+ The loop uploads `results.tsv` to the session each experiment; the dashboard's
87
+ session page then shows an Autoresearch tab (metric chart + results table).
88
+ - Skip-the-line boost: `fcloud sweep boost <name>` / pressing `s` in
89
+ `fcloud sweep status --watch` (offered while blocked on capacity) moves a
90
+ sweep to the fast claim lane; queued tasks re-lane immediately
91
+ (`SweepHandle.boost()` in the SDK). Freed machines alternate fast/free.
92
+ - `fcloud sweep status` explains WHY a sweep is waiting: a `why:` line under
93
+ `blocked on capacity` names the line (`3 session(s) ahead in the gpu_1x_l4
94
+ queue (1 fast-lane)`) and/or the platform (`a new machine is being
95
+ provisioned`, `no capacity ... (stockout)`, `cloud quota exhausted`, ...),
96
+ from the new `blocked.sku/queued_reason/queued_ahead/fast_ahead` status
97
+ fields; a `state: waiting` (pipeline) sweep names the upstream sweeps it
98
+ holds for. Older dispatchers omit the fields and status reads as before.
99
+ - Async sweep scheduling: `fcloud map --not-before WHEN` (park until), `--by WHEN`
100
+ (deadline; opportunistic idle-fill until `deadline - --runtime - pad`, then
101
+ normal launch) and `--runtime DUR` (per-task estimate, default 1h).
102
+ `fcloud sweep status` shows the window phase; a missed deadline marks the
103
+ sweep `deadline_missed` (webhook `job.deadline_missed`) but never cancels it.
104
+ - First-run login prompt: with no API key configured, the CLI offers to open
105
+ the dashboard login and store the key instead of failing with a bare error.
106
+ - CLI errors carry typed error codes and the request id; the client never
107
+ provisions hardware for a request the server would reject.
108
+
109
+ ### Changed
110
+ - Resuming a cold session now always prints one stderr line (`session <sid>
111
+ is cold — resuming (provisions <sku>); pass --no-resume to refuse`), even
112
+ when stderr is not a TTY or `FCLOUD_QUIET` is set — waking billed hardware
113
+ is a spend event, and non-TTY runs used to do it silently.
114
+ - Failed map task attempts now retry with a growing delay (45s × attempt,
115
+ capped at 10m) instead of relaunching immediately; preemption requeues
116
+ remain immediate and still don't consume the retry budget.
117
+
118
+ ### Fixed
119
+ - `fcloud map --volume name:/path` sent the custom mount path under a key
120
+ the server ignores, silently mounting the volume at its default path.
121
+ Custom mount paths now apply to sweep task sessions.
122
+
123
+ ## [0.1.3] - 2026-08-27
124
+
125
+ ### Changed
126
+ - `fcloud setup` installs the agent skill for all agents by default (Enter or
127
+ non-interactive stdin = `all`); `--agents none` opts out.
128
+
129
+ ## [0.1.2] - 2026-08-27
130
+
131
+ ### Changed
132
+ - README wording.
133
+
134
+ ## [0.1.1] - 2026-08-27
135
+
136
+ ### Changed
137
+ - README: sign up on the website and paste your API key; the editable
138
+ checkout install instructions are gone.
139
+
140
+ ## [0.1.0] - 2026-08-27
141
+
142
+ ### Changed
143
+ - Renamed: the package is `fcloud-sdk`, the import is `fcloud`, the CLI is
144
+ `fcloud`, env vars are `FCLOUD_*`, config lives in `~/.fcloud`, project
145
+ files are `fcloud.json`, new keys are `fcloud_sk_…`, and the client sends
146
+ `X-Fcloud-Client*` headers. Everything old keeps working: `import foom`,
147
+ the `foom` command, `FOOM_*`, `~/.foom`, `foom.json`, `foom_sk_` keys and
148
+ `foom.FoomError`. Map tasks now receive `FCLOUD_TASK_ID` /
149
+ `FCLOUD_WORLD_SIZE` / `FCLOUD_JOB` (the `FOOM_*` names are gone).
150
+
151
+ ### Added
152
+ - `foom sweep harvest <name> <remote-glob> <local-dir>` — download every
153
+ task's matching outputs in one command. Sessions are resolved from the
154
+ sweep and local paths are task-keyed (`<local-dir>/task-<index>/<path>`,
155
+ or `--flat`), replacing the per-sweep script that parsed status JSON for
156
+ session ids and ran N `foom download` calls. `--all-states` includes
157
+ failed and canceled tasks.
158
+ - `foom sweep status --watch [--interval S]` — the human live view,
159
+ repainted in place until the sweep is terminal. Appends instead of
160
+ repainting when stdout is not a terminal.
161
+ - `foom stop --wait [--timeout SECONDS]` — block until the workspace
162
+ manifest is durable, instead of polling `foom sessions` for `state=cold`.
163
+ `foom map --from <sid>` needs that manifest; the default timeout is 300s
164
+ and the command exits 1 if the wait expires (the stop itself stands).
165
+ - The `foom sweep status --json` document is now a documented, versioned
166
+ contract (`schema_version: 1`) — see `docs/sweep-status-schema.md`.
167
+ Additive changes keep the version; a removal or rename bumps it.
168
+ - Every request now carries `X-Foom-Client` / `X-Foom-Client-Version`, and a
169
+ client below the deployment's supported floor is rejected with HTTP 426 and
170
+ the new `foom.ClientTooOldError`, which names the upgrade command. This
171
+ replaces the previous failure mode for a stale client, an S3
172
+ `SignatureDoesNotMatch` on every >5 MiB upload that read as a credential
173
+ problem.
174
+
175
+ ### Changed
176
+ - `foom upload` now names the files that take the pre-signed cloud-storage
177
+ path (>5 MiB) rather than only the total, and names them again in the error
178
+ when that path fails — so a broken large-file upload is attributable to a
179
+ file, not to the command. `Session.upload()` returns them as `s3_files`
180
+ alongside `inline_files` and `s3_threshold_bytes`.
181
+
182
+ ## [0.1.0] — first public release
183
+
184
+ ### Added
185
+ - `foom` CLI: sessions, exec/run/shell/ssh/tunnel, background processes
186
+ (spawn/wait/logs/kill), file transfer (upload/download/ls/mount), volumes,
187
+ run-to-completion jobs, and `foom map` sweeps.
188
+ - Python SDK: `Client`, `Project`, `Session`, `Job`, `SweepHandle`, `Image`.
189
+ - `foom setup` installs the agent skill (`SKILL.md`) for Cursor, Claude Code
190
+ and Codex.
191
+ - `foom --version` and `foom.__version__`.
192
+ - Directory uploads skip hidden entries, dependency/cache directories and
193
+ symlinked files by default; `--include-hidden` / `--follow-symlinks` (CLI)
194
+ and `include_hidden=` / `follow_symlinks=` (SDK) opt back in.
195
+ - `FOOM_TELEMETRY=0` disables the best-effort CLI crash report.
196
+
197
+ ### Removed
198
+ Relative to the pre-release internal SDK (nothing here was ever on PyPI):
199
+ - Public names `MigrationInfo`, `MigrationStatus`, `SyncProgress`, the
200
+ `FcloudError` and `JobHandle` aliases (`FoomError` / `SweepHandle` remain),
201
+ and the `Session.migrate()` / `Client.watch()` / `Client.list_session_files()`
202
+ / `Client.download_file()` helpers — the operator-initiated migration
203
+ surface is not part of the public client.
204
+ - `Image.add_local_file()`, `Image.add_local_dir()`, `Image.copy()`,
205
+ `Image.workdir()` — use `Session.upload()` / `foom upload` for local files
206
+ and the `workdir=` argument on exec.
207
+ - Legacy `~/.fcloud/token`, `~/.fcloud/url` and `fcloud.json` lookups
208
+ (`~/.foom/*` and `foom.json` are the supported forms). `FCLOUD_API_KEY` /
209
+ `FCLOUD_URL` are still accepted as aliases of `FOOM_API_KEY` / `FOOM_URL`.
210
+ - Internal modules `foom.upload`, `foom.stream`, `foom.jobs`, `foom.client_ext`
211
+ and the `s3_uploader` / `websockets_transport` providers, with the
212
+ `websockets` dependency they pulled in.
213
+ - `DirectClient.run_streaming()` (no callers). `DirectClient.execute()` is
214
+ kept — internal harnesses use it.
215
+
216
+ ### Security
217
+ - Only `FOOM_*` keys from a project `.env` are imported into the process
218
+ environment; other keys stay reachable by name via `--secret KEY`.
219
+ - A plaintext (`ws://`) host address is refused when the API was reached over
220
+ `https://`; a plaintext API URL to a non-local host warns.
221
+ - `foom ssh` quotes its ProxyCommand and validates the session id before using
222
+ it in filesystem paths.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fcloud-sdk
3
- Version: 0.1.3
3
+ Version: 0.2.1
4
4
  Summary: Python SDK and CLI for the fcloud GPU compute platform
5
5
  Author: fcloud
6
6
  License-Expression: Apache-2.0
@@ -43,23 +43,35 @@ Requires Python 3.10+.
43
43
 
44
44
  ## Install
45
45
 
46
+ ```bash
47
+ curl -fsSL https://fcloud-home.vercel.app/install.sh | sh
48
+ ```
49
+
50
+ That installs the CLI from PyPI (via `uv` or `pipx`), signs you in through
51
+ the browser, and installs the fcloud skill for coding agents on your machine.
52
+ With your own Python:
53
+
46
54
  ```bash
47
55
  pip install fcloud-sdk
56
+ fcloud login
48
57
  ```
49
58
 
50
59
  ## Setup
51
60
 
52
- Create an account on https://fcloud-home.vercel.app.
53
- Enter a credit card, and copy your API key.
61
+ `fcloud login` opens https://fcloud-home.vercel.app to sign in (Google or
62
+ email) and add a card, then saves a key minted for this machine. Running any
63
+ `fcloud` command on a fresh install starts the same login.
54
64
 
55
65
  ```bash
56
- fcloud setup # prompts for the key; optionally installs the agent skill
66
+ fcloud login [--no-browser] [--agents auto|all|cursor|claude|codex|none]
67
+ fcloud set_token <key> # already have a key (CI, agents, a second machine)
68
+ fcloud setup # save a key you already have; install agent skills
57
69
  fcloud health # verify connectivity
58
70
  ```
59
71
 
60
- `fcloud setup` also installs (by default) the fcloud skill file for supported
61
- coding agents (Cursor, Claude Code, Codex) so an agent can drive fcloud for you.
62
- Use `--agents none` to skip that.
72
+ `fcloud login` installs the fcloud skill file for the coding agents it finds
73
+ (Cursor, Claude Code, Codex — `--agents all` for every one) so an agent can
74
+ drive fcloud for you. `fcloud setup --agents none` skips that.
63
75
 
64
76
  ## Quick start
65
77
 
@@ -139,6 +151,7 @@ Batch
139
151
  fcloud sweeps / fcloud sweep <status|logs|retry|cancel|wait> <name>
140
152
 
141
153
  Setup
154
+ fcloud login [--no-browser] [--agents auto|all|cursor|claude|codex|none]
142
155
  fcloud setup [--token KEY] [--agents all|cursor|claude|codex|none]
143
156
  fcloud set_token <api-key>
144
157
  fcloud --version
@@ -201,11 +214,13 @@ Other environment switches:
201
214
  | `FCLOUD_QUIET=1` | Suppress "still waiting" progress lines while a host is provisioned |
202
215
  | `FCLOUD_QUEUE_TIMEOUT=<seconds>` | How long to wait for capacity before giving up (default 1200) |
203
216
  | `FCLOUD_MIGRATE_RESTART=never` | Don't automatically re-run a command after a host rebuild (default `auto`) |
217
+ | `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
218
  | `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
219
  | `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
220
 
207
221
  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
222
+ volumes, checkpoint policy); `fcloud config` stores per-user defaults in
223
+ `~/.fcloud/config.json`. Note that fcloud will run the build steps it finds there, so treat a
209
224
  cloned repo's `fcloud.json` the way you would its Dockerfile.
210
225
 
211
226
  ## Agent skill
@@ -9,23 +9,35 @@ Requires Python 3.10+.
9
9
 
10
10
  ## Install
11
11
 
12
+ ```bash
13
+ curl -fsSL https://fcloud-home.vercel.app/install.sh | sh
14
+ ```
15
+
16
+ That installs the CLI from PyPI (via `uv` or `pipx`), signs you in through
17
+ the browser, and installs the fcloud skill for coding agents on your machine.
18
+ With your own Python:
19
+
12
20
  ```bash
13
21
  pip install fcloud-sdk
22
+ fcloud login
14
23
  ```
15
24
 
16
25
  ## Setup
17
26
 
18
- Create an account on https://fcloud-home.vercel.app.
19
- Enter a credit card, and copy your API key.
27
+ `fcloud login` opens https://fcloud-home.vercel.app to sign in (Google or
28
+ email) and add a card, then saves a key minted for this machine. Running any
29
+ `fcloud` command on a fresh install starts the same login.
20
30
 
21
31
  ```bash
22
- fcloud setup # prompts for the key; optionally installs the agent skill
32
+ fcloud login [--no-browser] [--agents auto|all|cursor|claude|codex|none]
33
+ fcloud set_token <key> # already have a key (CI, agents, a second machine)
34
+ fcloud setup # save a key you already have; install agent skills
23
35
  fcloud health # verify connectivity
24
36
  ```
25
37
 
26
- `fcloud setup` also installs (by default) the fcloud skill file for supported
27
- coding agents (Cursor, Claude Code, Codex) so an agent can drive fcloud for you.
28
- Use `--agents none` to skip that.
38
+ `fcloud login` installs the fcloud skill file for the coding agents it finds
39
+ (Cursor, Claude Code, Codex — `--agents all` for every one) so an agent can
40
+ drive fcloud for you. `fcloud setup --agents none` skips that.
29
41
 
30
42
  ## Quick start
31
43
 
@@ -105,6 +117,7 @@ Batch
105
117
  fcloud sweeps / fcloud sweep <status|logs|retry|cancel|wait> <name>
106
118
 
107
119
  Setup
120
+ fcloud login [--no-browser] [--agents auto|all|cursor|claude|codex|none]
108
121
  fcloud setup [--token KEY] [--agents all|cursor|claude|codex|none]
109
122
  fcloud set_token <api-key>
110
123
  fcloud --version
@@ -167,11 +180,13 @@ Other environment switches:
167
180
  | `FCLOUD_QUIET=1` | Suppress "still waiting" progress lines while a host is provisioned |
168
181
  | `FCLOUD_QUEUE_TIMEOUT=<seconds>` | How long to wait for capacity before giving up (default 1200) |
169
182
  | `FCLOUD_MIGRATE_RESTART=never` | Don't automatically re-run a command after a host rebuild (default `auto`) |
183
+ | `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
184
  | `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
185
  | `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
186
 
173
187
  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
188
+ volumes, checkpoint policy); `fcloud config` stores per-user defaults in
189
+ `~/.fcloud/config.json`. Note that fcloud will run the build steps it finds there, so treat a
175
190
  cloned repo's `fcloud.json` the way you would its Dockerfile.
176
191
 
177
192
  ## Agent skill
@@ -25,6 +25,9 @@ fcloud set_token <api_key> # one-time; saves to ~/.fcloud/token
25
25
  fcloud health # verify connectivity
26
26
  ```
27
27
 
28
+ (A human sets up with `fcloud login`, which signs in through the browser and
29
+ mints a key for the machine; agents use `set_token` with a key they were given.)
30
+
28
31
  Token resolution order:
29
32
 
30
33
  1. `api_key=` parameter to `Client()`
@@ -303,7 +306,8 @@ filesystem later; a **job** for batch/CI-style runs with declared outputs.
303
306
  | Command | Description |
304
307
  | ----------------------------- | ---------------------------------------------------- |
305
308
  | `fcloud create [--sku SKU]` | Create a session (cold — $0, no host, until first used) |
306
- | `fcloud sessions [--all]` | List sessions |
309
+ | `fcloud sessions [--all] [--limit N]` | List sessions with `$/HR` (billed rate), `SPENT` (compute so far) and `AGE` (time on its host) per row and a spending-now total (`--all --json` can be large; bound it with `--limit`) |
310
+ | `fcloud usage [--since 24h]` | What the account has been charged over a window (default 30 days), by resource and session |
307
311
  | `fcloud stop <session-id> [--wait]` | Stop a session now: halt GPU spend (files kept). `--wait` blocks until the workspace manifest is durable |
308
312
 
309
313
 
@@ -336,6 +340,39 @@ once**, keep one client attached per box for its whole run — e.g. N parallel
336
340
  creating N sessions and attending to them one at a time, which lets the idle
337
341
  ones lose their hosts.
338
342
 
343
+ ### Checkpoint/restore on preemption (and how to turn it off)
344
+
345
+ Sessions run on spot capacity. By default, when a host is reclaimed fcloud
346
+ **checkpoints** the live sandbox (GPU state included) and **restores** it on a
347
+ fresh box, so running processes continue. The restore is pinned to the region
348
+ the checkpoint lives in (cross-region transfer of a multi-GB image is not free),
349
+ so a restore can wait in `awaiting_restore_capacity` until that region has spot
350
+ capacity again.
351
+
352
+ If you would rather get **any** available host quickly and re-run your command
353
+ yourself, turn checkpoint/restore off for the session. A preempted
354
+ `--no-checkpoint` session is rebuilt cold on any host in any region:
355
+ `/workspace` is preserved (synced before the box dies), running processes are
356
+ lost, and the session carries `state_loss_reason=spot_preempt_checkpoint_disabled`
357
+ in `fcloud sessions --json`. `fcloud run`/`exec` re-run the command after a
358
+ rebuild (`FCLOUD_MIGRATE_RESTART`), so a resumable script (one that reloads its
359
+ own checkpoints from `/workspace` or a volume) is the natural pairing.
360
+
361
+ ```bash
362
+ fcloud run train.py --sku gpu_1x_h100 --no-checkpoint # this session only
363
+ fcloud create --sku gpu_1x_l4 --no-checkpoint # for the session's life
364
+ fcloud config set checkpoint off # my default, every project
365
+ fcloud config get checkpoint # -> off
366
+ fcloud config unset checkpoint # back to on
367
+ ```
368
+
369
+ Precedence, strongest first: `--checkpoint`/`--no-checkpoint` (or SDK
370
+ `checkpoint=True/False`) → `FCLOUD_CHECKPOINT=on|off` → `fcloud.json`
371
+ `"checkpoint": false` → `fcloud config set checkpoint` → server default (on).
372
+ The policy is fixed when the session is **created**; `--on <sid>` keeps the
373
+ session's existing policy. Jobs (`fcloud job`, `fcloud map`) never checkpoint.
374
+ `fcloud sessions` marks opted-out sessions `[no-checkpoint]`.
375
+
339
376
  ### File Transfer
340
377
 
341
378
  | Command | Description | Needs active session? |
@@ -470,9 +507,12 @@ detach or session close.
470
507
  Volumes are attached
471
508
  **after** the session is online, so a volume-mounted command queues and
472
509
  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.
510
+ already-running session (`--on <sid>`). Any number of sessions may attach the
511
+ same volume concurrently — commits merge per path, last writer wins per file
512
+ (so key parallel writers' outputs by distinct paths). Only `delete` and
513
+ `import` conflict while a volume is attached; browsing works any time. Append
514
+ `:ro` (`--volume data:/vol:ro`) to attach read-only: the session sees the
515
+ volume but its writes are discarded at close (no commit).
476
516
 
477
517
  Declare volumes once in `fcloud.json` to skip `--volume` on every command (see
478
518
  [Project Config](#project-config-fcloudjson)):
@@ -678,6 +718,18 @@ without it the cap is **8**, and submitting more tasks than that runs them
678
718
  in waves. Submit says so out loud, and the cap is changeable on a live
679
719
  sweep: `fcloud sweep set <name> --max-parallel N`.
680
720
 
721
+ **Scheduling (async).** `--not-before WHEN` parks the sweep until then;
722
+ `--by WHEN` sets a deadline: the platform runs tasks opportunistically on
723
+ already-idle hosts (no capacity is provisioned early) and switches to
724
+ normal launching at `deadline - --runtime - a provisioning pad`, so the
725
+ sweep finishes by the deadline without paying for eager scale-up.
726
+ `--runtime DUR` is the per-task runtime estimate (default 1h). WHEN is a
727
+ duration from now (`30m`, `8h`, `2d`) or an ISO time (`2026-09-01T02:00`,
728
+ local). A missed deadline never cancels the sweep — status shows
729
+ `deadline_missed` and the webhook fires `job.deadline_missed`. While
730
+ waiting, `fcloud sweep status` prints the window phase and when launches
731
+ begin.
732
+
681
733
  **Canary.** Task 0 runs first and gates the rest: a broken sweep costs one
682
734
  task, not N. `fcloud map` blocks until the canary passes, then detaches (Ctrl-C
683
735
  detaches the watch — it does NOT cancel). `--no-canary` / `--no-wait` opt out.
@@ -687,11 +739,53 @@ the rest — cheaper than `--no-canary`, which risks N bad tasks to save one.
687
739
  Failures don't consume the retry budget when caused by spot preemption — the
688
740
  task just requeues.
689
741
 
742
+ **Skip the line.** When tasks are blocked on capacity, `fcloud sweep boost
743
+ <name>` (or pressing `s` in `sweep status --watch`, offered only while a
744
+ line exists) moves the sweep to the fast claim lane: queued tasks skip
745
+ ahead of free-lane work, and freed machines alternate fast/free so nobody
746
+ is starved. SDK: `handle.boost()`. Unpriced for now.
747
+
690
748
  **Waiting for capacity is not failure.** A task with no host yet keeps its
691
749
  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.
750
+ `fcloud sweep status` prints `blocked on capacity: N task(s), oldest 6m12s`
751
+ followed by a `why:` line saying what the wait actually is — a line
752
+ (`3 session(s) ahead in the gpu_1x_l4 queue (1 fast-lane)`), the platform
753
+ (`a new machine is being provisioned`, `waiting for a busy machine to free
754
+ up`), or bad news worth acting on (`no capacity ... (stockout) — consider
755
+ another SKU`, `cloud quota exhausted`). Use it to decide: a line or a
756
+ provision resolves itself (or `boost` past the free lane); a stockout or
757
+ quota wall means pick another SKU or cancel. A `state: waiting` sweep is a
758
+ pipeline hold, and status names the upstream sweeps it waits on.
759
+
760
+ **Retry backoff.** A charged failure re-queues with a growing delay (45s ×
761
+ attempt, capped at 10m) so a deterministic crasher doesn't burn its budget
762
+ in seconds; preemption/lost-host requeues re-run immediately and never
763
+ consume the budget.
764
+
765
+ **Pipelines (`--after`).** `--after SWEEP[,SWEEP]` holds a sweep in
766
+ `waiting` until the named sweeps finish, then launches it — a durable
767
+ server-side barrier, so a whole generate → train → eval pipeline submits up
768
+ front and the client can disconnect:
769
+
770
+ ```bash
771
+ fcloud map --name gen --volume run42:/vol \
772
+ -- python3 gen.py --out /vol/data/shard-{i}.jsonl ::: {0..63}
773
+ fcloud map --name train --sku gpu_8x_h100 --volume run42:/vol --after gen \
774
+ -- python3 train.py --data /vol/data --ckpt /vol/ckpt
775
+ fcloud map --name eval --volume run42:/vol --after train \
776
+ -- python3 eval.py --ckpt /vol/ckpt --suite {} ::: mmlu,gsm8k,mbpp
777
+ ```
778
+
779
+ Data flows between stages through the shared volume: map tasks write
780
+ disjoint task-keyed paths, the next stage mounts the same volume and sees
781
+ the union (the gate waits for the upstream's volume commits to land before
782
+ releasing). An upstream failure fails the downstream sweep with
783
+ `upstream_failed:<name>` instead of running it on missing inputs; after
784
+ fixing and re-running the upstream, `fcloud sweep retry <name> --all`
785
+ re-arms the downstream. A `succeeded_partial` upstream counts as finished
786
+ unless `--after-strict`. Edges bind at submit, so upstreams must be
787
+ submitted first (which also makes cycles impossible). SDK:
788
+ `client.map(..., after=gen_handle)` (or a name, or a list; `after_strict=True`).
695
789
 
696
790
  **Tracking & recovery:**
697
791
  ```bash
@@ -699,15 +793,16 @@ fcloud sweeps # all sweeps: state + done/run/fail count
699
793
  fcloud sweep status <name> # counts + failures clustered by error, with exemplar task
700
794
  fcloud sweep status <name> --watch # same, repainted until terminal (--interval S)
701
795
  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)
796
+ 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
797
  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)
798
+ 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
799
  fcloud sweep cancel <name> [--remaining] # stop; --remaining keeps running tasks (partial success)
706
- fcloud sweep wait <name> # block until terminal; exit 0 on success
800
+ fcloud sweep wait <name> [--poll S] [--timeout S] # block until terminal; exit 0 = success, 124 = still running at timeout
707
801
  ```
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.
802
+ `--webhook URL` POSTs the status document on canary pass/fail and
803
+ completion — agents should submit with `--no-wait --json` and wake on the
804
+ webhook instead of polling. There is no account-default webhook: without
805
+ `--webhook`, nothing is POSTed.
711
806
 
712
807
  **The status JSON is a versioned public contract** (`schema_version: 1`),
713
808
  specified in `docs/sweep-status-schema.md`: `counts` is an open map keyed by
@@ -1086,6 +1181,7 @@ flags merge with (and re-map by name) the `volumes` defaults.
1086
1181
  | ------------ | ------------------------------------------ |
1087
1182
  | `sku` | Default hardware SKU for `exec` and `run` |
1088
1183
  | `volumes` | Volumes auto-attached to every `exec`/`run` session. Each entry is `"name"`, `"name:/mount"`, or `{"name": ..., "mount": ...}` |
1184
+ | `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
1185
  | `image.base` | Base image (default: `python:3.11-slim`) |
1090
1186
  | `image.apt` | Packages to `apt-get install` |
1091
1187
  | `image.pip` | Packages to `pip install` |
@@ -1123,6 +1219,8 @@ host (files restored from cloud storage) the next time you use it. You never run
1123
1219
  **Warmth**: `fcloud sessions` reports a session's state — `hot` (running, spending),
1124
1220
  `warm` (idle host held, spending), `stopping` (finishing teardown), `cold`
1125
1221
  (stopped, $0, resumes on use), or `preparing` (coming online).
1222
+ The `$/HR` column is the rate billed on the session's open usage interval while a
1223
+ host is held (0 when cold); `SPENT` is compute charged so far; the footer sums `$/HR`. `fcloud usage` reports what was actually charged.
1126
1224
 
1127
1225
  **Image**: Declarative container spec. Layers are cached by content hash. First run
1128
1226
  on a host triggers a build (30–120s); subsequent runs with the same image are instant.
@@ -1166,6 +1264,24 @@ CLI commands or explicitly via `client.project("name", image=...)`.
1166
1264
  remains pinned after a hard kill, recover with `fcloud stop <sid>` and then use the
1167
1265
  session again.
1168
1266
 
1267
+ ## Autonomous research loops (autoresearch)
1268
+
1269
+ To run an overnight keep/discard experiment loop on the user's code — after
1270
+ [karpathy/autoresearch](https://github.com/karpathy/autoresearch), with the GPU on
1271
+ fcloud instead of under the desk:
1272
+
1273
+ ```bash
1274
+ fcloud autoresearch init # writes autoresearch/SETUP.md + program.template.md
1275
+ ```
1276
+
1277
+ Then read `autoresearch/SETUP.md` and follow it. It walks you through deciding the
1278
+ metric, the one mutable file vs the frozen eval, and the fixed time budget with the
1279
+ user; you write `program.md`, run the baseline on a persistent session, and hand
1280
+ off to the loop, which only ever re-uploads the mutable file and greps the metric.
1281
+ Progress is visible on the session's page in the fcloud dashboard (Autoresearch
1282
+ tab: metric chart + results table) once the loop's `results.tsv` upload syncs;
1283
+ point the user there when the loop starts.
1284
+
1169
1285
  ## Anti-Patterns
1170
1286
 
1171
1287
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fcloud-sdk"
7
- version = "0.1.3"
7
+ version = "0.2.1"
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 = [