HARNESS / 代理工程下载 EPUB ↓
31 / 32

附录 D:随书 Harness 完整代码

以下代码由构建器直接从 lab/ 读取;可执行文件与书内版本同源。它是独立教学实现,不与四套项目的原协议兼容。

harness.py

"""Teaching harness: deterministic model, durable tool protocol, no API keys.

Path checks here are application checks, NOT an operating-system sandbox.
The journal assumes a local filesystem, one writer and cooperative tools.
"""
from __future__ import annotations
from contextlib import contextmanager
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Callable, Iterable, Protocol
import hashlib
import json
import os
import threading


def digest(data: bytes) -> str:
    return hashlib.sha256(data).hexdigest()


def canonical(value: object) -> bytes:
    return json.dumps(value, sort_keys=True, ensure_ascii=False, separators=(",", ":")).encode()


class HarnessError(Exception):
    pass


class ProtocolError(HarnessError):
    pass


class ReviewRequired(HarnessError):
    pass


class BudgetExceeded(HarnessError):
    pass


class VerificationFailed(HarnessError):
    pass


class TransientModelError(HarnessError):
    """Only a model adapter may mark a pre-reply request safe to retry."""


@dataclass(frozen=True)
class Call:
    id: str
    name: str
    arguments: dict


@dataclass(frozen=True)
class Reply:
    text: str
    calls: tuple[Call, ...] = ()


def decode_stream(events: Iterable[dict]) -> Reply:
    """Buffer fragments until a complete end marker; never execute partial calls."""
    text = []
    fragments: dict[str, dict] = {}
    ended = False
    for event in events:
        if ended:
            raise ProtocolError("event after reply.end")
        if event.get("type") == "text.delta":
            if not isinstance(event.get("text"), str):
                raise ProtocolError("text delta must be a string")
            text.append(event["text"])
        elif event.get("type") == "tool.delta":
            cid, name, args = event.get("id"), event.get("name"), event.get("arguments")
            if not isinstance(cid, str) or not cid or not isinstance(args, str):
                raise ProtocolError("invalid tool fragment")
            if cid not in fragments:
                if not isinstance(name, str) or not name:
                    raise ProtocolError("first fragment needs a tool name")
                fragments[cid] = {"name": name, "arguments": ""}
            elif name is not None and name != fragments[cid]["name"]:
                raise ProtocolError("tool name changed inside stream")
            fragments[cid]["arguments"] += args
        elif event.get("type") == "reply.end":
            if event.get("reason") != "stop":
                raise ProtocolError("truncated, cancelled or failed response")
            ended = True
        else:
            raise ProtocolError("unknown model stream event")
    if not ended:
        raise ProtocolError("stream ended without reply.end")
    calls = []
    for cid, data in fragments.items():
        try:
            args = json.loads(data["arguments"])
        except (ValueError, TypeError) as exc:
            raise ProtocolError("invalid tool JSON") from exc
        if not isinstance(args, dict):
            raise ProtocolError("tool arguments must be an object")
        calls.append(Call(cid, data["name"], args))
    return Reply("".join(text), tuple(calls))


class Model(Protocol):
    def stream(self, messages: list[dict], step: int) -> Iterable[dict]: ...


