feat: Railiance package and deploy (HARNESS-WP-0001-T06)
Container image, k8s namespace/deployment/smoke job, host venv install path, and deterministic agent-harness smoke (sandbox commit+push+hub) for remote verification without Claude Code on the worker host.
This commit is contained in:
parent
4144eba160
commit
67f1791491
15 changed files with 520 additions and 1 deletions
|
|
@ -106,6 +106,27 @@ def main(argv: list[str] | None = None) -> int:
|
|||
|
||||
sub.add_parser("profiles", help="List named tool profiles")
|
||||
|
||||
smoke = sub.add_parser(
|
||||
"smoke",
|
||||
help="Deterministic Railiance smoke: commit SMOKE.md, optional push, hub event",
|
||||
)
|
||||
smoke.add_argument(
|
||||
"--target-repo",
|
||||
help="Existing git checkout (default: clone executor-sandbox under --work-dir)",
|
||||
)
|
||||
smoke.add_argument(
|
||||
"--work-dir",
|
||||
default="~/work/executor-sandbox",
|
||||
help="Clone destination when --target-repo is omitted",
|
||||
)
|
||||
smoke.add_argument(
|
||||
"--remote",
|
||||
default="ssh://git@forgejo-agent-harness/coulomb/executor-sandbox.git",
|
||||
help="Git remote for sandbox clone",
|
||||
)
|
||||
smoke.add_argument("--no-hub", action="store_true", help="Skip hub reporting")
|
||||
smoke.add_argument("--no-push", action="store_true", help="Skip git push after commit")
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
if args.command == "validate":
|
||||
|
|
@ -114,6 +135,49 @@ def main(argv: list[str] | None = None) -> int:
|
|||
if args.command == "profiles":
|
||||
return _cmd_profiles(args)
|
||||
|
||||
if args.command == "smoke":
|
||||
from agent_harness.smoke import ensure_sandbox_clone, run_smoke
|
||||
|
||||
if args.target_repo:
|
||||
repo = Path(args.target_repo).expanduser().resolve()
|
||||
else:
|
||||
try:
|
||||
repo = ensure_sandbox_clone(
|
||||
Path(args.work_dir).expanduser(),
|
||||
remote=args.remote,
|
||||
)
|
||||
except Exception as exc:
|
||||
print(f"smoke clone failed: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
try:
|
||||
smoke_result = run_smoke(
|
||||
repo,
|
||||
report_to_hub=not args.no_hub,
|
||||
push=not args.no_push,
|
||||
)
|
||||
except Exception as exc:
|
||||
print(f"smoke failed: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"ok": smoke_result.run.ok and (
|
||||
smoke_result.pushed or args.no_push
|
||||
),
|
||||
"committed": smoke_result.run.committed,
|
||||
"pushed": smoke_result.pushed,
|
||||
"head_after": smoke_result.run.head_after,
|
||||
"tool_profile": smoke_result.run.tool_profile,
|
||||
"reason": smoke_result.run.reason,
|
||||
"push_reason": smoke_result.push_reason,
|
||||
"target_repo": str(repo),
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
ok = smoke_result.run.ok and (smoke_result.pushed or args.no_push)
|
||||
return 0 if ok else 1
|
||||
|
||||
if args.command == "mail-scan":
|
||||
from agent_harness.mailscan import run_mail_scan
|
||||
|
||||
|
|
|
|||
144
agent_harness/smoke.py
Normal file
144
agent_harness/smoke.py
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
"""Deterministic remote smoke without an LLM session.
|
||||
|
||||
Proves packaging, git write to the target repo, kaizen metrics, and hub
|
||||
reporting on Railiance. Full agentic sessions still need Claude Code (or a
|
||||
hosted adapter); this path is the T06 end-to-end gate when the CLI is absent.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from agent_harness.runner import RunResult, run_task
|
||||
from agent_harness.taskspec import TaskSpec
|
||||
|
||||
|
||||
@dataclass
|
||||
class SmokeResult:
|
||||
run: RunResult
|
||||
pushed: bool
|
||||
push_reason: str = ""
|
||||
|
||||
|
||||
class _CommittingSmokeAdapter:
|
||||
"""Minimal adapter: write SMOKE.md and commit (no network, no push)."""
|
||||
|
||||
def __init__(self, repo: Path, stamp: str):
|
||||
self.repo = repo
|
||||
self.stamp = stamp
|
||||
self.prompts: list[str] = []
|
||||
|
||||
def execute_prompt(self, prompt, config):
|
||||
self.prompts.append(prompt)
|
||||
path = self.repo / "SMOKE.md"
|
||||
path.write_text(
|
||||
f"# agent-harness smoke\n\nstamp: {self.stamp}\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
subprocess.run(["git", "add", "SMOKE.md"], cwd=self.repo, check=True)
|
||||
subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"-c",
|
||||
"user.email=agent-harness@railiance.local",
|
||||
"-c",
|
||||
"user.name=agent-harness",
|
||||
"commit",
|
||||
"-qm",
|
||||
f"harness smoke: {self.stamp}",
|
||||
],
|
||||
cwd=self.repo,
|
||||
check=True,
|
||||
)
|
||||
from llm_connect.models import LLMResponse
|
||||
|
||||
return LLMResponse(
|
||||
content=f"smoke committed {self.stamp}",
|
||||
model="smoke-adapter",
|
||||
usage={"input_tokens": 0, "output_tokens": 0},
|
||||
finish_reason="stop",
|
||||
)
|
||||
|
||||
|
||||
def _git(repo: Path, *args: str, check: bool = True) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
["git", "-C", str(repo), *args],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
check=check,
|
||||
)
|
||||
|
||||
|
||||
def ensure_sandbox_clone(
|
||||
dest: Path,
|
||||
*,
|
||||
remote: str = "ssh://git@forgejo-agent-harness/coulomb/executor-sandbox.git",
|
||||
) -> Path:
|
||||
"""Clone or fetch the executor-sandbox repo at *dest*."""
|
||||
dest = Path(dest).expanduser()
|
||||
if (dest / ".git").is_dir():
|
||||
_git(dest, "fetch", "origin", check=False)
|
||||
_git(dest, "checkout", "main", check=False)
|
||||
_git(dest, "pull", "--ff-only", "origin", "main", check=False)
|
||||
return dest
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
subprocess.run(
|
||||
["git", "clone", remote, str(dest)],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
)
|
||||
return dest
|
||||
|
||||
|
||||
def run_smoke(
|
||||
target_repo: Path,
|
||||
*,
|
||||
report_to_hub: bool = True,
|
||||
push: bool = True,
|
||||
agent: str = "coach",
|
||||
) -> SmokeResult:
|
||||
target_repo = Path(target_repo).expanduser().resolve()
|
||||
if not (target_repo / ".git").is_dir():
|
||||
raise RuntimeError(f"not a git repo: {target_repo}")
|
||||
|
||||
stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
||||
adapter = _CommittingSmokeAdapter(target_repo, stamp)
|
||||
spec = TaskSpec(
|
||||
title=f"harness smoke {stamp}",
|
||||
description=(
|
||||
"Deterministic smoke: write SMOKE.md and commit. "
|
||||
"No LLM session (Railiance packaging gate)."
|
||||
),
|
||||
target_repo=target_repo,
|
||||
agent=agent,
|
||||
labels=["harness", "smoke", "railiance"],
|
||||
completion_event_type="harness_smoke",
|
||||
timeout_seconds=120,
|
||||
)
|
||||
result = run_task(
|
||||
spec,
|
||||
adapter=adapter,
|
||||
report_to_hub=report_to_hub,
|
||||
write_metrics=True,
|
||||
)
|
||||
|
||||
pushed = False
|
||||
push_reason = ""
|
||||
if push and result.ok:
|
||||
push_proc = _git(target_repo, "push", "origin", "HEAD", check=False)
|
||||
if push_proc.returncode == 0:
|
||||
pushed = True
|
||||
else:
|
||||
push_reason = (push_proc.stderr or push_proc.stdout or "push failed").strip()[
|
||||
:300
|
||||
]
|
||||
elif not push:
|
||||
push_reason = "push skipped"
|
||||
|
||||
return SmokeResult(run=result, pushed=pushed, push_reason=push_reason)
|
||||
Loading…
Add table
Add a link
Reference in a new issue