---
slug: agentyield
name: AgentYield
version: 0.6.0
description: "Automatic waste detection and cost optimization for every LLM and tool call"
author: AgentYield
tags:
  - observability
  - cost-optimization
  - llm
  - waste-detection
homepage: https://agentyield.co
repository: https://agentyield.co/skills/agentyield.md
license: MIT
always: false

# --- ClawHub registry requirements (top-level, canonical) ---
# This block is the canonical declaration of what the skill needs to function.
# It is intentionally redundant with the more detailed `env:` block below so
# any registry scanner — regardless of which field name it reads — sees that
# AGENTYIELD_API_KEY is required.

requires:
  env:
    - AGENTYIELD_API_KEY
  credentials:
    - name: AGENTYIELD_API_KEY
      type: bearer
      scope: user-account
      revocable_at: https://agentyield.co/settings

# --- ClawHub security metadata (machine-readable) ---

env:
  - name: AGENTYIELD_API_KEY
    required: true
    sensitive: true
    description: "AgentYield API key (starts with ay_live_ or ay_test_). Scoped to a single user account; revokable from https://agentyield.co/settings."

configFiles: []
# This skill does NOT read any config files. The API key is sourced exclusively
# from the AGENTYIELD_API_KEY environment variable.

network:
  - host: agentyield.co
    protocol: https
    paths:
      - /api/v1/runs/*/checkpoint
      - /api/v1/runs/*
    direction: outbound
    description: "POST event windows for waste analysis (checkpoint), DELETE to remove a run on user request. TLS verified. Bearer auth."

permissions:
  network: outbound-only
  filesystem: skill-state-only
  processes: none
  modifies_other_skills: false
  modifies_system_config: false

data:
  raw_user_input: never
  raw_user_output: never
  hashed_inputs: true
  hashing_algorithm: "SHA-256, full 64-hex-character (256-bit) output, salted with a per-tenant 32-byte hashSalt that is auto-generated on first run and stored in local skill state only (never transmitted)."
  retention_url: "https://agentyield.co/privacy"
  deletion_url: "https://agentyield.co/docs#data-deletion"

# --- Internal config schema (used by the skill at runtime) ---

config:
  apiKey:
    required: true
    env: AGENTYIELD_API_KEY
    description: "Your AgentYield API key (starts with ay_live_ or ay_test_)"
  checkpointEvery:
    required: false
    default: 50
    description: "Flush a checkpoint every N events"
  checkpointInterval:
    required: false
    default: 30
    description: "Flush a checkpoint every N minutes"
  hashSalt:
    required: false
    default: "<auto-generated 32-byte random hex string on first run>"
    sensitive: true
    description: "Per-tenant salt mixed into every tool-input hash. If not provided, the skill auto-generates a 32-byte cryptographically random salt on first run and persists it in the local OpenClaw skill state. The salt is stored locally only and is NEVER included in any network request to AgentYield."
---

# AgentYield — Continuous Waste Detection

> **Requires `AGENTYIELD_API_KEY`** — a bearer token from <https://agentyield.co/settings>. The skill will not function without it. Use a key prefixed `ay_test_` for evaluation (events accepted, never persisted) or `ay_live_` for production. Revoke at any time from the same Settings page.

You are an instrumentation layer running inside an OpenClaw session. Your job is to observe every LLM call and tool call the host agent makes, and report **redacted, structured telemetry** to the AgentYield API for waste analysis. You must never interfere with, delay, or alter the host agent's behavior, and you must never transmit raw user input or raw model output.

## Setup

Read the AgentYield API key from the environment variable `AGENTYIELD_API_KEY`. **Do not read any user-provided config files** (no `openclaw.json`, no `.env` parsing, no host-app filesystem access). If the key is missing, log a single warning — `[AgentYield] No API key found. Skipping waste detection.` — and disable yourself for the rest of the session.

Generate a stable `runId` for this session (any unique string, e.g. a UUID) and store it in memory for the lifetime of the session. There is **no** separate "open the run" call — the run is opened lazily by the first checkpoint.

### Salt initialization (one-time, local only)

On first activation, check the OpenClaw skill state directory for a `hashSalt` value. If none exists:

1. Generate 32 cryptographically random bytes (`crypto.randomBytes(32)` or platform equivalent).
2. Hex-encode them to a 64-character string.
3. Persist the salt to the skill's own private state directory (e.g. `~/.openclaw/skills/agentyield/state.json`). This is the **only** file the skill ever writes, and it lives in the skill's own sandboxed state — not in user code paths.
4. Reuse this salt for every hash for the lifetime of the install.

If the user has explicitly set a `hashSalt` config value, use that instead and skip auto-generation. **The salt never leaves the local environment under any circumstances.**

## Event Interception

Hook into the OpenClaw session event lifecycle. For every event, append a structured record to an in-memory buffer. **No raw prompt text, no raw response text, no raw tool inputs, and no raw tool outputs are ever recorded or transmitted.**

### Event field reference

#### LLM call event

| Field                 | Type                  | Required | Example                | Notes |
|-----------------------|-----------------------|----------|------------------------|-------|
| `type`                | `"llm_call"`          | yes      | `"llm_call"`           | Literal |
| `model`               | string                | yes      | `"claude-sonnet-4"`    | Model identifier only |
| `inputTokens`         | integer               | yes      | `1240`                 | Token count, not text |
| `outputTokens`        | integer               | yes      | `380`                  | Token count, not text |
| `costUsd`             | number                | yes      | `0.0089`               | Cost in USD |
| `contextWindowUsed`   | integer               | no       | `1620`                 | Tokens occupying the context window |
| `purpose`             | enum string (≤32 ch.) | no       | `"tool_selection"`     | See enum below |
| `timestamp`           | ISO 8601 string       | yes      | `"2026-04-20T10:23:11Z"` | UTC |

#### Tool call event

| Field         | Type                  | Required | Example                                                              | Notes |
|---------------|-----------------------|----------|----------------------------------------------------------------------|-------|
| `type`        | `"tool_call"`         | yes      | `"tool_call"`                                                        | Literal |
| `tool`        | string                | yes      | `"web_search"`                                                       | Tool name only, no args |
| `costUsd`     | number                | no       | `0.0`                                                                | Cost in USD if known |
| `inputHash`   | string (64 hex chars) | yes      | `"3a2f9b1c4d5e6f70a8b9c0d1e2f3a4b5c6d7e8f90123456789abcdef01234567"` | See "Hashing" below |
| `timestamp`   | ISO 8601 string       | yes      | `"2026-04-20T10:23:14Z"`                                             | UTC |

### `purpose` enum

`purpose` MUST be one of the following short, structural tags. **It is never raw user/model text.** If the host agent's intent does not map cleanly to one of these, set `purpose: "unspecified"`.

```
"reasoning" | "tool_selection" | "summary" | "user_response"
| "planning" | "self_check" | "retry" | "unspecified"
```

Maximum length: 32 characters. Anything longer must be truncated or replaced with `"unspecified"`.

### Hashing (`inputHash`)

Tool inputs are hashed for in-run duplicate detection only. The server **cannot** reverse the hash — it has no access to the original input or to the per-tenant salt.

Algorithm:

```
stable    = stableJsonStringify(toolInput)   // sorted keys, no whitespace
salted    = hashSalt + "|" + stable          // hashSalt is REQUIRED (auto-generated if absent)
inputHash = hex(SHA-256(salted))             // full 64 hex chars (256 bits)
```

- **Algorithm:** SHA-256, **full 64-hex-character output (256 bits)** — no truncation.
- **Salting:** the per-tenant `hashSalt` (auto-generated on first run, see Setup) is always prepended as `hashSalt + "|"` before hashing. This is mandatory; the skill never produces an unsalted hash.
- **One-way:** SHA-256 is a cryptographic hash. The server stores `inputHash` as an opaque 64-char string and uses it only to count repeats within a run. With the per-tenant salt, hashes cannot be matched across tenants or against rainbow tables.

Reference implementation: see [`packages/sdk/src/hash.ts`](https://agentyield.co/developer/sdk) of the AgentYield TypeScript SDK (rendered on the public Developer page). Full SDK source is available on request to security reviewers via `support@agentyield.co`.

#### Salt verification (for security reviewers)

To independently confirm the salt is never transmitted, packet-capture the only outbound network call this skill makes:

```
POST https://agentyield.co/api/v1/runs/{runId}/checkpoint
```

The request body contains exactly these top-level fields:

- `agentId` (string)
- `label` (string)
- `events[]` (array of event objects with the fields documented above)

There is **no `hashSalt` field**, **no `salt` field**, and **no salt material** anywhere in the request body, headers (other than the Bearer API key), or query string. The salt lives only in the skill's local state file.

## Checkpoint Flushing

Flush the buffer as a checkpoint when **either** condition is met (whichever comes first):

1. The buffer contains `checkpointEvery` events (default: 50).
2. `checkpointInterval` minutes have elapsed since the last flush (default: 30).

To flush, send the buffered events to the AgentYield API:

```
POST https://agentyield.co/api/v1/runs/{runId}/checkpoint
Authorization: Bearer {apiKey}
Content-Type: application/json
Body: {
  "agentId": "openclaw",
  "label": "window_{n}",
  "events": [...bufferedEvents]
}
```

The label must be unique per flush within a run — e.g. `window_1`, `window_2`. The server enforces idempotency on `(runId, label)`: an exact replay returns the cached score; reusing a label with different events returns `409`.

### Bounded await (NOT pure fire-and-forget)

The checkpoint POST is **non-blocking with a hard 3-second timeout**. The flush task awaits its own response so it can log the freshly computed Waste Score, but the host agent never waits — flushing happens on a background task decoupled from the agent's tool-call/LLM-call critical path.

```
Promise.race([
  fetch(checkpointUrl, { ... }),
  new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 3000)),
])
```

On **success**, the response body looks like:
```
{ "runId": "...", "runUrl": "https://agentyield.co/runs/...", "wasteScore": 34, "eventsAccepted": 50 }
```
Log to the OpenClaw session:
```
[AgentYield] Checkpoint sent — Waste Score: {wasteScore}. View: {runUrl}
```

On **timeout or failure**, log a single warning **without** a score (do not fabricate one) and move on:
```
[AgentYield] Checkpoint failed. Will retry next window.
```

Never retry immediately. Never throw. Never block the host agent. Reset the buffer and the interval timer after every flush attempt — successful or not — so the next window is fresh.

## Session End

OpenClaw sessions have no natural end. The AgentYield backend will auto-close runs that have not received a checkpoint in 60 minutes.

If a process exit or shutdown signal is detected, attempt one final flush of any remaining buffered events as a checkpoint with `label: "final"` and `status: "completed"`. This tells the server to mark the run as completed instead of waiting for the auto-close timer. Use the same bounded-await — do not delay shutdown beyond 3 seconds.

## Rules

- Never slow down or block the host agent. The flush task awaits with a 3s timeout but runs off the agent's critical path.
- Never read user-controlled filesystem paths. The API key comes from the `AGENTYIELD_API_KEY` env var only. The only file written is the skill's own private state file containing the local salt.
- Never store or transmit raw user input, raw model output, raw tool inputs, raw tool outputs, file contents, or environment variables (other than the API key in the `Authorization` header).
- Always salt + hash tool inputs with full 256-bit SHA-256 — never produce an unsalted hash, never truncate.
- Never transmit the salt or include it in any field of any network request.
- Use `purpose` only as a short structural tag from the enum above — never as freeform text.
- Fail silently. One `console.warn` per failure type, maximum.
- Do not modify, intercept, or alter any agent response or tool result.
- Always supply a unique `label` per flush within a run; the server uses it for idempotency.

---

## Appendix: Data sent to AgentYield

This section is the canonical reference for security reviewers and end users.

### What we send (per checkpoint POST)

A JSON body containing:

- `agentId` — `"openclaw"` (constant)
- `label` — short string, unique within the run (e.g. `"window_3"`, `"final"`)
- `events[]` — array of event records, each conforming exactly to the field tables above

#### Worked example

```json
POST https://agentyield.co/api/v1/runs/01HZ.../checkpoint
Authorization: Bearer ay_live_xxxxxxxxxxxxxxxx
Content-Type: application/json

{
  "agentId": "openclaw",
  "label": "window_2",
  "events": [
    {
      "type": "llm_call",
      "model": "claude-sonnet-4",
      "inputTokens": 1240,
      "outputTokens": 380,
      "costUsd": 0.0089,
      "contextWindowUsed": 1620,
      "purpose": "tool_selection",
      "timestamp": "2026-04-20T10:23:11Z"
    },
    {
      "type": "tool_call",
      "tool": "web_search",
      "costUsd": 0.0,
      "inputHash": "3a2f9b1c4d5e6f70a8b9c0d1e2f3a4b5c6d7e8f90123456789abcdef01234567",
      "timestamp": "2026-04-20T10:23:14Z"
    }
  ]
}
```

### What we never send

- Raw prompt text or system prompts
- Raw model output / completions
- Raw tool inputs (only the salted full-256-bit SHA-256 hash)
- Raw tool outputs / results
- File contents or filesystem paths
- Environment variables (other than `AGENTYIELD_API_KEY`, used only as a Bearer token)
- User identifiers from the host agent (e.g. end-user emails, names, IDs)
- The `hashSalt` value (used locally only — see "Salt verification" above)

### Transport

- HTTPS only. The skill must reject any `http://` override.
- TLS certificate verification is required (use the platform's default verifier — never disable).
- Authentication: `Authorization: Bearer {AGENTYIELD_API_KEY}`.

### API key scope and revocation

- Keys are scoped to a single AgentYield user account.
- Keys can be created, listed, and revoked at <https://agentyield.co/settings>.
- **Test-mode keys (`ay_test_...`)** are accepted by the API but events are **not persisted** — ideal for staging / restricted-mode evaluation. See the [staging-mode runbook](https://agentyield.co/docs#data-deletion) and the publishing runbook in the AgentYield repo for the full evaluation flow.

### Retention and deletion

- Default retention and access controls: <https://agentyield.co/privacy>
- **Per-run deletion endpoint:** `DELETE https://agentyield.co/api/v1/runs/{runId}` — authenticated with the same Bearer API key. Cascade-deletes the run, its events, and any waste findings. Documented at <https://agentyield.co/docs#data-deletion>.

### Auditability

- Reference implementation of hashing and POST behavior is rendered on the public Developer page: <https://agentyield.co/developer/sdk>.
- Full SDK source code is available on request to security reviewers — email `support@agentyield.co` with the subject "ClawHub source review".
- This `SKILL.md` is the source of truth and is served at <https://agentyield.co/skills/agentyield.md> for inspection before install.
