J94/bit-vector-tensor-control-policy
0
1#!/usr/bin/env python32from __future__ import annotations3 4import argparse5import json6import re7import subprocess8import sys9import tempfile10from pathlib import Path11from typing import Any12 13import yaml14 15ROOT = Path(__file__).resolve().parents[1]16DEFAULT_CONFIG = ROOT / "self_improve.yaml"17DEFAULT_SCHEMA = ROOT / "schemas" / "self_improve_proposal_v0.json"18DEFAULT_INFERENCE = ROOT / "inference.yaml"19DEFAULT_USER_GOVERNANCE = ROOT / "build" / "system" / "user_governance.json"20 21 22def load_yaml(path: Path) -> dict[str, Any]:23 data = yaml.safe_load(path.read_text(encoding="utf-8"))24 if not isinstance(data, dict):25 raise ValueError(f"{path} did not decode to a mapping")26 return data27 28 29def load_json(path: Path) -> dict[str, Any]:30 data = json.loads(path.read_text(encoding="utf-8"))31 if not isinstance(data, dict):32 raise ValueError(f"{path} did not decode to an object")33 return data34 35 36def allowed_path(path: str, roots: list[str]) -> bool:37 for root in roots:38 if root.endswith("/"):39 if path.startswith(root):40 return True41 elif path == root:42 return True43 return False44 45 46def backend_timeout_seconds(backend: dict[str, Any]) -> float | None:47 raw = backend.get("timeout_seconds")48 if raw in (None, "", 0):49 return None50 timeout = float(raw)51 if timeout <= 0:52 raise ValueError("timeout_seconds must be positive when configured")53 return timeout54 55 56def sanitize_manifest_id(goal: str) -> str:57 slug = re.sub(r"[^a-z0-9]+", "-", goal.lower()).strip("-")58 slug = slug[:48] or "self-improve"59 return f"self-improve-{slug}"60 61 62def build_prompt(63 *,64 goal: str,65 config: dict[str, Any],66 system_context: dict[str, Any],67 policy: dict[str, Any],68 runtime_contract: dict[str, Any],69 default_benchmark: str,70 user_governance: dict[str, Any] | None,71) -> str:72 compact_context = {73 "current_position": {74 "slice_id": system_context.get("current_position", {}).get("slice_id"),75 "default_profile": system_context.get("current_position", {}).get("default_profile"),76 "role": system_context.get("current_position", {}).get("role"),77 },78 "latest_runtime_state": system_context.get("latest_runtime_state", {}),79 "agent_bootstrap": {80 "trust_order": system_context.get("agent_bootstrap", {}).get("trust_order", []),81 "first_move": system_context.get("agent_bootstrap", {}).get("first_move"),82 },83 }84 compact_policy = {85 "bits": policy.get("bits", []),86 "vectors": policy.get("vectors", []),87 "invariants": policy.get("invariants", []),88 }89 compact_runtime = {90 "one_liner": runtime_contract.get("one_liner"),91 "contract": runtime_contract.get("contract"),92 "acceptance_bar": runtime_contract.get("acceptance_bar", []),93 }94 compact_governance = None95 if user_governance:96 compact_governance = {97 "governing_rules": user_governance.get("governing_rules", []),98 "motif_rule": user_governance.get("motif_rule", ""),99 "next_moves": user_governance.get("next_moves", []),100 "operator_next_tasks": user_governance.get("operator_next_tasks", []),101 }102 return "\n".join(103 [104 "You are proposing one bounded self-improvement for the bit_vector_tensor_control_policy repo.",105 "Produce only JSON matching the schema.",106 "The proposal must be small, concrete, and safe to execute through the local runtime.",107 f"Maximum touched files: {config['max_files']}.",108 f"Allowed roots: {', '.join(config['allowed_roots'])}.",109 "Only use manifest actions of type `write_file`.",110 "Do not propose shell actions.",111 "Return full replacement content for every touched file.",112 "Prefer docs, configs, and thin runtime/policy glue over large rewrites.",113 f"Use this benchmark command unless a narrower benchmark is clearly better: {default_benchmark}.",114 "The change should improve the product shell itself, not produce an external research artifact.",115 "Prefer the highest-ranked partial or requested next move from user governance when it can be advanced in one bounded change.",116 "",117 "System context:",118 json.dumps(compact_context, ensure_ascii=True, separators=(",", ":")),119 "",120 "Policy context:",121 json.dumps(compact_policy, ensure_ascii=True, separators=(",", ":")),122 "",123 "Runtime contract:",124 json.dumps(compact_runtime, ensure_ascii=True, separators=(",", ":")),125 "",126 "User governance:",127 json.dumps(compact_governance or {}, ensure_ascii=True, separators=(",", ":")),128 "",129 f"Improvement goal: {goal}",130 ]131 )132 133 134def validate_proposal(proposal: dict[str, Any], config: dict[str, Any]) -> None:135 roots = config["allowed_roots"]136 target_files = proposal.get("target_files", [])137 if not target_files:138 raise ValueError("proposal did not include target_files")139 if len(target_files) > int(config["max_files"]):140 raise ValueError("proposal exceeded max_files")141 for path in target_files:142 if not allowed_path(path, roots):143 raise ValueError(f"target file outside allowed roots: {path}")144 manifest = proposal.get("manifest", {})145 actions = manifest.get("actions", [])146 if len(actions) == 0:147 raise ValueError("proposal manifest had no actions")148 if len(actions) > int(config["max_files"]):149 raise ValueError("proposal manifest exceeded max_files")150 action_paths = []151 for action in actions:152 if action.get("type") != "write_file":153 raise ValueError("proposal manifest included unsupported action type")154 path = action.get("path", "")155 if not allowed_path(path, roots):156 raise ValueError(f"manifest path outside allowed roots: {path}")157 action_paths.append(path)158 if sorted(target_files) != sorted(action_paths):159 raise ValueError("target_files and manifest action paths diverged")160 161 162def run_codex_proposal(163 *,164 goal: str,165 config_path: Path,166 system_context_path: Path,167 output_path: Path,168 schema_path: Path,169) -> dict[str, Any]:170 config = load_yaml(config_path)171 inference = load_yaml(DEFAULT_INFERENCE)172 backend_id = inference["default_backend"]173 backend = dict(inference["backends"][backend_id])174 proposal_model = config.get("proposal_model")175 if proposal_model:176 backend["model"] = proposal_model177 system_context = load_json(system_context_path)178 policy = load_json(ROOT / "policy" / "control_language_v0.json")179 runtime_contract = load_json(ROOT / "runtime" / "work_manifest_v0.json")180 user_governance = load_json(DEFAULT_USER_GOVERNANCE) if DEFAULT_USER_GOVERNANCE.exists() else None181 default_benchmark = config["default_benchmark"]["command"]182 prompt = build_prompt(183 goal=goal,184 config=config,185 system_context=system_context,186 policy=policy,187 runtime_contract=runtime_contract,188 default_benchmark=default_benchmark,189 user_governance=user_governance,190 )191 192 output_path.parent.mkdir(parents=True, exist_ok=True)193 with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as temp_schema:194 temp_schema.write(schema_path.read_text(encoding="utf-8"))195 temp_schema_path = Path(temp_schema.name)196 197 command = [backend.get("command", "codex"), "exec"]198 if backend.get("model"):199 command.extend(["-m", str(backend["model"])])200 if backend.get("sandbox"):201 command.extend(["-s", str(backend["sandbox"])])202 if backend.get("ephemeral", False):203 command.append("--ephemeral")204 if backend.get("skip_git_repo_check", False):205 command.append("--skip-git-repo-check")206 command.extend(207 [208 "-C",209 str(ROOT),210 "--output-schema",211 str(temp_schema_path),212 "-o",213 str(output_path),214 "-",215 ]216 )217 218 timeout_seconds = config.get("proposal_timeout_seconds")219 if timeout_seconds in (None, "", 0):220 timeout = backend_timeout_seconds(backend)221 else:222 timeout = float(timeout_seconds)223 if timeout <= 0:224 raise ValueError("proposal_timeout_seconds must be positive when configured")225 226 try:227 completed = subprocess.run(228 command,229 input=prompt,230 text=True,231 capture_output=True,232 cwd=ROOT,233 check=False,234 timeout=timeout,235 )236 except subprocess.TimeoutExpired as exc:237 raise RuntimeError(f"codex exec timed out after {exc.timeout} seconds") from exc238 finally:239 temp_schema_path.unlink(missing_ok=True)240 241 if completed.returncode != 0:242 raise RuntimeError(completed.stderr.strip() or "codex exec failed")243 244 proposal = load_json(output_path)245 proposal["manifest"]["manifest_id"] = sanitize_manifest_id(goal)246 proposal["manifest"]["goal"] = proposal.get("goal", goal)247 if not proposal.get("benchmark", {}).get("command"):248 proposal["benchmark"] = {"command": default_benchmark}249 validate_proposal(proposal, config)250 output_path.write_text(json.dumps(proposal, indent=2, sort_keys=True) + "\n", encoding="utf-8")251 return proposal252 253 254def main() -> int:255 parser = argparse.ArgumentParser(description="Use Codex CLI to propose one bounded self-improvement manifest.")256 parser.add_argument("--goal", required=True)257 parser.add_argument("--config", default=str(DEFAULT_CONFIG))258 parser.add_argument("--schema", default=str(DEFAULT_SCHEMA))259 parser.add_argument("--system-context", required=True)260 parser.add_argument("--output", required=True)261 args = parser.parse_args()262 263 proposal = run_codex_proposal(264 goal=args.goal,265 config_path=Path(args.config),266 system_context_path=Path(args.system_context),267 output_path=Path(args.output),268 schema_path=Path(args.schema),269 )270 json.dump(proposal, sys.stdout, indent=2)271 sys.stdout.write("\n")272 return 0273 274 275if __name__ == "__main__":276 raise SystemExit(main())277 