From 12193b0ec79eeb7e5e41e63903e78236e73c5bf5 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 14:20:39 -0400 Subject: [PATCH 01/12] Add portable-ai route to current sourceosctl --- bin/sourceosctl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bin/sourceosctl b/bin/sourceosctl index 46cfeec..be06d6e 100755 --- a/bin/sourceosctl +++ b/bin/sourceosctl @@ -64,6 +64,11 @@ if len(sys.argv) > 1 and sys.argv[1] == "native-assistant": sys.exit(native_assistant_main(sys.argv[2:])) +if len(sys.argv) > 1 and sys.argv[1] == "portable-ai": + from sourceosctl.commands.portable_ai_cli import main as portable_ai_main + + sys.exit(portable_ai_main(sys.argv[2:])) + if len(sys.argv) > 1 and sys.argv[1] == "local-agent": from sourceosctl.commands.local_agent_registry_cli import main as local_agent_main @@ -79,4 +84,4 @@ if len(sys.argv) > 2 and sys.argv[1:3] == ["doctor", "local-runtime"]: from sourceosctl.cli import main -sys.exit(main()) +sys.exit(main()) \ No newline at end of file From 88e6da22898899b9970230d31b904defb65b1c9c Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 14:23:21 -0400 Subject: [PATCH 02/12] Wire Portable AI validation into current Makefile --- Makefile | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 3576427..088b58e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -.PHONY: validate test scan-local-persistence validate-local-agents validate-local-agent-templates check-local-agent-drift validate-reasoning-cli +.PHONY: validate test scan-local-persistence validate-local-agents validate-local-agent-templates check-local-agent-drift validate-reasoning-cli validate-portable-ai validate-packaging -validate: test scan-local-persistence validate-local-agents validate-local-agent-templates check-local-agent-drift validate-reasoning-cli +validate: test scan-local-persistence validate-local-agents validate-local-agent-templates check-local-agent-drift validate-reasoning-cli validate-portable-ai validate-packaging @test -f README.md @test -f AGENTS.md @test -f .github/copilot-instructions.md @@ -29,3 +29,11 @@ validate-reasoning-cli: @python3 bin/sourceosctl reasoning inspect tests/fixtures/reasoning/deterministic >/dev/null @python3 bin/sourceosctl reasoning replay-plan tests/fixtures/reasoning/deterministic >/dev/null @python3 bin/sourceosctl reasoning events tests/fixtures/reasoning/deterministic >/dev/null + +validate-portable-ai: + @python3 bin/sourceosctl portable-ai profiles >/dev/null + @python3 bin/sourceosctl portable-ai preflight /tmp/SOURCEOS_AI --profile tiny-router >/dev/null + @python3 bin/sourceosctl portable-ai prepare /tmp/SOURCEOS_AI --profile tiny-router --dry-run >/dev/null + +validate-packaging: + @python3 scripts/validate_packaging.py From 618eaff65c1401fdf8781412e2b8484afdb74d32 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 14:24:25 -0400 Subject: [PATCH 03/12] Add Portable AI command parser on current main --- sourceosctl/commands/portable_ai_cli.py | 93 +++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 sourceosctl/commands/portable_ai_cli.py diff --git a/sourceosctl/commands/portable_ai_cli.py b/sourceosctl/commands/portable_ai_cli.py new file mode 100644 index 0000000..3f49da2 --- /dev/null +++ b/sourceosctl/commands/portable_ai_cli.py @@ -0,0 +1,93 @@ +"""Argument parser for the SourceOS Portable AI Kit command group.""" + +from __future__ import annotations + +import argparse + +from sourceosctl.commands import portable_ai, portable_ai_byom, portable_ai_runtime + +SURFACES = ["turtleterm", "agent-term", "bearbrowser", "local-web", "anythingllm-adapter"] +BYOM_TASK_CLASSES = [ + "operator-selected", "router", "triage", "summarization", "rewrite", "office-assist", + "artifact-drafting", "coding-assist", "repo-triage", "privacy-first-chat", "offline-fallback", + "operator-assist", "evidence-inspection", "workroom-local", "field-workroom", +] + + +def add_runtime_common(parser: argparse.ArgumentParser) -> None: + parser.add_argument("target_root", help="Target portable root") + parser.add_argument("--provider", default=portable_ai_runtime.DEFAULT_PROVIDER, choices=portable_ai_runtime.SUPPORTED_PROVIDERS) + parser.add_argument("--host", default=portable_ai_runtime.DEFAULT_HOST) + parser.add_argument("--port", type=int, default=portable_ai_runtime.DEFAULT_PORT) + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(prog="sourceosctl portable-ai", description="SourceOS Portable AI Kit helpers") + sub = parser.add_subparsers(dest="command", metavar="") + sub.required = True + + profiles_p = sub.add_parser("profiles", help="List built-in portable AI profiles") + profiles_p.set_defaults(func=portable_ai.profiles) + + preflight_p = sub.add_parser("preflight", help="Inspect a portable root target without changing it") + preflight_p.add_argument("target_root") + preflight_p.add_argument("--profile", default="laptop-safe", choices=sorted(portable_ai.PORTABLE_PROFILES)) + preflight_p.add_argument("--benchmark", action="store_true", default=False) + preflight_p.set_defaults(func=portable_ai.preflight) + + prepare_p = sub.add_parser("prepare", help="Render or execute portable root materialization") + prepare_p.add_argument("target_root") + prepare_p.add_argument("--profile", default="laptop-safe", choices=sorted(portable_ai.PORTABLE_PROFILES)) + prepare_p.add_argument("--dry-run", action="store_true", default=True, dest="dry_run") + prepare_p.add_argument("--execute", action="store_true", default=False) + prepare_p.add_argument("--policy-ok", action="store_true", default=False) + prepare_p.add_argument("--evidence-out", default=None) + prepare_p.set_defaults(func=portable_ai.prepare) + + byom_p = sub.add_parser("byom", help="Bring-your-own local model helpers") + byom_sub = byom_p.add_subparsers(dest="byom_command", metavar="") + byom_sub.required = True + byom_verify_p = byom_sub.add_parser("verify", help="Hash and verify a local model file") + byom_verify_p.add_argument("target_root") + byom_verify_p.add_argument("model_file") + byom_verify_p.add_argument("--name", default=None) + byom_verify_p.add_argument("--display-name", default=None) + byom_verify_p.add_argument("--pack-id", default=None) + byom_verify_p.add_argument("--license-ref", default="operator-attestation-required") + byom_verify_p.add_argument("--source-note", default=None) + byom_verify_p.add_argument("--task-class", action="append", choices=BYOM_TASK_CLASSES) + byom_verify_p.add_argument("--copy", action="store_true", default=False) + byom_verify_p.add_argument("--dry-run", action="store_true", default=True, dest="dry_run") + byom_verify_p.add_argument("--execute", action="store_true", default=False) + byom_verify_p.add_argument("--policy-ok", action="store_true", default=False) + byom_verify_p.add_argument("--evidence-out", default=None) + byom_verify_p.set_defaults(func=portable_ai_byom.verify) + + start_p = sub.add_parser("start-plan", help="Render a local runtime launch plan") + add_runtime_common(start_p) + start_p.add_argument("--surface", default="turtleterm", choices=SURFACES) + start_p.add_argument("--model", default=None) + start_p.set_defaults(func=portable_ai_runtime.start_plan) + + stop_p = sub.add_parser("stop-plan", help="Render a local runtime stop plan") + add_runtime_common(stop_p) + stop_p.set_defaults(func=portable_ai_runtime.stop_plan) + + inspect_p = sub.add_parser("inspect", help="Inspect portable root layout state") + inspect_p.add_argument("target_root") + inspect_p.set_defaults(func=portable_ai.inspect) + + evidence_p = sub.add_parser("evidence", help="Portable AI evidence helpers") + evidence_sub = evidence_p.add_subparsers(dest="evidence_command", metavar="") + evidence_sub.required = True + evidence_inspect_p = evidence_sub.add_parser("inspect", help="Inspect portable AI evidence JSON") + evidence_inspect_p.add_argument("path") + evidence_inspect_p.set_defaults(func=portable_ai.evidence_inspect) + + return parser + + +def main(argv=None) -> int: + parser = build_parser() + args = parser.parse_args(argv) + return args.func(args) or 0 From fefbb3c4a7fb9dc05259c67ff0099a4619ba3bad Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 14:25:42 -0400 Subject: [PATCH 04/12] Add minimal Portable AI helpers on current main --- sourceosctl/commands/portable_ai.py | 105 ++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 sourceosctl/commands/portable_ai.py diff --git a/sourceosctl/commands/portable_ai.py b/sourceosctl/commands/portable_ai.py new file mode 100644 index 0000000..7487bff --- /dev/null +++ b/sourceosctl/commands/portable_ai.py @@ -0,0 +1,105 @@ +from __future__ import annotations + +import datetime as dt +import json +import os +import shutil +from pathlib import Path +from typing import Any + +PORTABLE_LAYOUT_VERSION = "sourceos.portable-ai/v1alpha1" + +PORTABLE_PROFILES: dict[str, dict[str, Any]] = { + "tiny-router": {"displayName": "Tiny Router Kit", "minimumFreeGb": 8, "recommendedFreeGb": 16}, + "laptop-safe": {"displayName": "Laptop-safe Portable AI Kit", "minimumFreeGb": 16, "recommendedFreeGb": 32}, + "office-local": {"displayName": "Office-local Portable AI Kit", "minimumFreeGb": 32, "recommendedFreeGb": 64}, + "code-local": {"displayName": "Code-local Portable AI Kit", "minimumFreeGb": 32, "recommendedFreeGb": 64}, + "field-kit": {"displayName": "Field Operator Portable AI Kit", "minimumFreeGb": 64, "recommendedFreeGb": 128}, + "byom-gguf": {"displayName": "Bring-your-own GGUF Portable Kit", "minimumFreeGb": 8, "recommendedFreeGb": 64}, +} + +PORTABLE_DIRS = ["manifests", "models/blobs", "cache", "state/routes", "evidence/preflight", "evidence/materialization", "tmp"] + + +def _now() -> str: + return dt.datetime.now(dt.timezone.utc).isoformat() + + +def _print(payload: dict[str, Any]) -> int: + print(json.dumps(payload, indent=2, sort_keys=True)) + return 0 + + +def _root(value: str) -> Path: + return Path(value).expanduser().resolve() + + +def _disk(path: Path) -> dict[str, float | None]: + probe = path if path.exists() else path.parent + try: + total, used, free = shutil.disk_usage(probe) + except FileNotFoundError: + return {"totalGb": None, "usedGb": None, "freeGb": None} + gb = 1024 ** 3 + return {"totalGb": round(total / gb, 2), "usedGb": round(used / gb, 2), "freeGb": round(free / gb, 2)} + + +def profiles(_args) -> int: + return _print({"type": "PortableAIProfiles", "apiVersion": PORTABLE_LAYOUT_VERSION, "profiles": PORTABLE_PROFILES}) + + +def preflight(args) -> int: + target = _root(args.target_root) + profile_name = getattr(args, "profile", "laptop-safe") + profile = PORTABLE_PROFILES[profile_name] + disk = _disk(target) + parent = target if target.exists() else target.parent + writable = parent.exists() and os.access(parent, os.W_OK) + failures: list[str] = [] + warnings: list[str] = [] + free_gb = disk.get("freeGb") + if not writable: + failures.append("target parent is not writable") + if free_gb is not None and free_gb < profile["minimumFreeGb"]: + failures.append("free space below profile minimum") + elif free_gb is not None and free_gb < profile["recommendedFreeGb"]: + warnings.append("free space below profile recommendation") + return _print({ + "type": "PortablePreflightEvidence", + "apiVersion": PORTABLE_LAYOUT_VERSION, + "capturedAt": _now(), + "targetRoot": str(target), + "profile": profile_name, + "disk": disk, + "writable": writable, + "failures": failures, + "warnings": warnings, + "decision": "fail" if failures else "warn" if warnings else "pass", + }) + + +def prepare(args) -> int: + target = _root(args.target_root) + profile_name = args.profile + return _print({ + "type": "PortablePreparePlan", + "apiVersion": PORTABLE_LAYOUT_VERSION, + "capturedAt": _now(), + "targetRoot": str(target), + "profile": profile_name, + "wouldCreateDirectories": [str(target / rel) for rel in PORTABLE_DIRS], + "wouldWriteManifest": str(target / "manifests" / "portable-ai-root.json"), + "wouldStartProvider": False, + "wouldFetchRemoteModels": False, + }) + + +def inspect(args) -> int: + target = _root(args.target_root) + return _print({"type": "PortableAIInspect", "apiVersion": PORTABLE_LAYOUT_VERSION, "targetRoot": str(target), "exists": target.exists()}) + + +def evidence_inspect(args) -> int: + path = Path(args.path).expanduser() + payload = json.loads(path.read_text(encoding="utf-8")) + return _print({"path": str(path), "type": payload.get("type"), "apiVersion": payload.get("apiVersion")}) From 4857600b6e41c8228db6fa628e683248ea24c107 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 14:26:21 -0400 Subject: [PATCH 05/12] Add minimal Portable AI BYOM verifier on current main --- sourceosctl/commands/portable_ai_byom.py | 44 ++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 sourceosctl/commands/portable_ai_byom.py diff --git a/sourceosctl/commands/portable_ai_byom.py b/sourceosctl/commands/portable_ai_byom.py new file mode 100644 index 0000000..e49f6e7 --- /dev/null +++ b/sourceosctl/commands/portable_ai_byom.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +import hashlib +import json +from pathlib import Path +from typing import Any + + +def _print(payload: dict[str, Any]) -> int: + print(json.dumps(payload, indent=2, sort_keys=True)) + return 0 + + +def verify(args) -> int: + model_file = Path(args.model_file).expanduser().resolve() + target_root = Path(args.target_root).expanduser().resolve() + if not model_file.exists() or not model_file.is_file(): + raise SystemExit(f"model file not found: {model_file}") + digest = hashlib.sha256() + with model_file.open("rb") as handle: + for chunk in iter(lambda: handle.read(1024 * 1024), b""): + digest.update(chunk) + slug = args.name or model_file.stem + payload = { + "type": "PortableAIByomVerification", + "targetRoot": str(target_root), + "modelFile": str(model_file), + "name": slug, + "packId": args.pack_id or f"urn:srcos:model-carry-pack:byom-{slug}", + "displayName": args.display_name or slug, + "sha256": digest.hexdigest(), + "sizeBytes": model_file.stat().st_size, + "licenseRef": args.license_ref, + "sourceNote": args.source_note, + "taskClasses": args.task_class or ["operator-selected"], + "wouldCopy": bool(args.copy), + "wouldWriteManifest": bool(args.execute), + "routeEligibleBeforeReview": False, + "promptEgressDefault": "deny", + "toolUseDefault": "deny", + } + if args.execute and not args.policy_ok: + raise SystemExit("--execute requires --policy-ok") + return _print(payload) From bb95b8c6c1c6f40d4b3aeb7915dd7c315ca198d5 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 14:27:02 -0400 Subject: [PATCH 06/12] Add minimal Portable AI runtime plan helpers on current main --- sourceosctl/commands/portable_ai_runtime.py | 51 +++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 sourceosctl/commands/portable_ai_runtime.py diff --git a/sourceosctl/commands/portable_ai_runtime.py b/sourceosctl/commands/portable_ai_runtime.py new file mode 100644 index 0000000..81a116c --- /dev/null +++ b/sourceosctl/commands/portable_ai_runtime.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +DEFAULT_PROVIDER = "ollama-compatible" +SUPPORTED_PROVIDERS = ["ollama-compatible", "llama.cpp", "openai-compatible-local"] +DEFAULT_HOST = "127.0.0.1" +DEFAULT_PORT = 11434 + + +def _print(payload: dict[str, Any]) -> int: + print(json.dumps(payload, indent=2, sort_keys=True)) + return 0 + + +def start_plan(args) -> int: + root = Path(args.target_root).expanduser().resolve() + payload = { + "type": "PortableAIStartPlan", + "targetRoot": str(root), + "provider": args.provider, + "host": args.host, + "port": args.port, + "surface": args.surface, + "model": args.model, + "endpoint": f"http://{args.host}:{args.port}", + "wouldStartProvider": False, + "requiresAgentMachineActivation": True, + "requiresPolicyAdmission": True, + "promptEgressDefault": "deny", + "toolUseDefault": "deny", + } + return _print(payload) + + +def stop_plan(args) -> int: + root = Path(args.target_root).expanduser().resolve() + payload = { + "type": "PortableAIStopPlan", + "targetRoot": str(root), + "provider": args.provider, + "host": args.host, + "port": args.port, + "endpoint": f"http://{args.host}:{args.port}", + "wouldStopProvider": False, + "requiresAgentMachineTeardown": True, + "requiresPolicyAdmission": True, + } + return _print(payload) From f0dd744953c1f070e7bf4b913ca3f228ee71b94a Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 14:28:12 -0400 Subject: [PATCH 07/12] Add standalone Portable AI entrypoint on current main --- bin/sourceos-portable-ai | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 bin/sourceos-portable-ai diff --git a/bin/sourceos-portable-ai b/bin/sourceos-portable-ai new file mode 100644 index 0000000..daca399 --- /dev/null +++ b/bin/sourceos-portable-ai @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +"""Standalone SourceOS Portable AI Kit CLI.""" + +from __future__ import annotations + +import os +import sys + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from sourceosctl.commands.portable_ai_cli import main + +if __name__ == "__main__": + sys.exit(main()) From 158c0f12226711692be3dee5823efbf7e7159bf7 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 14:28:48 -0400 Subject: [PATCH 08/12] Add Portable AI packaging validator on current main --- scripts/validate_packaging.py | 70 +++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 scripts/validate_packaging.py diff --git a/scripts/validate_packaging.py b/scripts/validate_packaging.py new file mode 100644 index 0000000..44c06ae --- /dev/null +++ b/scripts/validate_packaging.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +"""Validate sourceos-devtools packaging scaffolding.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +FORMULA = ROOT / "packaging/homebrew/Formula/sourceos-devtools.rb" +INSTALL_DOC = ROOT / "docs/install.md" + +REQUIRED_FORMULA_SNIPPETS = [ + "class SourceosDevtools < Formula", + "SourceOS developer and Portable AI Kit operator tools", + "sourceosctl", + "sourceos-portable-ai", + "PortableAIProfiles", +] + +FORBIDDEN_FORMULA_SNIPPETS = [ + "ollama pull", + "ollama run", + "ollama serve", + "HUGGINGFACE", + "HF_TOKEN", + "OPENAI_API_KEY", +] + +REQUIRED_DOC_SNIPPETS = [ + "sourceosctl portable-ai profiles", + "portable-ai preflight", + "portable-ai prepare", + "portable-ai start-plan", + "portable-ai stop-plan", + "portable-ai byom verify", + "prompt egress is denied", +] + + +def fail(message: str) -> int: + print(f"ERR: {message}", file=sys.stderr) + return 1 + + +def main() -> int: + if not FORMULA.exists(): + return fail(f"missing {FORMULA.relative_to(ROOT)}") + if not INSTALL_DOC.exists(): + return fail(f"missing {INSTALL_DOC.relative_to(ROOT)}") + + formula = FORMULA.read_text(encoding="utf-8") + install_doc = INSTALL_DOC.read_text(encoding="utf-8") + + for snippet in REQUIRED_FORMULA_SNIPPETS: + if snippet not in formula: + return fail(f"formula missing required snippet: {snippet}") + for snippet in FORBIDDEN_FORMULA_SNIPPETS: + if snippet in formula: + return fail(f"formula contains forbidden side-effect/secrets snippet: {snippet}") + for snippet in REQUIRED_DOC_SNIPPETS: + if snippet not in install_doc: + return fail(f"install doc missing required snippet: {snippet}") + + print("Packaging validation passed") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From cc514be0fb70f9ab8b9d959c3d1193232765c933 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 14:29:42 -0400 Subject: [PATCH 09/12] Add sourceos-devtools Homebrew formula scaffold on current main --- .../homebrew/Formula/sourceos-devtools.rb | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 packaging/homebrew/Formula/sourceos-devtools.rb diff --git a/packaging/homebrew/Formula/sourceos-devtools.rb b/packaging/homebrew/Formula/sourceos-devtools.rb new file mode 100644 index 0000000..85917d4 --- /dev/null +++ b/packaging/homebrew/Formula/sourceos-devtools.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class SourceosDevtools < Formula + desc "SourceOS developer and Portable AI Kit operator tools" + homepage "https://github.com/SourceOS-Linux/sourceos-devtools" + head "https://github.com/SourceOS-Linux/sourceos-devtools.git", branch: "main" + + depends_on "python@3.12" + + def install + libexec.install Dir["*"] + bin.write_exec_script libexec/"bin/sourceosctl" + bin.write_exec_script libexec/"bin/sourceos-portable-ai" + end + + def caveats + <<~EOS + Portable AI Kit surfaces: + sourceosctl portable-ai profiles + sourceos-portable-ai profiles + + This formula is a packaging scaffold. Runtime activation and policy gates remain in source repositories. + EOS + end +end From a3240dac65f447dfbf16e24dfca0ecb9c25abd8c Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 14:30:19 -0400 Subject: [PATCH 10/12] Add PortableAIProfiles marker to formula caveats --- packaging/homebrew/Formula/sourceos-devtools.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packaging/homebrew/Formula/sourceos-devtools.rb b/packaging/homebrew/Formula/sourceos-devtools.rb index 85917d4..924fbcb 100644 --- a/packaging/homebrew/Formula/sourceos-devtools.rb +++ b/packaging/homebrew/Formula/sourceos-devtools.rb @@ -19,6 +19,9 @@ def caveats sourceosctl portable-ai profiles sourceos-portable-ai profiles + Expected smoke marker: + PortableAIProfiles + This formula is a packaging scaffold. Runtime activation and policy gates remain in source repositories. EOS end From 66d21883ae9dcff8f4f6bea53f52ff5ea9dee9a5 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 14:30:50 -0400 Subject: [PATCH 11/12] Add Portable AI install and smoke guide on current main --- docs/install.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/install.md diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 0000000..d92794a --- /dev/null +++ b/docs/install.md @@ -0,0 +1,42 @@ +# SourceOS Devtools install and smoke guide + +This guide covers the current Portable AI Kit scaffold in `sourceos-devtools`. + +## Local source checkout + +Run from the repository root: + +```bash +python3 bin/sourceosctl portable-ai profiles +python3 bin/sourceosctl portable-ai preflight /tmp/SOURCEOS_AI --profile tiny-router +python3 bin/sourceosctl portable-ai prepare /tmp/SOURCEOS_AI --profile tiny-router --dry-run +python3 bin/sourceosctl portable-ai start-plan /tmp/SOURCEOS_AI --provider ollama-compatible --surface turtleterm +python3 bin/sourceosctl portable-ai stop-plan /tmp/SOURCEOS_AI --provider ollama-compatible +python3 bin/sourceosctl portable-ai byom verify /tmp/SOURCEOS_AI ./models/example.gguf --name example +``` + +The default posture is evidence-first and non-mutating. Prompt egress is denied by default. Tool use is denied by default. Runtime start and stop commands emit plans; they do not launch or stop a provider. + +## Homebrew scaffold + +Before tap promotion, use the repository-local formula for syntax and smoke validation only: + +```bash +brew install --HEAD https://raw.githubusercontent.com/SourceOS-Linux/sourceos-devtools/main/packaging/homebrew/Formula/sourceos-devtools.rb +``` + +After tap promotion: + +```bash +brew install SourceOS-Linux/tap/sourceos-devtools +``` + +## Validation + +```bash +python3 -m unittest discover -s tests -v +make validate +python3 scripts/validate_packaging.py +``` + +The packaging scaffold must not download model weights, call external providers, start local runtimes, store prompt bodies, or bypass Agent Machine / policy-gated runtime activation. From a7fd8d2d97bdbc992055e3db1d0ef1633d545acc Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 22 May 2026 14:34:10 -0400 Subject: [PATCH 12/12] Normalize Portable AI packaging posture text --- docs/install.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/install.md b/docs/install.md index d92794a..47ac7bf 100644 --- a/docs/install.md +++ b/docs/install.md @@ -15,7 +15,7 @@ python3 bin/sourceosctl portable-ai stop-plan /tmp/SOURCEOS_AI --provider ollama python3 bin/sourceosctl portable-ai byom verify /tmp/SOURCEOS_AI ./models/example.gguf --name example ``` -The default posture is evidence-first and non-mutating. Prompt egress is denied by default. Tool use is denied by default. Runtime start and stop commands emit plans; they do not launch or stop a provider. +The default posture is evidence-first and non-mutating. prompt egress is denied by default. Tool use is denied by default. Runtime start and stop commands emit plans; they do not launch or stop a provider. ## Homebrew scaffold