Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/agent-harness-terminal-receipts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Agent Harness Terminal Receipts

on:
pull_request:
paths:
- 'docs/sourceos/AGENT_HARNESS_TERMINAL_RECEIPTS.md'
- 'schemas/agent-harness-terminal-receipts.schema.json'
- 'examples/agent-harness-terminal-receipts.example.json'
- 'scripts/verify-agent-harness-terminal-receipts.py'
- '.github/workflows/agent-harness-terminal-receipts.yml'
push:
branches:
- main
paths:
- 'docs/sourceos/AGENT_HARNESS_TERMINAL_RECEIPTS.md'
- 'schemas/agent-harness-terminal-receipts.schema.json'
- 'examples/agent-harness-terminal-receipts.example.json'
- 'scripts/verify-agent-harness-terminal-receipts.py'
- '.github/workflows/agent-harness-terminal-receipts.yml'

permissions:
contents: read

jobs:
validate-agent-harness-terminal-receipts:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: Validate TurtleTerm Agent Harness receipts
run: python3 scripts/verify-agent-harness-terminal-receipts.py
53 changes: 53 additions & 0 deletions docs/sourceos/AGENT_HARNESS_TERMINAL_RECEIPTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Agent Harness Terminal Receipt Surface

Status: v0.1 planning baseline
Owner plane: TurtleTerm governed operator surface
Consumers: SourceOS spec, AgentPlane, Policy Fabric, Memory Mesh, SCOPE-D, Delivery Excellence

## Purpose

This document defines the TurtleTerm receipt boundary for agent-harness operator work. The surface is intended to make sessions visible, bounded, policy-referenced, and replayable without assigning independent authority to agents or cognition layers.

## Boundary

TurtleTerm owns the local operator experience, wrapper receipts, approval receipts, pane references, local gateway evidence, and replayable workflow records.

TurtleTerm does not own AgentPlane graph execution, Policy Fabric authority decisions, Agent Machine provider lifecycle, Delivery Excellence scoreboards, Memory Mesh artifact storage, or SCOPE-D exercise execution.

## Receipt classes

TerminalSessionReceipt records a governed session with session id, actor ref, workspace ref, profile refs, policy admission ref, AgentPlane refs, timestamps, pane refs when applicable, and environment profile hash.

CommandReceipt records an execution event with event id, session ref, event hash, working directory, environment hash, artifact pointer refs, exit code, duration, policy decision ref, side-effect class, and replay eligibility.

MutationReceipt records an observed change with change id, execution ref, change class, target scope, run mode, policy decision ref, human-control event ref when required, before and after artifact refs when available, rollback ref, and denied operation refs.

OperatorApprovalReceipt records a typed human decision with approval id, actor ref, subject ref, decision, reason, timestamp, policy gate ref, AgentPlane ref, and Delivery Excellence human-control event ref.

## Integration notes

AgentPlane should cite TurtleTerm receipts in run, replay, session, evidence, diagnosis, and promotion surfaces.

Large outputs, transcripts, generated files, diffs, and local artifacts should be represented through Memory Mesh ArtifactPointer refs when large, sensitive, replay-critical, or customer-proof relevant.

Delivery Excellence should consume derived readouts such as success or failure, policy-blocked counts, change posture, approval latency, replay eligibility, operator intervention count, workflow cycle time, and customer-safe proof of work. It should not consume raw local transcripts unless policy permits it.

SCOPE-D should validate the governed-workflow boundary and provide checks that the policy reference model is preserved.

## Non-negotiables

- TurtleTerm must not grant ambient authority to agents.
- Agent Machine owns machine-local provider lifecycle.
- Policy Fabric decides controlled action authority.
- Outputs may require redaction and artifact pointers.
- Host-level changes must be explicit, policy-referenced, and rollback-aware.
- Human approvals are typed control events, not freeform notes.
- Delivery Excellence receives metrics and readouts, not uncontrolled raw logs.

## Near-term implementation path

