From 37a5165dfba790a421e71f095a40af5ce867477f Mon Sep 17 00:00:00 2001 From: Paul O'Reilly Date: Sun, 26 Apr 2026 12:17:42 +1200 Subject: [PATCH] init: seed framework reference content from agent-runtimes main repo --- .gitignore | 5 + ABOUT.md | 1 + README.md | 19 + .../agent-communication/v1/finalize.sh | 177 ++++ .../agent-communication/v1/harness.yaml | 19 + .../agent-communication/v1/init.sh | 45 + .../capabilities/python-dev/v1/harness.yaml | 19 + harnesses/capabilities/python-dev/v1/init.sh | 32 + .../tdd-file-lock/v1/harness.yaml | 8 + .../capabilities/tdd-file-lock/v1/init.sh | 34 + .../code-airouter-repo/v1/harness.yaml | 11 + .../code-haiku-repo/v1/harness.yaml | 10 + .../code-minimax-repo/v1/harness.yaml | 10 + .../composites/code-opus-repo/v1/harness.yaml | 10 + .../code-sonnet-repo/v1/harness.yaml | 10 + .../minimax-tdd-impl/v1/harness.yaml | 13 + .../planning-minimax-repo/v1/harness.yaml | 10 + .../planning-opus-repo/v1/harness.yaml | 10 + .../python-code-review/v1/harness.yaml | 9 + .../v1/harness.yaml | 11 + .../security-review-opus-repo/v1/harness.yaml | 10 + .../v1/harness.yaml | 10 + .../spec-writing-opus-repo/v1/harness.yaml | 10 + .../spec-writing-sonnet-repo/v1/harness.yaml | 10 + .../test-writing-opus-repo/v1/harness.yaml | 10 + .../test-writing-sonnet-repo/v1/harness.yaml | 10 + harnesses/contexts/agent-repo/v1/finalize.sh | 220 ++++ harnesses/contexts/agent-repo/v1/harness.yaml | 19 + harnesses/contexts/agent-repo/v1/init.sh | 93 ++ harnesses/contexts/airouter/v1/harness.yaml | 14 + .../alibaba-model-studio/v1/harness.yaml | 19 + .../contexts/anthropic-cloud/v1/harness.yaml | 14 + .../v1/best-practices/api-design.md | 1 + .../v1/best-practices/database-selection.md | 1 + .../v1/best-practices/docker.md | 1 + .../v1/best-practices/kubernetes.md | 1 + .../v1/best-practices/llm-code-security.md | 1 + .../v1/best-practices/secrets-management.md | 1 + .../best-practices/security-architecture.md | 1 + .../best-practices/spec-driven-development.md | 1 + .../best-practices/test-driven-development.md | 1 + .../contexts/best-practices/v1/harness.yaml | 10 + .../contexts/code-methodology/v1/CLAUDE.md | 110 ++ .../contexts/code-methodology/v1/harness.yaml | 10 + .../contexts/gitea-admin/v1/harness.yaml | 27 + .../contexts/gitea-https/v1/harness.yaml | 18 + harnesses/contexts/gitea-https/v1/init.sh | 29 + .../gitea-ssh-accelerators/v1/harness.yaml | 24 + .../gitea-ssh-accelerators/v1/init.sh | 29 + .../gitea-ssh-homelab/v1/harness.yaml | 24 + .../contexts/gitea-ssh-homelab/v1/init.sh | 29 + harnesses/contexts/gitea-ssh/v1/harness.yaml | 24 + harnesses/contexts/gitea-ssh/v1/init.sh | 32 + harnesses/contexts/mac-ollama/v1/harness.yaml | 18 + .../contexts/mac-vllm-mlx/v1/harness.yaml | 18 + .../minimax-code-methodology/v1/CLAUDE.md | 84 ++ .../minimax-code-methodology/v1/harness.yaml | 12 + .../minimax-code-methodology/v1/run-tests.sh | 32 + harnesses/contexts/minimax/v1/harness.yaml | 19 + harnesses/contexts/planning/v1/CLAUDE.md | 47 + .../planning/v1/best-practices/api-design.md | 463 +++++++++ .../v1/best-practices/database-selection.md | 100 ++ .../planning/v1/best-practices/docker.md | 47 + .../planning/v1/best-practices/kubernetes.md | 69 ++ .../v1/best-practices/llm-code-security.md | 800 +++++++++++++++ .../v1/best-practices/secrets-management.md | 108 ++ .../best-practices/security-architecture.md | 79 ++ .../best-practices/spec-driven-development.md | 250 +++++ .../best-practices/test-driven-development.md | 486 +++++++++ harnesses/contexts/planning/v1/harness.yaml | 11 + .../qwen-code-methodology/v1/CLAUDE.md | 104 ++ .../qwen-code-methodology/v1/harness.yaml | 11 + .../contexts/security-review/v1/CLAUDE.md | 55 + .../contexts/security-review/v1/harness.yaml | 11 + harnesses/contexts/spec-writing/v1/CLAUDE.md | 73 ++ .../contexts/spec-writing/v1/harness.yaml | 11 + harnesses/contexts/tdd-protect/v1/CLAUDE.md | 31 + .../contexts/tdd-protect/v1/harness.yaml | 10 + harnesses/contexts/test-writing/v1/CLAUDE.md | 78 ++ .../contexts/test-writing/v1/harness.yaml | 11 + harnesses/contexts/z-ai/v1/harness.yaml | 19 + model-registry/airouter-qwen3.yaml | 14 + model-registry/claude-haiku-4.yaml | 14 + model-registry/claude-opus-4.yaml | 14 + model-registry/claude-sonnet-4.yaml | 14 + model-registry/github-models-gpt4o.yaml | 14 + model-registry/gpt-4o-openai.yaml | 14 + model-registry/opencode-zen-sonnet.yaml | 14 + model-registry/openrouter-sonnet.yaml | 14 + model-registry/qwen3-235b.yaml | 14 + models/airouter-qwen3.yaml | 12 + models/claude-opus-4.yaml | 8 + models/claude-sonnet-4.yaml | 8 + models/github-models-gpt4o.yaml | 9 + models/gpt-4o-openai.yaml | 9 + models/opencode-zen-sonnet.yaml | 9 + models/openrouter-sonnet.yaml | 9 + models/qwen3-ollama.yaml | 8 + task-templates/airouter-code-repo.yaml | 19 + task-templates/haiku-code-repo.yaml | 17 + task-templates/minimax-code-repo.yaml | 20 + task-templates/minimax-planning.yaml | 19 + task-templates/minimax-security-review.yaml | 17 + task-templates/minimax-tdd-impl.yaml | 21 + task-templates/opus-code-repo.yaml | 17 + task-templates/opus-planning.yaml | 17 + task-templates/opus-security-review.yaml | 17 + task-templates/opus-spec-writer.yaml | 17 + task-templates/opus-test-writer.yaml | 17 + task-templates/output-validator.yaml | 10 + task-templates/sonnet-code-repo.yaml | 17 + task-templates/sonnet-security-review.yaml | 17 + task-templates/sonnet-spec-writer.yaml | 17 + task-templates/sonnet-test-writer.yaml | 17 + workflows/claude-agent.yaml | 41 + workflows/comparative-plan.yaml | 173 ++++ workflows/spec-planning-opus-gated.yaml | 955 ++++++++++++++++++ workflows/spec-planning.yaml | 854 ++++++++++++++++ 118 files changed, 6831 insertions(+) create mode 100644 .gitignore create mode 100644 ABOUT.md create mode 100644 README.md create mode 100755 harnesses/capabilities/agent-communication/v1/finalize.sh create mode 100644 harnesses/capabilities/agent-communication/v1/harness.yaml create mode 100755 harnesses/capabilities/agent-communication/v1/init.sh create mode 100644 harnesses/capabilities/python-dev/v1/harness.yaml create mode 100755 harnesses/capabilities/python-dev/v1/init.sh create mode 100644 harnesses/capabilities/tdd-file-lock/v1/harness.yaml create mode 100644 harnesses/capabilities/tdd-file-lock/v1/init.sh create mode 100644 harnesses/composites/code-airouter-repo/v1/harness.yaml create mode 100644 harnesses/composites/code-haiku-repo/v1/harness.yaml create mode 100644 harnesses/composites/code-minimax-repo/v1/harness.yaml create mode 100644 harnesses/composites/code-opus-repo/v1/harness.yaml create mode 100644 harnesses/composites/code-sonnet-repo/v1/harness.yaml create mode 100644 harnesses/composites/minimax-tdd-impl/v1/harness.yaml create mode 100644 harnesses/composites/planning-minimax-repo/v1/harness.yaml create mode 100644 harnesses/composites/planning-opus-repo/v1/harness.yaml create mode 100644 harnesses/composites/python-code-review/v1/harness.yaml create mode 100644 harnesses/composites/security-review-minimax-repo/v1/harness.yaml create mode 100644 harnesses/composites/security-review-opus-repo/v1/harness.yaml create mode 100644 harnesses/composites/security-review-sonnet-repo/v1/harness.yaml create mode 100644 harnesses/composites/spec-writing-opus-repo/v1/harness.yaml create mode 100644 harnesses/composites/spec-writing-sonnet-repo/v1/harness.yaml create mode 100644 harnesses/composites/test-writing-opus-repo/v1/harness.yaml create mode 100644 harnesses/composites/test-writing-sonnet-repo/v1/harness.yaml create mode 100644 harnesses/contexts/agent-repo/v1/finalize.sh create mode 100644 harnesses/contexts/agent-repo/v1/harness.yaml create mode 100644 harnesses/contexts/agent-repo/v1/init.sh create mode 100644 harnesses/contexts/airouter/v1/harness.yaml create mode 100644 harnesses/contexts/alibaba-model-studio/v1/harness.yaml create mode 100644 harnesses/contexts/anthropic-cloud/v1/harness.yaml create mode 120000 harnesses/contexts/best-practices/v1/best-practices/api-design.md create mode 120000 harnesses/contexts/best-practices/v1/best-practices/database-selection.md create mode 120000 harnesses/contexts/best-practices/v1/best-practices/docker.md create mode 120000 harnesses/contexts/best-practices/v1/best-practices/kubernetes.md create mode 120000 harnesses/contexts/best-practices/v1/best-practices/llm-code-security.md create mode 120000 harnesses/contexts/best-practices/v1/best-practices/secrets-management.md create mode 120000 harnesses/contexts/best-practices/v1/best-practices/security-architecture.md create mode 120000 harnesses/contexts/best-practices/v1/best-practices/spec-driven-development.md create mode 120000 harnesses/contexts/best-practices/v1/best-practices/test-driven-development.md create mode 100644 harnesses/contexts/best-practices/v1/harness.yaml create mode 100644 harnesses/contexts/code-methodology/v1/CLAUDE.md create mode 100644 harnesses/contexts/code-methodology/v1/harness.yaml create mode 100644 harnesses/contexts/gitea-admin/v1/harness.yaml create mode 100644 harnesses/contexts/gitea-https/v1/harness.yaml create mode 100755 harnesses/contexts/gitea-https/v1/init.sh create mode 100644 harnesses/contexts/gitea-ssh-accelerators/v1/harness.yaml create mode 100755 harnesses/contexts/gitea-ssh-accelerators/v1/init.sh create mode 100644 harnesses/contexts/gitea-ssh-homelab/v1/harness.yaml create mode 100755 harnesses/contexts/gitea-ssh-homelab/v1/init.sh create mode 100644 harnesses/contexts/gitea-ssh/v1/harness.yaml create mode 100755 harnesses/contexts/gitea-ssh/v1/init.sh create mode 100644 harnesses/contexts/mac-ollama/v1/harness.yaml create mode 100644 harnesses/contexts/mac-vllm-mlx/v1/harness.yaml create mode 100644 harnesses/contexts/minimax-code-methodology/v1/CLAUDE.md create mode 100644 harnesses/contexts/minimax-code-methodology/v1/harness.yaml create mode 100755 harnesses/contexts/minimax-code-methodology/v1/run-tests.sh create mode 100644 harnesses/contexts/minimax/v1/harness.yaml create mode 100644 harnesses/contexts/planning/v1/CLAUDE.md create mode 100644 harnesses/contexts/planning/v1/best-practices/api-design.md create mode 100644 harnesses/contexts/planning/v1/best-practices/database-selection.md create mode 100644 harnesses/contexts/planning/v1/best-practices/docker.md create mode 100644 harnesses/contexts/planning/v1/best-practices/kubernetes.md create mode 100644 harnesses/contexts/planning/v1/best-practices/llm-code-security.md create mode 100644 harnesses/contexts/planning/v1/best-practices/secrets-management.md create mode 100644 harnesses/contexts/planning/v1/best-practices/security-architecture.md create mode 100644 harnesses/contexts/planning/v1/best-practices/spec-driven-development.md create mode 100644 harnesses/contexts/planning/v1/best-practices/test-driven-development.md create mode 100644 harnesses/contexts/planning/v1/harness.yaml create mode 100644 harnesses/contexts/qwen-code-methodology/v1/CLAUDE.md create mode 100644 harnesses/contexts/qwen-code-methodology/v1/harness.yaml create mode 100644 harnesses/contexts/security-review/v1/CLAUDE.md create mode 100644 harnesses/contexts/security-review/v1/harness.yaml create mode 100644 harnesses/contexts/spec-writing/v1/CLAUDE.md create mode 100644 harnesses/contexts/spec-writing/v1/harness.yaml create mode 100644 harnesses/contexts/tdd-protect/v1/CLAUDE.md create mode 100644 harnesses/contexts/tdd-protect/v1/harness.yaml create mode 100644 harnesses/contexts/test-writing/v1/CLAUDE.md create mode 100644 harnesses/contexts/test-writing/v1/harness.yaml create mode 100644 harnesses/contexts/z-ai/v1/harness.yaml create mode 100644 model-registry/airouter-qwen3.yaml create mode 100644 model-registry/claude-haiku-4.yaml create mode 100644 model-registry/claude-opus-4.yaml create mode 100644 model-registry/claude-sonnet-4.yaml create mode 100644 model-registry/github-models-gpt4o.yaml create mode 100644 model-registry/gpt-4o-openai.yaml create mode 100644 model-registry/opencode-zen-sonnet.yaml create mode 100644 model-registry/openrouter-sonnet.yaml create mode 100644 model-registry/qwen3-235b.yaml create mode 100644 models/airouter-qwen3.yaml create mode 100644 models/claude-opus-4.yaml create mode 100644 models/claude-sonnet-4.yaml create mode 100644 models/github-models-gpt4o.yaml create mode 100644 models/gpt-4o-openai.yaml create mode 100644 models/opencode-zen-sonnet.yaml create mode 100644 models/openrouter-sonnet.yaml create mode 100644 models/qwen3-ollama.yaml create mode 100644 task-templates/airouter-code-repo.yaml create mode 100644 task-templates/haiku-code-repo.yaml create mode 100644 task-templates/minimax-code-repo.yaml create mode 100644 task-templates/minimax-planning.yaml create mode 100644 task-templates/minimax-security-review.yaml create mode 100644 task-templates/minimax-tdd-impl.yaml create mode 100644 task-templates/opus-code-repo.yaml create mode 100644 task-templates/opus-planning.yaml create mode 100644 task-templates/opus-security-review.yaml create mode 100644 task-templates/opus-spec-writer.yaml create mode 100644 task-templates/opus-test-writer.yaml create mode 100644 task-templates/output-validator.yaml create mode 100644 task-templates/sonnet-code-repo.yaml create mode 100644 task-templates/sonnet-security-review.yaml create mode 100644 task-templates/sonnet-spec-writer.yaml create mode 100644 task-templates/sonnet-test-writer.yaml create mode 100644 workflows/claude-agent.yaml create mode 100644 workflows/comparative-plan.yaml create mode 100644 workflows/spec-planning-opus-gated.yaml create mode 100644 workflows/spec-planning.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..31f085e --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.sops.env +*.sops.yaml +__pycache__/ +*.pyc +.DS_Store diff --git a/ABOUT.md b/ABOUT.md new file mode 100644 index 0000000..e050f77 --- /dev/null +++ b/ABOUT.md @@ -0,0 +1 @@ +description: Agent Runtimes framework reference config — harnesses, templates, workflows, model-registry, models diff --git a/README.md b/README.md new file mode 100644 index 0000000..afc6613 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# agent-runtime-framework + +Framework reference config repo for the agent-runtimes platform. + +This repo is attached at CP scope with tags `[harnesses, templates, workflows, model-registry, models]` +and provides the default content that all installations inherit. + +## Contents + +- `harnesses/` — capabilities, contexts, composites +- `task-templates/` — dispatch-task template YAMLs +- `workflows/` — workflow DAG definitions +- `model-registry/` — model scoring YAMLs +- `models/` — model endpoint YAMLs + +## Usage + +Registered as a CRS attachment at CP scope (priority 100). Customer installations +attach their own repos at lower priority to override or extend. diff --git a/harnesses/capabilities/agent-communication/v1/finalize.sh b/harnesses/capabilities/agent-communication/v1/finalize.sh new file mode 100755 index 0000000..deea230 --- /dev/null +++ b/harnesses/capabilities/agent-communication/v1/finalize.sh @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +"""ACL finalize script: validate staging path, copy to secure tmpfs, sign, commit. + +AC-10: Reject draft paths outside /workspace/.acl-staging/ +AC-40: Copy draft to root-owned /run/acl-finalize// before all processing +AC-41: Stamp + sign + commit in one process; no partial state outside finalize tmpfs +AC-42: Commit signed bytes via git hash-object --stdin + git commit-tree (no git add) +""" + +import os +import pathlib +import secrets +import subprocess +import sys + + +def validate_draft_path(draft_arg: str) -> pathlib.Path: + """AC-10: Validate draft resolves under /workspace/.acl-staging/.""" + staging_dir = pathlib.Path("/workspace/.acl-staging").resolve() + draft = pathlib.Path(draft_arg).resolve() + if staging_dir not in draft.parents: + print("error: draft path must be under /workspace/.acl-staging/", file=sys.stderr) + sys.exit(1) + if not draft.exists(): + print(f"error: draft file not found: {draft_arg}", file=sys.stderr) + sys.exit(1) + return draft + + +def copy_to_finalize_dir(draft: pathlib.Path, task_id: str) -> pathlib.Path: + """AC-40: Atomically copy draft to a root-owned finalize tmpfs path. + + After this call, only `tmp` is referenced — the original `draft` path + is never read again (closes the TOCTOU window AC-40 describes). + """ + finalize_dir = pathlib.Path("/run/acl-finalize") / task_id + finalize_dir.mkdir(parents=True, exist_ok=True, mode=0o700) + tmp = finalize_dir / (secrets.token_hex(8) + ".draft") + tmp.write_bytes(draft.read_bytes()) + tmp.chmod(0o600) + # From here, only use `tmp` — never reference `draft` again + return tmp + + +def sign_and_commit(tmp: pathlib.Path, task_id: str) -> None: + """AC-42: Canonicalize, sign, and commit via git hash-object --stdin. + + Never calls git add. Signed bytes go directly into git object store. + Retry loop (AC-31/AC-36): up to 3 attempts with pull --rebase on failure. + """ + # Import shared canonical library (AC-43) + _project = str(pathlib.Path(__file__).resolve().parent.parent.parent.parent.parent) + if _project not in sys.path: + sys.path.insert(0, _project) + from lib.acl_canonical import canonical_serialize, validate_frontmatter + + raw = tmp.read_bytes() + + # AC-13/AC-14: Validate and parse frontmatter + frontmatter = validate_frontmatter(raw) + + # AC-15: Canonical serialization + # Extract body (text after the second ---) + body = "" + text = raw.decode("utf-8") + parts = text.split("---\n", 2) + if len(parts) >= 3: + body = parts[2] + + canonical: bytes = canonical_serialize(frontmatter, body) + + # AC-16: Sign with Ed25519 over signed_sha256 || nonce || commit_hash + import hashlib + from cryptography.hazmat.primitives.serialization import load_pem_private_key + + key_bytes = pathlib.Path("/run/agent/acl/ed25519.key").read_bytes() + private_key = load_pem_private_key(key_bytes, password=None) + + signed_sha256 = hashlib.sha256(canonical).digest() + nonce_bytes = secrets.token_bytes(32) + # commit_hash placeholder — real value computed after git commit-tree + placeholder_commit_hash = bytes(32) + sig_input = signed_sha256 + nonce_bytes + placeholder_commit_hash + sig = private_key.sign(sig_input) + + signed = canonical + b"\n# sig: " + sig.hex().encode() + b"\n" + + # AC-42: git hash-object --stdin (no git add, no disk re-read after sign) + blob_result = subprocess.run( + ["git", "hash-object", "-w", "--stdin"], + input=signed, + capture_output=True, + check=True, + ) + blob_oid = blob_result.stdout.strip().decode() + + # AC-42: Assemble commit via git mktree + git commit-tree (NOT git commit) + import time as _time + + outbox_path = f"outbox/{task_id}/{int(_time.time() * 1000)}.md" + + # Build tree entry for the outbox blob + tree_input = f"100644 blob {blob_oid}\t{outbox_path}\n".encode() + tree_result = subprocess.run( + ["git", "mktree"], + input=tree_input, + capture_output=True, + check=True, + ) + tree_oid = tree_result.stdout.strip().decode() + + # Get current HEAD for parent commit + parent_result = subprocess.run( + ["git", "rev-parse", "HEAD"], + capture_output=True, + ) + parent_args = [] + if parent_result.returncode == 0 and parent_result.stdout.strip(): + parent_args = ["-p", parent_result.stdout.strip().decode()] + + # Create commit via git commit-tree (NOT git commit — AC-42) + commit_result = subprocess.run( + ["git", "commit-tree", tree_oid] + parent_args + ["-m", "ACL message"], + capture_output=True, + check=True, + ) + commit_oid = commit_result.stdout.strip().decode() + + # Update the local ref to point at the new commit + subprocess.run( + ["git", "update-ref", "refs/heads/main", commit_oid], + check=True, + ) + + # AC-31/AC-36: Retry push loop — up to 3 attempts with pull --rebase on failure + for _attempt in range(3): + push_result = subprocess.run(["git", "push"], capture_output=True) + if push_result.returncode == 0: + break + subprocess.run(["git", "pull", "--rebase"], check=False) + else: + print("error: push_race_lost", file=sys.stderr) + sys.exit(1) + + +def main(argv: list | None = None) -> int: + """Entry point.""" + if argv is None: + argv = sys.argv + + if len(argv) < 2: + print("usage: acl-finalize ", file=sys.stderr) + return 1 + + task_id = os.environ.get("ACL_TASK_ID", "unknown") + + # AC-10: Validate path is under staging dir + draft = validate_draft_path(argv[1]) + + # AC-40: Copy to root-owned finalize tmpfs before any processing + tmp = copy_to_finalize_dir(draft, task_id) + + try: + # AC-41: stamp + sign + commit in one process invocation + sign_and_commit(tmp, task_id) + finally: + # Clean up finalize copy on any exit + try: + tmp.unlink(missing_ok=True) + except Exception: + pass + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/harnesses/capabilities/agent-communication/v1/harness.yaml b/harnesses/capabilities/agent-communication/v1/harness.yaml new file mode 100644 index 0000000..f16c162 --- /dev/null +++ b/harnesses/capabilities/agent-communication/v1/harness.yaml @@ -0,0 +1,19 @@ +kind: capability +name: agent-communication +version: 1 +description: "ACL: agent-to-agent messaging via git-backed conversation repo" +requires: [] + +secrets_required: + - name: AGENT_ED25519_KEY + - name: AGENT_ED25519_PUB + - name: AGENT_MTLS_CERT + - name: AGENT_MTLS_KEY + +env: + ACL_TASK_ID: "" + ACL_ORIGIN_TASK_ID: "" + ACL_CONVERSATION_REPO_URL: "" + +scripts: + init: "./init.sh" diff --git a/harnesses/capabilities/agent-communication/v1/init.sh b/harnesses/capabilities/agent-communication/v1/init.sh new file mode 100755 index 0000000..fc7936f --- /dev/null +++ b/harnesses/capabilities/agent-communication/v1/init.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Agent communication capability layer initialization script +# Sets up the ACL tmpfs key directory. The dispatcher mounts +# /opt/harness/bin/acl-finalize directly (AC-2). + +set -euo pipefail + +echo "Initializing agent-communication capability layer..." + +# Create tmpfs for ACL key material (AC-7: noexec,nosuid,mode=0700) +ACL_KEY_DIR="/run/agent/acl" +mkdir -p "${ACL_KEY_DIR}" +mount -t tmpfs -o noexec,nosuid,mode=0700 tmpfs "${ACL_KEY_DIR}" + +# Write Ed25519 private key from env to tmpfs (AC-7: mode 0600) +if [[ -n "${AGENT_ED25519_KEY:-}" ]]; then + printf '%s' "${AGENT_ED25519_KEY}" > "${ACL_KEY_DIR}/ed25519.key" + chmod 0600 "${ACL_KEY_DIR}/ed25519.key" + unset AGENT_ED25519_KEY +fi + +# Write Ed25519 public key from env to tmpfs (mode 0644) +if [[ -n "${AGENT_ED25519_PUB:-}" ]]; then + printf '%s' "${AGENT_ED25519_PUB}" > "${ACL_KEY_DIR}/ed25519.pub" + chmod 0644 "${ACL_KEY_DIR}/ed25519.pub" + unset AGENT_ED25519_PUB +fi + +# Write mTLS cert and key from env to tmpfs (AC-8) +if [[ -n "${AGENT_MTLS_CERT:-}" ]]; then + printf '%s' "${AGENT_MTLS_CERT}" > "${ACL_KEY_DIR}/mtls.crt" + chmod 0600 "${ACL_KEY_DIR}/mtls.crt" + unset AGENT_MTLS_CERT +fi + +if [[ -n "${AGENT_MTLS_KEY:-}" ]]; then + printf '%s' "${AGENT_MTLS_KEY}" > "${ACL_KEY_DIR}/mtls.key" + chmod 0600 "${ACL_KEY_DIR}/mtls.key" + unset AGENT_MTLS_KEY +fi + +# Ensure /opt/harness/bin/ exists (dispatcher mounts acl-finalize here) +mkdir -p /opt/harness/bin + +echo "Agent communication environment ready" diff --git a/harnesses/capabilities/python-dev/v1/harness.yaml b/harnesses/capabilities/python-dev/v1/harness.yaml new file mode 100644 index 0000000..60e928f --- /dev/null +++ b/harnesses/capabilities/python-dev/v1/harness.yaml @@ -0,0 +1,19 @@ +kind: capability +name: python-dev +version: 1 +description: "Python dev tools: pytest, ruff, mypy, uv" +requires: [] + +packages: + apt: [python3-venv] + pip: [pytest, ruff, mypy, hypothesis, uv] + +env: + PIP_BREAK_SYSTEM_PACKAGES: "1" + +network_hosts: + - pypi.org + - files.pythonhosted.org + +scripts: + init: "./init.sh" \ No newline at end of file diff --git a/harnesses/capabilities/python-dev/v1/init.sh b/harnesses/capabilities/python-dev/v1/init.sh new file mode 100755 index 0000000..76c1a5e --- /dev/null +++ b/harnesses/capabilities/python-dev/v1/init.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Python development capability layer initialization script + +set -euo pipefail + +echo "Initializing python-dev capability layer..." + +# If pyproject.toml exists, install project in development mode +if [[ -f "/workspace/pyproject.toml" ]]; then + echo "Found pyproject.toml, installing project in development mode..." + cd /workspace + + # Create virtual environment if it doesn't exist + if [[ ! -d ".venv" ]]; then + python3 -m venv .venv + fi + + # Activate virtual environment + source .venv/bin/activate + + # Install project in development mode with all dependencies + pip install -e ".[dev,test]" 2>/dev/null || pip install -e . 2>/dev/null || { + echo "Could not install with extras, trying basic install..." + pip install -e . + } + + echo "Project installed in development mode" +else + echo "No pyproject.toml found, skipping project installation" +fi + +echo "Python development environment ready" \ No newline at end of file diff --git a/harnesses/capabilities/tdd-file-lock/v1/harness.yaml b/harnesses/capabilities/tdd-file-lock/v1/harness.yaml new file mode 100644 index 0000000..0fceceb --- /dev/null +++ b/harnesses/capabilities/tdd-file-lock/v1/harness.yaml @@ -0,0 +1,8 @@ +kind: capability +name: tdd-file-lock +version: 1 +description: "Locks /workspace/tests/ read-only (root-owned). Agent cannot modify test files." +requires: [] + +scripts: + init: "./init.sh" diff --git a/harnesses/capabilities/tdd-file-lock/v1/init.sh b/harnesses/capabilities/tdd-file-lock/v1/init.sh new file mode 100644 index 0000000..14b834a --- /dev/null +++ b/harnesses/capabilities/tdd-file-lock/v1/init.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# TDD file lock — runs as root before agent user takes over. +# Creates a root-owned immutable reference copy of tests/ and locks the +# working copy so the agent cannot write to any test file. +set -euo pipefail + +if [ ! -d "/workspace/tests" ]; then + echo "[tdd-file-lock] No /workspace/tests found — nothing to protect." + exit 0 +fi + +echo "[tdd-file-lock] Locking test files (running as $(id))..." + +# Root-owned reference copy — agent cannot chmod/write/delete these +mkdir -p /workspace/reference/tests +cp -r /workspace/tests/. /workspace/reference/tests/ +chown -R root:root /workspace/reference/tests +find /workspace/reference/tests -type f -exec chmod 444 {} \; +find /workspace/reference/tests -type d -exec chmod 555 {} \; + +# SHA256 checksums for post-task external verification +find /workspace/tests -name "*.py" | sort | xargs sha256sum > /workspace/.test-shas +chown root:root /workspace/.test-shas +chmod 444 /workspace/.test-shas + +# Lock the working tests/ directory — files and dirs owned by root, no write for anyone +chown -R root:root /workspace/tests +find /workspace/tests -type f -exec chmod 444 {} \; +find /workspace/tests -type d -exec chmod 555 {} \; + +TEST_COUNT=$(find /workspace/tests -name "*.py" | wc -l) +echo "[tdd-file-lock] Protected ${TEST_COUNT} test files." +echo "[tdd-file-lock] Immutable reference: /workspace/reference/tests/" +echo "[tdd-file-lock] SHA256 reference: /workspace/.test-shas" diff --git a/harnesses/composites/code-airouter-repo/v1/harness.yaml b/harnesses/composites/code-airouter-repo/v1/harness.yaml new file mode 100644 index 0000000..a48aca2 --- /dev/null +++ b/harnesses/composites/code-airouter-repo/v1/harness.yaml @@ -0,0 +1,11 @@ +kind: composite +name: code-airouter-repo +version: 1 +description: "Code agent with Airouter Qwen3.6 + repo clone via SSH" + +layers: + - context: qwen-code-methodology/v1 + - context: best-practices/v1 + - context: airouter/v1 + - context: gitea-ssh/v1 + - context: agent-repo/v1 diff --git a/harnesses/composites/code-haiku-repo/v1/harness.yaml b/harnesses/composites/code-haiku-repo/v1/harness.yaml new file mode 100644 index 0000000..9a5beed --- /dev/null +++ b/harnesses/composites/code-haiku-repo/v1/harness.yaml @@ -0,0 +1,10 @@ +kind: composite +name: code-haiku-repo +version: 1 +description: "Code agent with Haiku (Anthropic cloud) + repo clone via SSH" + +layers: + - context: code-methodology/v1 + - context: anthropic-cloud/v1 + - context: gitea-ssh/v1 + - context: agent-repo/v1 diff --git a/harnesses/composites/code-minimax-repo/v1/harness.yaml b/harnesses/composites/code-minimax-repo/v1/harness.yaml new file mode 100644 index 0000000..9afa61a --- /dev/null +++ b/harnesses/composites/code-minimax-repo/v1/harness.yaml @@ -0,0 +1,10 @@ +kind: composite +name: code-minimax-repo +version: 1 +description: "Code agent with MiniMax + repo clone via SSH" + +layers: + - context: minimax-code-methodology/v1 + - context: minimax/v1 + - context: gitea-ssh/v1 + - context: agent-repo/v1 diff --git a/harnesses/composites/code-opus-repo/v1/harness.yaml b/harnesses/composites/code-opus-repo/v1/harness.yaml new file mode 100644 index 0000000..3a6f717 --- /dev/null +++ b/harnesses/composites/code-opus-repo/v1/harness.yaml @@ -0,0 +1,10 @@ +kind: composite +name: code-opus-repo +version: 1 +description: "Code agent with Opus (Anthropic cloud) + repo clone via SSH" + +layers: + - context: code-methodology/v1 + - context: anthropic-cloud/v1 + - context: gitea-ssh/v1 + - context: agent-repo/v1 diff --git a/harnesses/composites/code-sonnet-repo/v1/harness.yaml b/harnesses/composites/code-sonnet-repo/v1/harness.yaml new file mode 100644 index 0000000..b50204f --- /dev/null +++ b/harnesses/composites/code-sonnet-repo/v1/harness.yaml @@ -0,0 +1,10 @@ +kind: composite +name: code-sonnet-repo +version: 1 +description: "Code agent with Sonnet (Anthropic cloud) + repo clone via SSH" + +layers: + - context: code-methodology/v1 + - context: anthropic-cloud/v1 + - context: gitea-ssh/v1 + - context: agent-repo/v1 diff --git a/harnesses/composites/minimax-tdd-impl/v1/harness.yaml b/harnesses/composites/minimax-tdd-impl/v1/harness.yaml new file mode 100644 index 0000000..fc3225e --- /dev/null +++ b/harnesses/composites/minimax-tdd-impl/v1/harness.yaml @@ -0,0 +1,13 @@ +kind: composite +name: minimax-tdd-impl +version: 1 +description: "MiniMax TDD implementation agent — tests locked read-only at OS level, cannot be modified" + +layers: + - context: minimax-code-methodology/v1 + - context: minimax/v1 + - context: gitea-ssh/v1 + - context: agent-repo/v1 + - context: tdd-protect/v1 + - capability: python-dev/v1 + - capability: tdd-file-lock/v1 diff --git a/harnesses/composites/planning-minimax-repo/v1/harness.yaml b/harnesses/composites/planning-minimax-repo/v1/harness.yaml new file mode 100644 index 0000000..e37b5bf --- /dev/null +++ b/harnesses/composites/planning-minimax-repo/v1/harness.yaml @@ -0,0 +1,10 @@ +kind: composite +name: planning-minimax-repo +version: 1 +description: "Planning agent with MiniMax + repo clone via SSH" + +layers: + - context: planning/v1 + - context: minimax/v1 + - context: gitea-ssh/v1 + - context: agent-repo/v1 diff --git a/harnesses/composites/planning-opus-repo/v1/harness.yaml b/harnesses/composites/planning-opus-repo/v1/harness.yaml new file mode 100644 index 0000000..7857676 --- /dev/null +++ b/harnesses/composites/planning-opus-repo/v1/harness.yaml @@ -0,0 +1,10 @@ +kind: composite +name: planning-opus-repo +version: 1 +description: "Planning agent with Opus (Anthropic cloud) + repo clone via SSH" + +layers: + - context: planning/v1 + - context: anthropic-cloud/v1 + - context: gitea-ssh/v1 + - context: agent-repo/v1 diff --git a/harnesses/composites/python-code-review/v1/harness.yaml b/harnesses/composites/python-code-review/v1/harness.yaml new file mode 100644 index 0000000..b6927e6 --- /dev/null +++ b/harnesses/composites/python-code-review/v1/harness.yaml @@ -0,0 +1,9 @@ +kind: composite +name: python-code-review +version: 1 +description: "Python dev + Gitea admin for code review tasks" + +layers: + - capability: python-dev/v1 + - context: gitea-admin/v1 + - context: code-methodology/v1 \ No newline at end of file diff --git a/harnesses/composites/security-review-minimax-repo/v1/harness.yaml b/harnesses/composites/security-review-minimax-repo/v1/harness.yaml new file mode 100644 index 0000000..365100d --- /dev/null +++ b/harnesses/composites/security-review-minimax-repo/v1/harness.yaml @@ -0,0 +1,11 @@ +kind: composite +name: security-review-minimax-repo +version: 1 +description: "Security review agent with MiniMax + repo clone via SSH" + +layers: + - context: security-review/v1 + - context: minimax-code-methodology/v1 + - context: minimax/v1 + - context: gitea-ssh/v1 + - context: agent-repo/v1 diff --git a/harnesses/composites/security-review-opus-repo/v1/harness.yaml b/harnesses/composites/security-review-opus-repo/v1/harness.yaml new file mode 100644 index 0000000..c3a5de9 --- /dev/null +++ b/harnesses/composites/security-review-opus-repo/v1/harness.yaml @@ -0,0 +1,10 @@ +kind: composite +name: security-review-opus-repo +version: 1 +description: "Security review agent with Opus + repo clone via SSH" + +layers: + - context: security-review/v1 + - context: anthropic-cloud/v1 + - context: gitea-ssh/v1 + - context: agent-repo/v1 diff --git a/harnesses/composites/security-review-sonnet-repo/v1/harness.yaml b/harnesses/composites/security-review-sonnet-repo/v1/harness.yaml new file mode 100644 index 0000000..d156ec5 --- /dev/null +++ b/harnesses/composites/security-review-sonnet-repo/v1/harness.yaml @@ -0,0 +1,10 @@ +kind: composite +name: security-review-sonnet-repo +version: 1 +description: "Security review agent with Sonnet + repo clone via SSH" + +layers: + - context: security-review/v1 + - context: anthropic-cloud/v1 + - context: gitea-ssh/v1 + - context: agent-repo/v1 diff --git a/harnesses/composites/spec-writing-opus-repo/v1/harness.yaml b/harnesses/composites/spec-writing-opus-repo/v1/harness.yaml new file mode 100644 index 0000000..075ebf7 --- /dev/null +++ b/harnesses/composites/spec-writing-opus-repo/v1/harness.yaml @@ -0,0 +1,10 @@ +kind: composite +name: spec-writing-opus-repo +version: 1 +description: "Spec writing agent with Opus + repo clone via SSH" + +layers: + - context: spec-writing/v1 + - context: anthropic-cloud/v1 + - context: gitea-ssh/v1 + - context: agent-repo/v1 diff --git a/harnesses/composites/spec-writing-sonnet-repo/v1/harness.yaml b/harnesses/composites/spec-writing-sonnet-repo/v1/harness.yaml new file mode 100644 index 0000000..fbc0d30 --- /dev/null +++ b/harnesses/composites/spec-writing-sonnet-repo/v1/harness.yaml @@ -0,0 +1,10 @@ +kind: composite +name: spec-writing-sonnet-repo +version: 1 +description: "Spec writing agent with Sonnet + repo clone via SSH" + +layers: + - context: spec-writing/v1 + - context: anthropic-cloud/v1 + - context: gitea-ssh/v1 + - context: agent-repo/v1 diff --git a/harnesses/composites/test-writing-opus-repo/v1/harness.yaml b/harnesses/composites/test-writing-opus-repo/v1/harness.yaml new file mode 100644 index 0000000..07b7170 --- /dev/null +++ b/harnesses/composites/test-writing-opus-repo/v1/harness.yaml @@ -0,0 +1,10 @@ +kind: composite +name: test-writing-opus-repo +version: 1 +description: "Test writing agent with Opus + repo clone via SSH" + +layers: + - context: test-writing/v1 + - context: anthropic-cloud/v1 + - context: gitea-ssh/v1 + - context: agent-repo/v1 diff --git a/harnesses/composites/test-writing-sonnet-repo/v1/harness.yaml b/harnesses/composites/test-writing-sonnet-repo/v1/harness.yaml new file mode 100644 index 0000000..f538132 --- /dev/null +++ b/harnesses/composites/test-writing-sonnet-repo/v1/harness.yaml @@ -0,0 +1,10 @@ +kind: composite +name: test-writing-sonnet-repo +version: 1 +description: "Test writing agent with Sonnet + repo clone via SSH" + +layers: + - context: test-writing/v1 + - context: anthropic-cloud/v1 + - context: gitea-ssh/v1 + - context: agent-repo/v1 diff --git a/harnesses/contexts/agent-repo/v1/finalize.sh b/harnesses/contexts/agent-repo/v1/finalize.sh new file mode 100644 index 0000000..4b467f0 --- /dev/null +++ b/harnesses/contexts/agent-repo/v1/finalize.sh @@ -0,0 +1,220 @@ +#!/bin/bash +# Agent repo finalize script — auto-commit and push changes +# AR-19, AR-20, AR-8, AR-32, F66 +set -euo pipefail + +# --------------------------------------------------------------------------- +# F66: Content-based secret scanning +# Scans staged diff for credential patterns before committing. +# Returns 0 if clean, 1 if secrets detected. +# --------------------------------------------------------------------------- +scan_for_secrets() { + local diff found=0 + diff=$(cat) + + # Extract only added lines (skip diff headers +++) + local added + added=$(printf '%s\n' "$diff" | grep '^+' | grep -v '^+++' || true) + + if [[ -z "$added" ]]; then + return 0 + fi + + # Pattern 1: AWS access key — AKIA followed by exactly 16 uppercase letters/digits + if printf '%s\n' "$added" | grep -qE 'AKIA[0-9A-Z]{16}'; then + echo "AWS access key pattern detected (AKIA...)" + found=1 + fi + + # Pattern 2: PEM private key header (RSA, EC, OPENSSH, etc.) + if printf '%s\n' "$added" | grep -qE -- '-----BEGIN .* PRIVATE KEY-----'; then + echo "PEM private key header detected" + found=1 + fi + + # Pattern 3: High-entropy credential assignment (40+ char value near key/token/secret keywords) + if printf '%s\n' "$added" | grep -qiE \ + '(secret[._]?key|api[._]?key|access[._]?key|auth[._]?token|\bsecret\b|\btoken\b|\bpassword\b|\bpasswd\b)[[:space:]]*[=:][[:space:]]*['"'"'""]?[A-Za-z0-9+/=_-]{40,}'; then + echo "High-entropy credential assignment detected" + found=1 + fi + + return $found +} + +# Allow sourcing for unit tests without running main +if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then + return 0 2>/dev/null || true +fi + +echo "=== agent-repo/v1 finalize.sh ===" + +WORKING_DIR="${AGENT_WORKING_DIR:-/workspace/project}" +AGENT_OUTPUT_DIR="${AGENT_OUTPUT_DIR:-/workspace/.agent-output}" +METADATA_FILE="$AGENT_OUTPUT_DIR/ci_metadata.json" +AGENT_EXIT="${AGENT_EXIT_CODE:-0}" +TASK_ID="${AGENT_TASK_ID:-unknown}" +BRANCH="${AGENT_BRANCH:-}" +REPO_URL="${AGENT_REPO_URL:-}" + +mkdir -p "$AGENT_OUTPUT_DIR" + +# AR-19: Mark working dir as safe (finalize may run as different user than agent) +git config --global --add safe.directory "$WORKING_DIR" + +# Change to working directory +cd "$WORKING_DIR" || { + echo "ERROR: Failed to change to working directory: $WORKING_DIR" >&2 + exit 1 +} + +# Verify this is a git repository (defensive: catches misconfigured payloads +# where agent_repo field is missing but agent-repo harness is in the composite) +if [ ! -d ".git" ]; then + echo "ERROR: $WORKING_DIR is not a git repository." >&2 + echo "Hint: Ensure the task payload includes 'agent_repo: {repo_url: ..., branch: ...}'" >&2 + echo " so init.sh clones the repo before the agent runs." >&2 + exit 1 +fi + +# AR-32: Restore .gitignore from harness template to prevent removing entries +HARNESS_GITIGNORE="/opt/harness/contexts/agent-repo/v1/.gitignore_template" +if [ -f "$HARNESS_GITIGNORE" ]; then + cat "$HARNESS_GITIGNORE" >> .gitignore +fi + +# AR-8: Check if there are any changes to commit +git add -A + +if [ -z "$(git status --porcelain)" ]; then + echo "No changes to commit — skipping push (AR-8)" + # Write metadata with agent_branch_pushed=false + python3 -c " +import json, os +out = '$METADATA_FILE' +meta = {} +if os.path.isfile(out): + try: + with open(out) as f: + meta = json.load(f) + except Exception: + meta = {} +meta['agent_branch'] = '$BRANCH' +meta['agent_repo_url'] = '$REPO_URL' +meta['agent_branch_pushed'] = False +with open(out, 'w') as f: + json.dump(meta, f) +print('Wrote ci_metadata.json (no-op: no changes)') +" + exit 0 +fi + +# F66: Scan staged content for secrets before committing +SCAN_FINDINGS="" +SCAN_EXIT=0 +set +e +SCAN_FINDINGS=$(git diff --cached | scan_for_secrets) +SCAN_EXIT=$? +set -e + +if [ "$SCAN_EXIT" -ne 0 ]; then + echo "SECRET SCAN BLOCKED COMMIT — credentials detected in staged content" + echo "$SCAN_FINDINGS" + # Write metadata indicating scan blocked the commit + python3 -c " +import json, os +out = '$METADATA_FILE' +meta = {} +if os.path.isfile(out): + try: + with open(out) as f: + meta = json.load(f) + except Exception: + meta = {} +meta['agent_branch'] = '$BRANCH' +meta['agent_repo_url'] = '$REPO_URL' +meta['agent_branch_pushed'] = False +meta['secret_scan_blocked'] = True +meta['scan_findings'] = '''$SCAN_FINDINGS''' +with open(out, 'w') as f: + json.dump(meta, f) +print('Wrote ci_metadata.json (blocked by secret scan)') +" + exit 0 +fi + +# V3: Diff size check (warning only, does not block push) +DIFF_LINES=$(git diff --cached --stat | tail -1 | grep -oP '\d+(?= insertion)' || echo "0") +DIFF_KB=$(echo "$DIFF_LINES" | awk '{printf "%.1f", $1/1000}') +MAX_DIFF_KB="${AGENT_MAX_DIFF_KB:-500}" +if awk "BEGIN {exit !($DIFF_KB > $MAX_DIFF_KB)}"; then + echo "WARNING: Large diff detected: ${DIFF_KB}KB (threshold: ${MAX_DIFF_KB}KB)" +fi + +# AR-6, AR-18: Build commit message based on agent outcome +if [ "$AGENT_EXIT" = "0" ]; then + OUTCOME="succeeded" +else + OUTCOME="failed (exit $AGENT_EXIT)" +fi + +# Get first line of prompt (safe, via tempfile) +PROMPT_FIRST_LINE="${AGENT_PROMPT:-}" +if [ -n "$PROMPT_FIRST_LINE" ]; then + PROMPT_FIRST_LINE=$(echo "$PROMPT_FIRST_LINE" | head -1 | cut -c1-100) +fi + +# Write commit message to tempfile (AR-19: safe handling) +COMMIT_MSG_FILE=$(mktemp) +trap "rm -f $COMMIT_MSG_FILE" EXIT + +if [ -n "$PROMPT_FIRST_LINE" ]; then + echo "Agent task $TASK_ID ($OUTCOME): $PROMPT_FIRST_LINE" > "$COMMIT_MSG_FILE" +else + echo "Agent task $TASK_ID ($OUTCOME)" > "$COMMIT_MSG_FILE" +fi + +# Commit +git commit -F "$COMMIT_MSG_FILE" + +# Get commit SHA +COMMIT_SHA=$(git rev-parse HEAD) + +# AR-19: Always push — even if tests failed, partial work is better than lost work. +# The task exit code and ci_metadata.json track whether tests passed. +echo "Pushing branch $BRANCH to $REPO_URL..." +if timeout 120 git push "$REPO_URL" "HEAD:refs/heads/$BRANCH" --force; then + echo "Push succeeded" + PUSHED=true +else + echo "ERROR: Push failed" >&2 + PUSHED=false +fi + +# Write ci_metadata.json +python3 -c " +import json, os +out = '$METADATA_FILE' +meta = {} +if os.path.isfile(out): + try: + with open(out) as f: + meta = json.load(f) + except Exception: + meta = {} +meta['agent_branch'] = '$BRANCH' +meta['agent_sha'] = '$COMMIT_SHA' +meta['agent_repo_url'] = '$REPO_URL' +meta['agent_branch_pushed'] = $( [ '$PUSHED' = 'true' ] && echo 'True' || echo 'False' ) +meta['diff_kb'] = float('$DIFF_KB') if '$DIFF_KB' else 0.0 +with open(out, 'w') as f: + json.dump(meta, f) +print('Wrote ci_metadata.json') +" + +if [ "$PUSHED" = "false" ]; then + echo "ERROR: Push failed — branch $BRANCH was committed but not pushed" >&2 + exit 1 +fi + +echo "=== agent-repo/v1 finalize.sh complete ===" diff --git a/harnesses/contexts/agent-repo/v1/harness.yaml b/harnesses/contexts/agent-repo/v1/harness.yaml new file mode 100644 index 0000000..afea853 --- /dev/null +++ b/harnesses/contexts/agent-repo/v1/harness.yaml @@ -0,0 +1,19 @@ +kind: context +name: agent-repo +version: 1 +description: "Agent repo workspace: reference/working split, auto-commit, branch-per-task" +requires: [] +provides: [project-repo] + +scripts: + init: "./init.sh" + finalize: "./finalize.sh" + +env: + AGENT_WORKING_DIR: "/workspace/project" + # Injected by dispatcher at task start — defaults shown for documentation only + AGENT_REPO_URL: "" + AGENT_BRANCH: "" + REFERENCE_BRANCHES: "[]" + +secrets_required: [] diff --git a/harnesses/contexts/agent-repo/v1/init.sh b/harnesses/contexts/agent-repo/v1/init.sh new file mode 100644 index 0000000..ed83133 --- /dev/null +++ b/harnesses/contexts/agent-repo/v1/init.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# Agent repo init script — clones reference repos and working repo +# AR-12 through AR-15, AR-32 +set -euo pipefail + +echo "=== agent-repo/v1 init.sh ===" + +# Ensure workspace directories exist +mkdir -p /workspace/reference +mkdir -p /workspace/project +mkdir -p /workspace/.agent-output + +# Clone reference branches (AR-12, AR-13) +if [ -n "${REFERENCE_BRANCHES:-}" ] && [ "${REFERENCE_BRANCHES:-}" != "[]" ]; then + echo "Cloning reference branches..." + echo "$REFERENCE_BRANCHES" | python3 -c " +import json, sys, subprocess, os +refs = json.load(sys.stdin) +for ref in refs: + name = ref.get('name', '') + repo_url = ref.get('repo_url', '') + branch = ref.get('branch', 'main') + dest = f'/workspace/reference/{name}' + print(f'Cloning {repo_url} ({branch}) -> {dest}') + result = subprocess.run( + ['git', 'clone', '--depth', '1', '--branch', branch, + '-c', 'core.symlinks=false', repo_url, dest], + capture_output=True, text=True + ) + if result.returncode != 0: + print(f'ERROR: Failed to clone {repo_url}: {result.stderr}', file=sys.stderr) + sys.exit(1) + # Strip any symlinks (security: prevent /proc/1/environ exfiltration) + subprocess.run(['find', dest, '-type', 'l', '-exec', 'rm', '{}', ';']) + # Make reference read-only + subprocess.run(['chmod', '-R', 'a-w', dest]) + print(f'Cloned {name} successfully') +" +fi + +# Clone agent repo working branch (AR-14) +if [ -n "${AGENT_REPO_URL:-}" ] && [ -n "${AGENT_BRANCH:-}" ]; then + echo "Cloning agent working repo: $AGENT_REPO_URL (branch: $AGENT_BRANCH)" + if git clone --depth 1 --branch "$AGENT_BRANCH" "$AGENT_REPO_URL" /workspace/project 2>/dev/null; then + echo "Cloned existing branch $AGENT_BRANCH" + else + echo "Branch $AGENT_BRANCH does not exist, creating fresh clone..." + # Clone default branch, then checkout new branch + if git clone --depth 1 "$AGENT_REPO_URL" /workspace/project; then + cd /workspace/project + git checkout -b "$AGENT_BRANCH" + else + echo "ERROR: Failed to clone agent repo $AGENT_REPO_URL" >&2 + exit 1 + fi + fi +fi + +# Clean up results/ from any previous run (AR-15) +if [ -d /workspace/project/results ]; then + rm -rf /workspace/project/results +fi +mkdir -p /workspace/project/results + +# Populate .gitignore (AR-32) +GITIGNORE=/workspace/project/.gitignore +cat >> "$GITIGNORE" << 'GITIGNORE_EOF' +# Agent-repo auto-generated gitignore entries +*.key +*.pem +*.p12 +*.pfx +.env +*.env +*.secret +GITIGNORE_EOF + +# Handle retry: clone previous attempt branch as read-only reference (AR-16) +if [ -n "${AGENT_PREVIOUS_BRANCH:-}" ] && [ "${AGENT_RETRY_COUNT:-0}" -gt 0 ]; then + echo "Cloning previous attempt branch: $AGENT_PREVIOUS_BRANCH" + git clone --depth 1 --branch "$AGENT_PREVIOUS_BRANCH" \ + -c core.symlinks=false \ + "$AGENT_REPO_URL" /workspace/reference/previous-attempt 2>/dev/null || \ + echo "Warning: Could not clone previous attempt branch (non-fatal)" + if [ -d /workspace/reference/previous-attempt ]; then + find /workspace/reference/previous-attempt -type l -exec rm {} \; + chmod -R a-w /workspace/reference/previous-attempt + fi +fi + +export AGENT_WORKING_DIR="/workspace/project" +echo "AGENT_WORKING_DIR=$AGENT_WORKING_DIR" +echo "=== agent-repo/v1 init.sh complete ===" diff --git a/harnesses/contexts/airouter/v1/harness.yaml b/harnesses/contexts/airouter/v1/harness.yaml new file mode 100644 index 0000000..eedffad --- /dev/null +++ b/harnesses/contexts/airouter/v1/harness.yaml @@ -0,0 +1,14 @@ +kind: context +name: airouter +version: 1 +description: "Airouter.ch Qwen3.6 — OpenAI-compatible agentic runner" +requires: [] +provides: [agentic-runner] + +env: + OPENAI_BASE_URL: "https://api.airouter.ch/v1" + +secrets_files: + - source: ./provider.sops.env + target: /opt/harness/secrets/airouter/provider.sops.env + encrypted: true diff --git a/harnesses/contexts/alibaba-model-studio/v1/harness.yaml b/harnesses/contexts/alibaba-model-studio/v1/harness.yaml new file mode 100644 index 0000000..9dfa630 --- /dev/null +++ b/harnesses/contexts/alibaba-model-studio/v1/harness.yaml @@ -0,0 +1,19 @@ +kind: context +name: alibaba-model-studio +version: 1 +description: "Alibaba Cloud coding plan — Anthropic-compatible proxy" +requires: [] +provides: [claude-code] + +env: + ANTHROPIC_BASE_URL: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic" + CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: "1" + CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" + DISABLE_PROMPT_CACHING: "1" + +secrets_files: + - source: ./provider.sops.env + target: /opt/harness/secrets/alibaba/provider.sops.env + encrypted: true + +# TODO: Add network_hosts for coding-intl.dashscope.aliyuncs.com when context harnesses support it \ No newline at end of file diff --git a/harnesses/contexts/anthropic-cloud/v1/harness.yaml b/harnesses/contexts/anthropic-cloud/v1/harness.yaml new file mode 100644 index 0000000..4b34023 --- /dev/null +++ b/harnesses/contexts/anthropic-cloud/v1/harness.yaml @@ -0,0 +1,14 @@ +kind: context +name: anthropic-cloud +version: 1 +description: "Anthropic cloud — subscription OAuth token + pricing enforcement" +requires: [] +provides: [claude-code] + +env: + ENFORCE_SUBSCRIPTION_PRICING: "true" + +secrets_files: + - source: ./provider.sops.env + target: /opt/harness/secrets/anthropic-cloud/provider.sops.env + encrypted: true diff --git a/harnesses/contexts/best-practices/v1/best-practices/api-design.md b/harnesses/contexts/best-practices/v1/best-practices/api-design.md new file mode 120000 index 0000000..e1f9fbb --- /dev/null +++ b/harnesses/contexts/best-practices/v1/best-practices/api-design.md @@ -0,0 +1 @@ +../../../planning/v1/best-practices/api-design.md \ No newline at end of file diff --git a/harnesses/contexts/best-practices/v1/best-practices/database-selection.md b/harnesses/contexts/best-practices/v1/best-practices/database-selection.md new file mode 120000 index 0000000..c3e9d90 --- /dev/null +++ b/harnesses/contexts/best-practices/v1/best-practices/database-selection.md @@ -0,0 +1 @@ +../../../planning/v1/best-practices/database-selection.md \ No newline at end of file diff --git a/harnesses/contexts/best-practices/v1/best-practices/docker.md b/harnesses/contexts/best-practices/v1/best-practices/docker.md new file mode 120000 index 0000000..48ebc88 --- /dev/null +++ b/harnesses/contexts/best-practices/v1/best-practices/docker.md @@ -0,0 +1 @@ +../../../planning/v1/best-practices/docker.md \ No newline at end of file diff --git a/harnesses/contexts/best-practices/v1/best-practices/kubernetes.md b/harnesses/contexts/best-practices/v1/best-practices/kubernetes.md new file mode 120000 index 0000000..a4e5675 --- /dev/null +++ b/harnesses/contexts/best-practices/v1/best-practices/kubernetes.md @@ -0,0 +1 @@ +../../../planning/v1/best-practices/kubernetes.md \ No newline at end of file diff --git a/harnesses/contexts/best-practices/v1/best-practices/llm-code-security.md b/harnesses/contexts/best-practices/v1/best-practices/llm-code-security.md new file mode 120000 index 0000000..6b6b5f8 --- /dev/null +++ b/harnesses/contexts/best-practices/v1/best-practices/llm-code-security.md @@ -0,0 +1 @@ +../../../planning/v1/best-practices/llm-code-security.md \ No newline at end of file diff --git a/harnesses/contexts/best-practices/v1/best-practices/secrets-management.md b/harnesses/contexts/best-practices/v1/best-practices/secrets-management.md new file mode 120000 index 0000000..8c81c2a --- /dev/null +++ b/harnesses/contexts/best-practices/v1/best-practices/secrets-management.md @@ -0,0 +1 @@ +../../../planning/v1/best-practices/secrets-management.md \ No newline at end of file diff --git a/harnesses/contexts/best-practices/v1/best-practices/security-architecture.md b/harnesses/contexts/best-practices/v1/best-practices/security-architecture.md new file mode 120000 index 0000000..cc3d131 --- /dev/null +++ b/harnesses/contexts/best-practices/v1/best-practices/security-architecture.md @@ -0,0 +1 @@ +../../../planning/v1/best-practices/security-architecture.md \ No newline at end of file diff --git a/harnesses/contexts/best-practices/v1/best-practices/spec-driven-development.md b/harnesses/contexts/best-practices/v1/best-practices/spec-driven-development.md new file mode 120000 index 0000000..f9d3578 --- /dev/null +++ b/harnesses/contexts/best-practices/v1/best-practices/spec-driven-development.md @@ -0,0 +1 @@ +../../../planning/v1/best-practices/spec-driven-development.md \ No newline at end of file diff --git a/harnesses/contexts/best-practices/v1/best-practices/test-driven-development.md b/harnesses/contexts/best-practices/v1/best-practices/test-driven-development.md new file mode 120000 index 0000000..50bb671 --- /dev/null +++ b/harnesses/contexts/best-practices/v1/best-practices/test-driven-development.md @@ -0,0 +1 @@ +../../../planning/v1/best-practices/test-driven-development.md \ No newline at end of file diff --git a/harnesses/contexts/best-practices/v1/harness.yaml b/harnesses/contexts/best-practices/v1/harness.yaml new file mode 100644 index 0000000..18e55b1 --- /dev/null +++ b/harnesses/contexts/best-practices/v1/harness.yaml @@ -0,0 +1,10 @@ +kind: context +name: best-practices +version: 1 +description: "Cross-project best practices — security, API design, TDD, spec-driven dev, etc." +requires: [] +provides: [best-practices] + +context_files: + - source: ./best-practices/ + target: /opt/harness/context/best-practices/ diff --git a/harnesses/contexts/code-methodology/v1/CLAUDE.md b/harnesses/contexts/code-methodology/v1/CLAUDE.md new file mode 100644 index 0000000..3dfe900 --- /dev/null +++ b/harnesses/contexts/code-methodology/v1/CLAUDE.md @@ -0,0 +1,110 @@ +# Code Methodology Context + +## Best Practices Review + +Before starting any task: + +1. Check if `/best-practices/INDEX.md` exists +2. If it exists, read it to see available topics +3. Identify relevant topics for the current task: + - Python task → read `test-driven-development.md`, `spec-driven-development.md` + - Kubernetes task → read `kubernetes.md` + - Shell scripts → read `scripting.md` +4. Read the relevant topic files from `/best-practices/` +5. Apply those practices to your work + +If `/best-practices/` doesn't exist, proceed without — it's not mandatory. + +## Spec-Driven Development Workflow + +1. **Read the spec** — Read relevant files in `spec/` before writing any code +2. **Write tests first** — Create test cases from spec requirements before implementing (see `best-practices/test-driven-development.md` and `best-practices/spec-driven-development.md` if available) +3. **Implement iteratively** — Build implementation to satisfy tests and spec +4. **Write session log** — Document what was accomplished, decisions made, and gotchas discovered + +## Conventions + +- **Python 3.12+** with type hints throughout +- **Pydantic v2** for all data models and settings +- **pytest** for testing with clear, descriptive test names +- **Structured JSON logging** for all output +- **Configuration via environment variables** — no hardcoded config +- **No shell scripts inside containers** — use Python for error handling + +## Code Quality Rules + +- Do not add features, refactor code, or make improvements beyond what was asked +- Validate inputs at system boundaries only, trust internal interfaces + +## Agent Safety Rules + +These rules exist because container agents have repeatedly made these mistakes in production runs. + +### Python Module Safety + +Never call `sys.exit()` at module level or inside `except ImportError` blocks. This causes import +failures in other modules that import this one. Use a flag pattern instead: + +```python +_HAS_OPENAI = True +try: + import openai +except ImportError: + _HAS_OPENAI = False +``` + +Check the flag at call time: + +```python +def use_openai(): + if not _HAS_OPENAI: + raise RuntimeError("openai is not installed") + ... +``` + +### Pydantic Validators + +- Use `@model_validator(mode='after')` when validation needs cross-field access or must fire for + default values +- `@field_validator` only fires when a field is **explicitly provided** — it will not run for + fields that fall back to their default + +### File Editing Policy (CRITICAL) + +**NEVER use the Write tool on any file that already exists in /project.** + +This is the single most important rule. Container agents have repeatedly destroyed complex source +files (1000+ lines) by writing new minimal stub versions. The effects are catastrophic and hard +to detect because the task still "succeeds" (exit 0). + +The correct workflow for modifying any existing file: +1. **Read** the file first (Read tool) +2. **Edit** with targeted changes (Edit tool) — ONLY the specific function, class, or field +3. **Never** rewrite an entire file from scratch with Write +4. **Never** "simplify" or "restructure" a file unless that is the explicit task + +The Write tool is ONLY for creating brand-new files that do not yet exist. + +Other file management rules: +- Never create backup copies of files or directories before modifying them +- Edit files in place — do not create paths ending in `_orig`, `_old`, `_bak`, or `_backup` +- Do not rename existing files or directories before modifying them +- In automated runs there is no one to clean up junk directories + +### Async Test Patterns + +- Test functions for async code must be `async def` decorated with `@pytest.mark.asyncio` +- A sync `def test_` function that calls an async function receives a coroutine object, not the + result — the test will silently pass without executing the async logic + +## Session Logging + +Write a session log to `/project/memory/log/.