← Back to Plugins
Tools

Bedrock Cache

peterb154 By peterb154 👁 8 views ▲ 0 votes

OpenClaw plugin that injects Bedrock Converse API cachePoint blocks for prompt caching on Claude models (~90% input cost savings)

GitHub

Configuration Example

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "bedrock:InvokeModel",
        "bedrock:InvokeModelWithResponseStream"
      ],
      "Resource": [
        "arn:aws:bedrock:*::foundation-model/anthropic.*",
        "arn:aws:bedrock:us-east-1:ACCOUNT_ID:inference-profile/us.anthropic.*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "bedrock:ListFoundationModels",
      "Resource": "*"
    }
  ]
}

README

# openclaw-bedrock-cache-plugin

OpenClaw plugin that injects [Bedrock Converse API `cachePoint` blocks](https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html) into API payloads, enabling prompt caching for Claude models on Amazon Bedrock.

**Result: ~90% input cost savings** on cached prefixes (system prompts, tool definitions).

## How it works

The Bedrock Converse API supports prompt caching via `cachePoint` marker blocks placed after static content. Cached prefixes are reused across requests within a 5-minute TTL window, charged at 10% of normal input token cost.

This plugin is a forked replacement for OpenClaw's built-in `amazon-bedrock` provider. It wraps the stream function via `streamWithPayloadPatch` to inject `{ cachePoint: { type: "default" } }` after:

1. **System prompt** — end of `system[]` array (skipped if OpenClaw core already adds one)
2. **Tool definitions** — end of `toolConfig.tools[]` array

All other built-in behavior is preserved: region resolution, error classification, replay hooks, guardrail support.

## Validated results

Tested with OpenClaw v2026.4.5 on Claude Sonnet 4.6 via Bedrock, confirmed via CloudWatch model invocation logs:

| Turn | inputTokens | cacheWrite | cacheRead | Savings |
|------|------------|------------|-----------|---------|
| 1st | 3 | 155,078 | 0 | (cache write) |
| 2nd | 3 | 34 | 155,078 | ~90% |

## Requirements

- OpenClaw v2026.4.x or later
- Amazon Bedrock access with Claude models (model access must be enabled in the AWS console)
- AWS IAM user or role with Bedrock invoke permissions

## AWS Configuration

### IAM permissions

The IAM user/role running OpenClaw needs these permissions:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "bedrock:InvokeModel",
        "bedrock:InvokeModelWithResponseStream"
      ],
      "Resource": [
        "arn:aws:bedrock:*::foundation-model/anthropic.*",
        "arn:aws:bedrock:us-east-1:ACCOUNT_ID:inference-profile/us.anthropic.*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "bedrock:ListFoundationModels",
      "Resource": "*"
    }
  ]
}
```

Replace `ACCOUNT_ID` with your AWS account ID and adjust the region if needed.

### AWS credentials

Set credentials as environment variables in your OpenClaw gateway service (e.g., systemd unit file):

```ini
# In your systemd service [Service] section or .env file:
Environment=AWS_ACCESS_KEY_ID=AKIA...
Environment=AWS_SECRET_ACCESS_KEY=...
Environment=AWS_REGION=us-east-1
```

Or if using named profiles:

```ini
Environment=AWS_PROFILE=your-profile
Environment=AWS_REGION=us-east-1
```

The plugin detects credentials via the standard AWS SDK resolution order: environment variables (`AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY`), AWS profile (`AWS_PROFILE`), or bearer token (`AWS_BEARER_TOKEN_FILE`).

### OpenClaw configuration

Configure Bedrock as the model provider in `~/.openclaw/openclaw.json`:

```json
{
  "agents": {
    "defaults": {
      "model": {
        "primary": "amazon-bedrock/us.anthropic.claude-sonnet-4-6",
        "fallbacks": [
          "amazon-bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0"
        ]
      },
      "params": {
        "cacheRetention": "short"
      }
    }
  },
  "plugins": {
    "entries": {
      "amazon-bedrock": {
        "enabled": false,
        "config": {
          "discovery": {
            "enabled": true,
            "region": "us-east-1",
            "providerFilter": ["anthropic"],
            "refreshInterval": 0,
            "defaultContextWindow": 32000,
            "defaultMaxTokens": 4096
          }
        }
      },
      "bedrock-cache": {
        "enabled": true
      }
    }
  }
}
```

Key settings:

| Field | Purpose |
|-------|---------|
| `agents.defaults.model.primary` | Bedrock model ID — use `amazon-bedrock/` prefix + inference profile ID |
| `agents.defaults.params.cacheRetention` | Set to `"short"` to enable OpenClaw's system prompt cache markers |
| `plugins.entries.amazon-bedrock.enabled` | **Must be `false`** — disables the built-in provider |
| `plugins.entries.amazon-bedrock.config.discovery` | Model discovery config (still read even when disabled, used by gateway) |
| `plugins.entries.bedrock-cache.enabled` | **Must be `true`** — enables this plugin |

#### Model IDs

Bedrock model IDs use the format `amazon-bedrock/<model-id>`. Common Claude models:

| Model | ID |
|-------|----|
| Claude Sonnet 4.6 | `amazon-bedrock/us.anthropic.claude-sonnet-4-6` |
| Claude Sonnet 4.5 | `amazon-bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0` |
| Claude Haiku 4.5 | `amazon-bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0` |

Use `us.anthropic.*` prefixed IDs (cross-region inference profiles) for automatic region routing, or `anthropic.*` for direct model IDs locked to a single region.

## Installation

### Step 1: Copy to bundled extensions directory

The plugin **must** be installed in OpenClaw's bundled extensions directory, not the global extensions directory. This is because OpenClaw's `bundledProviderAllowlistCompat` mode only allows `wrapStreamFn` hooks from plugins with `origin: "bundled"`.

```bash
# Find your OpenClaw dist directory
OPENCLAW_DIST=$(dirname $(readlink -f $(which openclaw)))/../dist
# Or if installed globally via pnpm:
# OPENCLAW_DIST=~/.local/share/pnpm/global/5/node_modules/openclaw/dist