class Journal:
    def __init__(self, path: Path):
        self.path = Path(path)
        self.path.parent.mkdir(parents=True, exist_ok=True)
        self.lock_path = self.path.with_suffix(self.path.suffix + ".lock")
        self.events = self._load()

    def _load(self) -> list[dict]:
        events = []
        previous = "0" * 64
        if not self.path.exists():
            return events
        raw = self.path.read_bytes()
        if raw and not raw.endswith(b"\n"):
            raise ProtocolError("torn journal tail; preserve and repair explicitly")
        for line in raw.splitlines():
            try:
                event = json.loads(line)
                signature = event.pop("hash")
                if event["seq"] != len(events) or event["previous"] != previous:
                    raise ValueError("sequence or hash chain mismatch")
                if digest(canonical(event)) != signature:
                    raise ValueError("record hash mismatch")
                event["hash"] = signature
                previous = signature
                events.append(event)
            except (ValueError, KeyError, TypeError, AttributeError) as exc:
                raise ProtocolError("invalid journal record") from exc
        return events

    @contextmanager
    def writer(self):
        try:
            fd = os.open(self.lock_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
        except FileExistsError as exc:
            raise HarnessError("journal writer lock exists; verify its owner before recovery") from exc
        try:
            with os.fdopen(fd, "w") as lock:
                lock.write(str(os.getpid()))
            self.events = self._load()
            yield self
        finally:
            self.lock_path.unlink(missing_ok=True)

    def append(self, kind: str, **data) -> dict:
        if not self.lock_path.exists():
            raise HarnessError("writer lock required")
        event = dict(seq=len(self.events), previous=self.events[-1]["hash"] if self.events else "0" * 64,
                     type=kind, data=data)
        event["hash"] = digest(canonical(event))
        with self.path.open("ab") as handle:
            handle.write(canonical(event) + b"\n")
            handle.flush()
            os.fsync(handle.fileno())
        self.events.append(event)
        return event


SCHEMAS = {
    "read_file": {"path": str},
    "write_file": {"path": str, "text": str, "expected_sha256": str},
    "check_file": {"path": str, "expected_text": str},
}


@dataclass(frozen=True)
class Policy:
    allow_write: bool = False
    max_read_bytes: int = 65536


class Tools:
    def __init__(self, workspace: Path, policy: Policy):
        self.workspace = workspace.resolve(strict=True)
        self.policy = policy

    def checked_path(self, value: str) -> Path:
        target = (self.workspace / value).resolve()
        if not target.is_relative_to(self.workspace):
            raise PermissionError("path escapes workspace")
        relative = target.relative_to(self.workspace)
        if relative.parts and relative.parts[0] == ".harness":
            raise PermissionError("harness state is reserved")
        return target

    def execute(self, call: Call) -> dict:
        schema = SCHEMAS.get(call.name)
        if schema is None or set(call.arguments) != set(schema):
            return dict(status="invalid", content="unknown tool or fields")
        if any(type(call.arguments[k]) is not t for k, t in schema.items()):
            return dict(status="invalid", content="wrong argument type")
        try:
            path = self.checked_path(call.arguments["path"])
            if call.name == "write_file":
                if not self.policy.allow_write:
                    raise PermissionError("write denied by policy")
                old = path.read_bytes() if path.exists() else None
                actual = digest(old) if old is not None else "missing"
                if actual != call.arguments["expected_sha256"]:
                    return dict(status="conflict", content="file changed; read again before proposing a write")
                # Teaching implementation: optimistic check + ordinary write is NOT atomic compare-and-swap.
                payload = call.arguments["text"].encode()
                if len(payload) > self.policy.max_read_bytes:
                    raise ValueError("write exceeds teaching size limit")
                with path.open("wb") as handle:
                    handle.write(payload)
                    handle.flush()
                    os.fsync(handle.fileno())
                return dict(status="ok", content=json.dumps({"sha256": digest(payload)}, sort_keys=True))
            with path.open("rb") as handle:
                raw = handle.read(self.policy.max_read_bytes + 1)
            if len(raw) > self.policy.max_read_bytes:
                raise ValueError("file exceeds read limit")
            text = raw.decode("utf-8")
            if call.name == "read_file":
                return dict(status="ok", content=json.dumps({"sha256": digest(raw), "text": text}, ensure_ascii=False))
            matched = text == call.arguments["expected_text"]
            return dict(status="ok" if matched else "error", content=json.dumps({"matched": matched}))
        except PermissionError as exc:
            return dict(status="denied", content=str(exc))
        except (OSError, ValueError, UnicodeError) as exc:
            return dict(status="error", content=str(exc))


@dataclass(frozen=True)
class Limits:
    model_requests: int = 8
    tool_calls: int = 12
    preview_chars: int = 2000
    transient_retries: int = 1


class Engine:
    def __init__(self, journal: Journal, tools: Tools, model: Model, *, limits: Limits = Limits(),
                 cancel: threading.Event | None = None):
        self.journal, self.tools, self.model = journal, tools, model
        self.limits = limits
        self.cancel = cancel or threading.Event()

    def events(self, kind: str) -> list[dict]:
        return [e for e in self.journal.events if e["type"] == kind]

    def messages(self) -> list[dict]:
        # The model projection excludes timing/administrative events.
        result = []
        for event in self.journal.events:
            if event["type"] == "user.input":
                result.append(dict(role="user", content=event["data"]["text"]))
            elif event["type"] == "model.reply":
                result.append(dict(role="assistant", **event["data"]))
            elif event["type"] == "tool.result":
                result.append(dict(role="tool", **event["data"]))
        return result

    def _finish_tool(self, call: Call, result: dict) -> None:
        content = result["content"]
        if len(content) > self.limits.preview_chars:
            artifacts = self.journal.path.parent / "artifacts"
            artifacts.mkdir(exist_ok=True)
            raw = content.encode()
            key = digest(raw)
            target = artifacts / (key + ".txt")
            with target.open("wb") as handle:
                handle.write(raw)
                handle.flush()
                os.fsync(handle.fileno())
            result = dict(result, content=content[:self.limits.preview_chars] + "\n[truncated]",
                          artifact="artifacts/" + target.name, sha256=key)
        self.journal.append("tool.result", call_id=call.id, name=call.name, **result)

    def _recover(self) -> None:
        completed = {e["data"]["call_id"] for e in self.events("tool.result")}
        for event in self.events("tool.started"):
            data = event["data"]
            if data["id"] not in completed:
                self._finish_tool(Call(data["id"], data["name"], data["arguments"]),
                                  dict(status="unknown", content="execution started, durable outcome missing; inspect external state"))
                completed.add(data["id"])
        if any(e["data"]["status"] == "unknown" for e in self.events("tool.result")):
            raise ReviewRequired("unknown outcome exists; do not auto-retry this session")

    def _pending(self) -> list[Call]:
        done = {e["data"]["call_id"] for e in self.events("tool.result")}
        return [Call(**call) for e in self.events("model.reply") for call in e["data"]["calls"] if call["id"] not in done]

    def _check_cancel(self) -> None:
        if self.cancel.is_set():
            self.journal.append("run.cancelled")
            raise HarnessError("cancelled at cooperative boundary")

    def run(self, goal: str, acceptance: Callable[[], bool]) -> str:
        with self.journal.writer():
            if not self.journal.events:
                self.journal.append("session.started", version=1, workspace=str(self.tools.workspace), policy=asdict(self.tools.policy))
                self.journal.append("user.input", text=goal)
            header = self.events("session.started")[0]["data"]
            if header["version"] != 1 or header["workspace"] != str(self.tools.workspace) or header["policy"] != asdict(self.tools.policy):
                raise ProtocolError("session version, workspace or policy changed")
            if self.events("user.input")[0]["data"]["text"] != goal:
                raise ProtocolError("new goal needs a new session")
            self._recover()
            finished = self.events("run.finished")
            if finished:
                if not acceptance():
                    raise VerificationFailed("saved completion no longer matches workspace")
                return finished[-1]["data"]["answer"]
            while True:
                self._check_cancel()
                for call in self._pending():
                    self._check_cancel()
                    if len(self.events("tool.started")) >= self.limits.tool_calls:
                        raise BudgetExceeded("tool budget exhausted")
                    # Durable intent BEFORE any possible side effect.
                    self.journal.append("tool.started", **asdict(call))
                    self._finish_tool(call, self.tools.execute(call))
                replies = self.events("model.reply")
                if replies and not replies[-1]["data"]["calls"]:
                    accepted = bool(acceptance())
                    self.journal.append("acceptance.checked", accepted=accepted)
                    if not accepted:
                        raise VerificationFailed("model answer does not satisfy acceptance")
                    answer = replies[-1]["data"]["text"]
                    self.journal.append("run.finished", answer=answer)
                    return answer
                reply = None
                for attempt in range(self.limits.transient_retries + 1):
                    if len(self.events("model.started")) >= self.limits.model_requests:
                        raise BudgetExceeded("model request budget exhausted")
                    self._check_cancel()
                    self.journal.append("model.started", step=len(replies), attempt=attempt)
                    try:
                        reply = decode_stream(self.model.stream(self.messages(), len(replies)))
                        self._check_cancel()
                        break
                    except TransientModelError as exc:
                        self.journal.append("model.retryable_error", error=str(exc))
                        if attempt == self.limits.transient_retries:
                            raise
                assert reply is not None
                used = {c["id"] for e in replies for c in e["data"]["calls"]}
                if any(c.id in used for c in reply.calls):
                    raise ProtocolError("tool call id reused across replies")
                self.journal.append("model.reply", text=reply.text, calls=[asdict(c) for c in reply.calls])


class DemoModel:
    """Scripted provider: exercise the same host boundaries without a model API."""
    def stream(self, messages: list[dict], step: int) -> Iterable[dict]:
        if step == 0:
            reply = Reply("先读取实际文件。", (Call("read-1", "read_file", {"path": "hello.txt"}),))
        elif step == 1:
            previous = next(m for m in reversed(messages) if m["role"] == "tool")
            if previous["status"] != "ok":
                reply = Reply("读取失败,停止。")
            else:
                observed = json.loads(previous["content"])
                reply = Reply("以读取到的哈希为前置条件修复拼写。", (Call("write-1", "write_file", {
                    "path": "hello.txt", "text": "hello, harness\n", "expected_sha256": observed["sha256"]}),))
        elif step == 2:
            reply = Reply("检查文件内容。", (Call("check-1", "check_file", {
                "path": "hello.txt", "expected_text": "hello, harness\n"}),))
        else:
            reply = Reply("工具阶段结束,是否完成由宿主验收。")
        yield {"type": "text.delta", "text": reply.text}
        for call in reply.calls:
            raw = json.dumps(call.arguments, ensure_ascii=False)
            middle = len(raw) // 2
            yield {"type": "tool.delta", "id": call.id, "name": call.name, "arguments": raw[:middle]}
            yield {"type": "tool.delta", "id": call.id, "arguments": raw[middle:]}
        yield {"type": "reply.end", "reason": "stop"}

demo.py

"""Run from repository root: python3 lab/demo.py --workspace /tmp/harness-book-demo"""
from pathlib import Path
import argparse
import json
from harness import DemoModel, Engine, HarnessError, Journal, Policy, Tools

parser = argparse.ArgumentParser()
parser.add_argument('--workspace', type=Path, required=True)
parser.add_argument('--read-only', action='store_true')
args = parser.parse_args()
workspace = args.workspace.resolve()
workspace.mkdir(parents=True, exist_ok=True)
target = workspace / 'hello.txt'
if not target.exists():
    target.write_text('helo, harness\n', encoding='utf-8')
journal = Journal(workspace / '.harness' / 'events.jsonl')
tools = Tools(workspace, Policy(allow_write=not args.read_only))
def acceptance():
    successful_check = any(e['type'] == 'tool.result' and e['data']['name'] == 'check_file'
                           and e['data']['status'] == 'ok' for e in journal.events)
    return successful_check and target.read_text(encoding='utf-8') == 'hello, harness\n'
try:
    answer = Engine(journal, tools, DemoModel()).run('修正 hello.txt 的拼写并检查结果', acceptance)
    print(json.dumps({'status': 'verified', 'answer': answer, 'events': len(journal.events)}, ensure_ascii=False))
except HarnessError as exc:
    print(json.dumps({'status': 'stopped', 'reason': str(exc)}, ensure_ascii=False))
    raise SystemExit(2)

非官方学习读物 · 固定源码快照 · 原始代码保留各自许可。GitHub · 源码依据 · 实验代码