Files
aios/active-runtime/app/aios_external_agent_runtime.py
T

1054 lines
43 KiB
Python

"""Credential-isolated external Agent runtime executor for Online Agent Tasks.
Task payloads never choose commands. A root-owned runtime profile chooses the executable and
I/O mode. The Online Agent worker writes a hash-bound request into an exchange directory; this
uncredentialed process executes Hermes/OpenClaw and returns a bounded, sanitized result.
"""
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import json
import os
from pathlib import Path
import re
import subprocess
import tempfile
import time
from typing import Any, Mapping, Sequence
from aios_model_governance import (
ModelGovernanceError,
ModelGovernanceStore,
RouteRequest,
)
REQUEST_SCHEMA = "aios.external-agent-runtime.request.v1"
RESULT_SCHEMA = "aios.external-agent-runtime.result.v1"
PROFILE_SCHEMA = "aios.external-agent-runtime.profile.v1"
SUPPORTED_RUNTIMES = frozenset({"hermes", "openclaw"})
REQUEST_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{15,254}$")
RUNTIME_RE = re.compile(r"^[a-z][a-z0-9_-]{2,31}$")
SENSITIVE_RE = re.compile(
r"(?i)(authorization\s*[:=]\s*bearer\s+\S+|"
r"(?:password|passwd|secret|token|api[_-]?key)\s*[:=]\s*\S+)"
)
SECRET_ENV_MARKERS = (
"TOKEN",
"SECRET",
"PASSWORD",
"CREDENTIAL",
"AUTHORIZATION",
"PRIVATE_KEY",
"API_KEY",
)
PROFILE_ENV_NAME_RE = re.compile(r"^[A-Z][A-Z0-9_]{1,63}$")
PROFILE_CREDENTIAL_NAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{2,127}$")
PROFILE_CREDENTIAL_KEY_RE = re.compile(r"^[A-Z][A-Z0-9_]{1,63}$")
PROVIDER_CREDENTIAL_ENV_NAMES = frozenset({"OPENAI_API_KEY"})
PROFILE_FIXED_ENV_DENY = frozenset(
{
"PATH",
"HOME",
"NO_COLOR",
"LC_ALL",
"LANG",
"LD_PRELOAD",
"LD_LIBRARY_PATH",
"PYTHONPATH",
"NODE_OPTIONS",
"BASH_ENV",
"SHELL",
"IFS",
}
)
MAX_REQUEST_BYTES = 256 * 1024
MAX_OUTPUT_BYTES = 128 * 1024
MAX_PROFILE_BYTES = 64 * 1024
MAX_CREDENTIAL_BYTES = 64 * 1024
DEFAULT_PROFILE_ROOT = Path("/etc/aios/external-agent-runtime.d")
DEFAULT_EXCHANGE_ROOT = Path("/var/lib/aios-external-agent-executor")
DEFAULT_MODEL_GOVERNANCE_DB = Path(
os.environ.get(
"AIOS_MODEL_GOVERNANCE_DB",
"/var/lib/aios-model-governance/state.sqlite3",
)
)
MODEL_GOVERNANCE_ENABLED = os.environ.get(
"AIOS_MODEL_GOVERNANCE_ENABLED", "0"
).strip().lower() in {"1", "true", "yes", "on"}
ALLOWED_EXECUTABLE_ROOTS = (
Path("/usr/bin"),
Path("/usr/local/bin"),
Path("/opt/aios/bin"),
Path("/opt/hermes"),
Path("/opt/openclaw"),
Path("/opt/aios-hermes-agent"),
Path("/opt/aios/openclaw-engineering"),
)
class ExternalAgentRuntimeError(RuntimeError):
pass
def canonical(value: object) -> bytes:
return json.dumps(
value, ensure_ascii=False, sort_keys=True, separators=(",", ":")
).encode("utf-8")
def sha256(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def sanitize_text(value: str) -> str:
return SENSITIVE_RE.sub("[REDACTED]", str(value))
def _bounded_text(value: str, limit: int = MAX_OUTPUT_BYTES) -> str:
data = sanitize_text(value).encode("utf-8", "replace")
if len(data) <= limit:
return data.decode("utf-8", "replace")
marker = b"\n...[TRUNCATED]...\n"
half = max(1, (limit - len(marker)) // 2)
return (data[:half] + marker + data[-half:]).decode("utf-8", "replace")
def _safe_runtime(value: object) -> str:
runtime = str(value or "").strip().lower()
if RUNTIME_RE.fullmatch(runtime) is None or runtime not in SUPPORTED_RUNTIMES:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_RUNTIME_UNSUPPORTED")
return runtime
def _assert_no_secret_env() -> None:
leaked = sorted(
key
for key, value in os.environ.items()
if value and any(marker in key.upper() for marker in SECRET_ENV_MARKERS)
)
# CREDENTIALS_DIRECTORY itself is systemd metadata; the directory is expected to be hidden.
leaked = [key for key in leaked if key != "CREDENTIALS_DIRECTORY"]
if leaked:
raise ExternalAgentRuntimeError(
"EXTERNAL_AGENT_CREDENTIAL_ENV_EXPOSED:" + ",".join(leaked)
)
def _safe_executable(value: object) -> str:
if not isinstance(value, str) or not value.startswith("/"):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_EXECUTABLE_ABSOLUTE_REQUIRED")
path = Path(value)
if path.is_symlink():
resolved = path.resolve(strict=True)
else:
resolved = path.resolve(strict=True)
if not resolved.is_file() or not os.access(resolved, os.X_OK):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_EXECUTABLE_INVALID")
if not any(resolved == root or root in resolved.parents for root in ALLOWED_EXECUTABLE_ROOTS):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_EXECUTABLE_ROOT_DENY")
return str(resolved)
def _safe_profile_script(value: object) -> str:
if not isinstance(value, str) or not value.startswith("/"):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_SCRIPT_ABSOLUTE_REQUIRED")
path = Path(value)
if path.is_symlink():
resolved = path.resolve(strict=True)
else:
resolved = path.resolve(strict=True)
if not resolved.is_file():
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_SCRIPT_INVALID")
if not any(resolved == root or root in resolved.parents for root in ALLOWED_EXECUTABLE_ROOTS):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_SCRIPT_ROOT_DENY")
return str(resolved)
def _profile_environment(value: object) -> tuple[tuple[str, str], ...]:
if value is None:
return ()
if not isinstance(value, Mapping):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_PROFILE_ENVIRONMENT_INVALID")
result: list[tuple[str, str]] = []
for raw_name, raw_value in value.items():
if not isinstance(raw_name, str) or PROFILE_ENV_NAME_RE.fullmatch(raw_name) is None:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_PROFILE_ENVIRONMENT_NAME_INVALID")
if raw_name in PROFILE_FIXED_ENV_DENY:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_PROFILE_ENVIRONMENT_NAME_DENY")
if any(marker in raw_name.upper() for marker in SECRET_ENV_MARKERS):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_PROFILE_SECRET_ENV_DENY")
if not isinstance(raw_value, str) or "\x00" in raw_value or "\n" in raw_value or "\r" in raw_value:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_PROFILE_ENVIRONMENT_VALUE_INVALID")
if len(raw_value) > 4096:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_PROFILE_ENVIRONMENT_VALUE_TOO_LARGE")
result.append((raw_name, raw_value))
return tuple(sorted(result))
def _profile_credential_environment(
value: object,
) -> tuple[tuple[str, str, str, str], ...]:
if value is None:
return ()
if not isinstance(value, Mapping):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_PROFILE_CREDENTIAL_ENV_INVALID")
result: list[tuple[str, str, str, str]] = []
for raw_env_name, raw_spec in value.items():
if (
not isinstance(raw_env_name, str)
or raw_env_name not in PROVIDER_CREDENTIAL_ENV_NAMES
or PROFILE_ENV_NAME_RE.fullmatch(raw_env_name) is None
):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_CREDENTIAL_ENV_NAME_DENY")
if not isinstance(raw_spec, Mapping):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_CREDENTIAL_SPEC_INVALID")
credential = raw_spec.get("credential")
fmt = str(raw_spec.get("format") or "dotenv")
key = raw_spec.get("key")
if (
not isinstance(credential, str)
or PROFILE_CREDENTIAL_NAME_RE.fullmatch(credential) is None
):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_CREDENTIAL_NAME_INVALID")
if fmt not in {"dotenv", "raw"}:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_CREDENTIAL_FORMAT_INVALID")
if fmt == "dotenv":
if not isinstance(key, str) or PROFILE_CREDENTIAL_KEY_RE.fullmatch(key) is None:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_CREDENTIAL_KEY_INVALID")
elif key is not None:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_CREDENTIAL_KEY_UNEXPECTED")
result.append((raw_env_name, credential, fmt, str(key or "")))
return tuple(sorted(result))
def _profile_model_governance(
value: object,
) -> tuple[bool, tuple[str, ...], tuple[str, ...], int, int]:
if value is None:
return False, (), (), 0, 0
if not isinstance(value, Mapping):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_MODEL_GOVERNANCE_PROFILE_INVALID")
allowed = {
"required",
"provider_ids",
"model_capabilities",
"max_output_tokens",
"input_reservation_floor_tokens",
}
if set(value) - allowed:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_MODEL_GOVERNANCE_PROFILE_FIELDS_INVALID")
required = bool(value.get("required", False))
providers = value.get("provider_ids") or []
capabilities = value.get("model_capabilities") or []
if (
not isinstance(providers, Sequence)
or isinstance(providers, (str, bytes))
or not all(isinstance(item, str) and item.strip() for item in providers)
):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_MODEL_PROVIDER_IDS_INVALID")
if (
not isinstance(capabilities, Sequence)
or isinstance(capabilities, (str, bytes))
or not all(isinstance(item, str) and item.strip() for item in capabilities)
):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_MODEL_CAPABILITIES_INVALID")
max_output_tokens = int(value.get("max_output_tokens") or 0)
input_reservation_floor_tokens = int(
value.get("input_reservation_floor_tokens") or 0
)
if max_output_tokens < 0 or max_output_tokens > 262144:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_MODEL_OUTPUT_LIMIT_INVALID")
if (
input_reservation_floor_tokens < 0
or input_reservation_floor_tokens > 262144
):
raise ExternalAgentRuntimeError(
"EXTERNAL_AGENT_MODEL_INPUT_RESERVATION_FLOOR_INVALID"
)
if required and not providers:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_MODEL_PROVIDER_IDS_REQUIRED")
if required and max_output_tokens < 1:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_MODEL_OUTPUT_LIMIT_REQUIRED")
return (
required,
tuple(sorted({item.strip() for item in providers})),
tuple(sorted({item.strip() for item in capabilities})),
max_output_tokens,
input_reservation_floor_tokens,
)
def _estimate_tokens(text: str) -> int:
# Conservative byte-based estimate; runtime-reported usage supersedes this at settlement.
data = text.encode("utf-8", "replace")
return max(1, (len(data) + 3) // 4)
def _extract_runtime_usage(
output_json: object,
) -> tuple[int | None, int | None, int | None]:
if not isinstance(output_json, Mapping):
return None, None, None
candidates: list[Mapping[str, Any]] = []
direct = output_json.get("usage")
if isinstance(direct, Mapping):
candidates.append(direct)
meta = output_json.get("meta")
if isinstance(meta, Mapping):
agent_meta = meta.get("agentMeta")
if isinstance(agent_meta, Mapping):
nested = agent_meta.get("usage")
if isinstance(nested, Mapping):
candidates.append(nested)
last_call = agent_meta.get("lastCallUsage")
if isinstance(last_call, Mapping):
candidates.append(last_call)
def number(mapping: Mapping[str, Any], *names: str) -> int | None:
for name in names:
value = mapping.get(name)
if isinstance(value, int) and not isinstance(value, bool) and value >= 0:
return value
return None
input_tokens = None
output_tokens = None
cost_micros = None
for usage in candidates:
if input_tokens is None:
input_tokens = number(usage, "input_tokens", "prompt_tokens", "input")
if output_tokens is None:
output_tokens = number(
usage, "output_tokens", "completion_tokens", "output"
)
if cost_micros is None:
cost_micros = number(usage, "cost_micros", "total_cost_micros")
if cost_micros is None and isinstance(meta, Mapping):
agent_meta = meta.get("agentMeta")
if isinstance(agent_meta, Mapping):
cost_usd = agent_meta.get("costUsd")
if (
isinstance(cost_usd, (int, float))
and not isinstance(cost_usd, bool)
and cost_usd >= 0
):
cost_micros = int(round(float(cost_usd) * 1_000_000))
return input_tokens, output_tokens, cost_micros
def _scope_from_payload(payload: Mapping[str, Any]) -> tuple[str, str | None, str | None, str]:
scope = payload.get("scope")
if not isinstance(scope, Mapping):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_MODEL_SCOPE_MISSING")
scope_type = str(scope.get("scope_type") or "legacy")
tenant_id = scope.get("tenant_id")
project_id = scope.get("project_id")
trace_id = str(payload.get("trace_id") or "").strip()
if not trace_id:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_MODEL_TRACE_ID_MISSING")
return (
scope_type,
str(tenant_id) if tenant_id not in (None, "") else None,
str(project_id) if project_id not in (None, "") else None,
trace_id,
)
def _model_replacements(decision: object | None) -> dict[str, str]:
if decision is None:
return {}
return {
"{provider_id}": str(getattr(decision, "provider_id")),
"{model_id}": str(getattr(decision, "model_id")),
"{model_version}": str(getattr(decision, "model_version")),
}
def _bind_model_values(
values: Sequence[tuple[str, str]],
decision: object | None,
) -> tuple[tuple[str, str], ...]:
replacements = _model_replacements(decision)
result: list[tuple[str, str]] = []
for key, value in values:
bound = value
for marker, replacement in replacements.items():
bound = bound.replace(marker, replacement)
result.append((key, bound))
return tuple(result)
def _runtime_model_binding_present(profile: "RuntimeProfile") -> bool:
markers = ("{model_id}", "{model_version}", "{provider_id}")
return any(marker in item for item in (*profile.argv, *(value for _, value in profile.environment)) for marker in markers)
@dataclass(frozen=True)
class RuntimeProfile:
runtime: str
argv: tuple[str, ...]
input_mode: str
output_mode: str
timeout_seconds: int
max_output_bytes: int
environment: tuple[tuple[str, str], ...]
credential_env: tuple[tuple[str, str, str, str], ...]
model_governance_required: bool
model_provider_ids: tuple[str, ...]
model_capabilities: tuple[str, ...]
model_max_output_tokens: int
model_input_reservation_floor_tokens: int
@classmethod
def load(
cls,
runtime: str,
profile_root: Path = DEFAULT_PROFILE_ROOT,
) -> "RuntimeProfile":
runtime = _safe_runtime(runtime)
root = Path(profile_root)
if root.is_symlink():
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_PROFILE_ROOT_SYMLINK_DENY")
path = root / f"{runtime}.json"
if path.is_symlink() or not path.is_file() or path.stat().st_size > MAX_PROFILE_BYTES:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_PROFILE_UNAVAILABLE")
try:
value = json.loads(path.read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_PROFILE_INVALID") from exc
if not isinstance(value, Mapping) or value.get("schema") != PROFILE_SCHEMA:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_PROFILE_INVALID")
if _safe_runtime(value.get("runtime")) != runtime:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_PROFILE_RUNTIME_MISMATCH")
argv = value.get("argv")
if (
not isinstance(argv, Sequence)
or isinstance(argv, (str, bytes))
or not argv
or not all(isinstance(item, str) and item and "\x00" not in item for item in argv)
):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_PROFILE_ARGV_INVALID")
executable = _safe_executable(argv[0])
normalized = [executable, *list(argv[1:])]
if any(item in {"sh", "bash", "-c", "--command"} for item in normalized[1:]):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_SHELL_MODE_DENY")
joined_argv = " ".join(normalized)
if re.search(r"(?i)(token|secret|password|api[_-]?key|authorization)\s*[:=]", joined_argv):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_PROFILE_SECRET_ARG_DENY")
if Path(executable).name in {"node", "nodejs"}:
if len(normalized) < 2 or normalized[1].startswith("-"):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_NODE_SCRIPT_REQUIRED")
normalized[1] = _safe_profile_script(normalized[1])
if any(item in {"-e", "--eval", "-p", "--print", "-r", "--require"} for item in normalized[1:]):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_NODE_CODE_MODE_DENY")
input_mode = str(value.get("input_mode") or "stdin_text")
output_mode = str(value.get("output_mode") or "stdout_text")
if input_mode not in {"stdin_text", "stdin_json", "file_json"}:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_INPUT_MODE_INVALID")
if output_mode not in {"stdout_text", "stdout_json", "file_json"}:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_OUTPUT_MODE_INVALID")
timeout = int(value.get("timeout_seconds") or 900)
max_output = int(value.get("max_output_bytes") or MAX_OUTPUT_BYTES)
if not 1 <= timeout <= 3600:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_TIMEOUT_INVALID")
if not 1024 <= max_output <= MAX_OUTPUT_BYTES:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_OUTPUT_LIMIT_INVALID")
environment = _profile_environment(value.get("environment"))
credential_env = _profile_credential_environment(value.get("credential_env"))
(
model_governance_required,
model_provider_ids,
model_capabilities,
model_max_output_tokens,
model_input_reservation_floor_tokens,
) = _profile_model_governance(value.get("model_governance"))
profile = cls(
runtime,
tuple(normalized),
input_mode,
output_mode,
timeout,
max_output,
environment,
credential_env,
model_governance_required,
model_provider_ids,
model_capabilities,
model_max_output_tokens,
model_input_reservation_floor_tokens,
)
if profile.model_governance_required and not _runtime_model_binding_present(profile):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_MODEL_BINDING_REQUIRED")
return profile
def validate_request(value: Mapping[str, Any]) -> dict[str, Any]:
if value.get("schema") != REQUEST_SCHEMA:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_REQUEST_SCHEMA_INVALID")
request_id = str(value.get("request_id") or "")
if REQUEST_ID_RE.fullmatch(request_id) is None:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_REQUEST_ID_INVALID")
runtime = _safe_runtime(value.get("runtime"))
task_id = str(value.get("task_id") or "")
attempt = value.get("attempt")
execution_digest = str(value.get("execution_digest") or "")
if not task_id or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._:-]{2,179}", task_id):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_TASK_ID_INVALID")
if (
not isinstance(attempt, int)
or isinstance(attempt, bool)
or not 1 <= attempt <= 1000
):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_ATTEMPT_INVALID")
if re.fullmatch(r"[0-9a-f]{64}", execution_digest) is None:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_EXECUTION_DIGEST_INVALID")
payload = value.get("payload")
if not isinstance(payload, Mapping):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_PAYLOAD_INVALID")
encoded = canonical(payload)
if len(encoded) > MAX_REQUEST_BYTES:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_PAYLOAD_TOO_LARGE")
goal = payload.get("goal")
instructions = payload.get("instructions")
if not isinstance(goal, str) or not goal.strip():
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_GOAL_REQUIRED")
if instructions is not None and not isinstance(instructions, str):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_INSTRUCTIONS_INVALID")
return {
"schema": REQUEST_SCHEMA,
"request_id": request_id,
"runtime": runtime,
"task_id": task_id,
"attempt": attempt,
"execution_digest": execution_digest,
"payload": dict(payload),
}
def render_prompt(payload: Mapping[str, Any]) -> str:
goal = str(payload.get("goal") or "").strip()
instructions = str(payload.get("instructions") or "").strip()
criteria = payload.get("success_criteria") or []
constraints = payload.get("constraints") or {}
lines = [
"AIOS Online Agent Task",
"",
"Goal:",
goal,
]
if instructions:
lines.extend(["", "Instructions:", instructions])
if criteria:
lines.extend(["", "Success criteria:", json.dumps(criteria, ensure_ascii=False, sort_keys=True)])
if constraints:
lines.extend(["", "Constraints:", json.dumps(constraints, ensure_ascii=False, sort_keys=True)])
lines.extend([
"",
"Return the final task result only. Do not claim repository, production, credential, or control-plane side effects unless they are explicitly represented in the task payload.",
])
return "\n".join(lines)
def _argv_with_files(
profile: RuntimeProfile,
input_path: Path,
output_path: Path,
prompt: str,
model_decision: object | None = None,
) -> list[str]:
replacements = {
"{input_json}": str(input_path),
"{output_json}": str(output_path),
"{prompt}": prompt,
**_model_replacements(model_decision),
}
result = []
for item in profile.argv:
for marker, replacement in replacements.items():
item = item.replace(marker, replacement)
result.append(item)
if profile.input_mode == "file_json" and not any("{input_json}" in item for item in profile.argv):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_INPUT_PLACEHOLDER_REQUIRED")
if profile.output_mode == "file_json" and not any("{output_json}" in item for item in profile.argv):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_OUTPUT_PLACEHOLDER_REQUIRED")
return result
def _credential_environment(profile: RuntimeProfile) -> dict[str, str]:
if not profile.credential_env:
return {}
raw_root = os.environ.get("CREDENTIALS_DIRECTORY")
if not raw_root or not raw_root.startswith("/"):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_CREDENTIAL_DIRECTORY_MISSING")
root = Path(raw_root)
if root.is_symlink() or not root.is_dir():
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_CREDENTIAL_DIRECTORY_INVALID")
result: dict[str, str] = {}
for env_name, credential, fmt, key in profile.credential_env:
path = root / credential
if path.is_symlink() or not path.is_file():
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_CREDENTIAL_FILE_INVALID")
try:
if path.stat().st_size > MAX_CREDENTIAL_BYTES:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_CREDENTIAL_TOO_LARGE")
raw = path.read_bytes()
except OSError as exc:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_CREDENTIAL_READ_FAILED") from exc
if b"\x00" in raw:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_CREDENTIAL_CONTENT_INVALID")
try:
text = raw.decode("utf-8")
except UnicodeDecodeError as exc:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_CREDENTIAL_CONTENT_INVALID") from exc
selected = ""
if fmt == "raw":
selected = text.strip()
else:
found = False
for line in text.splitlines():
candidate = line.strip()
if not candidate or candidate.startswith("#"):
continue
if candidate.startswith("export "):
candidate = candidate[7:].lstrip()
if "=" not in candidate:
continue
candidate_key, candidate_value = candidate.split("=", 1)
if candidate_key.strip() != key:
continue
if found:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_CREDENTIAL_KEY_DUPLICATE")
selected = candidate_value.strip()
if len(selected) >= 2 and selected[0] == selected[-1] and selected[0] in {"'", '"'}:
selected = selected[1:-1]
found = True
if not found:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_CREDENTIAL_KEY_MISSING")
if not selected or "\x00" in selected or "\n" in selected or "\r" in selected:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_CREDENTIAL_VALUE_INVALID")
if len(selected) > MAX_CREDENTIAL_BYTES:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_CREDENTIAL_VALUE_TOO_LARGE")
result[env_name] = selected
return result
def _parse_output(
profile: RuntimeProfile,
stdout: str,
output_path: Path,
) -> tuple[str, object]:
if profile.output_mode == "file_json":
if output_path.is_symlink() or not output_path.is_file():
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_OUTPUT_FILE_MISSING")
raw = output_path.read_bytes()
if len(raw) > profile.max_output_bytes:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_OUTPUT_TOO_LARGE")
text = raw.decode("utf-8", "replace")
try:
parsed = json.loads(text)
except json.JSONDecodeError as exc:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_OUTPUT_JSON_INVALID") from exc
return _bounded_text(text, profile.max_output_bytes), parsed
if len(stdout.encode("utf-8", "replace")) > profile.max_output_bytes:
stdout = _bounded_text(stdout, profile.max_output_bytes)
if profile.output_mode == "stdout_json":
try:
parsed = json.loads(stdout)
except json.JSONDecodeError as exc:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_OUTPUT_JSON_INVALID") from exc
return _bounded_text(stdout, profile.max_output_bytes), parsed
return _bounded_text(stdout, profile.max_output_bytes), None
def execute_request(
request_path: Path,
result_path: Path,
*,
profile_root: Path = DEFAULT_PROFILE_ROOT,
) -> None:
_assert_no_secret_env()
if request_path.is_symlink() or not request_path.is_file():
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_REQUEST_FILE_INVALID")
raw_request = request_path.read_bytes()
if len(raw_request) > MAX_REQUEST_BYTES:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_REQUEST_TOO_LARGE")
try:
request = validate_request(json.loads(raw_request.decode("utf-8")))
except json.JSONDecodeError as exc:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_REQUEST_JSON_INVALID") from exc
profile = RuntimeProfile.load(request["runtime"], profile_root)
governance_store: ModelGovernanceStore | None = None
route_request: RouteRequest | None = None
decision = None
governance_attempts: list[dict[str, Any]] = []
with tempfile.TemporaryDirectory(prefix=f"aios-{request['runtime']}-") as temporary:
temp_root = Path(temporary)
(temp_root / "home").mkdir()
input_path = temp_root / "input.json"
output_path = temp_root / "output.json"
input_path.write_bytes(canonical(request["payload"]))
prompt = render_prompt(request["payload"])
if profile.model_governance_required:
if not MODEL_GOVERNANCE_ENABLED:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_MODEL_GOVERNANCE_DISABLED")
scope_type, tenant_id, project_id, trace_id = _scope_from_payload(request["payload"])
governance_store = ModelGovernanceStore(DEFAULT_MODEL_GOVERNANCE_DB)
prompt_estimated_input_tokens = _estimate_tokens(prompt)
reserved_estimated_input_tokens = max(
prompt_estimated_input_tokens,
profile.model_input_reservation_floor_tokens,
)
route_request = RouteRequest(
request_id=request["request_id"] + ":model",
task_id=request["task_id"],
trace_id=trace_id,
scope_type=scope_type,
tenant_id=tenant_id,
project_id=project_id,
required_capabilities=tuple(
sorted(
{
f"agent.runtime.{request['runtime']}",
*profile.model_capabilities,
}
)
),
estimated_input_tokens=reserved_estimated_input_tokens,
max_output_tokens=profile.model_max_output_tokens,
)
try:
decision = governance_store.route(route_request)
except ModelGovernanceError as exc:
raise ExternalAgentRuntimeError(exc.code) from exc
if profile.model_provider_ids and decision.provider_id not in profile.model_provider_ids:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_MODEL_PROVIDER_PROFILE_DENY")
governance_attempts.append(
{
"request_id": decision.request_id,
"provider_id": decision.provider_id,
"model_id": decision.model_id,
"model_version": decision.model_version,
"fallback_index": decision.fallback_index,
}
)
try:
while True:
argv = _argv_with_files(
profile,
input_path,
output_path,
prompt,
decision,
)
stdin_data = None
if profile.input_mode == "stdin_json":
stdin_data = canonical(request["payload"]).decode("utf-8")
elif profile.input_mode == "stdin_text":
stdin_data = prompt
child_env = {
"PATH": "/usr/bin:/usr/local/bin:/bin",
"LC_ALL": "C.UTF-8",
"LANG": "C.UTF-8",
"HOME": str(temp_root / "home"),
"NO_COLOR": "1",
}
child_env.update(dict(_bind_model_values(profile.environment, decision)))
child_env.update(_credential_environment(profile))
started = time.monotonic()
failure_class = None
try:
completed = subprocess.run(
argv,
input=stdin_data,
capture_output=True,
text=True,
check=False,
shell=False,
timeout=profile.timeout_seconds,
cwd=str(temp_root),
env=child_env,
)
if completed.returncode != 0:
failure_class = f"process_exit_{completed.returncode}"
except subprocess.TimeoutExpired:
completed = None
failure_class = "timeout"
if failure_class is None and completed is not None:
break
if governance_store is None or decision is None or route_request is None:
if failure_class == "timeout":
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_TIMEOUT")
raise ExternalAgentRuntimeError(
f"EXTERNAL_AGENT_PROCESS_FAILED:{completed.returncode}:"
+ _bounded_text(completed.stderr, 2048)
)
try:
governance_store.record_failure(decision.request_id, failure_class)
decision = governance_store.fallback(route_request, decision.request_id)
except ModelGovernanceError as exc:
raise ExternalAgentRuntimeError(exc.code) from exc
if profile.model_provider_ids and decision.provider_id not in profile.model_provider_ids:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_MODEL_PROVIDER_PROFILE_DENY")
governance_attempts.append(
{
"request_id": decision.request_id,
"provider_id": decision.provider_id,
"model_id": decision.model_id,
"model_version": decision.model_version,
"fallback_index": decision.fallback_index,
"previous_failure_class": failure_class,
}
)
output_text, output_json = _parse_output(profile, completed.stdout, output_path)
model_governance: dict[str, Any] | None = None
if governance_store is not None and decision is not None:
input_tokens, output_tokens, actual_cost = _extract_runtime_usage(output_json)
try:
settlement = governance_store.record_success(
decision.request_id,
input_tokens=input_tokens,
output_tokens=output_tokens,
actual_cost_micros=actual_cost,
usage_source="runtime",
)
except ModelGovernanceError as exc:
raise ExternalAgentRuntimeError(exc.code) from exc
model_governance = {
"decision_id": decision.decision_id,
"request_id": decision.request_id,
"provider_id": decision.provider_id,
"model_id": decision.model_id,
"model_version": decision.model_version,
"policy_revision": decision.policy_revision,
"model_revision": decision.model_revision,
"fallback_index": decision.fallback_index,
"prompt_estimated_input_tokens": prompt_estimated_input_tokens,
"input_reservation_floor_tokens": (
profile.model_input_reservation_floor_tokens
),
"reserved_estimated_input_tokens": (
reserved_estimated_input_tokens
),
"settlement": settlement,
"attempts": governance_attempts,
"audit_chain_valid": governance_store.verify_audit_chain(),
}
result: dict[str, Any] = {
"schema": RESULT_SCHEMA,
"request_id": request["request_id"],
"request_sha256": sha256(canonical(request)),
"runtime": request["runtime"],
"task_id": request["task_id"],
"attempt": request["attempt"],
"execution_digest": request["execution_digest"],
"exit_code": completed.returncode,
"duration_ms": int((time.monotonic() - started) * 1000),
"output_text": output_text,
"output_sha256": sha256(output_text.encode("utf-8", "replace")),
"stderr_sha256": sha256(completed.stderr.encode("utf-8", "replace")),
"credential_material_exposed": False,
"control_plane_access_granted": False,
}
if output_json is not None:
result["output_json"] = output_json
if model_governance is not None:
result["model_governance"] = model_governance
result["result_sha256"] = sha256(canonical(result))
result_path.parent.mkdir(parents=True, exist_ok=True)
fd, name = tempfile.mkstemp(prefix=f".{result_path.name}.", dir=result_path.parent)
try:
os.fchmod(fd, 0o640)
with os.fdopen(fd, "w", encoding="utf-8") as handle:
json.dump(
result,
handle,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
)
handle.write("\n")
handle.flush()
os.fsync(handle.fileno())
os.replace(name, result_path)
finally:
if os.path.exists(name):
os.unlink(name)
finally:
if governance_store is not None:
governance_store.close()
class ExternalAgentExchange:
"""Worker-side idempotent request/result exchange."""
def __init__(self, root: Path = DEFAULT_EXCHANGE_ROOT) -> None:
self.root = Path(root)
def execute(
self,
*,
request_id: str,
runtime: str,
task_id: str,
attempt: int,
execution_digest: str,
payload: Mapping[str, Any],
timeout_seconds: int = 1800,
) -> dict[str, Any]:
request = validate_request(
{
"schema": REQUEST_SCHEMA,
"request_id": request_id,
"runtime": runtime,
"task_id": task_id,
"attempt": attempt,
"execution_digest": execution_digest,
"payload": dict(payload),
}
)
requests = self.root / "requests"
results = self.root / "results"
failed = self.root / "failed"
for directory in (requests, results, failed):
directory.mkdir(parents=True, exist_ok=True)
request_path = requests / f"{request_id}.json"
result_path = results / f"{request_id}.json"
failed_path = failed / f"{request_id}.json"
request_sha = sha256(canonical(request))
if result_path.is_file():
return self._validated_result(result_path, request, request_sha)
if failed_path.is_file():
value = json.loads(failed_path.read_text(encoding="utf-8"))
raise ExternalAgentRuntimeError(
str(value.get("error") or "EXTERNAL_AGENT_EXECUTOR_FAILED")
)
if request_path.exists():
try:
existing = json.loads(request_path.read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_EXISTING_REQUEST_INVALID") from exc
if sha256(canonical(existing)) != request_sha:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_REQUEST_ID_CONFLICT")
else:
fd, name = tempfile.mkstemp(prefix=f".{request_path.name}.", dir=requests)
try:
os.fchmod(fd, 0o640)
with os.fdopen(fd, "w", encoding="utf-8") as handle:
json.dump(request, handle, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
handle.write("\n")
handle.flush()
os.fsync(handle.fileno())
os.replace(name, request_path)
finally:
if os.path.exists(name):
os.unlink(name)
deadline = time.monotonic() + timeout_seconds
while time.monotonic() < deadline:
if result_path.is_file():
return self._validated_result(result_path, request, request_sha)
if failed_path.is_file():
try:
value = json.loads(failed_path.read_text(encoding="utf-8"))
except json.JSONDecodeError:
value = {"error": "EXTERNAL_AGENT_EXECUTOR_FAILED"}
raise ExternalAgentRuntimeError(
str(value.get("error") or "EXTERNAL_AGENT_EXECUTOR_FAILED")
)
time.sleep(0.25)
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_EXCHANGE_TIMEOUT")
@staticmethod
def _validated_result(
result_path: Path,
request: Mapping[str, Any],
request_sha: str,
) -> dict[str, Any]:
try:
value = json.loads(result_path.read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_RESULT_INVALID") from exc
if (
not isinstance(value, Mapping)
or value.get("schema") != RESULT_SCHEMA
or value.get("request_id") != request["request_id"]
or value.get("request_sha256") != request_sha
or value.get("runtime") != request["runtime"]
or value.get("task_id") != request["task_id"]
or value.get("attempt") != request["attempt"]
or value.get("execution_digest") != request["execution_digest"]
):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_RESULT_BINDING_INVALID")
material = dict(value)
declared = str(material.pop("result_sha256", ""))
if declared != sha256(canonical(material)):
raise ExternalAgentRuntimeError("EXTERNAL_AGENT_RESULT_DIGEST_INVALID")
return dict(value)
def serve(
root: Path = DEFAULT_EXCHANGE_ROOT,
profile_root: Path = DEFAULT_PROFILE_ROOT,
) -> None:
_assert_no_secret_env()
root = Path(root)
for directory in (root / "requests", root / "results", root / "failed"):
directory.mkdir(parents=True, exist_ok=True)
while True:
for request_path in sorted((root / "requests").glob("*.json")):
if request_path.is_symlink():
continue
result_path = root / "results" / request_path.name
failed_path = root / "failed" / request_path.name
try:
execute_request(request_path, result_path, profile_root=profile_root)
request_path.unlink(missing_ok=True)
except Exception as exc:
failed_path.write_text(
json.dumps(
{"error": sanitize_text(str(exc) or type(exc).__name__)},
ensure_ascii=False,
sort_keys=True,
)
+ "\n",
encoding="utf-8",
)
request_path.unlink(missing_ok=True)
time.sleep(0.25)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--root", default=str(DEFAULT_EXCHANGE_ROOT))
parser.add_argument("--profile-root", default=str(DEFAULT_PROFILE_ROOT))
parser.add_argument("--request")
parser.add_argument("--result")
args = parser.parse_args()
if args.request and args.result:
execute_request(
Path(args.request),
Path(args.result),
profile_root=Path(args.profile_root),
)
else:
serve(Path(args.root), Path(args.profile_root))