Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.org
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,9 @@ hand (an uncommitted tracked change there makes =updateInstead= refuse
every publish from the clone; the prepare step warns loudly if it
finds one). Ignored files (=.venv=, =node_modules=) are rebuilt each
job; committed work is durable instantly, dirty work to within
=wip_snapshot_minutes=.
=wip_snapshot_minutes=. The agent is told all of this in a
=WORKSPACE (local-disk tiering)= block of its prelude, including the
command that shows the last snapshot.

Earlier releases put the /whole/ mirror at =/local/mirrors= on one
node. That layout is retired: a session that still records it gets a
Expand Down
10 changes: 7 additions & 3 deletions docs/local-disk-tiering.org
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ Design note. Written on a =carleton-htc= slice (job 38661192,
n0036.savio4) against =main= at =80581c8=. Status: step (1) landed
(shared timer + snapshotter, PR #9); step (2) landed for confined
targets (PR #10) and for =salloc= targets, retiring the all-on-=/local=
layout (=sucoder/local_tier.py=, =slurm.local_disk= / =--local-disk=).
layout (=sucoder/local_tier.py=, =slurm.local_disk= / =--local-disk=);
step (3) landed: the prelude carries a =WORKSPACE (local-disk tiering)=
block whenever tiering is on (=MirrorManager._workspace_block=).

* Punchline

Expand Down Expand Up @@ -187,8 +189,10 @@ state the durability tiers:
intervened.
- Ignored :: never durable; rebuilt from scratch each job.

The prelude can print the last snapshot time so the agent knows the
loop is alive.
The prelude is rendered before launch, so it cannot print the last
snapshot time; it gives the one-line =git log= that answers it
instead, plus the clone and mirror paths, the mailbox rule, and the
warn-file path.

* Config and docs

Expand Down
8 changes: 5 additions & 3 deletions docs/persistent-presence.org
Original file line number Diff line number Diff line change
Expand Up @@ -466,9 +466,11 @@ and makes re-grab fast when turnover does happen. Recommended:
=time: "3-00:00:00"= with =max_idle= as the real courtesy guard.

** Handoff / rehydration
- Before turnover the agent writes a structured note to a fixed NFS
path, e.g. =~/mirrors/<mirror>/.sucoder/handoff.org=: current task,
branch, last commit SHA, open questions, next action.
- Before turnover the agent writes a structured note to
=.sucoder/handoff.org= in its working tree and commits it: current
task, branch, last commit SHA, open questions, next action. Under
local-disk tiering the commit is what carries it to the shared mirror
(writing into =~/mirrors/<mirror>= by hand would block every publish).
- On relaunch, =system_prompt_extra= instructs the agent to *first*
read the handoff + =git log -5= + =git status=, summarize the
resumed state back to the human, then continue. This turns a cold
Expand Down
65 changes: 65 additions & 0 deletions sucoder/mirror.py
Original file line number Diff line number Diff line change
Expand Up @@ -5254,6 +5254,13 @@ def _compose_context_prelude(self, ctx: MirrorContext) -> str:
if target_block:
blocks.append(target_block)

# Under local-disk tiering the agent's cwd is a node-local clone,
# not the mirror the prompts talk about; say so, with the facts
# it needs (where commits go, what survives, where to look).
workspace_block = self._workspace_block(ctx)
if workspace_block:
blocks.append(workspace_block)

agent_doc = self._agent_doc_block(ctx)
if agent_doc:
blocks.append(agent_doc)
Expand Down Expand Up @@ -5316,6 +5323,64 @@ def _target_prompt_block(self, ctx: MirrorContext) -> Optional[str]:
header = f"TARGET PROMPT ({self._collapse_home(prompt_path)})"
return f"{header}\n{content}"

def _workspace_block(self, ctx: MirrorContext) -> Optional[str]:
"""Describe the local-disk tiering layout to the agent, when it applies.

Rendered on the laptop before launch, so it cannot carry runtime
state (last snapshot time); it gives the command that answers that
instead. For a confined launch the job id is not known yet and the
path is spelled with ``$SUCODER_LOCAL_ROOT``, which the batch body
exports before starting tmux (``local_tier.cache_exports_sh``).
"""
if not ctx.is_remote:
return None
local_disk_root = getattr(self.executor, "local_disk_root", None) or None
if not local_disk_root:
return None
token = _sanitize_session_token(ctx.settings.name)
try:
mirror_path = self._resolve_remote_path(ctx)
except Exception as exc: # noqa: BLE001 - informational block, never fatal
self.logger.debug("Workspace block: could not resolve mirror path: %s", exc)
mirror_path = ctx.remote_mirror_path or "~/mirrors/<mirror>"
job_id = None if ctx.confined else getattr(self.executor, "slurm_job_id", None)
root = local_disk_root.rstrip("/") or "/"
if job_id:
work = work_path(local_disk_root, token, int(job_id))
local_root = f"{root}/job{job_id}"
else:
# Confined: the id is assigned by sbatch after this renders.
# $SUCODER_LOCAL_ROOT (= <root>/job<ID>) is exported by the
# batch body, so it expands in the agent's shell and tools.
work = f"$SUCODER_LOCAL_ROOT/mirrors/{token}"
local_root = f"{root}/job<ID>"
slurm = ctx.settings.remote.slurm if ctx.settings.remote else None
minutes = slurm.wip_snapshot_minutes if slurm else 10
cadence = (
f"every {minutes} minutes and at each deadline warning"
if minutes else "at each deadline warning only"
)
lines = [
"WORKSPACE (local-disk tiering)",
"You are working in a node-local clone, not in the shared mirror the prompts above describe.",
f"- Working clone (your cwd): {work}"
+ ("" if job_id else " ($SUCODER_LOCAL_ROOT is exported in your environment; `pwd` shows it resolved)"),
f"- Shared mirror (origin; durable; the human's push/pull target): {mirror_path}",
" Never edit its working tree by hand: an uncommitted tracked change there blocks every publish from this clone.",
"- Every commit is published to the shared mirror by a post-commit hook the moment it exists; the commit output",
" shows 'SUCODER: published'. A 'REJECTED' line means the human pushed first: `git pull --ff-only`, then commit",
" again. Never force-push to origin.",
f"- Uncommitted work (tracked or untracked, not ignored) is snapshotted to refs/sucoder/wip/{token} on the shared",
f" mirror {cadence}; the next launch restores it if no commit has landed since.",
f" Last snapshot: git -C {mirror_path} log -1 --format='%ci %s' refs/sucoder/wip/{token}",
f"- Ignored files (.venv, node_modules, caches) are never durable. Caches and $TMPDIR live under {local_root}",
" ($SUCODER_LOCAL_ROOT) and are rebuilt each job.",
f"- Deadline warnings: $HOME/.cache/sucoder/slurm-deadline-{token}.warn (30/15/5 minutes before the job's --time).",
"- Handoff notes go to .sucoder/handoff.org in this clone, committed.",
"- A job you dispatch to another node cannot see this clone: give it the shared mirror or a branch you have committed.",
]
return "\n".join(lines)

def _agent_doc_block(self, ctx: MirrorContext) -> Optional[str]:
"""Inject ``AGENT.md`` / ``AGENT.org`` for non-Claude agents.

Expand Down
98 changes: 98 additions & 0 deletions tests/test_mirror.py
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,104 @@ def fake_run_agent(args, **kwargs):
assert not calls[-1]["args"][-1].startswith("export ")


def _staged_prelude(calls):
"""The prelude text externalized over SSH stdin for a remote launch."""
for c in calls:
a = c["args"]
if a[0] == "sh" and "prelude-" in a[2] and c["kwargs"].get("input"):
return c["kwargs"]["input"]
raise AssertionError("no prelude was staged")


def test_prelude_workspace_block_unconfined_local_tier(tmp_path, monkeypatch):
"""With tiering on, the prelude tells the agent where it works, where
commits go, what survives, and how to check the last snapshot; the
unconfined path knows the job id, so paths are literal."""
manager, ctx = _remote_launch_manager(tmp_path, monkeypatch, local_disk_root="/local")
manager.config.system_prompt = tmp_path / "sys.org"
manager.config.system_prompt.write_text("SYS\n")
calls = []

def fake_run_agent(args, **kwargs):
calls.append({"args": list(args), "kwargs": kwargs})
return CommandResult(requested_args=list(args), executed_args=list(args),
stdout="", stderr="", returncode=0)

monkeypatch.setattr(manager.executor, "run_agent", fake_run_agent)
manager.launch_agent(ctx, sync=False)

prelude = _staged_prelude(calls)
assert "WORKSPACE (local-disk tiering)" in prelude
assert "Working clone (your cwd): /local/job1234567/mirrors/sample" in prelude
assert "Shared mirror (origin; durable; the human's push/pull target): /global/home/users/coder/mirrors/sample" in prelude
assert "refs/sucoder/wip/sample" in prelude
assert "git -C /global/home/users/coder/mirrors/sample log -1 --format='%ci %s' refs/sucoder/wip/sample" in prelude
assert "slurm-deadline-sample.warn" in prelude
assert "every 10 minutes and at each deadline warning" in prelude
assert ".sucoder/handoff.org in this clone" in prelude
# Ordering: after the system prompt, before the skill catalog.
assert prelude.index("SYSTEM PROMPT") < prelude.index("WORKSPACE (local-disk tiering)")


def test_prelude_workspace_block_absent_without_local_tier(tmp_path, monkeypatch):
manager, ctx = _remote_launch_manager(tmp_path, monkeypatch, local_disk_root=None)
manager.config.system_prompt = tmp_path / "sys.org"
manager.config.system_prompt.write_text("SYS\n")
calls = []

def fake_run_agent(args, **kwargs):
calls.append({"args": list(args), "kwargs": kwargs})
return CommandResult(requested_args=list(args), executed_args=list(args),
stdout="", stderr="", returncode=0)

monkeypatch.setattr(manager.executor, "run_agent", fake_run_agent)
manager.launch_agent(ctx, sync=False)
assert "WORKSPACE (local-disk tiering)" not in _staged_prelude(calls)


def test_prelude_workspace_block_confined_uses_runtime_job_id(tmp_path, monkeypatch):
"""A confined launch renders the prelude before sbatch assigns the job
id, so the clone path is spelled with $SLURM_JOB_ID."""
manager, ctx = _confined_manager(tmp_path, monkeypatch)
manager.executor.local_disk_root = "/local"
ctx.settings.remote.slurm.wip_snapshot_minutes = 0
calls = []
manager.executor.run_agent = _confined_responder(calls, sbatch_out="9")

manager._launch_confined(
ctx, ["claude"], remote_prelude_text=manager._compose_context_prelude(ctx),
prelude_sentinel="__X__", env=None, detached=True,
)
prelude = next(c["input"] for c in calls if c["args"][0] == "sh" and "prelude-" in c["args"][2])
assert "Working clone (your cwd): $SUCODER_LOCAL_ROOT/mirrors/sample" in prelude
assert "$SUCODER_LOCAL_ROOT is exported in your environment" in prelude
assert "at each deadline warning only" in prelude


def test_launch_agent_confined_local_tier_prelude_reaches_batch(tmp_path, monkeypatch):
"""End to end through launch_agent: a confined target with tiering gets
the WORKSPACE block (confined spelling) in the prelude that the batch
job's agent reads, even though the executor has no job id yet."""
manager, ctx = _confined_manager(tmp_path, monkeypatch)
manager.executor.local_disk_root = "/local"
manager.executor.slurm_job_id = None
manager.config.system_prompt = tmp_path / "sys.org"
manager.config.system_prompt.write_text("SYS\n")
monkeypatch.setattr(
MirrorManager, "_default_skills_catalog_path", lambda self: None,
)
calls = []
manager.executor.run_agent = _confined_responder(calls, sbatch_out="9")

manager.launch_agent(ctx, sync=False, detached=True)

assert ctx.confined is True
prelude = next(c["input"] for c in calls if c["args"][0] == "sh" and "prelude-" in c["args"][2])
assert "WORKSPACE (local-disk tiering)" in prelude
assert "Working clone (your cwd): $SUCODER_LOCAL_ROOT/mirrors/sample" in prelude
assert "/local/job" not in prelude.split("WORKSPACE (local-disk tiering)")[1].split("Shared mirror")[0]


def test_build_remote_agent_cmd_str_joins_and_appends_exec_bash(tmp_path: Path) -> None:
"""The extracted helper joins the command and appends ``; exec bash -l``.

Expand Down
Loading