drop the config and /applied mounts: an agent reads its config by cloning agent-configs, and the prompt says so
This commit is contained in:
parent
a781addcfe
commit
3c09e14a76
1 changed files with 103 additions and 288 deletions
|
|
@ -325,46 +325,28 @@ fn validate_bind_path(path: &str) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// `--tmpfs=<mount>/.git` for every bound git repo, hiding its metadata
|
||||
/// from inside the container.
|
||||
/// `--tmpfs=/knowledge/.git`, hiding the shared docs repo's metadata from
|
||||
/// inside the container.
|
||||
///
|
||||
/// Two kinds of mount qualify, for one reason: **the agent is given a
|
||||
/// working tree, never a repository.** `/knowledge` is the hive's shared
|
||||
/// docs — whose `.git/config` has held a credential the host-side worker
|
||||
/// embedded — and `/agents/<name>/config` is a config repo, an agent's own
|
||||
/// or a parent's read-only view of a child's. In both cases `.git` carries
|
||||
/// every branch and the full history of a document whose *currently
|
||||
/// deployed* value is the only thing a reader may act on, and an abandoned
|
||||
/// branch is indistinguishable from a live one.
|
||||
/// **The agent is given a working tree, never a repository.** `/knowledge` is
|
||||
/// the hive's shared docs, whose `.git/config` has held a credential the
|
||||
/// host-side worker embedded, and whose `.git` carries every branch and the
|
||||
/// full history of a document whose *currently deployed* value is the only
|
||||
/// thing a reader may act on: an abandoned branch is indistinguishable from a
|
||||
/// live one.
|
||||
///
|
||||
/// An overlay rather than an exported copy: there is no second tree to
|
||||
/// keep in sync, so nothing can go stale, and no code path has to remember
|
||||
/// to refresh it.
|
||||
/// An overlay rather than an exported copy: there is no second tree to keep in
|
||||
/// sync, so nothing can go stale, and no code path has to remember to refresh
|
||||
/// it. It is applied unconditionally: the directory is created empty and cloned
|
||||
/// into later, so a container started before the clone must already hide the
|
||||
/// `.git` that arrives.
|
||||
///
|
||||
/// ⚠️ Ordering matters — these must be appended **after** the `--bind`
|
||||
/// flags so nspawn mounts them on top of the already-mounted trees.
|
||||
/// Config mounts are matched by shape, not by a name list: the set is
|
||||
/// dynamic, growing with each child bound into a parent.
|
||||
///
|
||||
/// `/knowledge` is overlaid unconditionally: its directory is created empty
|
||||
/// and cloned into later, so a container started before the clone must
|
||||
/// already hide the `.git` that arrives. A config mount gets the overlay only
|
||||
/// when its host source has a `.git` entry: nspawn must create the mount
|
||||
/// point inside the read-only bind, which fails (`EROFS`) where there is
|
||||
/// nothing to mount over, as in an archetype agent's empty config directory.
|
||||
/// The entry is looked up without following a symlink, and a symlink named
|
||||
/// `.git` counts as present, so a path that leads somewhere else is still
|
||||
/// hidden.
|
||||
/// ⚠️ Ordering matters: these must be appended **after** the `--bind` flags so
|
||||
/// nspawn mounts them on top of the already-mounted trees.
|
||||
fn git_overlay_flags(binds: &[BindMount]) -> Vec<String> {
|
||||
binds
|
||||
.iter()
|
||||
.filter(|b| {
|
||||
let p = b.container_path.as_str();
|
||||
p == "/knowledge"
|
||||
|| (p.starts_with("/agents/")
|
||||
&& p.ends_with("/config")
|
||||
&& std::fs::symlink_metadata(Path::new(&b.host_path).join(".git")).is_ok())
|
||||
})
|
||||
.filter(|b| b.container_path == "/knowledge")
|
||||
.map(|b| format!("--tmpfs={}/.git", b.container_path))
|
||||
.collect()
|
||||
}
|
||||
|
|
@ -670,76 +652,33 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// Every bound git repo gets its `.git` overlaid — the knowledge tree
|
||||
/// and *each* config mount, an agent's own plus every child's.
|
||||
///
|
||||
/// The child case is the one worth pinning: that set grows at runtime
|
||||
/// as agents gain children, so a rule written as a list of names would
|
||||
/// silently stop covering new ones.
|
||||
/// The knowledge tree gets its `.git` overlaid, whatever its source holds:
|
||||
/// it is cloned into after the container may have started, so the overlay
|
||||
/// does not wait for a `.git` to exist.
|
||||
#[test]
|
||||
fn every_bound_git_repo_gets_its_dot_git_hidden() {
|
||||
let src = repo_source();
|
||||
let flags = git_overlay_flags(&[
|
||||
bind_from(&src, "/knowledge"),
|
||||
bind_from(&src, "/agents/atlas/config"),
|
||||
bind_from(&src, "/agents/kiddo/config"),
|
||||
]);
|
||||
assert_eq!(
|
||||
flags,
|
||||
[
|
||||
"--tmpfs=/knowledge/.git",
|
||||
"--tmpfs=/agents/atlas/config/.git",
|
||||
"--tmpfs=/agents/kiddo/config/.git",
|
||||
]
|
||||
);
|
||||
fn the_knowledge_tree_gets_its_dot_git_hidden() {
|
||||
let flags = git_overlay_flags(&[bind_from(&repo_source(), "/knowledge")]);
|
||||
assert_eq!(flags, ["--tmpfs=/knowledge/.git"]);
|
||||
let empty = scratch().to_string_lossy().into_owned();
|
||||
let flags = git_overlay_flags(&[bind_from(&empty, "/knowledge")]);
|
||||
assert_eq!(flags, ["--tmpfs=/knowledge/.git"]);
|
||||
}
|
||||
|
||||
/// ...and nothing else does. A blanket "overlay .git on every bind"
|
||||
/// would mask a real `.git` under `state/`, where an agent legitimately
|
||||
/// keeps working clones of its own.
|
||||
#[test]
|
||||
fn non_repo_mounts_are_left_alone() {
|
||||
fn other_mounts_are_left_alone() {
|
||||
let src = repo_source();
|
||||
let flags = git_overlay_flags(&[
|
||||
bind_from(&src, "/agents/atlas/state"),
|
||||
bind_from(&src, "/work"),
|
||||
bind_from(&src, "/applied"),
|
||||
bind_from(&src, "/agents/atlas/config-notes"),
|
||||
bind_from(&src, "/agents/atlas/config"),
|
||||
]);
|
||||
assert!(flags.is_empty(), "overlaid a non-repo mount: {flags:?}");
|
||||
}
|
||||
|
||||
/// A config mount whose source has no `.git` (an archetype agent's empty
|
||||
/// directory) gets no overlay: nspawn cannot create the mount point in a
|
||||
/// read-only bind. The sibling with a `.git` is the presence control.
|
||||
#[test]
|
||||
fn a_source_without_dot_git_gets_no_overlay() {
|
||||
let empty = scratch().to_string_lossy().into_owned();
|
||||
let flags = git_overlay_flags(&[
|
||||
bind_from(&empty, "/agents/updo/config"),
|
||||
bind_from(&repo_source(), "/agents/atlas/config"),
|
||||
]);
|
||||
assert_eq!(flags, ["--tmpfs=/agents/atlas/config/.git"]);
|
||||
}
|
||||
|
||||
/// `/knowledge` is cloned into after the container may have started, so
|
||||
/// its overlay does not wait for a `.git` to exist.
|
||||
#[test]
|
||||
fn an_empty_knowledge_source_still_gets_the_overlay() {
|
||||
let empty = scratch().to_string_lossy().into_owned();
|
||||
let flags = git_overlay_flags(&[bind_from(&empty, "/knowledge")]);
|
||||
assert_eq!(flags, ["--tmpfs=/knowledge/.git"]);
|
||||
}
|
||||
|
||||
/// A symlink named `.git` is not followed: it counts as present, even
|
||||
/// dangling, so the path it leads to is hidden rather than skipped.
|
||||
#[test]
|
||||
fn a_symlinked_dot_git_is_not_followed() {
|
||||
let dir = scratch();
|
||||
std::os::unix::fs::symlink("/nonexistent-target", dir.join(".git")).unwrap();
|
||||
let src = dir.to_string_lossy().into_owned();
|
||||
let flags = git_overlay_flags(&[bind_from(&src, "/agents/atlas/config")]);
|
||||
assert_eq!(flags, ["--tmpfs=/agents/atlas/config/.git"]);
|
||||
assert!(
|
||||
flags.is_empty(),
|
||||
"overlaid a non-knowledge mount: {flags:?}"
|
||||
);
|
||||
}
|
||||
|
||||
/// The whole rendered body, pinned literally — including the values,
|
||||
|
|
|
|||
Loading…
Reference in a new issue