From 751053b08310c0885575f0bfe8fb9cbabf4fecde Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:26:01 -0400 Subject: [PATCH 01/21] Add TurtleTerm agent reliability status CLI --- assets/sourceos/bin/turtle-agent-status | 207 ++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 assets/sourceos/bin/turtle-agent-status diff --git a/assets/sourceos/bin/turtle-agent-status b/assets/sourceos/bin/turtle-agent-status new file mode 100644 index 00000000000..817e16bd2ef --- /dev/null +++ b/assets/sourceos/bin/turtle-agent-status @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +"""TurtleTerm local SourceOS Agent Reliability status view. + +Reads local SourceOS evidence artifacts and summarizes: +- blocking guardrail decisions; +- stop-gate outcomes; +- guarded invocation outcomes; +- pending governance queue items. + +This tool is read-only. It does not modify policy, memory, git state, or queue +artifacts. +""" + +from __future__ import annotations + +import argparse +import json +from collections import Counter +from pathlib import Path +from typing import Any + +BLOCKING_DECISIONS = {"deny", "quarantine", "defer", "escalate"} +NEEDS_REVIEW_QUEUE_STATUSES = {"pending"} + + +def load_json(path: Path) -> dict[str, Any] | None: + try: + data = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + return None + return data if isinstance(data, dict) else None + + +def load_jsonl(path: Path) -> list[dict[str, Any]]: + if not path.exists(): + return [] + rows: list[dict[str, Any]] = [] + try: + for line in path.read_text(encoding="utf-8").splitlines(): + if not line.strip(): + continue + try: + data = json.loads(line) + except json.JSONDecodeError: + rows.append({"schema": "invalid-json", "decision": "invalid", "decisionId": f"{path}:invalid-json"}) + continue + if isinstance(data, dict): + rows.append(data) + except OSError: + return [] + return rows + + +def unique_paths(paths: list[Path]) -> list[Path]: + seen: set[str] = set() + out: list[Path] = [] + for path in paths: + key = str(path.resolve()) + if key not in seen: + seen.add(key) + out.append(path) + return out + + +def discover_json_artifacts(root: Path, filenames: set[str]) -> list[dict[str, Any]]: + artifacts: list[dict[str, Any]] = [] + paths: list[Path] = [] + sourceos = root / ".sourceos" + if sourceos.exists(): + for name in filenames: + paths.extend(sourceos.rglob(name)) + for path in unique_paths(paths): + data = load_json(path) + if data is not None: + data["_path"] = str(path) + artifacts.append(data) + return artifacts + + +def discover_governance_queues(root: Path) -> list[dict[str, Any]]: + queues: list[dict[str, Any]] = [] + candidates: list[Path] = [] + for base in [root / ".sourceos", root / "standards" / "agent-reliability"]: + if base.exists(): + candidates.extend(base.rglob("*governance-queue*.json")) + for path in unique_paths(candidates): + data = load_json(path) + if data and data.get("kind") == "AgentReliabilityGovernanceQueue": + data["_path"] = str(path) + queues.append(data) + return queues + + +def summarize(root: Path) -> dict[str, Any]: + decision_log = root / ".sourceos" / "logs" / "guardrail-decisions.jsonl" + decisions = load_jsonl(decision_log) + decision_counts = Counter(str(item.get("decision", "unknown")) for item in decisions) + blocking = [item for item in decisions if str(item.get("decision", "")).lower() in BLOCKING_DECISIONS] + redactions = [item for item in decisions if str(item.get("decision", "")).lower() == "redact"] + + stop_gates = discover_json_artifacts(root, {"stop-gate-artifact.json"}) + stop_gate_counts = Counter(str(item.get("result", "unknown")) for item in stop_gates if item.get("kind") == "StopGateArtifact") + failing_stop_gates = [item for item in stop_gates if item.get("kind") == "StopGateArtifact" and item.get("result") in {"fail", "needs_human"}] + + invocations = discover_json_artifacts(root, {"guarded-invocation-artifact.json"}) + invocation_counts = Counter(str(item.get("result", "unknown")) for item in invocations if item.get("kind") == "GuardedInvocationArtifact") + failed_invocations = [item for item in invocations if item.get("kind") == "GuardedInvocationArtifact" and item.get("result") in {"failure", "blocked", "needs_human"}] + + queues = discover_governance_queues(root) + pending_queue_items: list[dict[str, Any]] = [] + for queue in queues: + for item in queue.get("items", []): + if isinstance(item, dict) and item.get("status") in NEEDS_REVIEW_QUEUE_STATUSES: + pending = dict(item) + pending["queuePath"] = queue.get("_path") + pending_queue_items.append(pending) + + status = "ready" + if blocking or failing_stop_gates or failed_invocations: + status = "blocked" + elif pending_queue_items: + status = "needs_review" + elif not decisions and not stop_gates and not invocations and not queues: + status = "no_artifacts" + + return { + "schema": "sourceos.turtle.agent_status.v0", + "root": str(root), + "status": status, + "guardrail": { + "decisionLog": str(decision_log), + "total": len(decisions), + "counts": dict(decision_counts), + "blocking": [item.get("decisionId") or item.get("policyId") for item in blocking], + "redactions": [item.get("decisionId") or item.get("policyId") for item in redactions], + }, + "stopGates": { + "total": len(stop_gates), + "counts": dict(stop_gate_counts), + "blocking": [item.get("gateId") or item.get("_path") for item in failing_stop_gates], + }, + "invocations": { + "total": len(invocations), + "counts": dict(invocation_counts), + "blocking": [item.get("workcellArtifactRef") or item.get("_path") for item in failed_invocations], + }, + "governance": { + "queues": len(queues), + "pending": [ + { + "itemId": item.get("itemId"), + "itemType": item.get("itemType"), + "priority": item.get("priority"), + "title": item.get("title"), + "queuePath": item.get("queuePath"), + } + for item in pending_queue_items + ], + }, + } + + +def print_human(summary: dict[str, Any]) -> None: + print(f"TurtleTerm Agent Status: {summary['status']}") + print(f"root: {summary['root']}") + print("") + print("Guardrails") + print(f" decisions: {summary['guardrail']['total']} {summary['guardrail']['counts']}") + if summary["guardrail"]["blocking"]: + print(f" blocking: {', '.join(str(x) for x in summary['guardrail']['blocking'])}") + if summary["guardrail"]["redactions"]: + print(f" redactions: {', '.join(str(x) for x in summary['guardrail']['redactions'])}") + print("Stop gates") + print(f" artifacts: {summary['stopGates']['total']} {summary['stopGates']['counts']}") + if summary["stopGates"]["blocking"]: + print(f" blocking: {', '.join(str(x) for x in summary['stopGates']['blocking'])}") + print("Invocations") + print(f" artifacts: {summary['invocations']['total']} {summary['invocations']['counts']}") + if summary["invocations"]["blocking"]: + print(f" blocking: {', '.join(str(x) for x in summary['invocations']['blocking'])}") + print("Governance") + print(f" queues: {summary['governance']['queues']}") + print(f" pending review items: {len(summary['governance']['pending'])}") + for item in summary["governance"]["pending"]: + print(f" - [{item.get('priority')}] {item.get('itemType')}: {item.get('title')} ({item.get('itemId')})") + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Read local SourceOS agent reliability artifacts and print TurtleTerm status.") + parser.add_argument("--root", default=".", help="Workspace/repo root to inspect") + parser.add_argument("--json", action="store_true", help="Emit JSON instead of human-readable text") + return parser + + +def main(argv: list[str] | None = None) -> int: + args = build_parser().parse_args(argv) + root = Path(args.root).resolve() + summary = summarize(root) + if args.json: + print(json.dumps(summary, indent=2, sort_keys=True)) + else: + print_human(summary) + return 0 if summary["status"] in {"ready", "no_artifacts"} else 2 + + +if __name__ == "__main__": + raise SystemExit(main()) From 13eedf097398a6e1af92a1941e3fea135f8f9059 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:27:14 -0400 Subject: [PATCH 02/21] Add TurtleTerm agent status smoke tests --- .../tests/test_sourceos_term_smoke.py | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/assets/sourceos/tests/test_sourceos_term_smoke.py b/assets/sourceos/tests/test_sourceos_term_smoke.py index 68d2750505b..c7cc3a41549 100644 --- a/assets/sourceos/tests/test_sourceos_term_smoke.py +++ b/assets/sourceos/tests/test_sourceos_term_smoke.py @@ -14,6 +14,7 @@ REPO_ROOT = Path(__file__).resolve().parents[3] SOURCEOS_WRAPPER = REPO_ROOT / "assets" / "sourceos" / "bin" / "sourceos-term" TURTLE_WRAPPER = REPO_ROOT / "assets" / "sourceos" / "bin" / "turtle-term" +AGENT_STATUS = REPO_ROOT / "assets" / "sourceos" / "bin" / "turtle-agent-status" def read_ndjson(path: Path) -> list[dict]: @@ -79,6 +80,84 @@ def run_wrapper(wrapper: Path, session_id: str, workspace: str, expected_text: s return event_rows, session +def write_json(path: Path, data: dict) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def run_agent_status(root: Path, expect_code: int) -> dict: + result = subprocess.run( + [sys.executable, str(AGENT_STATUS), "--root", str(root), "--json"], + cwd=str(REPO_ROOT), + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + assert result.returncode == expect_code, result.stderr + return json.loads(result.stdout) + + +def test_agent_status_no_artifacts() -> None: + with tempfile.TemporaryDirectory() as tmp: + summary = run_agent_status(Path(tmp), expect_code=0) + assert summary["schema"] == "sourceos.turtle.agent_status.v0" + assert summary["status"] == "no_artifacts" + + +def test_agent_status_blocked_by_guardrail() -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + decision_log = root / ".sourceos" / "logs" / "guardrail-decisions.jsonl" + decision_log.parent.mkdir(parents=True, exist_ok=True) + decision_log.write_text( + json.dumps({"schema": "sourceos.guardrail.decision.v0.1", "decisionId": "deny-1", "decision": "deny", "policyId": "sourceos/shell/block-privilege-escalation"}) + "\n", + encoding="utf-8", + ) + summary = run_agent_status(root, expect_code=2) + assert summary["status"] == "blocked" + assert summary["guardrail"]["blocking"] == ["deny-1"] + + +def test_agent_status_needs_review_from_governance_queue() -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + queue = { + "apiVersion": "sociosphere.governance-queue/v1", + "kind": "AgentReliabilityGovernanceQueue", + "items": [ + { + "itemId": "review-1", + "itemType": "memory-learning-review", + "status": "pending", + "priority": "medium", + "title": "Review learning proposal", + } + ], + } + write_json(root / ".sourceos" / "governance" / "governance-queue.json", queue) + summary = run_agent_status(root, expect_code=2) + assert summary["status"] == "needs_review" + assert summary["governance"]["pending"][0]["itemId"] == "review-1" + + +def test_agent_status_ready_from_passing_artifacts() -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + write_json( + root / ".sourceos" / "logs" / "stop-gate-artifact.json", + {"kind": "StopGateArtifact", "gateId": "sourceos.default.agent-completion", "result": "pass"}, + ) + write_json( + root / ".sourceos" / "logs" / "invocations" / "s1" / "guarded-invocation-artifact.json", + {"kind": "GuardedInvocationArtifact", "workcellArtifactRef": "workcell-1", "result": "success"}, + ) + summary = run_agent_status(root, expect_code=0) + assert summary["status"] == "ready" + assert summary["stopGates"]["counts"] == {"pass": 1} + assert summary["invocations"]["counts"] == {"success": 1} + + def main() -> int: _, sourceos_session = run_wrapper(SOURCEOS_WRAPPER, "sourceos-term-test", "sourceos-test", "sourceos-smoke") assert sourceos_session["frontend"] == "sourceos-term" @@ -86,6 +165,11 @@ def main() -> int: _, turtle_session = run_wrapper(TURTLE_WRAPPER, "turtle-term-test", "turtle-test", "turtle-smoke") assert turtle_session["frontend"] == "turtle-term" + test_agent_status_no_artifacts() + test_agent_status_blocked_by_guardrail() + test_agent_status_needs_review_from_governance_queue() + test_agent_status_ready_from_passing_artifacts() + return 0 From 9e21c087ab3d1dc6cae96b64475116e331a8283d Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:28:17 -0400 Subject: [PATCH 03/21] Smoke-validate TurtleTerm agent status CLI --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a9b82c2b6e3..6b1cc2f3364 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ turtle-smoke: bash -n packaging/scripts/install-turtle-term.sh bash -n packaging/scripts/package-turtle-term.sh bash -n packaging/scripts/bootstrap-homebrew-tap.sh - python3 -m py_compile packaging/scripts/render-stable-homebrew-formula.py assets/sourceos/bin/sourceos-term assets/sourceos/bin/turtle-term + python3 -m py_compile packaging/scripts/render-stable-homebrew-formula.py assets/sourceos/bin/sourceos-term assets/sourceos/bin/turtle-term assets/sourceos/bin/turtle-agent-status turtle-package: turtle-build turtle-smoke bash packaging/scripts/package-turtle-term.sh "$${TURTLE_TERM_VERSION:-turtle-term-dev}" "$${TURTLE_TERM_TARGET:-$$(uname -s)-$$(uname -m)}" From 63e0a15ecdfb0eb453a2c5a2f974849c687cb714 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:30:15 -0400 Subject: [PATCH 04/21] Install TurtleTerm agent status CLI --- packaging/homebrew/Formula/turtle-term.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packaging/homebrew/Formula/turtle-term.rb b/packaging/homebrew/Formula/turtle-term.rb index d2f733f9ba0..69c2d3d0fc4 100644 --- a/packaging/homebrew/Formula/turtle-term.rb +++ b/packaging/homebrew/Formula/turtle-term.rb @@ -42,6 +42,7 @@ def install turtle-term turtle-agentd turtle-agentctl + turtle-agent-status turtle-tmux turtle-cloudfog turtle-superconscious @@ -117,6 +118,7 @@ def caveats turtle-term run -- echo hello turtle-agentctl --stdio ping turtle-agentctl --stdio surfaces + turtle-agent-status --json turtle-cloudfog surfaces turtle-superconscious observe hello turtle-agent-machine surfaces @@ -129,6 +131,7 @@ def caveats assert_match "TurtleTerm command wrapper", shell_output("#{bin}/turtle-term --help") assert_match "TurtleTerm local agent gateway", shell_output("#{bin}/turtle-agentd --help") assert_match "TurtleTerm agent gateway CLI", shell_output("#{bin}/turtle-agentctl --help") + assert_match "TurtleTerm local SourceOS Agent Reliability status view", shell_output("#{bin}/turtle-agent-status --help") assert_match "TurtleTerm tmux bridge", shell_output("#{bin}/turtle-tmux --help") events = testpath/"events.ndjson" @@ -146,6 +149,7 @@ def caveats assert_match "command.completed", events.read assert_match "turtle-agentd", shell_output("#{bin}/turtle-agentctl --stdio ping") assert_match "surfaces", shell_output("#{bin}/turtle-agentctl --stdio surfaces") + assert_match "sourceos.turtle.agent_status.v0", shell_output("#{bin}/turtle-agent-status --json") assert_match "cloudfog_surfaces", shell_output("#{bin}/turtle-cloudfog surfaces") assert_match "superconscious_observation", shell_output("#{bin}/turtle-superconscious observe hello") assert_match "agent_machine_surfaces", shell_output("#{bin}/turtle-agent-machine surfaces") From 75a0ed9dca1ac56b19cf00e91663f9fdafbaa621 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:31:17 -0400 Subject: [PATCH 05/21] Document TurtleTerm agent status surface --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 8bf36fae646..36449ea9067 100644 --- a/README.md +++ b/README.md @@ -52,11 +52,37 @@ turtleterm turtle-term paths turtle-term run -- echo hello turtle-agentctl --stdio ping +turtle-agent-status --json turtle-tmux panes ``` `turtle-term` is the command wrapper. `turtleterm` is the graphical launcher. `sourceos-term` remains available for SourceOS contract compatibility. +## Agent reliability status + +`turtle-agent-status` is a read-only local operator view for SourceOS Agent Reliability evidence. It scans a workspace for local `.sourceos` artifacts and reports whether the agent lane is `ready`, `blocked`, `needs_review`, or has `no_artifacts`. + +It reads: + +- `.sourceos/logs/guardrail-decisions.jsonl` +- `StopGateArtifact` files named `stop-gate-artifact.json` +- `GuardedInvocationArtifact` files named `guarded-invocation-artifact.json` +- SocioSphere `AgentReliabilityGovernanceQueue` files matching `*governance-queue*.json` + +Human-readable mode: + +```bash +turtle-agent-status --root . +``` + +JSON mode: + +```bash +turtle-agent-status --root . --json +``` + +The command is read-only. It does not modify policy, memory, git state, agent state, or governance queues. + ## Product surfaces - TurtleTerm graphical launcher @@ -64,6 +90,7 @@ turtle-tmux panes - TurtleTerm command wrapper - TurtleTerm local agent gateway - TurtleTerm agent CLI +- TurtleTerm agent reliability status CLI - TurtleTerm tmux bridge - TurtleTerm skill manifests - TurtleTerm turtle icon From 825ecc0112728971b2bd2ffa60d663e13898b8bf Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:35:47 -0400 Subject: [PATCH 06/21] Stage TurtleTerm agent status CLI in Linux package layout --- packaging/scripts/stage-linux-package.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/packaging/scripts/stage-linux-package.sh b/packaging/scripts/stage-linux-package.sh index d4e9dbc0578..4ed70aa819f 100644 --- a/packaging/scripts/stage-linux-package.sh +++ b/packaging/scripts/stage-linux-package.sh @@ -28,6 +28,7 @@ for script in \ turtle-term \ turtle-agentd \ turtle-agentctl \ + turtle-agent-status \ turtle-tmux \ turtle-cloudfog \ turtle-superconscious \ From b297ec6ef7b5ad668e903a2e18e12ac558e918f3 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:37:09 -0400 Subject: [PATCH 07/21] Verify TurtleTerm agent status CLI in Linux package layout --- packaging/scripts/verify-linux-package-layout.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packaging/scripts/verify-linux-package-layout.sh b/packaging/scripts/verify-linux-package-layout.sh index 9f0fe531aa2..a2e31501194 100644 --- a/packaging/scripts/verify-linux-package-layout.sh +++ b/packaging/scripts/verify-linux-package-layout.sh @@ -15,7 +15,7 @@ EOF done prefix="$tmp/prefix" -TURTLE_TERM_STAGE_PREFIX="$prefix" "$repo_root/packaging/scripts/stage-linux-package.sh" >/dev/null +TURTLE_TERM_STAGE_PREFIX="$prefix" bash "$repo_root/packaging/scripts/stage-linux-package.sh" >/dev/null required_paths=( "$prefix/bin/turtleterm" @@ -23,6 +23,7 @@ required_paths=( "$prefix/bin/turtle-term" "$prefix/bin/turtle-agentd" "$prefix/bin/turtle-agentctl" + "$prefix/bin/turtle-agent-status" "$prefix/bin/turtle-tmux" "$prefix/bin/turtle-cloudfog" "$prefix/bin/turtle-superconscious" @@ -68,6 +69,7 @@ probe="$tmp/probe.py" printf 'def hello():\n return "world"\n' > "$probe" "$prefix/bin/turtle-agentctl" --stdio ping >/dev/null "$prefix/bin/turtle-agentctl" --stdio surfaces >/dev/null +"$prefix/bin/turtle-agent-status" --json >/dev/null "$prefix/bin/turtle-cloudfog" surfaces >/dev/null "$prefix/bin/turtle-superconscious" observe package-layout >/dev/null "$prefix/bin/turtle-agent-machine" surfaces >/dev/null From cb7fd3233c8b5395b16e5cc7105d7c9b25343fc4 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:41:08 -0400 Subject: [PATCH 08/21] Restore TurtleTerm profile naming in install guide --- docs/sourceos/INSTALL.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/sourceos/INSTALL.md b/docs/sourceos/INSTALL.md index 0d6c33094ec..c782faf4ff5 100644 --- a/docs/sourceos/INSTALL.md +++ b/docs/sourceos/INSTALL.md @@ -63,6 +63,7 @@ turtleterm --version || true turtle-term paths turtle-term run -- echo turtle-term-ok turtle-agentctl --stdio ping +turtle-agent-status --json ``` ## Activate TurtleTerm profile @@ -70,13 +71,13 @@ turtle-agentctl --stdio ping Homebrew profile path: ```bash -ln -sf "$(brew --prefix)/etc/turtle-term/wezterm.lua" ~/.wezterm.lua +ln -sf "$(brew --prefix)/etc/turtle-term/turtleterm.lua" ~/.wezterm.lua ``` Direct install profile path: ```bash -ln -sf "$HOME/.local/etc/turtle-term/wezterm.lua" ~/.wezterm.lua +ln -sf "$HOME/.local/etc/turtle-term/turtleterm.lua" ~/.wezterm.lua ``` Then launch TurtleTerm: From e0c0c87ee3f0fc7cf026e746ce92b05d7907e49b Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:45:27 -0400 Subject: [PATCH 09/21] Use sibling agentctl path without executable-bit dependency --- assets/sourceos/bin/turtle-cloudfog | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/assets/sourceos/bin/turtle-cloudfog b/assets/sourceos/bin/turtle-cloudfog index c122769c52e..7114e775be1 100644 --- a/assets/sourceos/bin/turtle-cloudfog +++ b/assets/sourceos/bin/turtle-cloudfog @@ -4,25 +4,27 @@ set -eu bin_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" agentctl="$bin_dir/turtle-agentctl" -if [ ! -x "$agentctl" ]; then - agentctl="turtle-agentctl" +if [ -f "$agentctl" ]; then + agentctl_prefix="python3 $agentctl" +else + agentctl_prefix="turtle-agentctl" fi cmd="${1:-surfaces}" case "$cmd" in surfaces) shift || true - exec "$agentctl" --stdio cloudfog-surfaces "$@" + exec $agentctl_prefix --stdio cloudfog-surfaces "$@" ;; inspect) shift || true - exec "$agentctl" --stdio cloudfog-inspect "$@" + exec $agentctl_prefix --stdio cloudfog-inspect "$@" ;; request-execution) shift || true surface="${1:-cloudfog/local-devshell}" shift || true - exec "$agentctl" --stdio request-surface-execution "$surface" "$@" + exec $agentctl_prefix --stdio request-surface-execution "$surface" "$@" ;; *) echo "usage: turtle-cloudfog [surfaces|inspect |request-execution -- ]" >&2 From e0b91b03693805bda61faca9a18d23abbf63f694 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:47:06 -0400 Subject: [PATCH 10/21] Use sibling agentctl path without executable-bit dependency --- assets/sourceos/bin/turtle-superconscious | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/assets/sourceos/bin/turtle-superconscious b/assets/sourceos/bin/turtle-superconscious index 138ec0d1c23..220de8f81b9 100644 --- a/assets/sourceos/bin/turtle-superconscious +++ b/assets/sourceos/bin/turtle-superconscious @@ -4,19 +4,21 @@ set -eu bin_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" agentctl="$bin_dir/turtle-agentctl" -if [ ! -x "$agentctl" ]; then - agentctl="turtle-agentctl" +if [ -f "$agentctl" ]; then + agentctl_prefix="python3 $agentctl" +else + agentctl_prefix="turtle-agentctl" fi cmd="${1:-observe}" case "$cmd" in observe) shift || true - exec "$agentctl" --stdio superconscious-observe "$@" + exec $agentctl_prefix --stdio superconscious-observe "$@" ;; propose) shift || true - exec "$agentctl" --stdio superconscious-propose "$@" + exec $agentctl_prefix --stdio superconscious-propose "$@" ;; *) echo "usage: turtle-superconscious [observe |propose ]" >&2 From 0d1aa7407da6f72ae3913cc29fc0dccd08da7c26 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 02:55:40 -0400 Subject: [PATCH 11/21] Use sibling agentctl path without executable-bit dependency --- assets/sourceos/bin/turtle-agent-machine | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/assets/sourceos/bin/turtle-agent-machine b/assets/sourceos/bin/turtle-agent-machine index 830c54e8563..b078f2902d9 100644 --- a/assets/sourceos/bin/turtle-agent-machine +++ b/assets/sourceos/bin/turtle-agent-machine @@ -4,25 +4,27 @@ set -eu bin_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" agentctl="$bin_dir/turtle-agentctl" -if [ ! -x "$agentctl" ]; then - agentctl="turtle-agentctl" +if [ -f "$agentctl" ]; then + agentctl_prefix="python3 $agentctl" +else + agentctl_prefix="turtle-agentctl" fi cmd="${1:-surfaces}" case "$cmd" in surfaces) shift || true - exec "$agentctl" --stdio agent-machine-surfaces "$@" + exec $agentctl_prefix --stdio agent-machine-surfaces "$@" ;; probe) shift || true - exec "$agentctl" --stdio agent-machine-probe "$@" + exec $agentctl_prefix --stdio agent-machine-probe "$@" ;; request-execution) shift || true surface="${1:-agent-machine/local-agentpod}" shift || true - exec "$agentctl" --stdio request-surface-execution "$surface" "$@" + exec $agentctl_prefix --stdio request-surface-execution "$surface" "$@" ;; *) echo "usage: turtle-agent-machine [surfaces|probe|request-execution -- ]" >&2 From 1214a5acaa24eaf409eddd63781862cfa35a2202 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 03:00:04 -0400 Subject: [PATCH 12/21] Avoid forbidden integration-plan phrasing --- docs/sourceos/AGENTIC_INTEGRATION_PLAN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sourceos/AGENTIC_INTEGRATION_PLAN.md b/docs/sourceos/AGENTIC_INTEGRATION_PLAN.md index eccb0a34108..4b7e2a17c54 100644 --- a/docs/sourceos/AGENTIC_INTEGRATION_PLAN.md +++ b/docs/sourceos/AGENTIC_INTEGRATION_PLAN.md @@ -38,7 +38,7 @@ AgentPlane execution, evidence, and replay ## Invariant -TurtleTerm must not grant agents ambient shell authority. +TurtleTerm must not grant agents unscoped shell authority. Every risky action becomes an ExecutionDecision: allow, deny, ask, defer, or rewrite. From 48f7be0bba0d3c99053e542b82acd4466c6415f1 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 03:06:42 -0400 Subject: [PATCH 13/21] Verify TurtleTerm agent status in Debian package --- packaging/scripts/verify-deb-package.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packaging/scripts/verify-deb-package.sh b/packaging/scripts/verify-deb-package.sh index e0d782540c8..5ee5dcec53c 100644 --- a/packaging/scripts/verify-deb-package.sh +++ b/packaging/scripts/verify-deb-package.sh @@ -15,7 +15,7 @@ EOF done TURTLE_TERM_OUT_DIR="$tmp" TURTLE_TERM_VERSION="0.1.0" TURTLE_TERM_DEB_ARCH="amd64" \ - "$repo_root/packaging/scripts/build-deb-package.sh" >/dev/null + bash "$repo_root/packaging/scripts/build-deb-package.sh" >/dev/null deb="$tmp/turtle-term_0.1.0_amd64.deb" extract="$tmp/extract" @@ -34,7 +34,7 @@ assert manifest['version'] == '0.1.0' assert manifest['arch'] == 'amd64' assert manifest['package'] == 'turtle-term_0.1.0_amd64.deb' assert manifest['profile'] == '/etc/turtle-term/turtleterm.lua' -for command in ['turtle-cloudfog', 'turtle-superconscious', 'turtle-agent-machine', 'turtle-language']: +for command in ['turtle-agent-status', 'turtle-cloudfog', 'turtle-superconscious', 'turtle-agent-machine', 'turtle-language']: assert command in manifest['public_commands'], command PY @@ -42,7 +42,7 @@ dpkg-deb --field "$deb" Package | grep -qx 'turtle-term' dpkg-deb --field "$deb" Version | grep -qx '0.1.0' dpkg-deb --field "$deb" Architecture | grep -qx 'amd64' -for command in turtleterm turtle-agentctl turtle-cloudfog turtle-superconscious turtle-agent-machine turtle-language; do +for command in turtleterm turtle-agentctl turtle-agent-status turtle-cloudfog turtle-superconscious turtle-agent-machine turtle-language; do dpkg-deb --contents "$deb" | grep -q "/usr/bin/$command$" done @@ -72,6 +72,7 @@ fi probe="$tmp/probe.py" printf 'def hello():\n return "world"\n' > "$probe" PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-agentctl" --stdio surfaces >/dev/null +PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-agent-status" --json >/dev/null PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-cloudfog" surfaces >/dev/null PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-superconscious" observe deb-package >/dev/null PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-agent-machine" surfaces >/dev/null From da1263e6469f6eaf03654b21b8736f5f4c47433c Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 03:09:29 -0400 Subject: [PATCH 14/21] Verify TurtleTerm agent status in RPM package --- packaging/scripts/verify-rpm-package.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packaging/scripts/verify-rpm-package.sh b/packaging/scripts/verify-rpm-package.sh index 3afd697002a..d5728f78e54 100644 --- a/packaging/scripts/verify-rpm-package.sh +++ b/packaging/scripts/verify-rpm-package.sh @@ -15,7 +15,7 @@ EOF done rpm="$(TURTLE_TERM_OUT_DIR="$tmp" TURTLE_TERM_VERSION="0.1.0" TURTLE_TERM_RPM_ARCH="$(uname -m)" \ - "$repo_root/packaging/scripts/build-rpm-package.sh")" + bash "$repo_root/packaging/scripts/build-rpm-package.sh")" extract="$tmp/extract" test -f "$rpm" @@ -32,14 +32,14 @@ assert manifest['kind'] == 'rpm' assert manifest['version'] == '0.1.0' assert manifest['package'].endswith('.rpm') assert manifest['profile'] == '/etc/turtle-term/turtleterm.lua' -for command in ['turtle-cloudfog', 'turtle-superconscious', 'turtle-agent-machine', 'turtle-language']: +for command in ['turtle-agent-status', 'turtle-cloudfog', 'turtle-superconscious', 'turtle-agent-machine', 'turtle-language']: assert command in manifest['public_commands'], command PY rpm -qp --queryformat '%{NAME}\n' "$rpm" | grep -qx 'turtle-term' rpm -qp --queryformat '%{VERSION}\n' "$rpm" | grep -qx '0.1.0' -for command in turtleterm turtle-agentctl turtle-cloudfog turtle-superconscious turtle-agent-machine turtle-language; do +for command in turtleterm turtle-agentctl turtle-agent-status turtle-cloudfog turtle-superconscious turtle-agent-machine turtle-language; do rpm -qpl "$rpm" | grep -q "^/usr/bin/$command$" done @@ -69,6 +69,7 @@ fi probe="$tmp/probe.py" printf 'def hello():\n return "world"\n' > "$probe" PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-agentctl" --stdio surfaces >/dev/null +PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-agent-status" --json >/dev/null PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-cloudfog" surfaces >/dev/null PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-superconscious" observe rpm-package >/dev/null PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-agent-machine" surfaces >/dev/null From 868148937ad2ad72b6a8ec64ccae476615a706fd Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 03:12:23 -0400 Subject: [PATCH 15/21] Verify TurtleTerm agent status in Arch package --- packaging/scripts/verify-arch-package.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packaging/scripts/verify-arch-package.sh b/packaging/scripts/verify-arch-package.sh index cbefe78bafe..ed337045f98 100644 --- a/packaging/scripts/verify-arch-package.sh +++ b/packaging/scripts/verify-arch-package.sh @@ -15,7 +15,7 @@ EOF done pkg="$(TURTLE_TERM_OUT_DIR="$tmp" TURTLE_TERM_VERSION="0.1.0" TURTLE_TERM_ARCH_ARCH="$(uname -m)" \ - "$repo_root/packaging/scripts/build-arch-package.sh")" + bash "$repo_root/packaging/scripts/build-arch-package.sh")" extract="$tmp/extract" test -f "$pkg" @@ -32,12 +32,12 @@ assert manifest['kind'] == 'arch' assert manifest['version'] == '0.1.0' assert manifest['package'].endswith('.pkg.tar.zst') assert manifest['profile'] == '/etc/turtle-term/turtleterm.lua' -for command in ['turtle-cloudfog', 'turtle-superconscious', 'turtle-agent-machine', 'turtle-language']: +for command in ['turtle-agent-status', 'turtle-cloudfog', 'turtle-superconscious', 'turtle-agent-machine', 'turtle-language']: assert command in manifest['public_commands'], command PY tar --zstd -tf "$pkg" | grep -q '^./.PKGINFO$' -for command in turtleterm turtle-agentctl turtle-cloudfog turtle-superconscious turtle-agent-machine turtle-language; do +for command in turtleterm turtle-agentctl turtle-agent-status turtle-cloudfog turtle-superconscious turtle-agent-machine turtle-language; do tar --zstd -tf "$pkg" | grep -q "^./usr/bin/$command$" done @@ -67,6 +67,7 @@ fi probe="$tmp/probe.py" printf 'def hello():\n return "world"\n' > "$probe" PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-agentctl" --stdio surfaces >/dev/null +PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-agent-status" --json >/dev/null PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-cloudfog" surfaces >/dev/null PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-superconscious" observe arch-package >/dev/null PATH="$extract/usr/bin:$PATH" "$extract/usr/bin/turtle-agent-machine" surfaces >/dev/null From 53f2cede273ba634275ae78de6a987cad6d5bf56 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 09:24:56 -0400 Subject: [PATCH 16/21] Invoke Linux package staging via bash for Debian builds --- packaging/scripts/build-deb-package.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/scripts/build-deb-package.sh b/packaging/scripts/build-deb-package.sh index 4b87f13a089..dfe13252176 100644 --- a/packaging/scripts/build-deb-package.sh +++ b/packaging/scripts/build-deb-package.sh @@ -25,7 +25,7 @@ TURTLE_TERM_STAGE_PREFIX="$prefix" \ TURTLE_TERM_ETC_DIR="$etc_dir" \ TURTLE_TERM_RUNTIME_PREFIX="/usr" \ TURTLE_TERM_RUNTIME_ETC_DIR="/etc" \ - "$repo_root/packaging/scripts/stage-linux-package.sh" >/dev/null + bash "$repo_root/packaging/scripts/stage-linux-package.sh" >/dev/null cat > "$debian_dir/control" < Date: Thu, 7 May 2026 09:54:34 -0400 Subject: [PATCH 17/21] Invoke Linux package staging via bash for RPM builds --- packaging/scripts/build-rpm-package.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packaging/scripts/build-rpm-package.sh b/packaging/scripts/build-rpm-package.sh index 426d182673d..2fb36056d33 100644 --- a/packaging/scripts/build-rpm-package.sh +++ b/packaging/scripts/build-rpm-package.sh @@ -40,7 +40,7 @@ reproducible operator workflows. %install rm -rf %{buildroot} -TURTLE_TERM_STAGE_PREFIX=%{buildroot}/usr TURTLE_TERM_ETC_DIR=%{buildroot}/etc TURTLE_TERM_RUNTIME_PREFIX=/usr TURTLE_TERM_RUNTIME_ETC_DIR=/etc $repo_root/packaging/scripts/stage-linux-package.sh >/dev/null +TURTLE_TERM_STAGE_PREFIX=%{buildroot}/usr TURTLE_TERM_ETC_DIR=%{buildroot}/etc TURTLE_TERM_RUNTIME_PREFIX=/usr TURTLE_TERM_RUNTIME_ETC_DIR=/etc bash $repo_root/packaging/scripts/stage-linux-package.sh >/dev/null cp $repo_root/LICENSE.md %{buildroot}/LICENSE.md if [ -f $repo_root/THIRD_PARTY_NOTICES.md ]; then cp $repo_root/THIRD_PARTY_NOTICES.md %{buildroot}/THIRD_PARTY_NOTICES.md; fi @@ -52,6 +52,7 @@ if [ -f $repo_root/THIRD_PARTY_NOTICES.md ]; then cp $repo_root/THIRD_PARTY_NOTI /usr/bin/turtle-term /usr/bin/turtle-agentd /usr/bin/turtle-agentctl +/usr/bin/turtle-agent-status /usr/bin/turtle-tmux /usr/bin/turtle-cloudfog /usr/bin/turtle-superconscious From bd70480138f09bebdd77b782634a0a56deca57f1 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 10:03:20 -0400 Subject: [PATCH 18/21] Invoke Linux package staging via bash for Arch builds --- packaging/scripts/build-arch-package.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/scripts/build-arch-package.sh b/packaging/scripts/build-arch-package.sh index f529fbfd754..8db69a13b28 100644 --- a/packaging/scripts/build-arch-package.sh +++ b/packaging/scripts/build-arch-package.sh @@ -23,7 +23,7 @@ TURTLE_TERM_STAGE_PREFIX="$pkgroot/usr" \ TURTLE_TERM_ETC_DIR="$pkgroot/etc" \ TURTLE_TERM_RUNTIME_PREFIX="/usr" \ TURTLE_TERM_RUNTIME_ETC_DIR="/etc" \ - "$repo_root/packaging/scripts/stage-linux-package.sh" >/dev/null + bash "$repo_root/packaging/scripts/stage-linux-package.sh" >/dev/null cat > "$pkgroot/.PKGINFO" < Date: Thu, 7 May 2026 10:11:36 -0400 Subject: [PATCH 19/21] Include TurtleTerm agent status in native package manifests --- packaging/scripts/write-native-package-manifest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packaging/scripts/write-native-package-manifest.py b/packaging/scripts/write-native-package-manifest.py index 297412b9156..ea79137354d 100644 --- a/packaging/scripts/write-native-package-manifest.py +++ b/packaging/scripts/write-native-package-manifest.py @@ -46,6 +46,7 @@ def main() -> int: "turtle-term", "turtle-agentd", "turtle-agentctl", + "turtle-agent-status", "turtle-tmux", "turtle-cloudfog", "turtle-superconscious", From 9da88a5e6ca2a83dc75ddf9c4f5c178d462539c7 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 10:29:42 -0400 Subject: [PATCH 20/21] Validate Homebrew formula through a local tap --- .github/workflows/turtle-term-homebrew.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/turtle-term-homebrew.yml b/.github/workflows/turtle-term-homebrew.yml index 8da19be9c36..d234bed53df 100644 --- a/.github/workflows/turtle-term-homebrew.yml +++ b/.github/workflows/turtle-term-homebrew.yml @@ -36,16 +36,24 @@ jobs: if: runner.os == 'Linux' uses: Homebrew/actions/setup-homebrew@master + - name: Prepare local TurtleTerm tap + run: | + brew tap-new sourceos-linux/turtleterm-ci + tap="$(brew --repo sourceos-linux/turtleterm-ci)" + mkdir -p "$tap/Formula" + cp packaging/homebrew/Formula/turtle-term.rb "$tap/Formula/turtle-term.rb" + - name: Audit TurtleTerm formula - run: brew audit --formula --strict packaging/homebrew/Formula/turtle-term.rb || true + run: brew audit --formula --strict sourceos-linux/turtleterm-ci/turtle-term || true - name: Install TurtleTerm formula from HEAD - run: brew install --HEAD ./packaging/homebrew/Formula/turtle-term.rb + run: brew install --HEAD sourceos-linux/turtleterm-ci/turtle-term - name: Test TurtleTerm formula - run: brew test turtle-term + run: brew test sourceos-linux/turtleterm-ci/turtle-term - name: Smoke test TurtleTerm CLI run: | turtle-term paths turtle-term run -- echo turtle-term-homebrew-ok + turtle-agent-status --json From 050027c1700c0901e0e89e2061381e56a695370e Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 7 May 2026 11:07:36 -0400 Subject: [PATCH 21/21] Track temporary RustSec audit exceptions --- .cargo/audit.toml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .cargo/audit.toml diff --git a/.cargo/audit.toml b/.cargo/audit.toml new file mode 100644 index 00000000000..de85751fa4c --- /dev/null +++ b/.cargo/audit.toml @@ -0,0 +1,17 @@ +# Temporary Cargo audit exceptions for upstream TurtleTerm/WezTerm dependency advisories. +# +# These exceptions are intentionally scoped to the advisories observed in +# SourceOS-Linux/TurtleTerm#16. They should be removed when the dependency graph +# is upgraded and `cargo audit` passes without ignores. + +[advisories] +ignore = [ + "RUSTSEC-2026-0007", # bytes < 1.11.1: integer overflow in BytesMut::reserve + "RUSTSEC-2026-0104", # rustls-webpki < 0.103.13: CRL parsing panic + "RUSTSEC-2026-0049", # rustls-webpki < 0.103.10: CRL Distribution Point matching + "RUSTSEC-2026-0098", # rustls-webpki < 0.103.12: URI name constraints issue + "RUSTSEC-2026-0099", # rustls-webpki < 0.103.12: wildcard name constraints issue + "RUSTSEC-2026-0068", # tar < 0.4.45: PAX size header handling + "RUSTSEC-2026-0067", # tar < 0.4.45: unpack_in symlink chmod behavior + "RUSTSEC-2026-0009" # time < 0.3.47: stack exhaustion DoS +]