← Back to Plugins
Integration

Default Model Enforcer

FilHouston By FilHouston 👁 12 views ▲ 0 votes

OpenClaw plugin that enforces a model rotation strategy per session β€” seed on primary, then switch to fallback. Saves API costs without sacrificing quality.

GitHub

Configuration Example

{
  "plugins": {
    "allow": ["default-model-enforcer"],
    "entries": {
      "default-model-enforcer": {
        "enabled": true
      }
    }
  }
}

README

<h1 align="center">πŸ”„ Default Model Enforcer</h1>

<p align="center">
  <strong>An <a href="https://github.com/openclaw/openclaw">OpenClaw</a> plugin that enforces intelligent model rotation per session.</strong><br/>
  Seed on your primary model, then automatically switch to your preferred reasoning model β€” zero manual intervention.
</p>

<p align="center">
  <img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-007ACC?style=flat&logo=typescript&logoColor=white" />
  <img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-green.svg" />
  <img alt="OpenClaw Plugin" src="https://img.shields.io/badge/OpenClaw-Plugin-blueviolet" />
  <img alt="Version" src="https://img.shields.io/badge/version-14.0.0-blue" />
</p>

<p align="center">
  <a href="#features">Features</a> β€’
  <a href="#how-it-works">How It Works</a> β€’
  <a href="#installation">Installation</a> β€’
  <a href="#configuration">Configuration</a> β€’
  <a href="#architecture">Architecture</a> β€’
  <a href="#api-reference">API Reference</a> β€’
  <a href="#license">License</a>
</p>

---

## Why?

Running multi-agent systems means dealing with **different model strengths for different tasks.** Some models excel at fast context ingestion and structured bootstrapping, while others shine in complex reasoning and creative output. But manually switching models mid-session? Nobody has time for that.

The Default Model Enforcer (DME) automates this pattern: **use a fast, efficient model for session initialization** (system prompt processing, memory loading, context priming) β€” then **automatically rotate to your preferred reasoning model** for all subsequent interaction turns.

**Use cases:**
- 🏎️ **Latency optimization** β€” Fast model for bootstrap, powerful model for conversation
- πŸ’° **Cost control** β€” Cheaper model handles the boilerplate first turn, premium model handles the real work
- πŸ§ͺ **A/B model strategies** β€” Enforce consistent model assignment across sessions without manual intervention
- πŸ€– **Multi-agent fleets** β€” Different agents can run different rotation strategies via their config

**Result:** Optimal model selection per conversation phase, fully automated, zero manual intervention.

## Features

- πŸ”„ **Automatic Model Rotation** β€” First turn uses `model.primary`, all subsequent turns use the first `model.fallbacks` entry
- πŸ«€ **Heartbeat Awareness** β€” Heartbeat/cron polls always use the primary model and never advance the session phase
- πŸ›‘οΈ **Graceful Degradation** β€” If the primary model is unavailable and the runtime falls back internally, DME stays in `seed_pending` and retries next turn
- βœ‹ **Manual Override Respected** β€” `/model` command sets a manual override; DME backs off entirely for that session
- 🧹 **Auto-Cleanup** β€” Stale session states (>24h) are pruned automatically
- πŸ“ **Cross-Context State** β€” File-backed state (`/tmp/dme-state.json`) shared between gateway and plugin execution contexts
- ⚑ **Atomic Writes** β€” State persistence uses write-to-temp + rename to prevent corruption
- πŸ”Œ **Idempotent Registration** β€” Safe across hot-reloads and multi-context environments

## How It Works

```
Session Start
     β”‚
     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  seed_pending    β”‚ ← First normal turn β†’ force PRIMARY model
β”‚                  β”‚
β”‚  llm_output      β”‚ ← Verify: did primary actually run?
β”‚  confirms seed?  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚ yes
         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  seed_done       β”‚ ← All subsequent turns β†’ force FALLBACK model
β”‚                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Special transitions:
  /model command  β†’ manual_override (DME hands off)
  /new or /reset  β†’ state cleared (next session starts fresh)
  heartbeat       β†’ always PRIMARY, never advances phase
```

### Phase Machine

| Phase | Behavior | Transition |
|-------|----------|------------|
| `seed_pending` | Force primary model | β†’ `seed_done` when primary confirmed via `llm_output` |
| `seed_done` | Force first fallback model | Stays until session ends |
| `manual_override` | No intervention | User took control via `/model` |

### Seed Verification

DME doesn't blindly trust `before_model_resolve`. It **verifies** in the `llm_output` hook that the primary model actually ran. If the runtime silently fell back to a different model (e.g. due to rate limits or unavailability), DME stays in `seed_pending` and retries on the next turn. This ensures the rotation only triggers after a confirmed primary run.

## Installation

### Prerequisites

