feat(orchestrator): mount the host's agents-council state read-only as /kept
The W4 resources view reads gates.db and the resources/ files of the host sampler. The installer creates ~/.local/state/agents-council/resources and writes KVB_ORCHESTRATOR_KEPT into .env; compose binds it read-only at /kept without creating the host path, and the server passes --kept to the hub. 🎯 Fix: 90 % 💥 Break: 3 % 💸 Cost: 100 % ⏱️ Time: 100 % 🛡️ Security: 100 % 🏆 Score: 87 🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 % Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1bcc8c135f
commit
64c63efc26
6 files changed
+22
-8
No files matched your search
@@ -49,6 +49,8 @@ services:
|
||||
- /tree/branch-tree.json
|
||||
- --timers
|
||||
- /timers
|
||||
- --kept
|
||||
- /kept
|
||||
- --vendor
|
||||
- /app/vendor
|
||||
- --cleanup
|
||||
@@ -117,6 +119,12 @@ services:
|
||||
read_only: true
|
||||
bind:
|
||||
create_host_path: false
|
||||
- type: bind
|
||||
source: "${KVB_ORCHESTRATOR_KEPT:?written into .env by scripts/install.sh}"
|
||||
target: /kept
|
||||
read_only: true
|
||||
bind:
|
||||
create_host_path: false
|
||||
- type: bind
|
||||
source: "${KVB_ORCHESTRATOR_APPROVERS:?written into .env by scripts/install.sh}"
|
||||
target: /approvers
|
||||
|
||||
@@ -49,6 +49,7 @@ class Places:
|
||||
vendor: Path
|
||||
cleanup: Path
|
||||
offices: Path
|
||||
kept: Path
|
||||
|
||||
|
||||
def environment(templates: Path) -> jinja2.Environment:
|
||||
@@ -89,6 +90,7 @@ class Hub(Mcp, Reviewing, Asking, Messaging, Choosing, Approving, Denying, Reque
|
||||
agent_sessions: Path
|
||||
model: Model
|
||||
timers: Path
|
||||
kept: Path
|
||||
settings: Settings
|
||||
redirects: Redirects
|
||||
speech: Speech
|
||||
@@ -137,6 +139,7 @@ def open_hub(council: Path, pages: Path, public_port: int, state: Path, host_sta
|
||||
agent_sessions=state / SNAPSHOT,
|
||||
model=model,
|
||||
timers=places.timers,
|
||||
kept=places.kept,
|
||||
council=council,
|
||||
pages=pages,
|
||||
public_port=public_port,
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
--tree-file: the JSON file `pkgmgr branch tree --output` writes, mounted
|
||||
read-only; read again whenever it changes.
|
||||
--timers: directory holding the systemd user units (*.timer).
|
||||
--kept: ~/.local/state/agents-council, mounted read-only: gates.db and
|
||||
the resources/ files of the host sampler.
|
||||
--vendor: directory holding the vendored Bootstrap Icons sprite,
|
||||
3d-force-graph and three with their licence texts.
|
||||
--cleanup: host-mounted directory the hub writes signed branch deletions
|
||||
@@ -78,7 +80,7 @@ def main() -> int:
|
||||
parser.add_argument("--bind", required=True)
|
||||
for name in ("--listen-port", "--public-port"):
|
||||
parser.add_argument(name, type=int, required=True)
|
||||
paths = ("--council", "--pages", "--state", "--host-state", "--skills", "--transcripts", "--templates", "--tree-file", "--timers", "--vendor", "--cleanup", "--offices", "--llm-key-file", "--approvers")
|
||||
paths = ("--council", "--pages", "--state", "--host-state", "--skills", "--transcripts", "--templates", "--tree-file", "--timers", "--kept", "--vendor", "--cleanup", "--offices", "--llm-key-file", "--approvers")
|
||||
for name in (*paths, "--piper", "--voices", "--whisper", "--whisper-model", "--scratch"):
|
||||
parser.add_argument(name, type=Path, required=True)
|
||||
for name in ("--llm-url", "--llm-model", "--operator-voice"):
|
||||
@@ -94,7 +96,7 @@ def main() -> int:
|
||||
transcripts=args.transcripts,
|
||||
scripts=Scripts(skills=args.skills, council=args.council, home=args.state),
|
||||
templates=args.templates,
|
||||
places=Places(tree_file=args.tree_file, timers=args.timers, vendor=args.vendor, cleanup=args.cleanup, offices=args.offices),
|
||||
places=Places(tree_file=args.tree_file, timers=args.timers, vendor=args.vendor, cleanup=args.cleanup, offices=args.offices, kept=args.kept),
|
||||
model=Model(url=args.llm_url.strip(), model=args.llm_model.strip(), key=read_key(args.llm_key_file)),
|
||||
engines=Engines(piper=args.piper, voices=args.voices, operator_voice=args.operator_voice, whisper=args.whisper, whisper_model=args.whisper_model, scratch=args.scratch),
|
||||
approvers=args.approvers,
|
||||
|
||||
@@ -150,7 +150,7 @@ install_hub() {
|
||||
port="$(port_of "${REPO_ROOT}/orchestrator/.env")"
|
||||
log "skills: starting the Agent Orchestrator on http://localhost:${port}/"
|
||||
rm -rf "${HUB}"
|
||||
mkdir -p "${HUB}" "${STATE}" "${APPROVALS}" "${COUNCIL}" "${HOME}/.claude/projects" "${TREE_DIR}" "${UNITS}" "${APPROVERS}"
|
||||
mkdir -p "${HUB}" "${STATE}" "${APPROVALS}" "${COUNCIL}" "${HOME}/.claude/projects" "${TREE_DIR}" "${UNITS}" "${APPROVERS}" "${HOME}/.local/state/agents-council/resources"
|
||||
chmod 700 "${APPROVERS}"
|
||||
cp -a "${REPO_ROOT}/orchestrator/." "${HUB}/"
|
||||
# Python loads a cached .pyc whose header matches the source mtime and size, and cp -a keeps the mtime: one planted in the sandbox-writable tree would run on the host
|
||||
@@ -158,8 +158,8 @@ install_hub() {
|
||||
chmod +x "${HUB}/client/permission_hook.py" "${HUB}/client/runner.py" "${HUB}/client/approval.py" "${HUB}/client/cleanup.py" "${HUB}/client/offices.py" "${HUB}/client/sessions.py"
|
||||
log "skills: the PermissionRequest hook client is ${HUB}/client/permission_hook.py"
|
||||
log "skills: approvals need a pinned passkey: settings, 'Passkey registrieren', then 'make orchestrator-pin CREDENTIAL=<blob>'; until then every executor refuses"
|
||||
printf 'KVB_ORCHESTRATOR_USER=%s:%s\nKVB_ORCHESTRATOR_STATE=%s\nKVB_ORCHESTRATOR_COUNCIL=%s\nKVB_ORCHESTRATOR_SKILLS=%s\nKVB_ORCHESTRATOR_PROJECTS=%s\nKVB_ORCHESTRATOR_TREE=%s\nKVB_ORCHESTRATOR_TIMERS=%s\nKVB_ORCHESTRATOR_APPROVERS=%s\n' \
|
||||
"$(id -u)" "$(id -g)" "${STATE}" "${COUNCIL}" "${HOME}/.claude/skills" "${HOME}/.claude/projects" "${TREE_DIR}" "${UNITS}" "${APPROVERS}" >>"${HUB}/.env"
|
||||
printf 'KVB_ORCHESTRATOR_USER=%s:%s\nKVB_ORCHESTRATOR_STATE=%s\nKVB_ORCHESTRATOR_COUNCIL=%s\nKVB_ORCHESTRATOR_SKILLS=%s\nKVB_ORCHESTRATOR_PROJECTS=%s\nKVB_ORCHESTRATOR_TREE=%s\nKVB_ORCHESTRATOR_TIMERS=%s\nKVB_ORCHESTRATOR_APPROVERS=%s\nKVB_ORCHESTRATOR_KEPT=%s\n' \
|
||||
"$(id -u)" "$(id -g)" "${STATE}" "${COUNCIL}" "${HOME}/.claude/skills" "${HOME}/.claude/projects" "${TREE_DIR}" "${UNITS}" "${APPROVERS}" "${HOME}/.local/state/agents-council" >>"${HUB}/.env"
|
||||
if ! docker compose --file "${HUB}/compose.yml" up -d --build; then
|
||||
warn "skills: docker compose failed; the Agent Orchestrator stays down until 'make orchestrator-install' succeeds."
|
||||
fi
|
||||
|
||||
+3
-2
@@ -47,11 +47,12 @@ class HubCase(unittest.TestCase):
|
||||
self.projects = base / "projects"
|
||||
self.tree_file = base / "tree" / "branch-tree.json"
|
||||
self.timers = base / "timers"
|
||||
self.kept = base / "kept"
|
||||
self.cleanup = base / "state" / "cleanup"
|
||||
self.offices = base / "state" / "offices"
|
||||
self.approvers = base / "config" / "approvers.json"
|
||||
self.replay = base / "replay"
|
||||
for path in (self.pages, self.state, self.outside, self.projects, self.tree_file.parent, self.timers):
|
||||
for path in (self.pages, self.state, self.outside, self.projects, self.tree_file.parent, self.timers, self.kept):
|
||||
path.mkdir()
|
||||
silence = mock.patch.object(server.Handler, "log_message")
|
||||
silence.start()
|
||||
@@ -70,7 +71,7 @@ class HubCase(unittest.TestCase):
|
||||
transcripts=self.projects,
|
||||
scripts=self.scripts,
|
||||
templates=HUB / "templates",
|
||||
places=server.Places(tree_file=self.tree_file, timers=self.timers, vendor=HUB / "vendor", cleanup=self.cleanup, offices=self.offices),
|
||||
places=server.Places(tree_file=self.tree_file, timers=self.timers, vendor=HUB / "vendor", cleanup=self.cleanup, offices=self.offices, kept=self.kept),
|
||||
model=server.Model(url="", model="", key=""),
|
||||
engines=self.engines,
|
||||
approvers=self.approvers,
|
||||
|
||||
@@ -175,7 +175,7 @@ class TestHubSpeech(HubCase):
|
||||
args = {
|
||||
"--bind": "127.0.0.1", "--listen-port": "0", "--public-port": "1", "--council": str(self.council), "--pages": str(self.pages), "--state": str(self.state),
|
||||
"--host-state": str(HOST_STATE), "--skills": str(self.outside), "--transcripts": str(self.projects), "--templates": str(HUB / "templates"),
|
||||
"--tree-file": str(self.tree_file), "--timers": str(self.timers), "--vendor": str(HUB / "vendor"), "--cleanup": str(self.cleanup), "--offices": str(self.offices),
|
||||
"--tree-file": str(self.tree_file), "--timers": str(self.timers), "--kept": str(self.kept), "--vendor": str(HUB / "vendor"), "--cleanup": str(self.cleanup), "--offices": str(self.offices),
|
||||
"--llm-key-file": os.devnull, "--llm-url": "", "--llm-model": "",
|
||||
"--piper": str(self.engines.piper), "--voices": str(self.engines.voices), "--operator-voice": "en_US-test-high:9", "--whisper": str(self.engines.whisper),
|
||||
"--whisper-model": str(self.engines.whisper_model), "--scratch": str(self.engines.scratch), "--approvers": str(self.approvers),
|
||||
|
||||
Reference in new issue
Block a user