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
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -162,3 +162,4 @@ cython_debug/
|
|||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
llm_connect_vendor/
|
||||
|
|
|
|||
35
Containerfile
Normal file
35
Containerfile
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# agent-harness — shared unattended agent runtime (Railiance / local).
|
||||
# Build: docker build -f Containerfile -t agent-harness:local .
|
||||
# Optional llm-connect sibling: docker build --build-arg WITH_LLM_CONNECT=1 ...
|
||||
FROM python:3.12-slim
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PATH="/home/harness/.local/bin:$PATH" \
|
||||
STATE_HUB_URL=http://state-hub.state-hub.svc.cluster.local:8000
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends git openssh-client ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& groupadd -g 10001 harness \
|
||||
&& useradd -u 10001 -g 10001 -m -s /usr/sbin/nologin harness
|
||||
|
||||
COPY pyproject.toml README.md ./
|
||||
COPY agent_harness ./agent_harness
|
||||
# Populated by `make image` from ../llm-connect when present.
|
||||
COPY llm_connect_vendor ./llm_connect_vendor
|
||||
|
||||
RUN pip install --no-cache-dir "httpx>=0.27" "PyYAML>=6.0" \
|
||||
&& pip install --no-cache-dir --no-deps . \
|
||||
&& if [ -f llm_connect_vendor/pyproject.toml ]; then \
|
||||
pip install --no-cache-dir ./llm_connect_vendor; \
|
||||
fi \
|
||||
&& rm -rf /root/.cache/pip
|
||||
|
||||
USER 10001:10001
|
||||
|
||||
# No long-running HTTP API yet — image is invoked as CLI (run / smoke / validate).
|
||||
ENTRYPOINT ["agent-harness"]
|
||||
CMD ["profiles"]
|
||||
33
Makefile
Normal file
33
Makefile
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
.PHONY: test image image-export deploy-rsync help
|
||||
|
||||
UV ?= $(shell command -v uv 2>/dev/null || echo uv)
|
||||
IMAGE ?= agent-harness:railiance01
|
||||
TAR ?= /tmp/agent-harness-railiance01.tar
|
||||
|
||||
help: ## Show targets
|
||||
@grep -Eh '^[a-zA-Z_-]+:.*?##' $(MAKEFILE_LIST) | \
|
||||
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-16s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
test: ## Run unit tests
|
||||
PYTHONPATH=".:$$HOME/llm-connect" python3 -m pytest tests/ -q
|
||||
|
||||
image: ## Build container image (vendors ../llm-connect when present)
|
||||
rm -rf llm_connect_vendor
|
||||
mkdir -p llm_connect_vendor
|
||||
if [ -d ../llm-connect/llm_connect ]; then \
|
||||
cp -a ../llm-connect/llm_connect ../llm-connect/pyproject.toml ../llm-connect/README.md llm_connect_vendor/ 2>/dev/null || \
|
||||
cp -a ../llm-connect/llm_connect ../llm-connect/pyproject.toml llm_connect_vendor/; \
|
||||
touch llm_connect_vendor/README.md; \
|
||||
fi
|
||||
docker build -f Containerfile -t $(IMAGE) .
|
||||
rm -rf llm_connect_vendor
|
||||
|
||||
image-export: image ## Save image tar for k3s import
|
||||
docker save -o $(TAR) $(IMAGE)
|
||||
@ls -lh $(TAR)
|
||||
|
||||
deploy-rsync: ## Rsync tree to railiance01:~/agent-harness
|
||||
rsync -az --delete \
|
||||
--exclude '.git' --exclude '__pycache__' --exclude '.pytest_cache' \
|
||||
--exclude 'llm_connect_vendor' --exclude '*.egg-info' \
|
||||
./ railiance01:agent-harness/
|
||||
|
|
@ -26,8 +26,13 @@ agent-harness run --task-file examples/task-hello-sandbox.json [--no-hub] [--no-
|
|||
|
||||
# Deterministic mailbox scan (no LLM session)
|
||||
agent-harness mail-scan --target-repo ~/binky-control
|
||||
|
||||
# Railiance packaging smoke (commit + optional push + hub; no Claude required)
|
||||
agent-harness smoke --work-dir ~/work/executor-sandbox
|
||||
```
|
||||
|
||||
Railiance deploy: [deploy/README.md](deploy/README.md).
|
||||
|
||||
Each `run` resolves the agent's tool profile + budget from the target
|
||||
repo's instance manifest (default `green-commit-only`), loads a persona
|
||||
bundle (`kaizen-agentic schedule prepare`), runs a bounded agentic
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
55
deploy/README.md
Normal file
55
deploy/README.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# Railiance deployment (HARNESS-WP-0001-T06)
|
||||
|
||||
Single shared harness instance on **railiance01**. Secrets stay on the host
|
||||
(Lanes 2–3); the container image is the portable runtime package.
|
||||
|
||||
## Layout
|
||||
|
||||
| Path | Role |
|
||||
|------|------|
|
||||
| `Containerfile` | Image: Python CLI + git + openssh; optional vendored llm-connect |
|
||||
| `deploy/k8s/railiance/` | Namespace, ConfigMap, Deployment, smoke Job |
|
||||
| `deploy/scripts/railiance-smoke.sh` | Host e2e: clone sandbox → commit → push → hub |
|
||||
| `agent-harness smoke` | Deterministic smoke (no Claude Code required) |
|
||||
|
||||
## Prerequisites (done 2026-07-17)
|
||||
|
||||
- Lane 2 deploy key on host + Forgejo write on `coulomb/executor-sandbox`
|
||||
- Lane 3 AppRole under `~/.local/agent-harness/approle-binky-mail`
|
||||
- `source ~/.local/agent-harness/env`
|
||||
- Hub: `http://127.0.0.1:18000` (ops-bridge) or in-cluster `state-hub.state-hub.svc`
|
||||
|
||||
## Build & load image (workstation → railiance01)
|
||||
|
||||
```bash
|
||||
# from agent-harness repo root
|
||||
make image # tags agent-harness:railiance01
|
||||
make image-export # /tmp/agent-harness-railiance01.tar
|
||||
scp /tmp/agent-harness-railiance01.tar railiance01:/tmp/
|
||||
ssh railiance01 sudo k3s ctr images import /tmp/agent-harness-railiance01.tar
|
||||
```
|
||||
|
||||
## Apply k8s
|
||||
|
||||
```bash
|
||||
rsync -a deploy/k8s/railiance/ railiance01:agent-harness/deploy/k8s/railiance/
|
||||
ssh railiance01 kubectl apply -k agent-harness/deploy/k8s/railiance/
|
||||
ssh railiance01 kubectl -n agent-harness rollout status deploy/agent-harness
|
||||
```
|
||||
|
||||
## Host smoke (authoritative e2e gate)
|
||||
|
||||
Full path uses the host deploy key and hub bridge:
|
||||
|
||||
```bash
|
||||
ssh railiance01 'bash ~/agent-harness/deploy/scripts/railiance-smoke.sh'
|
||||
```
|
||||
|
||||
Expect: local commit + push to `executor-sandbox`, hub event `harness_smoke`,
|
||||
`.kaizen/metrics/coach/` on the sandbox checkout.
|
||||
|
||||
## Personal follow-ups (not T06)
|
||||
|
||||
- At **binky cutover only**: attach the same deploy key to `coulomb/binky-control`
|
||||
- Claude Code on the host (or hosted adapter) for real agentic sessions
|
||||
- T03 issue-core intake for scheduled task consumption
|
||||
7
deploy/k8s/railiance/00-namespace.yaml
Normal file
7
deploy/k8s/railiance/00-namespace.yaml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: agent-harness
|
||||
labels:
|
||||
app.kubernetes.io/part-of: agent-harness
|
||||
app.kubernetes.io/name: agent-harness
|
||||
13
deploy/k8s/railiance/configmap.yaml
Normal file
13
deploy/k8s/railiance/configmap.yaml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: agent-harness-config
|
||||
namespace: agent-harness
|
||||
labels:
|
||||
app.kubernetes.io/name: agent-harness
|
||||
data:
|
||||
# In-cluster State Hub (production). Host-level smoke may override to
|
||||
# http://127.0.0.1:18000 (ops-bridge reverse tunnel to workstation hub).
|
||||
STATE_HUB_URL: "http://state-hub.state-hub.svc.cluster.local:8000"
|
||||
BAO_ADDR: "https://bao.coulomb.social"
|
||||
VAULT_ADDR: "https://bao.coulomb.social"
|
||||
52
deploy/k8s/railiance/deployment.yaml
Normal file
52
deploy/k8s/railiance/deployment.yaml
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Long-lived instance placeholder until T03 task intake polls issue-core.
|
||||
# Keeps one ready replica with harness CLI + git tools; no LLM session here.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: agent-harness
|
||||
namespace: agent-harness
|
||||
labels:
|
||||
app.kubernetes.io/name: agent-harness
|
||||
app.kubernetes.io/part-of: agent-harness
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: agent-harness
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: agent-harness
|
||||
app.kubernetes.io/part-of: agent-harness
|
||||
spec:
|
||||
securityContext:
|
||||
fsGroup: 10001
|
||||
containers:
|
||||
- name: agent-harness
|
||||
image: agent-harness:railiance01
|
||||
imagePullPolicy: Never
|
||||
command: ["sleep", "infinity"]
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: agent-harness-config
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 10001
|
||||
runAsGroup: 10001
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
47
deploy/k8s/railiance/job-smoke.yaml
Normal file
47
deploy/k8s/railiance/job-smoke.yaml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# One-shot smoke Job. Prefer host-level smoke (deploy/scripts/railiance-smoke.sh)
|
||||
# when deploy keys live on the host. This Job is for image/k8s path verification
|
||||
# with --no-push (no deploy key in-cluster yet).
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: agent-harness-smoke
|
||||
namespace: agent-harness
|
||||
labels:
|
||||
app.kubernetes.io/name: agent-harness
|
||||
app.kubernetes.io/component: smoke
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 600
|
||||
backoffLimit: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: agent-harness
|
||||
app.kubernetes.io/component: smoke
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
securityContext:
|
||||
fsGroup: 10001
|
||||
containers:
|
||||
- name: smoke
|
||||
image: agent-harness:railiance01
|
||||
imagePullPolicy: Never
|
||||
args:
|
||||
- "profiles"
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: agent-harness-config
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 10001
|
||||
runAsGroup: 10001
|
||||
8
deploy/k8s/railiance/kustomization.yaml
Normal file
8
deploy/k8s/railiance/kustomization.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: agent-harness
|
||||
resources:
|
||||
- 00-namespace.yaml
|
||||
- configmap.yaml
|
||||
- deployment.yaml
|
||||
- job-smoke.yaml
|
||||
24
deploy/scripts/railiance-smoke.sh
Executable file
24
deploy/scripts/railiance-smoke.sh
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env bash
|
||||
# Host-level Railiance smoke for agent-harness (HARNESS-WP-0001-T06).
|
||||
# Run on railiance01 after install. Uses deploy key + AppRole env + hub :18000.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="${AGENT_HARNESS_ROOT:-$HOME/agent-harness}"
|
||||
# shellcheck disable=SC1090
|
||||
source "${HOME}/.local/agent-harness/env"
|
||||
export STATE_HUB_URL="${STATE_HUB_URL:-http://127.0.0.1:18000}"
|
||||
export PATH="${HOME}/.local/bin:${PATH}"
|
||||
export PYTHONPATH="${ROOT}:${HOME}/llm-connect${PYTHONPATH:+:$PYTHONPATH}"
|
||||
|
||||
cd "$ROOT"
|
||||
if ! command -v agent-harness >/dev/null 2>&1; then
|
||||
python3 -m pip install --user -e . -q
|
||||
fi
|
||||
|
||||
echo "STATE_HUB_URL=$STATE_HUB_URL"
|
||||
curl -sS -m 5 "$STATE_HUB_URL/state/health" || curl -sS -m 5 "${STATE_HUB_URL%/}/" || true
|
||||
echo
|
||||
|
||||
agent-harness smoke \
|
||||
--work-dir "${HOME}/work/executor-sandbox" \
|
||||
--remote "ssh://git@forgejo-agent-harness/coulomb/executor-sandbox.git"
|
||||
31
tests/test_smoke.py
Normal file
31
tests/test_smoke.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from agent_harness.smoke import run_smoke
|
||||
|
||||
|
||||
def _make_repo(tmp_path: Path) -> Path:
|
||||
repo = tmp_path / "sandbox"
|
||||
repo.mkdir()
|
||||
subprocess.run(["git", "init", "-q"], cwd=repo, check=True)
|
||||
(repo / "README.md").write_text("sandbox\n")
|
||||
subprocess.run(["git", "add", "."], cwd=repo, check=True)
|
||||
subprocess.run(
|
||||
["git", "-c", "user.email=t@t", "-c", "user.name=t", "commit", "-qm", "init"],
|
||||
cwd=repo,
|
||||
check=True,
|
||||
)
|
||||
return repo
|
||||
|
||||
|
||||
def test_run_smoke_commits_without_push(tmp_path: Path) -> None:
|
||||
repo = _make_repo(tmp_path)
|
||||
result = run_smoke(repo, report_to_hub=False, push=False)
|
||||
assert result.run.ok is True
|
||||
assert result.run.committed is True
|
||||
assert (repo / "SMOKE.md").is_file()
|
||||
assert result.pushed is False
|
||||
metrics = repo / ".kaizen" / "metrics" / "coach" / "executions.jsonl"
|
||||
assert metrics.is_file()
|
||||
|
|
@ -91,7 +91,7 @@ Lanes 2–3). Verify the sandbox smoke task end-to-end remotely; hub via
|
|||
|
||||
```task
|
||||
id: HARNESS-WP-0001-T06
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "bc1b29b0-3a35-4a35-a113-87f3413b51a5"
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue