Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
e0f36e6
Add shared agent loop runtime and transport abstraction
bradvin Feb 24, 2026
b8b29c9
Add agent run/session orchestration, runner, and REST endpoints
bradvin Feb 24, 2026
64a7930
Remove duplicated legacy loop logic from Chat helper
bradvin Feb 24, 2026
b94dcbd
Add comprehensive agent runtime regression test harness
bradvin Feb 24, 2026
fd8868b
Fix agent runtime state-machine regressions and retry safety
bradvin Feb 24, 2026
1f10de7
Document agent loop runtime architecture and state transitions
bradvin Feb 24, 2026
7a3bc46
Expand agent-loop spec coverage tests for positive and negative flows
bradvin Feb 24, 2026
089f2d9
Implement spawn/idempotency flows and chat in-progress handoff
bradvin Feb 24, 2026
fa813b2
added doc for streaming support (future)
bradvin Feb 24, 2026
c0e4082
documented agent loop findings
bradvin Feb 24, 2026
c65a80f
Phase 1: harden stale-run recovery and idempotency races
bradvin Feb 24, 2026
b6327d6
Phase 2: separate retry accounting and release run locks on pause
bradvin Feb 24, 2026
4094ff2
Phase 3: enforce wall-time limits and policy violation modes
bradvin Feb 24, 2026
307fa01
Phase 4: unify chat first-slice persistence and execution identity
bradvin Feb 24, 2026
271e568
Phase 5: consolidate enqueue and runtime utility paths
bradvin Feb 24, 2026
603bab6
Phase 5: refresh runtime and policy docs
bradvin Feb 24, 2026
9e0b1ed
Panel: poll agent runs after in_progress chat responses
bradvin Feb 24, 2026
add4826
Runtime: extend chat wall-time and reset per-slice step budget
bradvin Feb 24, 2026
befeae7
new build
bradvin Feb 24, 2026
d11851c
Runner: preserve pause state and answer all tool calls
bradvin Feb 24, 2026
0ac1617
Panel: use events cursor and dedupe polled tool calls
bradvin Feb 24, 2026
d31b5b1
Loop: defer over-limit tool calls without chat errors
bradvin Feb 24, 2026
ec92b6d
new build
bradvin Feb 24, 2026
477df8a
Panel: show in-progress reply as thinking-style status
bradvin Feb 24, 2026
0e9ec51
Panel: cycle thinking dots as ., .., ...
bradvin Feb 24, 2026
b337c65
Fix tool-call dedup across polling and terminal results
bradvin Feb 24, 2026
4056eb3
Persist terminal chat run result into history
bradvin Feb 24, 2026
d12a9e5
Refocus chat textarea after stream completes
bradvin Feb 24, 2026
17bb739
Hide stale in-progress status after new response
bradvin Feb 24, 2026
c8acd61
Reduce in-progress continuation latency
bradvin Feb 24, 2026
3076391
Rotate in-progress polling messages during long runs
bradvin Feb 24, 2026
65c0fcc
Fix model history to start from first user message
bradvin Feb 24, 2026
b94c14a
Slow in-progress polling and message rotation cadence
bradvin Feb 24, 2026
4915aa7
Tune run polling cadence to midpoint latency
bradvin Feb 24, 2026
d0f6301
Increase chat tool budget and improve empty terminal replies
bradvin Feb 24, 2026
c911aa8
Keep in-progress status visible during tool event bursts
bradvin Feb 24, 2026
84ac3cb
Use requesting user identity for resumed tool permissions
bradvin Feb 24, 2026
6f6cf25
Update clawpress.pot
bradvin Feb 24, 2026
1145fe5
removed old docs
bradvin Feb 26, 2026
35de27e
Update agent-loop-findings.md
bradvin Feb 26, 2026
c626136
Fix run attempt semantics for paused continuation claims
bradvin Feb 26, 2026
c2c3a4b
Remove resolved agent loop findings document
bradvin Feb 26, 2026
0566d67
Merge branch 'develop' into codex/agent-loop-runtime
bradvin Feb 27, 2026
db7705f
Fix terminal card fallback and resolve run-store WPCS issues
bradvin Feb 27, 2026
ad73795
Switch plugin-check to dist flow and update plugin metadata
bradvin Feb 27, 2026
4bbbf7b
Fix remaining dist plugin-check packaging issues
bradvin Feb 27, 2026
f04d0c0
Ignore trademark false-positive in dist plugin check
bradvin Feb 27, 2026
66b9ce2
Fix direct DB parameter warnings in agent stores
bradvin Feb 27, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,5 @@ jobs:
tools: composer:v2
- name: Install Composer dependencies
run: composer install --no-interaction --prefer-dist --no-progress
- name: Run plugin standards check
- name: Run dist plugin standards check
run: npm run plugin:check
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ npm run lint:changed
npm run plugin:check
```

`npm run plugin:check` starts a disposable local WordPress environment via `wp-env`, installs the official Plugin Check plugin, and runs `wp plugin check` against ClawPress.
`npm run plugin:check` builds a distribution zip, starts a disposable local WordPress environment via `wp-env`, installs the official Plugin Check plugin, installs the dist zip under a temporary slug, and runs `wp plugin check` against that packaged artifact.

## Tests

Expand Down
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
# ClawPress
=== ClawPress ===
Contributors: bradvin
Tags: ai, assistant, admin
Requires at least: 6.9
Tested up to: 6.9
Requires PHP: 8.1
Stable tag: 0.0.2
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

AI assistant for WordPress admins.

== Description ==

![ClawPress Logo](img/clawpress-logo-500x500.png)

The AI for WordPress that actually does things
ClawPress is the AI for WordPress that actually does things.

[Preview in WordPress Playground](https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/bradvin/clawpress/refs/heads/main/blueprint.json)

Expand Down
16 changes: 13 additions & 3 deletions bin/plugin-check
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,21 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

cd "$REPO_ROOT"

echo "Starting wp-env for plugin check..."
DIST_SLUG="clawpress-dist-check"
EXPECTED_SLUG="clawpress"
DIST_ZIP_PATH="/var/www/html/wp-content/plugins/clawpress/dist/clawpress.zip"

echo "Building plugin zip for dist plugin check..."
npm run plugin-zip

echo "Starting wp-env for dist plugin check..."
npx wp-env start

echo "Installing/updating Plugin Check plugin..."
npx wp-env run tests-cli wp plugin install plugin-check --activate --force

echo "Running Plugin Check against clawpress..."
npx wp-env run tests-cli wp plugin check clawpress
echo "Installing dist zip into temporary plugin slug..."
npx wp-env run tests-cli -- bash -lc "set -euo pipefail; rm -rf /tmp/plugin-check-dist /var/www/html/wp-content/plugins/${DIST_SLUG}; mkdir -p /tmp/plugin-check-dist; unzip -q ${DIST_ZIP_PATH} -d /tmp/plugin-check-dist; DIST_SOURCE=\$(find /tmp/plugin-check-dist -mindepth 1 -maxdepth 1 -type d | head -n 1); if [[ -z \"\${DIST_SOURCE}\" ]]; then echo 'Unable to locate extracted plugin directory from dist zip.' >&2; exit 1; fi; cp -R \"\${DIST_SOURCE}\" /var/www/html/wp-content/plugins/${DIST_SLUG};"

echo "Running Plugin Check against dist plugin contents..."
npx wp-env run tests-cli wp plugin check "${DIST_SLUG}" --slug="${EXPECTED_SLUG}" --ignore-codes=trademarked_term
2 changes: 1 addition & 1 deletion build/panel/panel-rtl.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion build/panel/panel.asset.php
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-element', 'wp-i18n'), 'version' => '80b5e6a1d154a9389ca4');
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-element', 'wp-i18n'), 'version' => 'e8334107ad65c63f6222');
2 changes: 1 addition & 1 deletion build/panel/panel.css

Large diffs are not rendered by default.

30 changes: 15 additions & 15 deletions build/panel/panel.js

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions clawpress.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<?php
/**
* Plugin Name: ClawPress
* Description: AI assistant tools for WordPress admin workflows.
* Version: 0.0.2
* Requires PHP: 8.1
* Requires at least: 6.9
* License: GPLv2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: clawpress
* Domain Path: /languages
*
Expand Down
214 changes: 214 additions & 0 deletions docs/agent-loop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
# Agent Loop Runtime

This document describes how the ClawPress agent loop executes work across chat, background runner, and spawn flows.

## Scope

The runtime spans:

- `Agent_Loop_Helper`: turn/slice execution engine.
- `Agent_Runner`: Action Scheduler worker that claims and executes run slices.
- `Agent_Run_Controller`: REST API for creating, spawning, enqueueing, and polling runs.
- `Agent_Run_Helper` + `Agent_Session_Helper`: lifecycle/state helpers.
- `Agent_Run_Store` + `Agent_Session_Store` + `Agent_Event_Store`: persistence.
- `Polling_Transport` / `Null_Transport`: event delivery.

Shared runtime utilities:

- `Agent_Runner::enqueue_run_slice_action()` is the single enqueue path used by chat, run-controller, and runner retry/continuation logic.
- `Provider_Helper::resolve_provider_and_model()` is the default provider/model resolver for both chat and loop runtime.
- `Agent_Loop_Helper::classify_provider_error_type()` is reused by chat and loop runtime error handling.

## Core Model

### Session

Session rows (`clawpress_agent_sessions`) represent a logical execution stream and lease ownership.

Important fields:

- `status`: `idle`, `running`, `paused`, `error`.
- `lease_owner`, `lease_token`, `lease_expires_at_gmt`: session-level lease lock.
- `last_run_status`, `consecutive_failures`: health/telemetry.

### Run

Run rows (`clawpress_agent_runs`) represent one queued/claimed execution attempt.

Important fields:

- `status`: `queued`, `running`, `paused`, terminal states (`done`, `error`, `timeout`, `requires_confirmation`, etc.).
- `lock_token`, `lock_expires_at_gmt`: run-level lock.
- `attempt`, `max_attempts`: retry accounting.
- `retry_count`: retry/backoff accounting separate from continuation slices.
- `next_retry_at_gmt`: deferred scheduling.
- `resume_cursor_json`: continuation state between slices.
- `meta_json`: request payload and runtime metadata.

### Event

Event rows (`clawpress_agent_events`) are append-only progress records for polling clients.

## Execution Paths

## 1) Chat Path (`Chat_Helper` -> `Agent_Loop_Helper::run_slice`)

1. Resolve provider/model.
2. Create and claim chat session/run records up front (`run_id`, `session_id`).
3. Build runtime request payload.
4. Execute a bounded slice through `Agent_Loop_Helper::run_slice`.
5. Persist first-slice outcomes onto the same run/session state.
6. Emit run-linked events from the first slice (no pre-run event gap).
7. If slice completes: return assistant text/card/context/tool-call trace.
8. If slice returns `in_progress`: persist `resume_cursor`, enqueue background continuation, and return `run_id` + `session_id` + `status=in_progress` for polling.

If provider is unavailable, it returns deterministic offline mode without background run creation.

Execution identity:

- Chat, run-creation, and spawn adapters resolve execution user consistently.
- Preferred execution user is configured `agent_user_id`; fallback is requesting user.

## 2) Background Path (`Agent_Run_Controller` + `Agent_Runner`)

1. `POST /clawpress/v1/agent/runs` creates session/run and enqueues slice action.
2. Runner claims runnable run (`queued`/`paused`, retry time reached).
- Runnable scan also includes stale `running` rows whose run lock lease has expired.
3. Runner claims session lease.
4. Runner executes one slice via `Agent_Loop_Helper::run_slice`.
5. Runner either:
- completes run (`done`, `requires_confirmation`, `error`, etc.), or
- pauses run with `resume_cursor` and `next_retry_at_gmt`, then re-enqueues.

## 3) Spawn Path (`POST /agent/spawn`)

1. Create a new `spawned_agent` session.
2. Seed first run metadata (`message`, slice budgets, retry limits).
3. Enqueue first slice.
4. Return `session_id` + `run_id` immediately.

## Locking and Claim Rules

### Run claim

`Agent_Run_Helper::claim_run` supports:

- fresh claim from `queued` (attempt stays at current value; first claim remains `1`),
- retry claim from `paused` (attempt increments),
- stale reclaim from expired `running` lease (attempt increments).

Heartbeat scan intentionally includes expired `running` leases so stale claims are actually discoverable in production.

### Session claim

`Agent_Session_Helper::claim_session` supports:

- `idle` and `paused` sessions,
- stale reclaim of expired `running` lease.

This prevents deadlock when a run pauses and later needs to resume under a paused session.

## Retry and Failure Semantics

Session failure counter behavior (`update_run_completion`):

- reset to `0` for `success`, `done`, `requires_confirmation`,
- unchanged for `paused`,
- incremented for error-like outcomes.

This keeps time-slicing and confirmation waits from being treated as failures.

Retry model notes:

- `attempt` tracks claim lifecycle visibility.
- `retry_count` tracks error-driven retries/backoff only.
- continuation pauses (`in_progress`) do not increment `retry_count`.
- paused progress releases run lock metadata (`lock_token`, `claimed_by`, lease timestamps) because lock scope is one slice.
- runner enforces trigger policy `max_wall_time_seconds` and marks run `timeout` when exceeded.
- when `allow_background_followups` is false, the runner does not enqueue additional slices after `in_progress` or retryable error outcomes.

Policy violation modes:

- `deny`: tool call is rejected with policy error payload.
- `degrade`: tool call returns a successful degraded no-op payload so the model can continue.
- `fail`: tool call returns an error payload tagged as fail mode (`*_fail` policy code).

## Manual Re-enqueue Safety

`POST /agent/runs/{run_id}/enqueue` is intentionally guarded:

- only terminal/retryable statuses can be re-enqueued,
- active statuses (`queued`, `running`, `paused`) return HTTP `409`,
- DB update is compare-and-swap on `id + status` to avoid racey resets.

This prevents accidental mutation of active runs and stale-state rewrites.

## Idempotency

`idempotency_key` is supported on run creation:

- `POST /agent/runs` with the same `session_id + idempotency_key` returns the existing run instead of inserting a duplicate.
- duplicate create requests return HTTP `200` with `deduplicated=true`.
- unknown explicit `session_id` now returns HTTP `404` to prevent orphan run creation.
- run table enforces unique `(session_id, idempotency_key)` for non-null keys to harden concurrent create races.
- helper fallback re-reads by idempotency key when insert returns no ID, covering duplicate-key race windows.

## Agent Loop Callback Compatibility

`Agent_Loop_Helper` supports custom `online_reply_generator` callables with varying arity.

Arity resolver supports:

- array callables (`[object, 'method']`),
- closures/string callables,
- invokable objects (`__invoke`).

Invokable object reflection uses `ReflectionMethod(__invoke)` and catches both `ReflectionException` and `TypeError`.

## REST Endpoints

`Agent_Run_Controller` registers:

- `POST /clawpress/v1/agent/runs`
- `POST /clawpress/v1/agent/spawn`
- `POST /clawpress/v1/agent/runs/{run_id}/enqueue`
- `GET /clawpress/v1/agent/runs/{run_id}`
- `GET /clawpress/v1/agent/runs/{run_id}/events?after={event_id}&limit={n}`

## Eventing

- `Polling_Transport` appends runtime events via `Agent_Event_Helper`.
- Runner emits operational events (slice started/paused/completed).
- Polling endpoint returns incremental cursor (`next_cursor`) for client-side streaming.

## Test Harness Coverage

The runtime harness uses `tests/Support/AgentRuntimeWpdb.php` and covers:

- run/session claim and completion lifecycle,
- paused-session reclaim behavior,
- attempt accounting correctness,
- enqueue safety rules,
- paused and requires-confirmation failure accounting,
- runner progression from queued run to completion,
- runner missing-session and unclaimable-session negative flows,
- controller create/enqueue/events behavior,
- controller idempotency and unknown-session negative flows,
- spawn endpoint flow,
- chat `in_progress` handoff with run metadata,
- loop invocation with invokable callback arity,
- loop streaming-mode transport fallback and slice `in_progress` payload semantics.

Primary tests:

- `tests/Unit/AgentRunHelperTest.php`
- `tests/Unit/AgentRunnerTest.php`
- `tests/Unit/AgentRunControllerTest.php`
- `tests/Unit/AgentLoopHelperTest.php`

## Operational Notes

- `Agent_Runner` is wired in plugin bootstrap and listens to `clawpress_run_scheduled_tasks` and `clawpress_agent_run_slice`.
- Action Scheduler async actions are preferred; delayed slices use single scheduled actions.
- Chat and REST controllers delegate run-slice scheduling to `Agent_Runner::enqueue_run_slice_action()` to avoid duplicate enqueue behavior.
- Keep helper/store boundaries intact: controllers and runner call helpers; helpers call stores.
10 changes: 0 additions & 10 deletions docs/lint-baseline.md

This file was deleted.

35 changes: 0 additions & 35 deletions docs/lint-debt-reduction-plan.md

This file was deleted.

Loading