atagen

roundabout

public
Star0
Watch0

Commit e9a5f5a41749

tarball

atagen <boss@atagen.co>2026-10-25 11:32 UTC

6 files changed+34-15
artifacts: let gc stay inside one project

The over-cap nudge pointed every agent at gc --cap, whose idle pass reaps all projects, so an agent with one fat target got told to delete everyone else's builds. gc --project scopes both passes to the current root, and the nudge only suggests the global form when the global cap is the one breached. gc --cap also re-measures first, since trimming to a cap off a stale size deletes more than it needs to.
Modified file: README.md+1 additions−1 deletions
@@ -17,7 +17,7 @@ rabt expect --size l # building soon, save me a seat
1717 rabt watch 7 # exits when lease 7 is done or overdue
1818 rabt kill 7
1919 rabt extend 7 10m
20-rabt artifacts check | list | gc [--cap] | pin <path>
20+rabt artifacts check | list | gc [--cap] [--project] | pin <path>
2121 ```
2222
2323 `run` exits with the build's code, 124 if `--deadline` killed it, 75 if `--max-wait` ran out.
Modified file: plugin/skills/roundabout/SKILL.md+1 additions−1 deletions
@@ -66,7 +66,7 @@ you'll get told when a build is well past its time estimate. decide, don't ignor
6666
6767 ## artifacts
6868
69-build output is capped per project and in total. `rabt artifacts check` compares, `list` shows the offenders, `gc` clears idle stuff, `gc --cap` keeps going until under. `pin <path>` protects something you need.
69+build output is capped per project and in total. `rabt artifacts check` compares, `list` shows the offenders, `gc` clears idle stuff, `gc --cap` keeps going until under, `--project` leaves everyone else's stuff alone. `pin <path>` protects something you need.
7070
7171 if told you're over, clean up before you finish. scratch dirs full of `target/` and `result` links count too.
7272
Modified file: src/artifacts.rs+17 additions−3 deletions
@@ -176,6 +176,14 @@ impl Report {
176176 self.over_project || self.over_global
177177 }
178178
179+ pub const fn remedy(&self) -> &'static str {
180+ if self.over_global {
181+ "rabt artifacts gc --cap"
182+ } else {
183+ "rabt artifacts gc --project --cap"
184+ }
185+ }
186+
179187 pub fn describe(&self, cfg: &Config) -> String {
180188 let mut out = Vec::new();
181189 if let Some((root, size)) = &self.project
@@ -267,11 +275,17 @@ pub struct Gc {
267275 pub failed: Vec<String>,
268276 }
269277
270-pub fn gc(cfg: &Config, to_cap: bool, dry_run: bool) -> Gc {
271- let entries = refresh(cfg, None, cfg.artifacts.size_ttl.0.as_secs());
278+pub fn gc(cfg: &Config, project: Option<&Path>, to_cap: bool, dry_run: bool) -> Gc {
279+ let max_age = if to_cap {
280+ RECHECK_SECS
281+ } else {
282+ cfg.artifacts.size_ttl.0.as_secs()
283+ };
284+ let entries = refresh(cfg, project, max_age);
272285 let active = active_roots();
273286 let idle_after = cfg.artifacts.idle_after.0.as_secs();
274- let movable = |e: &Entry| !e.pinned && !active.contains(&e.root);
287+ let movable =
288+ |e: &Entry| !e.pinned && !active.contains(&e.root) && project.is_none_or(|r| e.root == r);
275289
276290 let mut doomed: Vec<PathBuf> = entries
277291 .iter()
Modified file: src/cli.rs+10 additions−3 deletions
@@ -103,6 +103,9 @@ pub enum Artifacts {
103103 /// keep going, oldest first, until under the caps
104104 #[pound(long)]
105105 cap: bool,
106+ /// leave other projects alone
107+ #[pound(long)]
108+ project: bool,
106109 #[pound(short = 'n', long)]
107110 dry_run: bool,
108111 },
@@ -130,7 +133,7 @@ fn artifacts(cfg: &Config, action: Artifacts) -> i32 {
130133 }
131134 println!("everything: {} of {}", r.global, cfg.artifacts.global_cap);
132135 if r.over() {
133- println!("over. `rabt artifacts gc --cap`");
136+ println!("over. `{}`", r.remedy());
134137 1
135138 } else {
136139 0
@@ -150,8 +153,12 @@ fn artifacts(cfg: &Config, action: Artifacts) -> i32 {
150153 }
151154 0
152155 }
153- Artifacts::Gc { cap, dry_run } => {
154- let g = artifacts::gc(cfg, cap, dry_run);
156+ Artifacts::Gc {
157+ cap,
158+ project,
159+ dry_run,
160+ } => {
161+ let g = artifacts::gc(cfg, project.then_some(root.as_path()), cap, dry_run);
155162 for p in &g.removed {
156163 println!(
157164 "{}{}",
Modified file: src/hook.rs+3 additions−3 deletions
@@ -336,9 +336,9 @@ fn stop(cfg: &Config, event: &str, input: &Input) {
336336 return;
337337 }
338338 let msg = format!(
339- "{}. run `rabt artifacts gc --cap`, or `rabt artifacts list` and clear out what you \
340- don't need",
341- report.describe(cfg)
339+ "{}. run `{}`, or `rabt artifacts list` and clear out what you don't need",
340+ report.describe(cfg),
341+ report.remedy()
342342 );
343343 let out = match cfg.artifacts.stop {
344344 StopPolicy::Block => json!({ "decision": "block", "reason": msg }),
Modified file: src/run.rs+2 additions−4 deletions
@@ -228,12 +228,10 @@ pub fn run(cfg: &Config, a: &Args) -> i32 {
228228 if report.over() {
229229 let msg = report.describe(cfg);
230230 if cfg.artifacts.over_cap == OverCap::Block {
231- say(&format!("{msg}. clean up first, `rabt artifacts gc --cap`"));
231+ say(&format!("{msg}. clean up first, `{}`", report.remedy()));
232232 return EXIT_GAVE_UP;
233233 }
234- say(&format!(
235- "{msg}. `rabt artifacts gc --cap` when you get a sec"
236- ));
234+ say(&format!("{msg}. `{}` when you get a sec", report.remedy()));
237235 }
238236
239237 let req = Acquire {