1. Align wrapper receipts with SourceOS execution receipt boundaries.
2. Add examples for session, execution, change, and approval receipts.
3. Add a verifier requiring policy refs for controlled action classes.
4. Add Delivery Excellence projection examples.
5. Add SCOPE-D boundary checks for the governed operator workflow.
50 changes: 50 additions & 0 deletions examples/agent-harness-terminal-receipts.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"schemaVersion": "v0.1",
"kind": "AgentHarnessTerminalReceipts",
"terminalSessionReceipt": {
"sessionId": "turtleterm-session-agent-harness-v0.1",
"actorRef": "github://mdheller",
"workspaceRef": "github://SocioProphet/sociosphere",
"shellProfile": "sourceos-local-dev",
"gatewayProfile": "turtle-agentd-local",
"policyAdmissionRef": "github://SocioProphet/policy-fabric/pull/60",
"agentplaneRunRef": "github://SocioProphet/agentplane/pull/107",
"muxPaneRefs": [],
"environmentProfileHash": "sha256:1111111111111111111111111111111111111111111111111111111111111111"
},
"commandReceipt": {
"commandId": "command-agent-harness-validate-runtime-contracts",
"terminalSessionRef": "turtleterm-session-agent-harness-v0.1",
"commandHash": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"workingDirectory": "~/dev/agentplane",
"environmentProfileHash": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
"stdoutPointerRef": "artifact://sha256/3333333333333333333333333333333333333333333333333333333333333333",
"stderrPointerRef": "artifact://sha256/4444444444444444444444444444444444444444444444444444444444444444",
"exitCode": 0,
"policyDecisionRef": "github://SocioProphet/policy-fabric/pull/60",
"sideEffectClass": "none",
"replayEligible": true
},
"mutationReceipt": {
"mutationId": "mutation-none-agent-harness-v0.1",
"commandRef": "command-agent-harness-validate-runtime-contracts",
"mutationClass": "none",
"targetScope": "workspace-only",
"mode": "dry-run",
"policyDecisionRef": "github://SocioProphet/policy-fabric/pull/60",
"humanControlEventRef": "",
"rollbackRef": "",
"mutatedHost": false,
"deniedOperationRefs": []
},
"operatorApprovalReceipt": {
"approvalId": "operator-approval-agent-harness-baseline",
"actorRef": "github://mdheller",
"subjectRef": "github://SourceOS-Linux/TurtleTerm/pull/5",
"decision": "deferred",
"reason": "Baseline receipt fixture only; live terminal mutation approval is not requested.",
"policyGateRef": "github://SocioProphet/policy-fabric/pull/60",
"agentplaneRunRef": "github://SocioProphet/agentplane/pull/107",
"deliveryExcellenceEventRef": "github://SocioProphet/delivery-excellence-automation/pull/7"
}
}
78 changes: 78 additions & 0 deletions schemas/agent-harness-terminal-receipts.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://sourceos.dev/schemas/turtleterm/agent-harness-terminal-receipts.schema.json",
"title": "AgentHarnessTerminalReceipts",
"type": "object",
"additionalProperties": false,
"required": ["schemaVersion", "kind", "terminalSessionReceipt", "commandReceipt", "mutationReceipt", "operatorApprovalReceipt"],
"properties": {
"schemaVersion": { "const": "v0.1" },
"kind": { "const": "AgentHarnessTerminalReceipts" },
"terminalSessionReceipt": {
"type": "object",
"additionalProperties": false,
"required": ["sessionId", "actorRef", "workspaceRef", "shellProfile", "gatewayProfile", "policyAdmissionRef", "agentplaneRunRef", "environmentProfileHash"],
"properties": {
"sessionId": { "type": "string" },
"actorRef": { "type": "string" },
"workspaceRef": { "type": "string" },
"shellProfile": { "type": "string" },
"gatewayProfile": { "type": "string" },
"policyAdmissionRef": { "type": "string" },
"agentplaneRunRef": { "type": "string" },
"muxPaneRefs": { "type": "array", "items": { "type": "string" } },
"environmentProfileHash": { "type": "string" }
}
},
"commandReceipt": {
"type": "object",
"additionalProperties": false,
"required": ["commandId", "terminalSessionRef", "commandHash", "workingDirectory", "environmentProfileHash", "exitCode", "policyDecisionRef", "sideEffectClass", "replayEligible"],
"properties": {
"commandId": { "type": "string" },
"terminalSessionRef": { "type": "string" },
"commandHash": { "type": "string" },
"workingDirectory": { "type": "string" },
"environmentProfileHash": { "type": "string" },
"stdoutPointerRef": { "type": "string" },
"stderrPointerRef": { "type": "string" },
"exitCode": { "type": "integer" },
"policyDecisionRef": { "type": "string" },
"sideEffectClass": { "type": "string", "enum": ["none", "workspace-write", "host-mutation", "secret-access", "network-service", "deployment"] },
"replayEligible": { "type": "boolean" }
}
},
"mutationReceipt": {
"type": "object",
"additionalProperties": false,
"required": ["mutationId", "commandRef", "mutationClass", "targetScope", "mode", "policyDecisionRef", "mutatedHost"],
"properties": {
"mutationId": { "type": "string" },
"commandRef": { "type": "string" },
"mutationClass": { "type": "string" },
"targetScope": { "type": "string" },
"mode": { "type": "string", "enum": ["dry-run", "live"] },
"policyDecisionRef": { "type": "string" },
"humanControlEventRef": { "type": "string" },
"rollbackRef": { "type": "string" },
"mutatedHost": { "type": "boolean" },
"deniedOperationRefs": { "type": "array", "items": { "type": "string" } }
}
},
"operatorApprovalReceipt": {
"type": "object",
"additionalProperties": false,
"required": ["approvalId", "actorRef", "subjectRef", "decision", "policyGateRef", "agentplaneRunRef", "deliveryExcellenceEventRef"],
"properties": {
"approvalId": { "type": "string" },
"actorRef": { "type": "string" },
"subjectRef": { "type": "string" },
"decision": { "type": "string", "enum": ["approved", "rejected", "deferred", "accepted-risk", "revoked"] },
"reason": { "type": "string" },
"policyGateRef": { "type": "string" },
"agentplaneRunRef": { "type": "string" },
"deliveryExcellenceEventRef": { "type": "string" }
}
}
}
}
78 changes: 78 additions & 0 deletions scripts/verify-agent-harness-terminal-receipts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""Verify TurtleTerm Agent Harness terminal receipt fixture."""

from __future__ import annotations

import json
import sys
from pathlib import Path
from typing import Any

ROOT = Path(__file__).resolve().parents[1]
SCHEMA = ROOT / "schemas" / "agent-harness-terminal-receipts.schema.json"
EXAMPLE = ROOT / "examples" / "agent-harness-terminal-receipts.example.json"


class ValidationError(Exception):
pass


def load_json(path: Path) -> Any:
with path.open("r", encoding="utf-8") as handle:
return json.load(handle)


def require(condition: bool, message: str) -> None:
if not condition:
raise ValidationError(message)


def validate(data: dict[str, Any]) -> None:
require(SCHEMA.exists(), f"missing schema: {SCHEMA}")
require(data.get("schemaVersion") == "v0.1", "schemaVersion must be v0.1")
require(data.get("kind") == "AgentHarnessTerminalReceipts", "kind mismatch")

session = data.get("terminalSessionReceipt")
require(isinstance(session, dict), "terminalSessionReceipt must be object")
for field in ["sessionId", "actorRef", "workspaceRef", "shellProfile", "gatewayProfile", "policyAdmissionRef", "agentplaneRunRef", "environmentProfileHash"]:
require(field in session, f"terminalSessionReceipt missing {field}")
require(session["environmentProfileHash"].startswith("sha256:"), "environmentProfileHash must be a sha256 ref")

command = data.get("commandReceipt")
require(isinstance(command, dict), "commandReceipt must be object")
for field in ["commandId", "terminalSessionRef", "commandHash", "workingDirectory", "environmentProfileHash", "exitCode", "policyDecisionRef", "sideEffectClass", "replayEligible"]:
require(field in command, f"commandReceipt missing {field}")
require(command["commandHash"].startswith("sha256:"), "commandHash must be a sha256 ref")
require(command["policyDecisionRef"], "commandReceipt requires policyDecisionRef")
if command["exitCode"] != 0:
require(command["replayEligible"] is False, "failed command should not be replayEligible in baseline fixture")

mutation = data.get("mutationReceipt")
require(isinstance(mutation, dict), "mutationReceipt must be object")
for field in ["mutationId", "commandRef", "mutationClass", "targetScope", "mode", "policyDecisionRef", "mutatedHost"]:
require(field in mutation, f"mutationReceipt missing {field}")
require(mutation["mode"] in {"dry-run", "live"}, "invalid mutation mode")
if mutation["mutatedHost"]:
require(mutation["mode"] == "live", "mutatedHost=true requires mode=live")
require(mutation.get("humanControlEventRef"), "live host mutation requires human control event ref")

approval = data.get("operatorApprovalReceipt")
require(isinstance(approval, dict), "operatorApprovalReceipt must be object")
for field in ["approvalId", "actorRef", "subjectRef", "decision", "policyGateRef", "agentplaneRunRef", "deliveryExcellenceEventRef"]:
require(field in approval, f"operatorApprovalReceipt missing {field}")
require(approval["decision"] in {"approved", "rejected", "deferred", "accepted-risk", "revoked"}, "invalid operator decision")


def main() -> int:
try:
data = load_json(EXAMPLE)
validate(data)
except (json.JSONDecodeError, ValidationError) as exc:
print(f"TurtleTerm Agent Harness receipt validation failed: {exc}", file=sys.stderr)
return 1
print("OK: TurtleTerm Agent Harness receipt fixture validates")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading