diff --git a/docs/SCHEMAS.md b/docs/SCHEMAS.md index 82d7acca8..a5a6bf230 100644 --- a/docs/SCHEMAS.md +++ b/docs/SCHEMAS.md @@ -34,6 +34,8 @@ These describe data written and consumed at runtime — handoffs between session | [`scenario.v1.schema.json`](https://github.com/boshu2/agentops/blob/main/schemas/scenario.v1.schema.json) | Behavioral validation scenarios stored in `.agents/holdout/`. | | [`eval-suite.v1.schema.json`](https://github.com/boshu2/agentops/blob/main/schemas/eval-suite.v1.schema.json) | Public canary and private holdout evaluation suite manifests. See [`Eval Environment`](contracts/eval-environment.md). | | [`eval-run.v1.schema.json`](https://github.com/boshu2/agentops/blob/main/schemas/eval-run.v1.schema.json) | Evaluation run records, scorecards, baseline comparisons, runtime hygiene, and artifacts. See [`Eval Environment`](contracts/eval-environment.md). | +| [`remote-compute-target.schema.json`](https://github.com/boshu2/agentops/blob/main/schemas/remote-compute-target.schema.json) | Product-neutral GasCity-backed remote compute target configuration. See [`Remote Compute`](contracts/remote-compute.md). | +| [`remote-session-event.schema.json`](https://github.com/boshu2/agentops/blob/main/schemas/remote-session-event.schema.json) | Remote session events and idempotent command delivery records. See [`Remote Compute`](contracts/remote-compute.md). | | [`swarm-evidence.schema.json`](https://github.com/boshu2/agentops/blob/main/schemas/swarm-evidence.schema.json) | Permissive shape for files written by swarm workers to `.agents/swarm/results/.json`. Companion strict schema: [`contracts/swarm-worker-result.schema.json`](contracts/swarm-worker-result.schema.json). | | [`finding.json`](https://github.com/boshu2/agentops/blob/main/schemas/finding.json) | Canonical finding-item schema for validation skills. Compatible subset of [`contracts/finding-artifact.schema.json`](contracts/finding-artifact.schema.json). | diff --git a/docs/contracts/agent-worker.md b/docs/contracts/agent-worker.md index 6bd726e6b..6657a2ced 100644 --- a/docs/contracts/agent-worker.md +++ b/docs/contracts/agent-worker.md @@ -22,6 +22,11 @@ Required operations: | `artifacts` | list or fetch produced artifacts | | `terminal` | classify final state and failure reason | +Remote compute uses the same `AgentWorker`/`AgentSession` contract. A +GasCity-backed remote node is a provider-backed worker location, not a separate +SSH/tmux product runtime. Bootstrap transport may prepare the node, but session +operations still flow through the provider contract above. + ## Session Fields Every `AgentSession` must expose: @@ -36,7 +41,9 @@ Every `AgentSession` must expose: | `provider_request_id` | provider request/log correlation ID such as `X-GC-Request-Id` | | `session_id` | provider session identity | | `event_cursor` | last consumed stream/list cursor | -| `status` | `starting`, `running`, `waiting`, `completed`, `failed`, `cancelled`, `lost`, or `provider_unreachable` | +| `status` | `starting`, `running`, `waiting`, `completed`, `failed`, `cancelled`, `lost`, `provider_unreachable`, or `unknown` | +| `command_id` | latest AgentOps remote command identity when the session is command-driven | +| `idempotency_key` | latest idempotency key recorded before provider delivery | ## Terminal Classification @@ -49,6 +56,7 @@ Terminal state must be explicit: | cancelled by AgentOps | `cancelled` | | session ID previously known but provider cannot find it | `lost` | | provider readiness unavailable before terminal state | `provider_unreachable` | +| accepted command has no provable delivery outcome after crash | `unknown` | | stream disconnected but REST reconciliation pending | `running` with degraded stream | `lost` and `provider_unreachable` must not be reported as success. diff --git a/docs/contracts/gascity-integration.md b/docs/contracts/gascity-integration.md index ca66f9756..781e10957 100644 --- a/docs/contracts/gascity-integration.md +++ b/docs/contracts/gascity-integration.md @@ -89,6 +89,20 @@ The first adapter must cover: Session IDs and aliases returned by GasCity must be persisted in daemon ledger events before AgentOps reports accepted daemon work as running. +## Remote Compute Usage + +Remote compute targets use GasCity as the product session substrate. AgentOps +may use SSH, local shell, or manual setup as `bootstrap_transport` to install, +start, or diagnose a GC node, but prompt delivery, attach, nudge, cancel, +event replay, transcripts, artifacts, and terminal classification must use the +public GasCity API/SSE surface. + +For every remote command, AgentOps records command intent and idempotency before +delivery. If the process crashes after an attempted delivery but before a +provider acknowledgement is durable, recovery records `delivery_unknown` and +reconciles through GasCity request IDs, event replay, transcript evidence, or +artifact evidence before retrying. + ## Event And SSE Replay The adapter must support both list and stream forms: diff --git a/docs/contracts/index.md b/docs/contracts/index.md index 0da0d7a9f..73b6ce0be 100644 --- a/docs/contracts/index.md +++ b/docs/contracts/index.md @@ -39,6 +39,13 @@ skills, the runtime, and external integrations. Public GasCity API/SSE boundary, mutation headers, request IDs, readiness, replay, versioning, and adapter rules. +- :material-lan-connect: **[Remote Compute](remote-compute.md)** + + --- + + Product-neutral RemoteTarget, RemoteSession, command ledger, recovery, and + GasCity-first remote execution contract. + - :material-robot-outline: **[AgentWorker Runtime](agent-worker.md)** --- diff --git a/docs/contracts/remote-compute.md b/docs/contracts/remote-compute.md new file mode 100644 index 000000000..dcf6c3fe1 --- /dev/null +++ b/docs/contracts/remote-compute.md @@ -0,0 +1,184 @@ +# Remote Compute Contract + +Remote compute is the AgentOps product boundary for running agent sessions on a +configured remote node while keeping product names generic. Bushido is a +private dogfood target and soak harness, not a public AgentOps namespace. + +This contract freezes the nouns used by `ao compute`, daemon jobs, GasCity +worker sessions, and recovery projections before implementation starts. + +## Contract Version + +| Field | Value | +|-------|-------| +| `schema_version` | `1` | +| Target schema | `remote-compute-target.schema.json` | +| Session event schema | `remote-session-event.schema.json` | +| Primary session provider | `gascity-api-sse` | +| Bootstrap transports | `ssh`, `local`, `manual`, `none` | + +## Core Nouns + +| Noun | Definition | +|------|------------| +| `RemoteTarget` | Operator-configured product target for a GasCity city endpoint plus bootstrap metadata. It is not a host alias alone. | +| `RemoteNode` | Host or process environment running the GasCity supervisor and worker providers. A node may be reached by bootstrap transport, but session state comes from GasCity. | +| `BootstrapTransport` | Setup or rescue mechanism such as SSH. It may install, start, or diagnose GasCity; it is not a product session provider. | +| `SessionProvider` | Runtime API that owns sessions, events, transcripts, artifacts, and provider request IDs. The first provider is GasCity API/SSE. | +| `RemoteSession` | AgentOps durable view of one provider session, owned by a daemon job or foreground command record. | +| `RemoteCommand` | Idempotent prompt or control delivery attempt recorded by AgentOps before provider delivery. | + +## Target Identity + +Every `RemoteTarget` must define stable identity without embedding secrets. + +Required target fields: + +- `target_id`: local stable name used by CLI and projections. +- `provider`: `gascity`. +- `gascity.endpoint`: HTTP(S) endpoint for the GasCity supervisor. +- `gascity.city`: target city name. +- `bootstrap_transport`: one of `ssh`, `local`, `manual`, or `none`. +- `bootstrap_profile`: optional host, user, workdir, and supervisor hints used + only for bootstrap or rescue. +- `auth_ref`: pointer to credentials by reference, never inline secret value. +- `capabilities`: explicit booleans for API/SSE, sessions, transcripts, + artifacts, cancel, provider readiness, and context sync. +- `redaction`: fields that must be redacted from logs and proof artifacts. + +Bushido may appear as a private target profile or live-soak label. It must not +create an `ao bushido` command family or a product-specific provider kind. + +## Session Identity + +Every `RemoteSession` must be reconstructable from the daemon ledger or the +foreground command ledger after a crash. + +Required session fields: + +- `session_id`: AgentOps remote session identity. +- `provider`: `gascity`. +- `target.target_id` and `target.city`. +- `job_id` and `attempt_id` when daemon-owned. +- `request_id`: AgentOps request correlation ID. +- `provider_request_id`: provider response correlation value such as + `X-GC-Request-Id`. +- `provider_session_id`: GasCity session ID or alias. +- `event_cursor`: last consumed list/SSE cursor. +- `transcript_refs`: durable transcript references. +- `artifact_refs`: durable artifact references. +- `terminal_status`: one of `completed`, `failed`, `cancelled`, `lost`, + `provider_unreachable`, or `unknown` when terminal classification is not yet + proven. + +Raw tmux pane state, SSH command exit state, and local process presence are not +authoritative terminal state for product remote sessions. + +## Command Ledger + +AgentOps must record command intent before delivery to GasCity. + +Each `RemoteCommand` includes: + +- `command_id`: AgentOps command identity. +- `session_id`: AgentOps remote session identity. +- `idempotency_key`: stable key for provider delivery and replay. +- `kind`: `prompt`, `control`, `cancel`, or future registered kind. +- `payload_ref` or redacted payload summary. +- `status`: `recorded`, `delivery_attempted`, `accepted`, `rejected`, + `delivery_unknown`, or `superseded`. +- `provider_request_id` when available. +- `recorded_at`, `last_attempted_at`, and optional `accepted_at`. + +After a crash or network split, AgentOps must not blindly resend commands whose +delivery state is unknown. It must mark the command `delivery_unknown`, then +reconcile through provider request ID, idempotency key, session events, or +transcript evidence before retrying or reporting failure. + +## Event Model + +Remote session events are append-only facts consumed by daemon projections, +status UX, and recovery flows. + +Required event classes: + +- `session_recorded` +- `session_started` +- `command_recorded` +- `command_delivery_attempted` +- `command_accepted` +- `command_rejected` +- `command_delivery_unknown` +- `event_cursor_advanced` +- `transcript_ref` +- `artifact_ref` +- `terminal_state` + +Every event carries `session_id`, `provider`, `target`, `event_cursor`, +`terminal_status`, `command_id`, `idempotency_key`, and `artifact_refs` fields. +Fields may be `null` only when the event class does not apply to that value. + +## State Ownership + +The daemon ledger remains authoritative for daemon-owned remote sessions. +Remote-specific code may keep read models, but projections must rebuild from +ledger events and provider replay. + +| Surface | Ownership | +|---------|-----------| +| `.agents/remote/targets.json` | Optional local target registry for CLI use. | +| `.agents/daemon/ledger.jsonl` | Durable job, session, command, and projection facts for daemon-owned work. | +| GasCity API/SSE | Provider source of truth for provider session status, events, transcripts, and artifacts. | +| OpenClaw | Read-only projection consumer; it does not own remote session mutation. | + +Foreground `ao compute` commands may write local command/session proof records, +but daemon-submitted work must not report accepted or running until the daemon +ledger append succeeds. + +## GasCity First + +The product remote execution path is GasCity API/SSE: + +1. Diagnose target readiness through GasCity health, readiness, provider + readiness, city readiness, and event replay surfaces. +2. Record AgentOps session and command intent. +3. Deliver start, nudge, cancel, and attach operations through the public + GasCity API. +4. Persist `X-GC-Request-Id`, provider session ID, event cursor, transcript + refs, artifact refs, and terminal classification. +5. Rebuild AgentOps projections from the ledger and provider replay. + +SSH/tmux is bootstrap and rescue only. A direct SSH/tmux product provider +requires a new contract revision and must not be introduced in RC-01. + +## Recovery Rules + +Recovery must prefer durable evidence in this order: + +1. AgentOps daemon ledger command/session events. +2. GasCity list/event replay using `event_cursor` or `Last-Event-ID`. +3. GasCity transcript and artifact evidence. +4. Bootstrap/rescue diagnostics. + +If the provider is unreachable before terminal proof, classify the session as +`provider_unreachable`. If a previously accepted provider session cannot be +found after reconciliation, classify it as `lost`. Neither state is success. + +## Security And Privacy + +- No inline secrets in target or session documents. +- Auth values are referenced by `auth_ref` and redacted in logs. +- Remote command payloads are either redacted or stored by durable proof ref. +- Local daemon mutation rules apply before remote session mutations are added. +- Live Bushido GC-city soak tests are opt-in and use private target labels. + +## Conformance Checks + +RC-01 is valid when these checks pass: + +```bash +rg -n "RemoteTarget|RemoteNode|RemoteSession|RemoteCommand|GasCity|bootstrap_transport" docs/contracts/remote-compute.md +python3 -m json.tool schemas/remote-compute-target.schema.json >/dev/null +python3 -m json.tool schemas/remote-session-event.schema.json >/dev/null +bash scripts/check-contract-compatibility.sh +``` diff --git a/docs/documentation-index.md b/docs/documentation-index.md index 45d67e633..7f75b4f05 100644 --- a/docs/documentation-index.md +++ b/docs/documentation-index.md @@ -202,6 +202,7 @@ - [`.agents/` Write Surfaces](contracts/agents-write-surfaces.md) — Catalogued top-level subdirs that production code writes under `.agents/`, gated by `scripts/check-agents-write-surfaces.sh` - [AgentOps Daemon Contract](contracts/agentops-daemon.md) — Always-on daemon ledger, job lifecycle, activation, readiness, projection, and threat model contract - [GasCity Integration Contract](contracts/gascity-integration.md) — Narrow handwritten GasCity adapter, fake/live split, compatibility matrix, and API/SSE expectations +- [Remote Compute Contract](contracts/remote-compute.md) — Product-neutral RemoteTarget, RemoteSession, command ledger, recovery, and GasCity-first remote execution contract - [AgentWorker Runtime Contract](contracts/agent-worker.md) — Generic headless Codex/Claude worker and AgentSession lifecycle contract for daemon jobs - [OpenClaw Consumer API Contract](contracts/openclaw-consumer-api.md) — Read-only consumer snapshot API and authorized local trigger contract - [Repo Execution Profile Schema](contracts/repo-execution-profile.schema.json) — Machine-readable schema for repo execution profiles @@ -211,6 +212,8 @@ - [MemRL Policy Profile Example](contracts/memrl-policy.profile.example.json) — Example deterministic MemRL retry/escalation policy profile - [Eval Suite Schema](https://github.com/boshu2/agentops/blob/main/schemas/eval-suite.v1.schema.json) — JSON Schema for public canary and private holdout evaluation suites - [Eval Run Schema](https://github.com/boshu2/agentops/blob/main/schemas/eval-run.v1.schema.json) — JSON Schema for evaluation run records and scorecards +- [Remote Compute Target Schema](https://github.com/boshu2/agentops/blob/main/schemas/remote-compute-target.schema.json) — JSON Schema for product-neutral GasCity-backed remote compute targets +- [Remote Session Event Schema](https://github.com/boshu2/agentops/blob/main/schemas/remote-session-event.schema.json) — JSON Schema for remote session event and idempotent command ledger records - [Next-Work Queue Schema](contracts/next-work.schema.md) — Contract for `.agents/rpi/next-work.jsonl` - [RPI Phase Result Schema](contracts/rpi-phase-result.schema.json) — Machine-readable schema for RPI phase results - [RPI C2 Events Schema](contracts/rpi-c2-events.schema.json) — Machine-readable schema for per-run `.agents/rpi/runs//events.jsonl` diff --git a/schemas/remote-compute-target.schema.json b/schemas/remote-compute-target.schema.json new file mode 100644 index 000000000..5a64651da --- /dev/null +++ b/schemas/remote-compute-target.schema.json @@ -0,0 +1,175 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentops.dev/schemas/remote-compute-target.schema.json", + "title": "AgentOps Remote Compute Target v1", + "description": "Product-neutral target configuration for a GasCity-backed remote compute node. Secrets are referenced, never embedded.", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "target_id", + "provider", + "gascity", + "bootstrap_transport", + "bootstrap_profile", + "auth_ref", + "capabilities", + "redaction" + ], + "properties": { + "$schema": { + "type": "string" + }, + "schema_version": { + "type": "integer", + "const": 1 + }, + "target_id": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$" + }, + "display_name": { + "type": "string", + "minLength": 1 + }, + "provider": { + "type": "string", + "enum": ["gascity"] + }, + "gascity": { + "type": "object", + "additionalProperties": false, + "required": ["endpoint", "city"], + "properties": { + "endpoint": { + "type": "string", + "minLength": 1 + }, + "city": { + "type": "string", + "minLength": 1 + }, + "node_id": { + "type": "string" + }, + "supervisor_hint": { + "type": "string" + }, + "api_contract_version": { + "type": "string" + } + } + }, + "bootstrap_transport": { + "type": "string", + "enum": ["ssh", "local", "manual", "none"] + }, + "bootstrap_profile": { + "type": "object", + "additionalProperties": false, + "required": ["transport"], + "properties": { + "transport": { + "type": "string", + "enum": ["ssh", "local", "manual", "none"] + }, + "host": { + "type": "string" + }, + "user": { + "type": "string" + }, + "workdir": { + "type": "string" + }, + "gc_binary": { + "type": "string" + }, + "supervisor_command": { + "type": "string" + }, + "notes": { + "type": "string" + } + } + }, + "auth_ref": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "name"], + "properties": { + "kind": { + "type": "string", + "enum": ["keychain", "env", "file", "agent", "none"] + }, + "name": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string" + } + } + }, + "capabilities": { + "type": "object", + "additionalProperties": false, + "required": [ + "api_sse", + "sessions", + "transcripts", + "artifacts", + "cancel", + "provider_readiness", + "context_sync" + ], + "properties": { + "api_sse": { + "type": "boolean" + }, + "sessions": { + "type": "boolean" + }, + "transcripts": { + "type": "boolean" + }, + "artifacts": { + "type": "boolean" + }, + "cancel": { + "type": "boolean" + }, + "provider_readiness": { + "type": "boolean" + }, + "context_sync": { + "type": "boolean" + } + } + }, + "redaction": { + "type": "object", + "additionalProperties": false, + "required": ["omit_secrets", "fields"], + "properties": { + "omit_secrets": { + "type": "boolean", + "const": true + }, + "fields": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + } + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } +} diff --git a/schemas/remote-session-event.schema.json b/schemas/remote-session-event.schema.json new file mode 100644 index 000000000..bed5c5ff6 --- /dev/null +++ b/schemas/remote-session-event.schema.json @@ -0,0 +1,188 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentops.dev/schemas/remote-session-event.schema.json", + "title": "AgentOps Remote Session Event v1", + "description": "Append-only event shape for AgentOps remote sessions and idempotent command delivery.", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "event_id", + "occurred_at", + "event_type", + "session_id", + "provider", + "target", + "event_cursor", + "terminal_status", + "command_id", + "idempotency_key", + "artifact_refs" + ], + "properties": { + "$schema": { + "type": "string" + }, + "schema_version": { + "type": "integer", + "const": 1 + }, + "event_id": { + "type": "string", + "minLength": 1 + }, + "occurred_at": { + "type": "string", + "format": "date-time" + }, + "event_type": { + "type": "string", + "enum": [ + "session_recorded", + "session_started", + "command_recorded", + "command_delivery_attempted", + "command_accepted", + "command_rejected", + "command_delivery_unknown", + "event_cursor_advanced", + "transcript_ref", + "artifact_ref", + "terminal_state" + ] + }, + "session_id": { + "type": "string", + "minLength": 1 + }, + "provider_session_id": { + "oneOf": [ + { "type": "null" }, + { "type": "string", "minLength": 1 } + ] + }, + "provider": { + "type": "string", + "enum": ["gascity"] + }, + "target": { + "type": "object", + "additionalProperties": false, + "required": ["target_id", "city"], + "properties": { + "target_id": { + "type": "string", + "minLength": 1 + }, + "city": { + "type": "string", + "minLength": 1 + }, + "node_id": { + "oneOf": [ + { "type": "null" }, + { "type": "string" } + ] + } + } + }, + "job_id": { + "oneOf": [ + { "type": "null" }, + { "type": "string", "minLength": 1 } + ] + }, + "attempt_id": { + "oneOf": [ + { "type": "null" }, + { "type": "string", "minLength": 1 } + ] + }, + "request_id": { + "oneOf": [ + { "type": "null" }, + { "type": "string", "minLength": 1 } + ] + }, + "provider_request_id": { + "oneOf": [ + { "type": "null" }, + { "type": "string", "minLength": 1 } + ] + }, + "event_cursor": { + "oneOf": [ + { "type": "null" }, + { "type": "string", "minLength": 1 }, + { "type": "integer", "minimum": 0 } + ] + }, + "terminal_status": { + "oneOf": [ + { "type": "null" }, + { + "type": "string", + "enum": [ + "completed", + "failed", + "cancelled", + "lost", + "provider_unreachable", + "unknown" + ] + } + ] + }, + "command_id": { + "oneOf": [ + { "type": "null" }, + { "type": "string", "minLength": 1 } + ] + }, + "idempotency_key": { + "oneOf": [ + { "type": "null" }, + { "type": "string", "minLength": 1 } + ] + }, + "command_status": { + "oneOf": [ + { "type": "null" }, + { + "type": "string", + "enum": [ + "recorded", + "delivery_attempted", + "accepted", + "rejected", + "delivery_unknown", + "superseded" + ] + } + ] + }, + "transcript_refs": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "artifact_refs": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "message": { + "type": "string" + }, + "redacted_payload_ref": { + "oneOf": [ + { "type": "null" }, + { "type": "string", "minLength": 1 } + ] + } + } +}