- [OpenClaw](https://github.com/openclaw/openclaw) v2026.2.x or later
- At least one agent with `model.primary` and `model.fallbacks` configured

### Setup

1. **Clone into your extensions directory:**

```bash
cd ~/.openclaw/extensions
git clone https://github.com/FilHouston/openclaw-default-model-enforcer.git default-model-enforcer
```

2. **Enable the plugin in `openclaw.json`:**

```jsonc
{
  "plugins": {
    "allow": ["default-model-enforcer"],
    "entries": {
      "default-model-enforcer": {
        "enabled": true
      }
    }
  }
}
```

3. **Restart the gateway:**

```bash
openclaw gateway restart
```

4. **Verify in logs:**

```
[DME] registered v14 (file-backed state)
```

## Configuration

DME reads model configuration from your agent's existing `model` block in `openclaw.json`. No additional plugin-specific configuration is required.

```jsonc
{
  "agents": {
    "list": [
      {
        "id": "main",
        "model": {
          "primary": "openai/gpt-4.1",              // ← Used for seed turn + heartbeats
          "fallbacks": ["anthropic/claude-sonnet-4"]  // ← First entry used after seed
        }
      }
    ]
  }
}
```

| Field | Purpose |
|-------|---------|
| `model.primary` | Model used for the first turn of each session and all heartbeats |
| `model.fallbacks[0]` | Model used for all subsequent normal turns after seed confirmation |

## Architecture

### State Persistence

**v14** uses file-backed state at `/tmp/dme-state.json` instead of in-memory maps. This solves a critical cross-context isolation issue where OpenClaw's `[gateway]` and `[plugins]` execution contexts maintained separate `globalThis` scopes.

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  [gateway]   β”‚     β”‚  [plugins]   β”‚
β”‚   context    β”‚     β”‚   context    β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚                    β”‚
       β”‚  read/write        β”‚  read/write
       β”‚                    β”‚
       β–Ό                    β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  /tmp/dme-state.json         β”‚
  β”‚  (atomic writes via rename)  β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

### Session Key Strategy

Sessions are identified by a composite key with priority:

1. **Primary:** `agent:<agentId>:sid:<sessionId>` β€” most stable identifier
2. **Fallback:** `agent:<agentId>:skey:<sessionKey>` β€” used when `sessionId` is unavailable

On `session_start`, ambiguous fallback-only states for the same agent are cleaned up to prevent ghost entries.

### Hook Registration

Registration is idempotent per API instance using a `WeakSet` on `globalThis`. This prevents duplicate hook registration during hot-reloads while still allowing registration across multiple API contexts (gateway + embedded agents).

## API Reference

### Hooks Registered

| Hook | Purpose |
|------|---------|
| `session_start` | Initialize session state as `seed_pending`, prune stale entries |
| `before_model_resolve` | Override model based on current phase |
| `llm_output` | Verify seed completion, confirm or retry |
| `command:model` | Transition to `manual_override` |
| `command:new` | Clear session state |
| `command:reset` | Clear session state |
| `session_end` | Clear session state |

### Log Markers

All log entries are prefixed with `[DME]` for easy filtering:

```bash
# Watch DME activity in real-time
openclaw gateway logs | grep "\[DME\]"
```

Expected sequence for a healthy session:

```
[DME] session_start: agent=main key=agent:main:sid:abc123 phase=seed_pending
[DME] before_model_resolve: agent=main key=agent:main:sid:abc123 phase=seed_pending -> primary openai/gpt-4.1
[DME] llm_output: seed confirmed agent=main key=agent:main:sid:abc123 ... -> phase=seed_done
[DME] before_model_resolve: agent=main key=agent:main:sid:abc123 phase=seed_done -> fallback anthropic/claude-sonnet-4
```

## Troubleshooting

| Symptom | Likely Cause | Fix |
|---------|-------------|-----|
| DME not activating | Plugin not in `plugins.allow` | Add `"default-model-enforcer"` to the allow list |
| Always stays on primary | Primary model unavailable, seed never confirmed | Check model availability, review `llm_output` logs |
| State lost after restart | `/tmp` cleared on reboot | Expected behavior β€” sessions re-seed automatically |
| Duplicate registration warnings | Multiple hot-reloads | Harmless β€” idempotent guard prevents actual duplicates |

## Development

```bash
# Run with verbose logging
openclaw gateway restart
openclaw gateway logs --follow

# Inspect current state
cat /tmp/dme-state.json | python3 -m json.tool

# Clear all state (forces re-seed on next turn)
rm /tmp/dme-state.json
```

## Changelog

See [CHANGELOG.md](CHANGELOG.md) for version history.

## License

[MIT](LICENSE) β€” do whatever you want with it.

## Author

**Philipp Just** β€” [GitHub](https://github.com/FilHouston)

Built as part of a multi-agent AI infrastructure running on [OpenClaw](https://github.com/openclaw/openclaw).
integration

Comments

Sign in to leave a comment

Loading comments...