# Copy plugin files
mkdir -p "$OPENCLAW_DIST/extensions/bedrock-cache"
cp index.js package.json openclaw.plugin.json "$OPENCLAW_DIST/extensions/bedrock-cache/"
```

### Step 2: Disable the built-in amazon-bedrock plugin

```bash
openclaw plugins disable amazon-bedrock
```

### Step 3: Restart the gateway

```bash
# systemd
systemctl --user restart openclaw-gateway

# or manual
openclaw gateway --force
```

### Step 4: Verify

```bash
openclaw plugins doctor
```

You should see no errors for `bedrock-cache`. The diagnostic `provider already registered: amazon-bedrock` from `plugins doctor` CLI is expected (the CLI loads both plugins in its own context; the gateway only loads ours).

## After OpenClaw upgrades

OpenClaw upgrades replace the `dist/extensions/` directory, removing the plugin. Re-run Step 1 and restart the gateway after upgrading.

## Monitoring cache performance

Enable [Bedrock model invocation logging](https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html) to see cache metrics in CloudWatch:

```bash
# Create log group
aws logs create-log-group --log-group-name /aws/bedrock/model-invocation-logs

# Create service role for Bedrock to write logs
aws iam create-role \
  --role-name BedrockModelInvocationLoggingRole \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {"Service": "bedrock.amazonaws.com"},
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {"aws:SourceAccount": "ACCOUNT_ID"},
        "ArnLike": {"aws:SourceArn": "arn:aws:bedrock:us-east-1:ACCOUNT_ID:*"}
      }
    }]
  }'

aws iam put-role-policy \
  --role-name BedrockModelInvocationLoggingRole \
  --policy-name CloudWatchLogsWrite \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Action": ["logs:CreateLogStream", "logs:PutLogEvents"],
      "Resource": "arn:aws:logs:us-east-1:ACCOUNT_ID:log-group:/aws/bedrock/model-invocation-logs:*"
    }]
  }'

# Enable logging
aws bedrock put-model-invocation-logging-configuration \
  --logging-config '{
    "cloudWatchConfig": {
      "logGroupName": "/aws/bedrock/model-invocation-logs",
      "roleArn": "arn:aws:iam::ACCOUNT_ID:role/BedrockModelInvocationLoggingRole"
    },
    "textDataDeliveryEnabled": true
  }'
```

Query cache metrics with CloudWatch Logs Insights:

```
fields @timestamp,
  output.outputBodyJson.usage.inputTokens as input,
  output.outputBodyJson.usage.cacheWriteInputTokens as cacheWrite,
  output.outputBodyJson.usage.cacheReadInputTokens as cacheRead,
  output.outputBodyJson.usage.outputTokens as output
| filter operation = "ConverseStream"
| sort @timestamp desc
| limit 50
```

## Why not a standard plugin install?

OpenClaw's plugin system has a `bundledProviderAllowlistCompat` mode that restricts `wrapStreamFn` hooks to plugins with `origin: "bundled"`. Plugins installed via `openclaw plugins install` get `origin: "global"` and their `wrapStreamFn` hooks are silently ignored for built-in provider IDs.

This means the plugin must be placed directly in `dist/extensions/` alongside the built-in plugins. This is the only way for the `wrapStreamFn` hook to fire when Bedrock models are invoked.

## Cost impact

For a 155k token system prompt + tool definitions (typical for OpenClaw agents):

| | Without caching | With caching (after 1st turn) |
|---|---|---|
| Input cost per turn | ~$0.47 | ~$0.047 |
| Savings | — | **~$0.42/turn** |

Cache write (first turn) costs ~25% more than normal input. Break-even after ~1.3 turns.

## License

MIT
tools

Comments

Sign in to leave a comment

Loading comments...