feat: appove and go

This commit is contained in:
thanhnv
2026-07-19 09:37:16 +07:00
parent 13fae3e6c3
commit 709b6cccd6
24 changed files with 1245 additions and 70 deletions
@@ -8,10 +8,12 @@ audit, or metric records.
import argparse
import fcntl
import hashlib
import importlib.util
import json
import os
import re
import shlex
import shutil
import subprocess
import tempfile
import time
@@ -36,6 +38,11 @@ SECURITY = os.path.join(BIN, "security-check.sh")
STATE_ROOT = os.environ.get("CASAN_STATE_ROOT") or os.path.join(ROOT, ".specify")
PROJECT_REGISTRY = os.path.join(ROOT, "packages", "casan-harness", "level5", "project-registry.json")
APPROVAL_INBOX = os.path.join(BIN, "approval-inbox.py")
_MANIFEST_SPEC = importlib.util.spec_from_file_location(
"casan_goal_project_manifest", os.path.join(BIN, "project_manifest.py")
)
PROJECT_MANIFEST = importlib.util.module_from_spec(_MANIFEST_SPEC)
_MANIFEST_SPEC.loader.exec_module(PROJECT_MANIFEST)
CONTEXT_EXTENSIONS = {".md", ".txt", ".json", ".yaml", ".yml", ".ts", ".tsx", ".js", ".mjs", ".py", ".sh", ".prisma", ".css", ".html"}
CONTEXT_IGNORED = {"node_modules", ".git", "dist", "build", "coverage", ".vite", "tmp", "logs", "__pycache__"}
SENSITIVE_NAMES = {".env", ".env.local", "credentials", "credentials.json", "secrets.json", "id_rsa", "id_ed25519"}
@@ -173,8 +180,35 @@ def context_excerpt_is_sensitive(text: str) -> bool:
))
def restricted_context_paths(goal: str):
"""Return an explicit user-declared file/directory boundary, if present."""
section = re.search(
r"(?:chỉ được đọc và thay đổi|chỉ làm việc trong|only (?:read and )?(?:modify|change)|only work (?:in|within))\s*:\s*"
r"(.*?)(?=\n\s*(?:không sửa bất kỳ file nào khác|do not (?:modify|change) any other file|không thay đổi|không thêm|vấn đề|yêu cầu|verification|$))",
goal,
re.IGNORECASE | re.DOTALL,
)
if not section:
return []
paths = []
for match in re.findall(r"(?:apps|packages|docs)/[A-Za-z0-9_./*-]+", section.group(1), re.IGNORECASE):
cleaned = match.rstrip(".,:;)")
scoped_directory = cleaned.endswith(("/*", "/**"))
normalized = cleaned.rstrip("/*")
if normalized and all(path != normalized for path, _ in paths):
paths.append((normalized, scoped_directory))
return paths
def context_candidates(project: dict, goal: str):
terms = {term.lower() for term in re.findall(r"[A-Za-z0-9_-]{3,}", goal)}
explicit_paths = []
for match in re.findall(r"(?:apps|packages|docs)/[A-Za-z0-9_./*-]+", goal, re.IGNORECASE):
cleaned = match.rstrip(".,:;)")
scoped_directory = cleaned.endswith(("/*", "/**"))
normalized_path = cleaned.rstrip("/*").lower()
if normalized_path and all(path != normalized_path for path, _ in explicit_paths):
explicit_paths.append((normalized_path, scoped_directory))
candidates = []
seen = set()
for relative_root, absolute_root in project["roots"]:
@@ -200,6 +234,22 @@ def context_candidates(project: dict, goal: str):
continue
haystack = (relative + "\n" + raw[:4000]).lower()
score = sum(4 if term in relative.lower() else 1 for term in terms if term in haystack)
relative_lower = relative.lower()
matching_paths = [
(path, scoped_directory)
for path, scoped_directory in explicit_paths
if relative_lower == path or relative_lower.startswith(path + "/")
]
if matching_paths:
# Directory globs under "only work in" sections describe the
# actual patch surface. Exact source-file mentions come next;
# architecture/requirement references remain supporting context.
if any(scoped_directory for _, scoped_directory in matching_paths):
score += 3_000
elif os.path.splitext(relative_lower)[1] in {".ts", ".tsx", ".js", ".jsx", ".py", ".prisma", ".sql", ".sh"}:
score += 2_000
else:
score += 1_000
if relative.endswith(("README.md", "architecture.md", "technical_architecture.md", "package.json")):
score += 3
candidates.append((score, relative, raw))
@@ -209,6 +259,17 @@ def context_candidates(project: dict, goal: str):
def build_context(job_path: str, project_id: str, goal: str, write_intent=False):
project = registered_project(project_id)
candidates = context_candidates(project, goal)
restricted_paths = restricted_context_paths(goal)
if restricted_paths:
candidates = [
candidate for candidate in candidates
if any(
candidate[1] == path or (scoped_directory and candidate[1].startswith(path.rstrip("/") + "/"))
for path, scoped_directory in restricted_paths
)
]
if not candidates:
raise ValueError("goal_context_explicit_scope_empty")
excerpts, manifest_files, characters = [], [], 0
# A diff can only apply when the model sees the exact target-file content.
# Read-only analysis stays compact; write-intent gives the three most
@@ -261,12 +322,39 @@ def build_context(job_path: str, project_id: str, goal: str, write_intent=False)
def requests_side_effect(goal: str) -> bool:
normalized = " ".join(goal.lower().split())
patterns = [
r"^(hãy\s+)?(làm luôn|sửa|thay đổi|triển khai|thực hiện|hoàn thành|chạy|tạo|xóa|cài đặt|commit|push)\b",
# The objective's leading command is authoritative. A scoped constraint
# such as "Không thay đổi API contract" must not turn "Hoàn thiện ..."
# into a read-only request. Conversely, a genuinely read-only audit does
# not begin with one of these implementation commands.
leading_write_patterns = [
r"^(hãy\s+)?(làm luôn|sửa|thay đổi|triển khai|thực hiện|hoàn thiện|hoàn thành|chạy|tạo|xóa|cài đặt|commit|push)\b",
r"^(please\s+)?(implement|fix|change|deploy|run|create|delete|install|commit|push)\b",
r"\b(thực hiện thao tác|sửa code|ghi file|mở pull request|create a pull request)\b",
]
return any(re.search(pattern, normalized) for pattern in patterns)
if any(re.search(pattern, normalized) for pattern in leading_write_patterns):
return True
patch_requests = re.finditer(r"\b(tạo|generate|xuất|produce)\b.{0,40}\b(patch|unified git diff)\b", normalized)
explicit_patch_request = any(
not normalized[max(0, match.start() - 8):match.start()].endswith(("không ", "do not "))
for match in patch_requests
)
explicit_read_only = bool(re.search(r"\b(không|do not)\s+(sửa|thay đổi|triển khai|implement|fix|tạo|apply|áp dụng)\b", normalized))
if explicit_read_only and not explicit_patch_request:
return False
imperative_patterns = [r"\b(thực hiện thao tác|sửa code|ghi file|mở pull request|create a pull request)\b"]
if any(re.search(pattern, normalized) for pattern in imperative_patterns):
return True
# Structured objectives commonly start with a phase label rather than the
# imperative itself (for example "PHASE 1 — Hoàn thiện backend..."). Keep
# those write requests on the patch + approval path without classifying a
# read-only audit that merely mentions source files as a side effect.
structured_write_patterns = [
r"\b(hoàn thiện|triển khai|implement|fix|sửa|thay đổi)\b.{0,120}\b(ứng dụng|backend|frontend|module|component|api|source|code|file)\b",
r"\b(tạo|generate|xuất|produce)\b.{0,40}\b(patch|unified git diff)\b",
r"\b(apply|áp dụng)\b.{0,40}\b(patch|thay đổi|change)\b",
]
return any(re.search(pattern, normalized) for pattern in structured_write_patterns)
def extract_patch(text: str) -> str:
@@ -344,6 +432,17 @@ def normalize_unified_diff(patch: str) -> str:
return "\n".join(normalized) + "\n" if changed else patch
def mechanical_patch_error(error: object) -> bool:
"""Identify git parser errors that hunk recounting can safely repair."""
normalized = str(error).lower()
return any(marker in normalized for marker in (
"corrupt patch",
"patch fragment without header",
"malformed patch",
"unexpected end of file in patch",
))
def validate_write_output(text: str) -> str:
"""Fail at the producing harness when a write-intent reply is not a diff.
@@ -356,7 +455,7 @@ def validate_write_output(text: str) -> str:
validate_patch_check(patch)
return patch
except ValueError as original_error:
if "corrupt patch" not in str(original_error):
if not mechanical_patch_error(original_error):
raise
normalized = normalize_unified_diff(patch)
if normalized == patch:
@@ -387,14 +486,106 @@ def patch_repair_models(primary_model: str):
"""Use direct cloud -> gateway -> local order for one bounded H2 recovery."""
configured = [item.strip() for item in os.environ.get("CASAN_GOAL_PATCH_REPAIR_MODELS", "").split(",") if item.strip()]
unique, seen = [], set()
for candidate in configured + [primary_model]:
ordered = [primary_model, *configured] if primary_model.startswith("account:") else [*configured, primary_model]
for candidate in ordered:
if candidate and candidate not in seen:
seen.add(candidate)
unique.append(candidate)
return unique
def model_preflight_enabled() -> bool:
configured = os.environ.get("CASAN_GOAL_MODEL_PREFLIGHT")
return configured == "1" if configured is not None else os.environ.get("CASAN_PROFILE") == "prod"
def model_preflight(model: str):
"""Probe real patch generation before assigning a model to H2/H3.
Model discovery only proves that an ID is advertised. This bounded probe
verifies the generation route and unified-diff capability, and caches the
verdict so a broken gateway is not retried for every goal.
"""
if not model_preflight_enabled():
return True, "preflight_disabled"
if model.startswith("account:"):
endpoint = os.environ.get("CASAN_AUTH_BRIDGE_URL", "")
elif model.startswith("openai-compatible:"):
endpoint = os.environ.get("CASAN_OPENAI_COMPATIBLE_BASE_URL", "")
else:
endpoint = os.environ.get("OLLAMA_HOST", "")
cache_key = sha(f"{model}|{endpoint}")
cache_directory = os.path.join(STATE_ROOT, "cache")
cache_path = os.path.join(cache_directory, "goal-model-preflight.json")
lock_path = os.path.join(cache_directory, "goal-model-preflight.lock")
os.makedirs(cache_directory, mode=0o700, exist_ok=True)
ttl = int(os.environ.get("CASAN_GOAL_MODEL_PREFLIGHT_TTL_SEC", "600"))
with open(lock_path, "a", encoding="utf-8") as lock:
fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
try:
cache = load_json(cache_path) if os.path.isfile(cache_path) else {}
except (OSError, ValueError, json.JSONDecodeError):
cache = {}
cached = cache.get(cache_key, {}) if isinstance(cache, dict) else {}
age = time.time() - float(cached.get("checked_epoch", 0))
if age <= max(30, ttl):
fcntl.flock(lock.fileno(), fcntl.LOCK_UN)
return bool(cached.get("healthy")), f"cached:{cached.get('reason', 'unknown')}"
fcntl.flock(lock.fileno(), fcntl.LOCK_UN)
prompt = (
"Patch capability probe. Return ONLY a unified git diff inside a ```diff fence that changes "
"the only line in probe.txt from old to new. No prose. The diff must begin with "
"`diff --git a/probe.txt b/probe.txt`."
)
timeout_setting = (
os.environ.get("CASAN_GOAL_LOCAL_MODEL_PREFLIGHT_TIMEOUT_SEC", "90")
if model.startswith("ollama:")
else os.environ.get("CASAN_GOAL_MODEL_PREFLIGHT_TIMEOUT_SEC", "30")
)
timeout = max(5, min(int(timeout_setting), 120))
if model.startswith("account:"):
ok, output, _, reason = call_account_model(model.split(":", 1)[1], prompt, timeout)
else:
ok, output, _, reason = call_model(
model, prompt, model.startswith(("openai:", "anthropic:", "openai-compatible:")),
timeout_seconds=timeout, max_output_tokens=512,
)
healthy = False
if ok:
try:
patch = extract_patch(output)
healthy = (
"diff --git a/probe.txt b/probe.txt" in patch
and "\n-old\n" in patch
and "\n+new\n" in patch
)
reason = "ok" if healthy else "patch_probe_contract_invalid"
except ValueError as error:
reason = str(error)
record = {
"model": model,
"provider": provider_for_model(model),
"healthy": healthy,
"reason": reason[:180],
"checked_at": now(),
"checked_epoch": time.time(),
}
with open(lock_path, "a", encoding="utf-8") as lock:
fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
try:
cache = load_json(cache_path) if os.path.isfile(cache_path) else {}
except (OSError, ValueError, json.JSONDecodeError):
cache = {}
cache[cache_key] = record
atomic_json(cache_path, cache)
fcntl.flock(lock.fileno(), fcntl.LOCK_UN)
return healthy, reason
def provider_for_model(model: str) -> str:
if model.startswith("account:"):
return f"{model.split(':', 1)[1]}-account"
if model.startswith("openai:"):
return "openai"
if model.startswith("anthropic:"):
@@ -420,7 +611,7 @@ def repair_write_output(model: str, original_prompt: str, invalid_output: str, c
def repair_prompt_for(previous_output: str, error: str) -> str:
hunk_instruction = (
"The previous diff is syntactically corrupt. Recompute every `@@ -old,count +new,count @@` header from the exact added/removed/context lines that follow it; do not omit or invent any hunk line. "
if "corrupt patch" in error.lower() else ""
if mechanical_patch_error(error) else ""
)
return (
f"Your previous response violated the required write-output contract: {error}. " +
@@ -434,7 +625,21 @@ def repair_write_output(model: str, original_prompt: str, invalid_output: str, c
last_metadata, last_reason = {}, "goal_patch_missing"
attempts = []
for attempt_number, candidate in enumerate(candidates, start=1):
ok, output, metadata, reason = call_model(candidate, repair_prompt, candidate.startswith(("openai:", "anthropic:", "openai-compatible:")), max_output_tokens=patch_output_tokens())
healthy, preflight_reason = model_preflight(candidate)
if not healthy:
last_reason = f"goal_patch_model_preflight_failed:{preflight_reason}"
attempts.append({
"attempt": attempt_number,
"provider": provider_for_model(candidate),
"model": candidate,
"status": "failed",
"reason": last_reason,
})
continue
if candidate.startswith("account:"):
ok, output, metadata, reason = call_account_model(candidate.split(":", 1)[1], repair_prompt, 300)
else:
ok, output, metadata, reason = call_model(candidate, repair_prompt, candidate.startswith(("openai:", "anthropic:", "openai-compatible:")), max_output_tokens=patch_output_tokens())
metadata = dict(metadata)
metadata["repair_model"] = candidate
metadata["repair_attempt"] = attempt_number
@@ -464,7 +669,7 @@ def repair_write_output(model: str, original_prompt: str, invalid_output: str, c
# incomplete first diff. Give the same stronger direct model one
# corrective pass containing its own failed patch and git error
# before sending source context to a weaker gateway.
if candidate.endswith("-codex") and attempt_number < patch_repair_attempts():
if (candidate.endswith("-codex") or candidate == "account:codex") and attempt_number < patch_repair_attempts():
candidates.insert(attempt_number, candidate)
del candidates[patch_repair_attempts():]
repair_prompt = repair_prompt_for(output, last_reason)
@@ -493,8 +698,52 @@ def merge_usage(primary: dict, additional: dict) -> dict:
return merged
def validate_patch_semantics(job: dict, artifact: str, changed_files: list[str]) -> list[dict]:
"""Build and test an applied patch in an isolated workspace before approval."""
artifact = os.path.realpath(artifact)
if os.path.commonpath([ROOT, artifact]) != ROOT or not os.path.isfile(artifact):
raise ValueError("goal_patch_artifact_path_denied")
manifest = PROJECT_MANIFEST.load(ROOT, project_id=str(job.get("project") or ""))
commands = PROJECT_MANIFEST.verification_commands(manifest, changed_files)
if not commands:
raise ValueError("goal_patch_verification_unmapped")
results = []
with tempfile.TemporaryDirectory(prefix="casan-goal-verify-") as sandbox:
paths = ["package.json", "package-lock.json", *manifest["source_roots"]]
paths.extend(str(value) for value in job.get("workspace", {}).get("context_roots", []))
for relative in dict.fromkeys(paths):
source = os.path.realpath(os.path.join(ROOT, relative))
if os.path.commonpath([ROOT, source]) != ROOT or not os.path.exists(source):
continue
destination = os.path.join(sandbox, relative)
os.makedirs(os.path.dirname(destination), exist_ok=True)
if os.path.isdir(source):
shutil.copytree(source, destination, dirs_exist_ok=True, symlinks=True, ignore=shutil.ignore_patterns("node_modules", "dist", "coverage"))
else:
shutil.copy2(source, destination)
dependencies = os.path.join(ROOT, "node_modules")
if os.path.isdir(dependencies):
os.symlink(dependencies, os.path.join(sandbox, "node_modules"), target_is_directory=True)
applied = subprocess.run(
["git", "apply", "--whitespace=error", artifact], cwd=sandbox,
capture_output=True, text=True, timeout=30,
)
if applied.returncode != 0:
raise ValueError("goal_patch_sandbox_apply_failed:" + (applied.stderr or applied.stdout).strip()[:300])
timeout = max(30, int(os.environ.get("CASAN_GOAL_PATCH_VERIFY_TIMEOUT_SEC", "300")))
for command in commands:
result = subprocess.run(command, cwd=sandbox, capture_output=True, text=True, timeout=timeout)
output = (result.stdout + result.stderr)[-4000:]
results.append({"command": " ".join(command), "exit_code": result.returncode, "output": output})
if result.returncode != 0:
raise ValueError("goal_patch_verification_failed:" + output[-1200:])
return results
def validate_and_store_patch(job_path: str, job: dict, patch: str) -> dict:
roots = [str(value).strip("/") for value in job.get("workspace", {}).get("context_roots", [])]
explicit_scope = restricted_context_paths(str(job.get("goal") or ""))
roots = [path.strip("/") for path, _ in explicit_scope] or [str(value).strip("/") for value in job.get("workspace", {}).get("context_roots", [])]
directory_roots = {path.strip("/") for path, scoped_directory in explicit_scope if scoped_directory}
changed = []
header_paths = []
for line in patch.splitlines():
@@ -514,7 +763,7 @@ def validate_and_store_patch(job_path: str, job: dict, patch: str) -> dict:
path = path[2:]
if path.startswith("/") or ".." in path.split("/"):
raise ValueError("goal_patch_path_denied")
if not any(path == root or path.startswith(root + "/") for root in roots):
if not any(path == root or (root in directory_roots and path.startswith(root + "/")) for root in roots):
raise ValueError(f"goal_patch_outside_workspace:{path}")
changed.append(path)
if not changed or len(set(changed)) > 20:
@@ -529,7 +778,12 @@ def validate_and_store_patch(job_path: str, job: dict, patch: str) -> dict:
if check.returncode != 0:
os.unlink(artifact)
raise ValueError("goal_patch_check_failed:" + (check.stderr or check.stdout).strip()[:160])
return {"path": os.path.relpath(artifact, ROOT), "sha256": sha(patch), "files": sorted(set(changed)), "bytes": len(patch.encode("utf-8")), "status": "awaiting_approval", "preview": patch[:50_000]}
try:
verification = validate_patch_semantics(job, artifact, sorted(set(changed)))
except Exception:
os.unlink(artifact)
raise
return {"path": os.path.relpath(artifact, ROOT), "sha256": sha(patch), "files": sorted(set(changed)), "bytes": len(patch.encode("utf-8")), "status": "awaiting_approval", "preview": patch[:50_000], "preapproval_verification": verification}
def submit_side_effect(job: dict, manifest: dict, patch_artifact: dict) -> dict:
@@ -702,8 +956,8 @@ def reviewer_candidates(account_provider: str, cloud_model: str, local_model: st
candidates.append({"kind": "model", "provider": "omniroute", "model": value, "value": value})
local_reviewer = os.environ.get("CASAN_GOAL_LOCAL_REVIEWER_MODEL", "").strip() or local_model
if local_reviewer:
candidates.append({"kind": "model", "provider": os.environ.get("CASAN_GOAL_LOCAL_PROVIDER", "local-policy"), "model": local_reviewer, "value": local_reviewer})
if local_reviewer and os.environ.get("CASAN_GOAL_ENABLE_LOCAL_REVIEWER", "1") == "1":
candidates.append({"kind": "model", "provider": os.environ.get("CASAN_GOAL_LOCAL_REVIEWER_PROVIDER", os.environ.get("CASAN_GOAL_LOCAL_PROVIDER", "local-policy")), "model": local_reviewer, "value": local_reviewer})
unique, seen = [], set()
for candidate in candidates:
@@ -714,15 +968,17 @@ def reviewer_candidates(account_provider: str, cloud_model: str, local_model: st
return unique
def run_reviewer_chain(job_path: str, prompt: str, account_provider: str, cloud_model: str, local_model: str):
def run_reviewer_chain(job_path: str, prompt: str, account_provider: str, cloud_model: str, local_model: str, max_output_tokens=None, excluded_models=None):
max_attempts = max(1, min(int(os.environ.get("CASAN_GOAL_REVIEWER_MAX_ATTEMPTS", "5")), 10))
deadline_seconds = max(1, min(int(os.environ.get("CASAN_GOAL_REVIEWER_DEADLINE_SEC", "360")), 900))
deadline = time.monotonic() + deadline_seconds
ledger = []
update_job(job_path, reviewer_attempts=ledger)
last_reason = "reviewer_candidates_unavailable"
candidates = reviewer_candidates(account_provider, cloud_model, local_model)
if len(candidates) > max_attempts and candidates[-1].get("provider") == os.environ.get("CASAN_GOAL_LOCAL_PROVIDER", "local-policy"):
excluded = set(excluded_models or [])
candidates = [candidate for candidate in reviewer_candidates(account_provider, cloud_model, local_model) if candidate.get("value") not in excluded]
local_reviewer_provider = os.environ.get("CASAN_GOAL_LOCAL_REVIEWER_PROVIDER", os.environ.get("CASAN_GOAL_LOCAL_PROVIDER", "local-policy"))
if len(candidates) > max_attempts and candidates[-1].get("provider") == local_reviewer_provider:
candidates = candidates[:max_attempts - 1] + [candidates[-1]] if max_attempts > 1 else [candidates[-1]]
else:
candidates = candidates[:max_attempts]
@@ -734,10 +990,26 @@ def run_reviewer_chain(job_path: str, prompt: str, account_provider: str, cloud_
attempt_number = len(ledger) + 1
stage(job_path, "cloud-reviewer", "running", f"Reviewer attempt {attempt_number}/{max_attempts}", candidate["provider"], candidate["model"])
started_at, started_clock = now(), time.monotonic()
if candidate["kind"] == "model" and max_output_tokens is not None:
healthy, preflight_reason = model_preflight(candidate["value"])
if not healthy:
reason = f"model_preflight_failed:{preflight_reason}"
ledger.append({
"attempt": attempt_number, "provider": candidate["provider"], "model": candidate["model"],
"status": "failed", "reason": reason, "retryable": True,
"started_at": started_at, "finished_at": now(),
"latency_ms": int((time.monotonic() - started_clock) * 1000),
})
update_job(job_path, reviewer_attempts=ledger)
last_reason = reason
continue
if candidate["kind"] == "account":
ok, result, metadata, reason = call_account_model(candidate["value"], prompt, remaining)
else:
ok, result, metadata, reason = call_model(candidate["value"], prompt, candidate["provider"] != os.environ.get("CASAN_GOAL_LOCAL_PROVIDER", "local-policy"), remaining)
if max_output_tokens is None:
ok, result, metadata, reason = call_model(candidate["value"], prompt, candidate["provider"] != local_reviewer_provider, remaining)
else:
ok, result, metadata, reason = call_model(candidate["value"], prompt, candidate["provider"] != local_reviewer_provider, remaining, max_output_tokens=max_output_tokens)
retryable = False if ok else reviewer_failure_retryable(reason)
ledger.append({
"attempt": attempt_number, "provider": candidate["provider"], "model": candidate["model"],
@@ -846,6 +1118,12 @@ def run(job_path: str) -> int:
})
emit(goal_id, "H4-security", "running", "Objective and workspace snapshot passed; model outputs pending")
local_healthy, local_preflight_reason = model_preflight(local_model)
if not local_healthy:
stage(job_path, "local-worker", "error", f"model_preflight_failed:{local_preflight_reason}", job.get("local_provider", ""), local_model)
emit(goal_id, "H2-tool", "error", "Local worker failed patch-generation preflight", {"provider": job.get("local_provider", ""), "model": local_model, "reason": local_preflight_reason})
raise RuntimeError(f"local_worker_preflight_failed:{local_preflight_reason}")
stage(job_path, "local-worker", "running", "Local model is developing the primary solution", job.get("local_provider", ""), local_model)
emit(goal_id, "H2-tool", "running", "Local worker is developing a solution", {"provider": job.get("local_provider", ""), "model": local_model})
output_contract = (
@@ -859,7 +1137,15 @@ def run(job_path: str) -> int:
"Do not claim to inspect any filesystem outside this snapshot and do not perform side effects.\n\n"
f"OBJECTIVE:\n{safe_goal}\n\nWORKSPACE SNAPSHOT ({project_id}):\n{context_bundle}"
)
ok, local_draft, local_meta, reason = call_model(local_model, local_prompt, False)
if local_model.startswith("account:"):
ok, local_draft, local_meta, reason = call_account_model(local_model.split(":", 1)[1], local_prompt, 300)
else:
ok, local_draft, local_meta, reason = call_model(
local_model,
local_prompt,
False,
max_output_tokens=patch_output_tokens() if write_intent else None,
)
if not ok:
stage(job_path, "local-worker", "error", reason, job.get("local_provider", ""), local_model)
emit(goal_id, "H2-tool", "error", "Local worker failed", {"reason": reason})
@@ -897,14 +1183,18 @@ def run(job_path: str) -> int:
stage(job_path, "local-worker", "error", reason, provider_for_model(actual_repair_model), actual_repair_model)
emit(goal_id, "H2-tool", "error", "Worker violated patch output contract after repair", {"reason": reason, "repair_provider": provider_for_model(actual_repair_model), "repair_model": actual_repair_model, "repair_attempts": repaired_meta.get("repair_attempts", [])})
raise ValueError(reason)
stage(job_path, "local-worker", "pass", "Primary solution prepared", job.get("local_provider", ""), local_model)
effective_local_model = str(local_meta.get("repair_model") or local_model)
effective_local_provider = provider_for_model(effective_local_model)
stage(job_path, "local-worker", "pass", "Primary solution prepared", effective_local_provider, effective_local_model)
update_job(
job_path,
local_draft=safe_local,
local_usage=local_meta,
patch_repair_attempts=local_meta.get("repair_attempts", []),
effective_local_provider=effective_local_provider,
effective_local_model=effective_local_model,
)
emit(goal_id, "H2-tool", "pass", "Local solution prepared", {"provider": job.get("local_provider", ""), "model": local_model, **local_meta})
emit(goal_id, "H2-tool", "pass", "Primary solution prepared", {"provider": effective_local_provider, "model": effective_local_model, **local_meta})
stage(job_path, "cloud-reviewer", "running", "Independent reviewer is challenging and improving the local solution", job.get("cloud_provider", ""), cloud_model)
emit(goal_id, "H3-eval", "running", "Cloud reviewer is evaluating the local solution", {"provider": job.get("cloud_provider", ""), "model": cloud_model})
@@ -923,7 +1213,13 @@ def run(job_path: str) -> int:
f"LOCAL WORKER PROPOSAL:\n{safe_local[:5000]}"
)
cloud_ok, cloud_result, cloud_meta, cloud_reason, reviewer = run_reviewer_chain(
job_path, review_prompt, account_provider, cloud_model, local_model
job_path,
review_prompt,
account_provider,
cloud_model,
local_model,
max_output_tokens=patch_output_tokens() if write_intent else None,
excluded_models={effective_local_model},
)
reviewer_provider = str(reviewer.get("provider") or job.get("cloud_provider", ""))
reviewer_model = str(reviewer.get("model") or cloud_model)
@@ -965,8 +1261,13 @@ def run(job_path: str) -> int:
patch_artifact["approval_id"] = proposal["id"]
final_status = "requires_approval"
metric_status = "degraded"
safe_result = "Implementation patch generated and independently reviewed. Approval is required before applying it to the workspace."
emit(goal_id, "H7-orchestration", "blocked", "Reviewed patch awaits approval", {"proposal_id": proposal["id"], "patch_sha256": patch_artifact["sha256"]})
if cloud_ok:
safe_result = "Implementation patch generated and independently reviewed. Approval is required before applying it to the workspace."
wait_detail = "Independently reviewed patch awaits approval"
else:
safe_result = "Implementation patch generated and validated. Automated H3 review was unavailable, so Independent Reviewer approval is required before applying it to the workspace."
wait_detail = "Validated patch awaits Independent Reviewer approval after H3 degradation"
emit(goal_id, "H7-orchestration", "blocked", wait_detail, {"proposal_id": proposal["id"], "patch_sha256": patch_artifact["sha256"], "cloud_incorporated": cloud_ok})
job = update_job(job_path, status=final_status, result=safe_result, patch_artifact=patch_artifact, approval=approval, cloud_usage=cloud_meta, finished_at=now())
else:
job = update_job(job_path, status=final_status, result=safe_result, cloud_usage=cloud_meta, finished_at=now())