agentation
Visual UI annotation tool for AI agents. Drop the React toolbar into any app — humans click elements and leave feedback, agents receive structured CSS selectors, bounding boxes, and React component trees to find exact code. Supports MCP watch-loop, platform-specific hooks (Claude Code / Codex / Gemini CLI / OpenCode), webhook delivery, and autonomous self-driving critique with agent-browser.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install supercent-io-skills-template-agentation
Repository
Skill path: .agent-skills/agentation
Visual UI annotation tool for AI agents. Drop the React toolbar into any app — humans click elements and leave feedback, agents receive structured CSS selectors, bounding boxes, and React component trees to find exact code. Supports MCP watch-loop, platform-specific hooks (Claude Code / Codex / Gemini CLI / OpenCode), webhook delivery, and autonomous self-driving critique with agent-browser.
Open repositoryBest for
Primary workflow: Design Product.
Technical facets: Full Stack, Frontend, Data / AI, Designer, Integration.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: supercent-io.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install agentation into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/supercent-io/skills-template before adding agentation to shared team environments
- Use agentation for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: agentation
description: Visual UI annotation tool for AI agents. Drop the React toolbar into any app — humans click elements and leave feedback, agents receive structured CSS selectors, bounding boxes, and React component trees to find exact code. Supports MCP watch-loop, platform-specific hooks (Claude Code / Codex / Gemini CLI / OpenCode), webhook delivery, and autonomous self-driving critique with agent-browser.
compatibility: React 18+, Node.js 18+
allowed-tools: Read Write Bash Grep Glob
metadata:
tags: ui-feedback, browser-annotation, visual-feedback, mcp, react, ai-agent, design-review, css-selector
platforms: Claude Code, Codex, Gemini CLI, OpenCode, Cursor, Windsurf, ChatGPT
keyword: agentation
version: 1.0.0
source: benjitaylor/agentation
---
# agentation — Visual UI Feedback Bridge for AI Agents
> **The missing link between human eyes and agent code.**
>
> Instead of describing "the blue button in the sidebar," you hand the agent `.sidebar > button.primary`. It can `grep` for that directly.
---
## When to use this skill
- Human needs to point at a UI element and give feedback — without writing selectors
- Running iterative UI/UX review cycles between human and coding agent
- Building a watch-loop where agent auto-fixes every annotation a human leaves
- Capturing CSS selectors, bounding boxes, and React component trees for precise code targeting
- Autonomous design critique via `agent-browser` + self-driving pattern
- Integrating visual feedback into agent hooks so annotations auto-appear in agent context
---
## 1. Architecture
```
agentation (monorepo)
├── agentation → npm: agentation (React toolbar component)
│ └── src/index.ts → exports Agentation component + types + utilities
└── agentation-mcp → npm: agentation-mcp (MCP server + CLI)
├── src/cli.ts → agentation-mcp CLI (init, server, doctor)
└── src/server/ → HTTP REST API (port 4747) + SSE events + MCP stdio tools
```
**Two modes of operation:**
| Mode | How it works |
|------|-------------|
| **Copy-Paste** | Human annotates → clicks Copy → pastes markdown into agent chat |
| **Agent Sync** | `endpoint` prop connects toolbar to MCP server → agent uses `agentation_watch_annotations` loop |
---
## 2. Installation
### 2.1 React Component (toolbar)
```bash
npm install agentation -D
# or: pnpm add agentation -D / yarn add agentation -D / bun add agentation -D
```
**Requirements**: React 18+, desktop browser, zero runtime deps beyond React (desktop only — no mobile)
> 🔗 **Local-first by design**: Annotations are stored locally and auto-sync when connected to the MCP server.
> - **Offline operation** — Annotations can be created without a server
> - **Session continuity** — Same session persists after page refresh, no duplicates
> - **Agent-first** — resolve/dismiss is handled by the agent
### 2.2 MCP Server — Universal Setup (Recommended)
> **Fastest method** — Auto-detects all installed agents and configures them (Claude Code, Cursor, Codex, Windsurf, and 9+ more agents):
```bash
npx add-mcp "npx -y agentation-mcp server"
```
Or install manually:
```bash
npm install agentation-mcp -D
npx agentation-mcp server # HTTP :4747 + MCP stdio
npx agentation-mcp server --port 8080 # custom port
npx agentation-mcp doctor # verify setup
```
### 2.3 Claude Code — Official Skill (Minimal Setup)
> Recommended for Claude Code users — automatically handles framework detection, package installation, and layout integration:
```bash
npx skills add benjitaylor/agentation
# then in Claude Code:
/agentation
```
---
## 3. React Component Setup
### Basic (Copy-Paste mode — no server needed)
```tsx
import { Agentation } from 'agentation';
function App() {
return (
<>
<YourApp />
{process.env.NODE_ENV === 'development' && <Agentation />}
</>
);
}
```
### Next.js App Router
```tsx
// app/layout.tsx
import { Agentation } from 'agentation';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
{children}
{process.env.NODE_ENV === 'development' && (
<Agentation endpoint="http://localhost:4747" />
)}
</body>
</html>
);
}
```
### Next.js Pages Router
```tsx
// pages/_app.tsx
import { Agentation } from 'agentation';
export default function App({ Component, pageProps }) {
return (
<>
<Component {...pageProps} />
{process.env.NODE_ENV === 'development' && (
<Agentation endpoint="http://localhost:4747" />
)}
</>
);
}
```
### Full Props Reference
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `endpoint` | `string` | — | MCP server URL for Agent Sync mode |
| `sessionId` | `string` | — | Pre-existing session ID to join |
| `onAnnotationAdd` | `(a: Annotation) => void` | — | Callback when annotation created |
| `onAnnotationDelete` | `(a: Annotation) => void` | — | Callback when annotation deleted |
| `onAnnotationUpdate` | `(a: Annotation) => void` | — | Callback when annotation edited |
| `onAnnotationsClear` | `(a: Annotation[]) => void` | — | Callback when all cleared |
| `onCopy` | `(markdown: string) => void` | — | Callback with markdown on copy |
| `onSubmit` | `(output: string, annotations: Annotation[]) => void` | — | On "Send Annotations" click |
| `copyToClipboard` | `boolean` | `true` | Set false to suppress clipboard write |
| `onSessionCreated` | `(sessionId: string) => void` | — | Called on new session creation |
| `webhookUrl` | `string` | — | Webhook URL to receive annotation events |
---
## 4. MCP Server Setup — All Platforms
**Fastest method — Universal (auto-detects 9+ agents):**
```bash
npx add-mcp "npx -y agentation-mcp server"
```
> [add-mcp](https://github.com/neondatabase/add-mcp) auto-detects Claude Code, Cursor, Codex, Windsurf, and more, writing directly to the correct config.
**Start server / verify:**
```bash
npx agentation-mcp server # HTTP :4747 + MCP stdio
npx agentation-mcp server --port 8080 # custom port
npx agentation-mcp doctor # verify setup
```
---
### Claude Code (`.claude/`)
**Minimal setup — Official Claude Code Skill (Recommended):**
```bash
npx skills add benjitaylor/agentation
# In Claude Code:
/agentation
```
**Universal MCP auto-setup (Claude Code + 9+ agents):**
```bash
npx add-mcp "npx -y agentation-mcp server"
```
**Interactive wizard (Claude Code only):**
```bash
npx agentation-mcp init
```
**Option A — CLI (recommended):**
```bash
claude mcp add agentation -- npx -y agentation-mcp server
```
**Option B — config file** (`~/.claude/claude_desktop_config.json` for global, or `.claude/mcp.json` for project-level):
```json
{
"mcpServers": {
"agentation": {
"command": "npx",
"args": ["-y", "agentation-mcp", "server"]
}
}
}
```
**Interactive wizard (Claude Code only):**
```bash
npx agentation-mcp init
```
**UserPromptSubmit hook** — auto-inject pending annotations on every message.
Add to `.claude/settings.json` (project) or `~/.claude/settings.json` (global):
```json
{
"hooks": {
"UserPromptSubmit": [
{
"type": "command",
"command": "curl -sf --connect-timeout 1 http://localhost:4747/pending 2>/dev/null | python3 -c \"import sys,json;d=json.load(sys.stdin);c=d['count'];exit(0)if c==0 else[print(f'\\n=== AGENTATION: {c} UI annotations ===\\n'),*[print(f\\\"[{i+1}] {a['element']} ({a['elementPath']})\\n {a['comment']}\\n\\\")for i,a in enumerate(d['annotations'])],print('=== END ===\\n')]\" 2>/dev/null;exit 0"
}
]
}
}
```
---
### Codex CLI (`~/.codex/`)
Add to `~/.codex/config.toml`:
```toml
# Agentation MCP Server
[[mcp_servers]]
name = "agentation"
command = "npx"
args = ["-y", "agentation-mcp", "server"]
# Optional: teach Codex about watch-loop
developer_instructions = """
When user says "watch mode" or "agentation watch", call agentation_watch_annotations in a loop.
For each annotation: acknowledge it, fix the code using the elementPath CSS selector, resolve with summary.
"""
```
Restart Codex CLI after editing `config.toml`.
---
### Gemini CLI (`~/.gemini/`)
**Option A — CLI:**
```bash
gemini mcp add agentation npx -y agentation-mcp server
# or with explicit scope
gemini mcp add -s user agentation npx -y agentation-mcp server
```
**Option B — config file** (`~/.gemini/settings.json` for global, `.gemini/settings.json` for project):
```json
{
"mcpServers": {
"agentation": {
"command": "npx",
"args": ["-y", "agentation-mcp", "server"]
}
}
}
```
**AfterAgent hook** — trigger annotation check after each agent turn:
```json
{
"mcpServers": {
"agentation": {
"command": "npx",
"args": ["-y", "agentation-mcp", "server"]
}
},
"hooks": {
"AfterAgent": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "curl -sf --connect-timeout 1 http://localhost:4747/pending 2>/dev/null | python3 -c \"import sys,json;d=json.load(sys.stdin);c=d.get('count',0);[print(f'[agentation] {c} pending annotations'),exit(1)]if c>0 else exit(0)\" 2>/dev/null;exit 0",
"description": "Check for pending agentation annotations"
}
]
}
]
}
}
```
---
### OpenCode (`~/.config/opencode/`)
Add to `~/.config/opencode/opencode.json`:
```json
{
"mcp": {
"agentation": {
"type": "local",
"command": ["npx", "-y", "agentation-mcp", "server"]
}
}
}
```
With environment variables:
```json
{
"mcp": {
"agentation": {
"type": "local",
"command": ["npx", "-y", "agentation-mcp", "server"],
"environment": {
"AGENTATION_STORE": "sqlite",
"AGENTATION_EVENT_RETENTION_DAYS": "7"
}
}
}
}
```
Restart OpenCode after editing. MCP tools (`agentation_*`) will be available immediately.
---
### Universal (npx add-mcp)
Works for any MCP-compatible agent:
```bash
npx add-mcp "npx -y agentation-mcp server"
```
---
### Quick-Setup Script
Save and run `bash setup-agentation-mcp.sh [--all | --claude | --codex | --gemini | --opencode]`:
```bash
#!/usr/bin/env bash
# setup-agentation-mcp.sh — Register agentation MCP for all agent platforms
set -euo pipefail
SETUP_CLAUDE=false; SETUP_CODEX=false; SETUP_GEMINI=false; SETUP_OPENCODE=false
while [[ $# -gt 0 ]]; do
case "$1" in
--claude) SETUP_CLAUDE=true ;;
--codex) SETUP_CODEX=true ;;
--gemini) SETUP_GEMINI=true ;;
--opencode) SETUP_OPENCODE=true ;;
--all) SETUP_CLAUDE=true; SETUP_CODEX=true; SETUP_GEMINI=true; SETUP_OPENCODE=true ;;
esac
shift
done
[[ "$SETUP_CLAUDE$SETUP_CODEX$SETUP_GEMINI$SETUP_OPENCODE" == "falsefalsefalsefalse" ]] && \
SETUP_CLAUDE=true && SETUP_CODEX=true && SETUP_GEMINI=true && SETUP_OPENCODE=true
MCP_JSON='"agentation": {"command": "npx", "args": ["-y", "agentation-mcp", "server"]}'
# Claude Code
if [[ "$SETUP_CLAUDE" == "true" ]]; then
mkdir -p ~/.claude
CFG=~/.claude/claude_desktop_config.json
if [[ -f "$CFG" ]] && command -v jq &>/dev/null; then
jq ".mcpServers += {$MCP_JSON}" "$CFG" > "$CFG.tmp" && mv "$CFG.tmp" "$CFG"
else
echo "{\"mcpServers\": {$MCP_JSON}}" > "$CFG"
fi
echo "✅ Claude Code: $CFG"
fi
# Codex CLI
if [[ "$SETUP_CODEX" == "true" ]]; then
mkdir -p ~/.codex
CFG=~/.codex/config.toml
if ! grep -q "agentation" "$CFG" 2>/dev/null; then
printf '\n[[mcp_servers]]\nname = "agentation"\ncommand = "npx"\nargs = ["-y", "agentation-mcp", "server"]\n' >> "$CFG"
fi
echo "✅ Codex CLI: $CFG"
fi
# Gemini CLI
if [[ "$SETUP_GEMINI" == "true" ]]; then
mkdir -p ~/.gemini
CFG=~/.gemini/settings.json
if [[ -f "$CFG" ]] && command -v jq &>/dev/null; then
jq ".mcpServers += {$MCP_JSON}" "$CFG" > "$CFG.tmp" && mv "$CFG.tmp" "$CFG"
else
echo "{\"mcpServers\": {$MCP_JSON}}" > "$CFG"
fi
echo "✅ Gemini CLI: $CFG"
fi
# OpenCode
if [[ "$SETUP_OPENCODE" == "true" ]]; then
mkdir -p ~/.config/opencode
CFG=~/.config/opencode/opencode.json
ENTRY='"agentation": {"type": "local", "command": ["npx", "-y", "agentation-mcp", "server"]}'
if [[ -f "$CFG" ]] && command -v jq &>/dev/null; then
jq ".mcp += {$ENTRY}" "$CFG" > "$CFG.tmp" && mv "$CFG.tmp" "$CFG"
else
echo "{\"mcp\": {$ENTRY}}" > "$CFG"
fi
echo "✅ OpenCode: $CFG"
fi
echo ""
echo "Done. Restart your agent(s) and run: npx agentation-mcp server"
```
---
## 5. MCP Tools (Agent API)
| Tool | Parameters | Description |
|------|-----------|-------------|
| `agentation_list_sessions` | none | List all active annotation sessions |
| `agentation_get_session` | `sessionId: string` | Get session with all annotations |
| `agentation_get_pending` | `sessionId: string` | Get pending annotations for a session |
| `agentation_get_all_pending` | none | Get pending annotations across ALL sessions |
| `agentation_acknowledge` | `annotationId: string` | Mark annotation as acknowledged (agent is working on it) |
| `agentation_resolve` | `annotationId: string, summary?: string` | Mark as resolved with optional summary |
| `agentation_dismiss` | `annotationId: string, reason: string` | Dismiss with required reason |
| `agentation_reply` | `annotationId: string, message: string` | Add reply to annotation thread |
| `agentation_watch_annotations` | `sessionId?: string, batchWindowSeconds?: number (default 10, max 60), timeoutSeconds?: number (default 120, max 300)` | **Block until new annotations arrive** — core watch-loop tool |
---
## 6. Workflow Patterns
### Pattern 1: Copy-Paste (Simplest, No Server)
```
1. Human opens app in browser
2. Clicks agentation toolbar → activates
3. Clicks element → adds comment → clicks Copy
4. Pastes markdown output into agent chat
5. Agent receives CSS selectors, elementPath, boundingBox
6. Agent greps/edits code using selector
```
### Pattern 2: MCP Watch Loop (Recommended for iterative review)
```
Agent: agentation_watch_annotations (blocks up to 120s)
→ Human adds annotation in browser
→ Agent receives batch immediately
→ Agent: agentation_acknowledge(annotationId)
→ Agent makes code changes using elementPath as grep target
→ Agent: agentation_resolve(annotationId, "Changed button color to #3b82f6")
→ Agent: agentation_watch_annotations (loops again)
```
**CLAUDE.md / GEMINI.md / Codex developer_instructions — add for automated watch:**
```markdown
When I say "watch mode" or "agentation watch", call agentation_watch_annotations in a loop.
For each annotation received:
1. Call agentation_acknowledge(annotationId)
2. Use elementPath to locate the code: Grep(elementPath) or search codebase for CSS class
3. Make the minimal change described in the comment
4. Call agentation_resolve(annotationId, "<brief summary of what was changed>")
Continue watching until I say stop, or until timeout.
```
### Pattern 3: Platform-Specific Hook (Passive Injection)
The hook from Section 4 auto-appends pending annotations to every agent message — no "watch mode" needed. Works across all platforms.
### Pattern 4: Autonomous Self-Driving Critique
Two-agent setup for fully autonomous UI review cycles:
**Session 1 (Critic — uses `agent-browser`):**
```bash
# Start headed browser pointing at your dev server
agent-browser open http://localhost:3000
agent-browser snapshot -i
# Agent navigates, clicks elements via agentation toolbar, adds critique
# Annotations flow to agentation MCP server automatically
```
**Session 2 (Fixer — watches MCP):**
```
agentation_watch_annotations → receives critique → acknowledge → edit → resolve → loop
```
### Pattern 5: Webhook Integration
```tsx
<Agentation webhookUrl="https://your-server.com/webhook" />
# or env var:
# AGENTATION_WEBHOOK_URL=https://your-server.com/webhook
```
---
## 7. Annotation Type (Full Schema)
```typescript
type Annotation = {
// Core
id: string;
x: number; // % of viewport width (0-100)
y: number; // px from document top
comment: string; // User's feedback text
element: string; // Tag name: "button", "div", etc.
elementPath: string; // CSS selector: "body > main > button.cta" ← grep target
timestamp: number;
// Context
selectedText?: string;
boundingBox?: { x: number; y: number; width: number; height: number };
nearbyText?: string;
cssClasses?: string;
nearbyElements?: string;
computedStyles?: string;
fullPath?: string;
accessibility?: string;
reactComponents?: string; // "App > Dashboard > Button" ← component grep target
isMultiSelect?: boolean;
isFixed?: boolean;
// Lifecycle (server-synced)
sessionId?: string;
url?: string;
intent?: "fix" | "change" | "question" | "approve";
severity?: "blocking" | "important" | "suggestion";
status?: "pending" | "acknowledged" | "resolved" | "dismissed";
thread?: ThreadMessage[];
createdAt?: string;
updatedAt?: string;
resolvedAt?: string;
resolvedBy?: "human" | "agent";
};
```
**Annotation lifecycle:**
```
pending → acknowledged → resolved
↘ dismissed (requires reason)
```
---
## 8. HTTP REST API (port 4747)
```bash
# Sessions
POST /sessions # Create session
GET /sessions # List all sessions
GET /sessions/:id # Get session + annotations
# Annotations
POST /sessions/:id/annotations # Add annotation
GET /annotations/:id # Get annotation
PATCH /annotations/:id # Update annotation
DELETE /annotations/:id # Delete annotation
GET /sessions/:id/pending # Pending for session
GET /pending # ALL pending across sessions
# Events (SSE streaming)
GET /sessions/:id/events # Session stream
GET /events # Global stream (?domain=filter)
# Health
GET /health
GET /status
```
---
## 9. Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `AGENTATION_STORE` | `memory` or `sqlite` | `sqlite` |
| `AGENTATION_WEBHOOK_URL` | Single webhook URL | — |
| `AGENTATION_WEBHOOKS` | Comma-separated webhook URLs | — |
| `AGENTATION_EVENT_RETENTION_DAYS` | Days to keep events | `7` |
SQLite storage: `~/.agentation/store.db`
---
## 10. Programmatic Utilities
```typescript
import {
identifyElement, identifyAnimationElement,
getElementPath, getNearbyText, getElementClasses,
isInShadowDOM, getShadowHost, closestCrossingShadow,
loadAnnotations, saveAnnotations, getStorageKey,
type Annotation, type Session, type ThreadMessage,
} from 'agentation';
```
---
## 11. Platform Support Matrix
| Platform | Config File | MCP Key | Hook |
|----------|------------|---------|------|
| **Claude Code** | `~/.claude/claude_desktop_config.json` | `mcpServers` | `hooks.UserPromptSubmit` in `settings.json` |
| **Codex CLI** | `~/.codex/config.toml` | `[[mcp_servers]]` (TOML) | `developer_instructions` + `notify` |
| **Gemini CLI** | `~/.gemini/settings.json` | `mcpServers` | `hooks.AfterAgent` in `settings.json` |
| **OpenCode** | `~/.config/opencode/opencode.json` | `mcp` (`type: "local"`) | Skills system (no hook needed) |
| **Cursor / Windsurf** | `.cursor/mcp.json` / `.windsurf/mcp.json` | `mcpServers` | — |
---
## Best practices
1. Always gate `<Agentation>` with `NODE_ENV === 'development'` — never ship to production
2. Use MCP watch-loop over copy-paste for iterative cycles — eliminates context switching
3. Call `agentation_acknowledge` immediately when starting a fix — signals human
4. Include a `summary` in `agentation_resolve` — gives human traceability
5. Process `severity: "blocking"` annotations first in the watch loop
6. Use `elementPath` as the primary grep/search target in code — it's a valid CSS selector
7. Use `reactComponents` field when the codebase is React — matches component names directly
8. Add the appropriate hook for your platform (Section 4) for zero-friction passive injection
9. For autonomous self-driving, use `agent-browser` in headed mode with `agentation` mounted
---
## 12. jeo Integration (annotate keyword)
> agentation integrates as the **VERIFY_UI** phase of the jeo skill.
> This follows the same pattern as plannotator operating in `planui` / `ExitPlanMode`.
> `annotate` is the primary keyword. `agentui` is kept as a backward-compatible alias.
### How it works
```
plannotator (planui): agentation (annotate):
Write plan.md Mount <Agentation> in app UI
↓ blocking ↓ blocking
Run plannotator agentation_watch_annotations
↓ ↓
Approve/Feedback in UI Create annotation in UI
↓ ↓
Confirm approved:true annotation ack→fix→resolve
↓ ↓
Enter EXECUTE Next step or loop
```
### Trigger Keywords
| Keyword | Platform | Action |
|--------|----------|------|
| `annotate` | Claude Code | `agentation_watch_annotations` MCP blocking call |
| `annotate` | Codex | `ANNOTATE_READY` signal → `jeo-notify.py` HTTP polling |
| `annotate` | Gemini | GEMINI.md instruction: HTTP REST polling pattern |
| `/jeo-annotate` | OpenCode | opencode.json `mcp.agentation` + instructions |
| `agentui` *(deprecated)* | All platforms | Same behavior as above — backward-compatible alias |
| `UI review` | All platforms | Same as `annotate` |
### Using with jeo
```bash
# 1. agentation auto-registered when installing jeo
bash .agent-skills/jeo/scripts/install.sh --with-agentation
# Or full install:
bash .agent-skills/jeo/scripts/install.sh --all
# 2. Mount agentation component in app
# app/layout.tsx or pages/_app.tsx:
# <Agentation endpoint="http://localhost:4747" />
# 3. Start MCP server
npx agentation-mcp server
# 4. Enter annotate keyword in agent → watch loop starts (agentui also works as backward-compatible alias)
# Claude Code: direct MCP tool call
# Codex: output ANNOTATE_READY (or AGENTUI_READY) → notify hook auto-polls
# Gemini: GEMINI.md HTTP polling pattern
# OpenCode: /jeo-annotate slash command (or /jeo-agentui — deprecated)
```
### Separation from plannotator (Phase Guard)
plannotator and agentation use the same blocking loop pattern but **only operate in different phases**:
| Tool | Allowed phase | Hook Guard |
|------|-----------|------------|
| **plannotator** | `plan` only | `jeo-state.json` → `phase === "plan"` |
| **agentation** | `verify` / `verify_ui` only | `jeo-state.json` → `phase === "verify_ui"` |
Each platform's hook script checks the `phase` field in `jeo-state.json` to prevent execution in the wrong phase.
Without this guard, both tools could run simultaneously in Gemini's `AfterAgent` hook.
### Pre-flight Check
3-step check before entering VERIFY_UI:
1. **Server status**: `GET /health` — whether agentation-mcp server is running
2. **Session exists**: `GET /sessions` — whether `<Agentation>` component is mounted
3. **Pending annotations**: `GET /pending` — number of annotations to process
After passing, set `phase` in `jeo-state.json` to `"verify_ui"` and `agentation.active` to `true`.
### Loop Verification Test
```bash
# Run agentation watch loop integration test
bash .agent-skills/agentation/scripts/verify-loop.sh
# Quick test (skip error cases)
bash .agent-skills/agentation/scripts/verify-loop.sh --quick
```
4-step verification: Server Health → Annotation CRUD → ACK-RESOLVE Cycle → Error Cases
### Evaluation Flow (jeo VERIFY_UI phase)
```
jeo "<task>"
│
[1] PLAN (plannotator loop) ← approve plan.md
[2] EXECUTE (team/bmad)
[3] VERIFY
├─ agent-browser snapshot
├─ Pre-flight check (server + session + pending)
└─ annotate → VERIFY_UI (agentation loop) ← this phase (agentui also backward-compatible)
├─ ACK → FIND → FIX → RESOLVE
├─ RE-SNAPSHOT (agent-browser) ← re-check after fix
└─ update agentation fields in jeo-state.json
[4] CLEANUP
```
> For detailed jeo integration: see [jeo SKILL.md](../jeo/SKILL.md) Section 3.3.1 detailed workflow
## References
- [agentation repo](https://github.com/benjitaylor/agentation)
- [agentation npm](https://www.npmjs.com/package/agentation)
- [agentation-mcp npm](https://www.npmjs.com/package/agentation-mcp)
- [Gemini CLI MCP docs](https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md)
- [agent-browser skill](../agent-browser/SKILL.md)
## Metadata
- Version: 1.1.0
- Source: benjitaylor/agentation (PolyForm Shield 1.0.0)
- Packages: `[email protected]`, `[email protected]`
- Last updated: 2026-03-05
- Scope: UI annotation bridge for human-agent feedback loops — Claude Code, Codex, Gemini CLI, OpenCode
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### ../jeo/SKILL.md
```markdown
---
name: jeo
description: "JEO — Integrated AI agent orchestration skill. Plan with ralph+plannotator, execute with team/bmad, verify browser behavior with agent-browser, apply UI feedback with agentation(annotate), auto-cleanup worktrees after completion. Supports Claude, Codex, Gemini CLI, and OpenCode. Install: ralph, omc, omx, ohmg, bmad, plannotator, agent-browser, agentation."
compatibility: "Requires git, node>=18, bash. Optional: bun, docker."
allowed-tools: Read Write Bash Grep Glob Task
metadata:
tags: jeo, orchestration, ralph, plannotator, agentation, annotate, agentui, UI-review, team, bmad, omc, omx, ohmg, agent-browser, multi-agent, workflow, worktree-cleanup, browser-verification, ui-feedback
platforms: Claude, Codex, Gemini, OpenCode
keyword: jeo
version: 1.3.1
source: supercent-io/skills-template
---
# JEO — Integrated Agent Orchestration
> Keyword: `jeo` · `annotate` · `UI-review` · `agentui (deprecated)` | Platforms: Claude Code · Codex CLI · Gemini CLI · OpenCode
>
> A unified skill providing fully automated orchestration flow:
> Plan (ralph+plannotator) → Execute (team/bmad) → UI Feedback (agentation/annotate) → Cleanup (worktree cleanup)
## Control Layers
JEO uses one cross-platform abstraction for orchestration:
- `settings`: platform/runtime configuration such as Claude hooks, Codex `config.toml`, Gemini `settings.json`, MCP registration, and prompt parameters
- `rules`: policy constraints that must hold on every platform
- `hooks`: event callbacks that enforce those rules on each platform
The key JEO rules are:
- do not reopen the PLAN gate when the current plan hash already has a terminal result
- only a revised plan resets `plan_gate_status` to `pending`
- do not process agentation annotations before explicit submit/onSubmit opens the submit gate
The authoritative state is `.omc/state/jeo-state.json`. Hooks may help advance the workflow, but they must obey the state file.
---
## 0. Agent Execution Protocol (follow immediately upon `jeo` keyword detection)
> The following are commands, not descriptions. Execute them in order. Each step only proceeds after the previous one completes.
### STEP 0: State File Bootstrap (required — always first)
```bash
mkdir -p .omc/state .omc/plans .omc/logs
```
If `.omc/state/jeo-state.json` does not exist, create it:
<!-- NOTE: The `worktrees` array was removed from the initial schema as it is not yet implemented.
Add it back when multi-worktree parallel execution tracking is needed.
worktree-cleanup.sh queries git worktree list directly, so it works without this field. -->
```json
{
"phase": "plan",
"task": "<detected task>",
"plan_approved": false,
"plan_gate_status": "pending",
"plan_current_hash": null,
"last_reviewed_plan_hash": null,
"last_reviewed_plan_at": null,
"plan_review_method": null,
"team_available": null,
"retry_count": 0,
"last_error": null,
"checkpoint": null,
"created_at": "<ISO 8601>",
"updated_at": "<ISO 8601>",
"agentation": {
"active": false,
"session_id": null,
"keyword_used": null,
"submit_gate_status": "idle",
"submit_signal": null,
"submit_received_at": null,
"submitted_annotation_count": 0,
"started_at": null,
"timeout_seconds": 120,
"annotations": { "total": 0, "acknowledged": 0, "resolved": 0, "dismissed": 0, "pending": 0 },
"completed_at": null,
"exit_reason": null
}
}
```
Notify the user:
> "JEO activated. Phase: PLAN. Add the `annotate` keyword if a UI feedback loop is needed."
---
### STEP 0.1: Error Recovery Protocol (applies to all STEPs)
**Checkpoint recording — immediately after entering each STEP:**
```python
# Execute immediately at the start of each STEP (agent updates jeo-state.json directly)
python3 -c "
import json, datetime, os, subprocess, tempfile
try:
root = subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], stderr=subprocess.DEVNULL).decode().strip()
except:
root = os.getcwd()
f = os.path.join(root, '.omc/state/jeo-state.json')
if os.path.exists(f):
import fcntl
with open(f, 'r+') as fh:
fcntl.flock(fh, fcntl.LOCK_EX)
try:
d = json.load(fh)
d['checkpoint']='<current_phase>' # 'plan'|'execute'|'verify'|'cleanup'
d['updated_at']=datetime.datetime.utcnow().isoformat()+'Z'
fh.seek(0)
json.dump(d, fh, ensure_ascii=False, indent=2)
fh.truncate()
finally:
fcntl.flock(fh, fcntl.LOCK_UN)
" 2>/dev/null || true
```
**last_error recording — on pre-flight failure or exception:**
```python
python3 -c "
import json, datetime, os, subprocess, fcntl
try:
root = subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], stderr=subprocess.DEVNULL).decode().strip()
except:
root = os.getcwd()
f = os.path.join(root, '.omc/state/jeo-state.json')
if os.path.exists(f):
with open(f, 'r+') as fh:
fcntl.flock(fh, fcntl.LOCK_EX)
try:
d = json.load(fh)
d['last_error']='<error message>'
d['retry_count']=d.get('retry_count',0)+1
d['updated_at']=datetime.datetime.utcnow().isoformat()+'Z'
fh.seek(0)
json.dump(d, fh, ensure_ascii=False, indent=2)
fh.truncate()
finally:
fcntl.flock(fh, fcntl.LOCK_UN)
" 2>/dev/null || true
```
**Checkpoint-based resume on restart:**
```python
# If jeo-state.json already exists, resume from checkpoint
python3 -c "
import json, os, subprocess
try:
root = subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], stderr=subprocess.DEVNULL).decode().strip()
except:
root = os.getcwd()
f = os.path.join(root, '.omc/state/jeo-state.json')
if os.path.exists(f):
d=json.load(open(f))
cp=d.get('checkpoint')
err=d.get('last_error')
if err: print(f'Previous error: {err}')
if cp: print(f'Resuming from: {cp}')
" 2>/dev/null || true
```
> **Rule**: Before `exit 1` in pre-flight, always update `last_error` and increment `retry_count`.
> If `retry_count >= 3`, ask the user whether to abort.
---
### STEP 1: PLAN (never skip)
**Pre-flight (required before entering):**
```bash
# Record checkpoint
python3 -c "
import json,datetime,os,subprocess,fcntl,tempfile
try:
root=subprocess.check_output(['git','rev-parse','--show-toplevel'],stderr=subprocess.DEVNULL).decode().strip()
except:
root=os.getcwd()
f=os.path.join(root,'.omc/state/jeo-state.json')
if os.path.exists(f):
with open(f,'r+') as fh:
fcntl.flock(fh,fcntl.LOCK_EX)
try:
d=json.load(fh)
d.update({'checkpoint':'plan','updated_at':datetime.datetime.utcnow().isoformat()+'Z'})
fh.seek(0); json.dump(d,fh,ensure_ascii=False,indent=2); fh.truncate()
finally:
fcntl.flock(fh,fcntl.LOCK_UN)
" 2>/dev/null || true
# NOTE: Claude Code — skip this entire bash block.
# plannotator is a hook-only binary; calling it directly always fails.
# For Claude Code: call EnterPlanMode → write plan → call ExitPlanMode.
# The ExitPlanMode PermissionRequest hook fires plannotator automatically.
# The following script is for Codex / Gemini / OpenCode only.
# GUARD: enforce no-repeat PLAN review by plan hash.
# same hash + terminal gate status => skip reopening the plan gate
# revised plan.md content => reset gate to pending and review again
PLAN_GATE_STATUS=$(python3 -c "
import json, os
try:
s = json.load(open('.omc/state/jeo-state.json'))
print(s.get('plan_gate_status', 'pending'))
except Exception:
print('pending')
" 2>/dev/null || echo "pending")
HASH_MATCH=$(python3 -c "
import hashlib, json, os
try:
s = json.load(open('.omc/state/jeo-state.json'))
if not os.path.exists('plan.md'):
print('no-match')
else:
current_hash = hashlib.sha256(open('plan.md', 'rb').read()).hexdigest()
print('match' if current_hash == (s.get('last_reviewed_plan_hash') or '') else 'no-match')
except Exception:
print('no-match')
" 2>/dev/null || echo "no-match")
if [[ "$HASH_MATCH" == "match" && "$PLAN_GATE_STATUS" =~ ^(approved|manual_approved)$ ]]; then
echo "✅ Current plan hash already approved. Skipping re-review."
exit 0
fi
# BUG FIX (v1.3.1): feedback_required + same hash must NOT exit 0 (approved).
# The plan has outstanding feedback and must be revised before re-opening plannotator.
# Exiting 0 here would cause JEO to proceed to EXECUTE with an unapproved plan.
if [[ "$HASH_MATCH" == "match" && "$PLAN_GATE_STATUS" == "feedback_required" ]]; then
echo "❌ Feedback pending for this plan hash — revise plan.md content (new hash required) before re-opening plannotator."
echo " Check .omc/state/jeo-state.json → plannotator_feedback for the recorded feedback."
exit 1
fi
if [[ "$HASH_MATCH" == "match" && "$PLAN_GATE_STATUS" == "infrastructure_blocked" ]]; then
echo "⚠️ Infrastructure blocked for current plan hash. Use manual TTY gate or set plan_gate_status='manual_approved' in jeo-state.json."
exit 32
fi
# plannotator is mandatory for the PLAN step (Codex/Gemini/OpenCode).
# If missing, JEO auto-installs it before opening the PLAN gate.
# Resolve the JEO scripts directory (works from any CWD)
_JEO_SCRIPTS=""
for _candidate in \
"${JEO_SKILL_DIR:-}/scripts" \
"$HOME/.agent-skills/jeo/scripts" \
"$HOME/.codex/skills/jeo/scripts" \
"$(pwd)/.agent-skills/jeo/scripts" \
"scripts" \
; do
if [ -f "${_candidate}/plannotator-plan-loop.sh" ]; then
_JEO_SCRIPTS="$_candidate"
break
fi
done
if [ -z "$_JEO_SCRIPTS" ]; then
echo "❌ JEO scripts not found. Re-run: bash setup-codex.sh (or setup-gemini.sh)"
exit 1
fi
if ! bash "${_JEO_SCRIPTS}/ensure-plannotator.sh"; then
echo "❌ plannotator auto-install failed: cannot proceed with PLAN step."
echo " Retry: bash ${_JEO_SCRIPTS}/../scripts/install.sh --with-plannotator"
exit 1
fi
# Required PLAN gate (Codex / Gemini / OpenCode):
# - Must wait until approve/feedback is received
# - Auto-restart on session exit (up to 3 times)
# - After 3 exits, ask user whether to end PLAN
FEEDBACK_DIR=$(python3 -c "import hashlib,os; h=hashlib.md5(os.getcwd().encode()).hexdigest()[:8]; d=f'/tmp/jeo-{h}'; os.makedirs(d,exist_ok=True); print(d)" 2>/dev/null || echo '/tmp')
FEEDBACK_FILE="${FEEDBACK_DIR}/plannotator_feedback.txt"
bash "${_JEO_SCRIPTS}/plannotator-plan-loop.sh" plan.md "$FEEDBACK_FILE" 3
PLAN_RC=$?
if [ "$PLAN_RC" -eq 0 ]; then
echo "✅ Plan approved"
elif [ "$PLAN_RC" -eq 10 ]; then
echo "❌ Plan not approved — apply feedback, revise plan.md, and retry"
exit 1
elif [ "$PLAN_RC" -eq 32 ]; then
echo "⚠️ plannotator UI unavailable (sandbox/CI). Entering Conversation Approval Mode:"
echo " 1. Output plan.md content to user in conversation"
echo " 2. Ask user: 'approve' to proceed or provide feedback"
echo " 3. DO NOT proceed to EXECUTE until user explicitly approves"
exit 32
elif [ "$PLAN_RC" -eq 30 ] || [ "$PLAN_RC" -eq 31 ]; then
echo "⛔ PLAN exit decision (or awaiting confirmation). Confirm with user before retrying."
exit 1
else
echo "❌ plannotator PLAN gate failed (code=$PLAN_RC)"
exit 1
fi
mkdir -p .omc/plans .omc/logs
```
1. Write `plan.md` (include goal, steps, risks, and completion criteria)
2. **Invoke plannotator** (per platform):
- **Claude Code (hook mode — only supported method)**:
`plannotator` is a hook-only binary. It cannot be called via MCP tool or CLI directly.
Call `EnterPlanMode`, write the plan content in plan mode, then call `ExitPlanMode`.
The `ExitPlanMode` PermissionRequest hook fires the JEO Claude plan-gate wrapper automatically.
That wrapper must skip re-entry when the current plan hash already has a terminal review result.
Wait for the hook to return before proceeding — approved or feedback will arrive via the hook result.
- **Codex / Gemini / OpenCode**: run blocking CLI (never use `&`):
```bash
# _JEO_SCRIPTS must be resolved first via the dynamic path discovery block in the pre-flight above
bash "${_JEO_SCRIPTS}/plannotator-plan-loop.sh" plan.md /tmp/plannotator_feedback.txt 3
```
If `plannotator` is missing, JEO must auto-run `bash "${_JEO_SCRIPTS}/ensure-plannotator.sh"` first and continue only after the CLI is available.
3. Check result:
- `approved: true` (Claude Code: hook returns approved) → update `jeo-state.json` `phase` to `"execute"` and `plan_approved` to `true` → **enter STEP 2**
- Not approved (Claude Code: hook returns feedback; others: `exit 10`) → read feedback, revise `plan.md` → repeat step 2
- Infrastructure blocked (`exit 32`) → localhost bind unavailable (e.g., sandbox/CI). Use manual gate in TTY; confirm with user and retry outside sandbox in non-TTY
- Session exited 3 times (`exit 30/31`) → ask user whether to end PLAN and decide to abort or resume
**NEVER: enter EXECUTE without `approved: true`. NEVER: run with `&` background.**
**NEVER: reopen the same unchanged plan after `approved` or `manual_approved` — it is already approved.**
**NEVER: treat `feedback_required` as approved — it means the plan was REJECTED and must be revised.**
**When `feedback_required` + same hash → exit 1 (not 0), revise plan content to get a new hash, then re-open plannotator.**
---
### STEP 2: EXECUTE
**Pre-flight (auto-detect team availability):**
```bash
# Record checkpoint
python3 -c "
import json,datetime,os,subprocess,fcntl
try:
root=subprocess.check_output(['git','rev-parse','--show-toplevel'],stderr=subprocess.DEVNULL).decode().strip()
except:
root=os.getcwd()
f=os.path.join(root,'.omc/state/jeo-state.json')
if os.path.exists(f):
with open(f,'r+') as fh:
fcntl.flock(fh,fcntl.LOCK_EX)
try:
d=json.load(fh)
d.update({'checkpoint':'execute','updated_at':datetime.datetime.utcnow().isoformat()+'Z'})
fh.seek(0); json.dump(d,fh,ensure_ascii=False,indent=2); fh.truncate()
finally:
fcntl.flock(fh,fcntl.LOCK_UN)
" 2>/dev/null || true
TEAM_AVAILABLE=false
if [[ "${CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS:-}" =~ ^(1|true|True|yes|YES)$ ]]; then
TEAM_AVAILABLE=true
elif python3 -c "
import json, os, sys
try:
s = json.load(open(os.path.expanduser('~/.claude/settings.json')))
val = s.get('env', {}).get('CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS', '')
sys.exit(0 if str(val) in ('1', 'true', 'True', 'yes') else 1)
except Exception:
sys.exit(1)
" 2>/dev/null; then
TEAM_AVAILABLE=true
fi
export TEAM_AVAILABLE_BOOL="$TEAM_AVAILABLE"
python3 -c "
import json,os,subprocess,fcntl
try:
root=subprocess.check_output(['git','rev-parse','--show-toplevel'],stderr=subprocess.DEVNULL).decode().strip()
except:
root=os.getcwd()
f=os.path.join(root,'.omc/state/jeo-state.json')
if os.path.exists(f):
with open(f,'r+') as fh:
fcntl.flock(fh,fcntl.LOCK_EX)
try:
d=json.load(fh)
d['team_available']=os.environ.get('TEAM_AVAILABLE_BOOL','false').lower()=='true'
fh.seek(0); json.dump(d,fh,ensure_ascii=False,indent=2); fh.truncate()
finally:
fcntl.flock(fh,fcntl.LOCK_UN)
" 2>/dev/null || true
```
1. Update `jeo-state.json` `phase` to `"execute"`
2. **Team available (Claude Code + omc)**:
```
/omc:team 3:executor "<task>"
```
3. **Claude Code but no team**:
```
echo "❌ JEO requires Claude Code team mode. Re-run bash scripts/setup-claude.sh, restart Claude Code, and confirm CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1."
exit 1
```
Never fall back to single-agent execution in Claude Code.
4. **No omc (BMAD fallback — Codex / Gemini / OpenCode only)**:
```
/workflow-init # Initialize BMAD
/workflow-status # Check current step
```
---
### STEP 3: VERIFY
1. Update `jeo-state.json` `phase` to `"verify"`
2. **Basic verification with agent-browser** (when browser UI is present):
```bash
agent-browser snapshot http://localhost:3000
```
3. `annotate` keyword detected → **enter STEP 3.1**
4. Otherwise → **enter STEP 4**
---
### STEP 3.1: VERIFY_UI (only when `annotate` keyword is detected)
1. Pre-flight check (required before entering):
```bash
# Auto-start server, auto-install package, auto-inject component (see §3.3.1 for full script)
bash scripts/ensure-agentation.sh --project-dir "${PROJECT_DIR:-$PWD}" --endpoint "http://localhost:4747" || true
# If agentation-mcp server is still not healthy after pre-flight → graceful skip to STEP 4
if ! curl -sf --connect-timeout 2 http://localhost:4747/health >/dev/null 2>&1; then
python3 -c "
import json,os,subprocess,fcntl,time
try:
root=subprocess.check_output(['git','rev-parse','--show-toplevel'],stderr=subprocess.DEVNULL).decode().strip()
except:
root=os.getcwd()
f=os.path.join(root,'.omc/state/jeo-state.json')
if os.path.exists(f):
with open(f,'r+') as fh:
fcntl.flock(fh,fcntl.LOCK_EX)
try:
d=json.load(fh)
d['last_error']='agentation-mcp not running; VERIFY_UI skipped'
d['updated_at']=time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime())
fh.seek(0); json.dump(d,fh,ensure_ascii=False,indent=2); fh.truncate()
finally:
fcntl.flock(fh,fcntl.LOCK_UN)
" 2>/dev/null || true
# Proceed to STEP 4 CLEANUP (no exit 1 — graceful skip)
fi
```
2. Update `jeo-state.json`: `phase = "verify_ui"`, `agentation.active = true`, `agentation.submit_gate_status = "waiting_for_submit"`
3. Wait for explicit human submit:
- **Claude Code**: wait for `UserPromptSubmit` after the user presses **Send Annotations** / `onSubmit`
- **Codex / Gemini / OpenCode**: wait until the human confirms submission and the agent emits `ANNOTATE_READY` (or compatibility alias `AGENTUI_READY`)
4. Before that submit signal arrives, do not read `/pending`, do not acknowledge annotations, and do not start the fix loop
5. After submit arrives, switch `agentation.submit_gate_status = "submitted"` and record `submit_signal`, `submit_received_at`, and `submitted_annotation_count`
6. **Claude Code (MCP)**: blocking call to `agentation_watch_annotations` (`batchWindowSeconds:10`, `timeoutSeconds:120`)
7. **Codex / Gemini / OpenCode (HTTP)**: polling loop via `GET http://localhost:4747/pending`
8. Process each annotation: `acknowledge` → navigate code via `elementPath` → apply fix → `resolve`
9. `count=0` or timeout → reset the submit gate or finish the sub-phase → **enter STEP 4**
**NEVER: process draft annotations before submit/onSubmit.**
---
### STEP 4: CLEANUP
**Pre-flight (check before entering):**
```bash
# Record checkpoint
python3 -c "
import json,datetime,os,subprocess,fcntl
try:
root=subprocess.check_output(['git','rev-parse','--show-toplevel'],stderr=subprocess.DEVNULL).decode().strip()
except:
root=os.getcwd()
f=os.path.join(root,'.omc/state/jeo-state.json')
if os.path.exists(f):
with open(f,'r+') as fh:
fcntl.flock(fh,fcntl.LOCK_EX)
try:
d=json.load(fh)
d.update({'checkpoint':'cleanup','updated_at':datetime.datetime.utcnow().isoformat()+'Z'})
fh.seek(0); json.dump(d,fh,ensure_ascii=False,indent=2); fh.truncate()
finally:
fcntl.flock(fh,fcntl.LOCK_UN)
" 2>/dev/null || true
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "⚠️ Not a git repository — skipping worktree cleanup"
else
UNCOMMITTED=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
[[ "$UNCOMMITTED" -gt 0 ]] && echo "⚠️ ${UNCOMMITTED} uncommitted change(s) — recommend commit/stash before cleanup"
fi
```
1. Update `jeo-state.json` `phase` to `"cleanup"`
2. Worktree cleanup:
```bash
bash scripts/worktree-cleanup.sh || git worktree prune
```
3. Update `jeo-state.json` `phase` to `"done"`
---
## 1. Quick Start
> **Source of truth**: `https://github.com/supercent-io/skills-template`
> Local paths like `~/.claude/skills/jeo/` are copies installed via `npx skills add`.
> To update to the latest version, reinstall using the command below.
```bash
# Install JEO (npx skills add — recommended)
npx skills add https://github.com/supercent-io/skills-template --skill jeo
# Full install (all AI tools + all components)
bash scripts/install.sh --all
# Check status
bash scripts/check-status.sh
# Individual AI tool setup
bash scripts/setup-claude.sh # Claude Code plugin + hooks
bash scripts/setup-codex.sh # Codex CLI developer_instructions
bash scripts/setup-gemini.sh # Gemini CLI hooks + GEMINI.md
bash scripts/setup-opencode.sh # OpenCode plugin registration
```
---
## 2. Installed Components
Tools that JEO installs and configures:
| Tool | Description | Install Command |
|------|------|-----------|
| **omc** (oh-my-claudecode) | Claude Code multi-agent orchestration | `/plugin marketplace add https://github.com/Yeachan-Heo/oh-my-claudecode` |
| **omx** | Multi-agent orchestration for OpenCode | `bunx oh-my-opencode setup` |
| **ohmg** | Multi-agent framework for Gemini CLI | `bunx oh-my-ag` |
| **bmad** | BMAD workflow orchestration | Included in skills |
| **ralph** | Self-referential completion loop | Included in omc or install separately |
| **plannotator** | Visual plan/diff review | Auto-installed during PLAN via `bash scripts/ensure-plannotator.sh` (or preinstall with `bash scripts/install.sh --with-plannotator`) |
| **agentation** | UI annotation → agent code fix integration (`annotate` keyword, `agentui` compatibility maintained) | `bash scripts/install.sh --with-agentation` |
| **agent-browser** | Headless browser for AI agents — **primary tool for browser behavior verification** | `npm install -g agent-browser` |
| **playwriter** | Playwright-based browser automation (optional) | `npm install -g playwriter` |
---
## 3. JEO Workflow
### Full Flow
```
jeo "<task>"
│
▼
[1] PLAN (ralph + plannotator)
Draft plan with ralph → visual review with plannotator → Approve/Feedback
│
▼
[2] EXECUTE
├─ team available? → /omc:team N:executor "<task>"
│ staged pipeline: plan→prd→exec→verify→fix
└─ no team? → /bmad /workflow-init → run BMAD steps
│
▼
[3] VERIFY (agent-browser — default behavior)
Verify browser behavior with agent-browser
→ capture snapshot → confirm UI/functionality is working
│
├─ with annotate keyword → [3.3.1] VERIFY_UI (agentation watch loop)
│ agentation_watch_annotations blocking → annotation ack→fix→resolve loop
│
▼
[4] CLEANUP
After all work is done → bash scripts/worktree-cleanup.sh
git worktree prune
```
### 3.1 PLAN Step (ralph + plannotator)
> **Platform note**: The `/ralph` slash command is only available in Claude Code (omc).
> Use the "alternative method" below for Codex/Gemini/OpenCode.
**Claude Code (omc):**
```bash
/ralph "jeo-plan: <task>" --completion-promise="PLAN_APPROVED" --max-iterations=5
```
**Codex / Gemini / OpenCode (alternative):**
```bash
# Session-isolated feedback directory (prevents concurrent run conflicts)
FEEDBACK_DIR=$(python3 -c "import hashlib,os; h=hashlib.md5(os.getcwd().encode()).hexdigest()[:8]; d=f'/tmp/jeo-{h}'; os.makedirs(d,exist_ok=True); print(d)" 2>/dev/null || echo '/tmp')
FEEDBACK_FILE="${FEEDBACK_DIR}/plannotator_feedback.txt"
# 1. Write plan.md directly, then review with plannotator (blocking — no &)
PLANNOTATOR_RUNTIME_HOME="${FEEDBACK_DIR}/.plannotator"
mkdir -p "$PLANNOTATOR_RUNTIME_HOME"
touch /tmp/jeo-plannotator-direct.lock && python3 -c "
import json
print(json.dumps({'tool_input': {'plan': open('plan.md').read(), 'permission_mode': 'acceptEdits'}}))
" | env HOME="$PLANNOTATOR_RUNTIME_HOME" PLANNOTATOR_HOME="$PLANNOTATOR_RUNTIME_HOME" plannotator > "$FEEDBACK_FILE" 2>&1
# ↑ Run without &: waits until user clicks Approve/Send Feedback in browser
# 2. Check result and branch
if python3 -c "
import json, sys
try:
d = json.load(open('$FEEDBACK_FILE'))
sys.exit(0 if d.get('approved') is True else 1)
except Exception:
sys.exit(1)
" 2>/dev/null; then
echo "PLAN_APPROVED" # → enter EXECUTE step
else
echo "PLAN_FEEDBACK" # → read \"$FEEDBACK_FILE\", replan, repeat above
fi
```
> **Important**: Do not run with `&` (background). Must run blocking to receive user feedback.
Common flow:
- Generate plan document (`plan.md`)
- Run plannotator blocking → browser UI opens automatically
- Review plan in browser → Approve or Send Feedback
- Approve (`"approved":true`) → enter [2] EXECUTE step
- Feedback → read `/tmp/plannotator_feedback.txt` annotations and replan (loop)
- **exit 32 (sandbox/CI — Conversation Approval Mode)**:
1. Output full `plan.md` content to user
2. Ask: "⚠️ plannotator UI unavailable. Reply 'approve' to proceed or provide feedback."
3. **WAIT for user response — do NOT proceed to EXECUTE**
4. On approval → update `jeo-state.json` `plan_approved=true, plan_gate_status="manual_approved"` → EXECUTE
5. On feedback → revise `plan.md`, retry loop, repeat
**Claude Code manual run:**
```
Shift+Tab×2 → enter plan mode → plannotator runs automatically when plan is complete
```
### 3.2 EXECUTE Step
**When team is available (Claude Code + omc):**
```bash
/omc:team 3:executor "jeo-exec: <task based on approved plan>"
```
- staged pipeline: team-plan → team-prd → team-exec → team-verify → team-fix
- Maximize speed with parallel agent execution
**When Claude Code team mode is unavailable:**
```bash
echo "❌ JEO requires /omc:team in Claude Code. Run bash scripts/setup-claude.sh, restart Claude Code, then retry."
exit 1
```
- Do not degrade to single-agent mode
**When team is unavailable (BMAD fallback — Codex / Gemini / OpenCode):**
```bash
/workflow-init # Initialize BMAD workflow
/workflow-status # Check current step
```
- Proceed in order: Analysis → Planning → Solutioning → Implementation
- Review documents with plannotator after each step completes
### 3.3 VERIFY Step (agent-browser — default behavior)
When browser-based functionality is present, verify behavior with `agent-browser`.
```bash
# Capture snapshot from the URL where the app is running
agent-browser snapshot http://localhost:3000
# Check specific elements (accessibility tree ref method)
agent-browser snapshot http://localhost:3000 -i
# → check element state using @eN ref numbers
# Save screenshot
agent-browser screenshot http://localhost:3000 -o verify.png
```
> **Default behavior**: Automatically runs the agent-browser verification step when browser-related work is complete.
> Backend/CLI tasks without a browser UI skip this step.
### 3.3.1 VERIFY_UI Step (annotate — agentation watch loop)
Runs the agentation watch loop when the `annotate` keyword is detected. (The `agentui` keyword is also supported for backward compatibility.)
This follows the same pattern as plannotator operating in `planui` / `ExitPlanMode`.
**Prerequisites (auto-resolved by pre-flight):**
1. `npx agentation-mcp server` (HTTP :4747) is running — **auto-started if not running**
2. `agentation` npm package installed in the project — **auto-installed if absent**
3. `<Agentation endpoint="http://localhost:4747" />` is mounted in the app — **auto-injected into entry point if absent**
**Pre-flight Check (required before entering — common to all platforms):**
```bash
# ── Step 1: Auto-start agentation-mcp server if not running ─────────────────
JEO_AGENTATION_ENDPOINT="${JEO_AGENTATION_ENDPOINT:-http://localhost:4747}"
JEO_AGENTATION_PORT="${JEO_AGENTATION_PORT:-4747}"
if ! curl -sf --connect-timeout 2 "${JEO_AGENTATION_ENDPOINT}/health" >/dev/null 2>&1; then
echo "[JEO][ANNOTATE] agentation-mcp not running — attempting auto-start on port ${JEO_AGENTATION_PORT}..."
if command -v npx >/dev/null 2>&1; then
npx -y agentation-mcp server --port "${JEO_AGENTATION_PORT}" >/tmp/agentation-mcp.log 2>&1 &
AGENTATION_MCP_PID=$!
echo "[JEO][ANNOTATE] started agentation-mcp (PID ${AGENTATION_MCP_PID})"
# Wait up to 8 seconds for server to become healthy
for i in $(seq 1 8); do
sleep 1
if curl -sf --connect-timeout 1 "${JEO_AGENTATION_ENDPOINT}/health" >/dev/null 2>&1; then
echo "[JEO][ANNOTATE] ✅ agentation-mcp server ready"
break
fi
done
if ! curl -sf --connect-timeout 2 "${JEO_AGENTATION_ENDPOINT}/health" >/dev/null 2>&1; then
echo "[JEO][ANNOTATE] ⚠️ agentation-mcp failed to start — skipping VERIFY_UI"
python3 -c "
import json,os,subprocess,fcntl,time
try:
root=subprocess.check_output(['git','rev-parse','--show-toplevel'],stderr=subprocess.DEVNULL).decode().strip()
except:
root=os.getcwd()
f=os.path.join(root,'.omc/state/jeo-state.json')
if os.path.exists(f):
with open(f,'r+') as fh:
fcntl.flock(fh,fcntl.LOCK_EX)
try:
d=json.load(fh)
d['last_error']='agentation-mcp failed to start; VERIFY_UI skipped'
d['updated_at']=time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime())
fh.seek(0); json.dump(d,fh,ensure_ascii=False,indent=2); fh.truncate()
finally:
fcntl.flock(fh,fcntl.LOCK_UN)
" 2>/dev/null || true
# Proceed to STEP 4 CLEANUP (no exit 1 — graceful skip)
fi
else
echo "[JEO][ANNOTATE] ⚠️ npx not found — cannot auto-start agentation-mcp. Skipping VERIFY_UI."
python3 -c "
import json,os,subprocess,fcntl,time
try:
root=subprocess.check_output(['git','rev-parse','--show-toplevel'],stderr=subprocess.DEVNULL).decode().strip()
except:
root=os.getcwd()
f=os.path.join(root,'.omc/state/jeo-state.json')
if os.path.exists(f):
with open(f,'r+') as fh:
fcntl.flock(fh,fcntl.LOCK_EX)
try:
d=json.load(fh)
d['last_error']='npx not found; agentation-mcp auto-start skipped; VERIFY_UI skipped'
d['updated_at']=time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime())
fh.seek(0); json.dump(d,fh,ensure_ascii=False,indent=2); fh.truncate()
finally:
fcntl.flock(fh,fcntl.LOCK_UN)
" 2>/dev/null || true
# Proceed to STEP 4 CLEANUP (no exit 1 — graceful skip)
fi
fi
# ── Step 2: Auto-install agentation package + inject <Agentation> if needed ──
# Only runs when server is confirmed healthy
if curl -sf --connect-timeout 2 "${JEO_AGENTATION_ENDPOINT}/health" >/dev/null 2>&1; then
SESSIONS=$(curl -sf "${JEO_AGENTATION_ENDPOINT}/sessions" 2>/dev/null || echo "[]")
S_COUNT=$(echo "$SESSIONS" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo 0)
if [ "$S_COUNT" -eq 0 ]; then
echo "[JEO][ANNOTATE] No active sessions — running ensure-agentation.sh to install package and inject component..."
# Locate ensure-agentation.sh (try common installation paths)
ENSURE_SCRIPT=""
for candidate in \
"$(dirname "${BASH_SOURCE[0]:-$0}")/ensure-agentation.sh" \
"$HOME/.claude/skills/jeo/scripts/ensure-agentation.sh" \
"$HOME/.agent-skills/jeo/scripts/ensure-agentation.sh" \
"$HOME/.codex/skills/jeo/scripts/ensure-agentation.sh"; do
if [[ -f "$candidate" ]]; then
ENSURE_SCRIPT="$candidate"
break
fi
done
if [[ -n "$ENSURE_SCRIPT" ]]; then
ENSURE_EXIT=0
bash "$ENSURE_SCRIPT" \
--project-dir "${PROJECT_DIR:-$PWD}" \
--endpoint "${JEO_AGENTATION_ENDPOINT}" || ENSURE_EXIT=$?
if [ "$ENSURE_EXIT" -eq 0 ]; then
echo "[JEO][ANNOTATE] ensure-agentation completed — waiting up to 15s for browser to reconnect..."
# Wait for dev server hot-reload and browser reconnection
for i in $(seq 1 15); do
sleep 1
NEW_S_COUNT=$(curl -sf "${JEO_AGENTATION_ENDPOINT}/sessions" 2>/dev/null | \
python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo 0)
if [ "$NEW_S_COUNT" -gt 0 ]; then
echo "[JEO][ANNOTATE] ✅ Browser session established (${NEW_S_COUNT} session(s))"
S_COUNT=$NEW_S_COUNT
break
fi
done
if [ "$S_COUNT" -eq 0 ]; then
echo "[JEO][ANNOTATE] ⚠️ Component injected but no browser session yet."
echo " → Refresh the browser at your app URL, then re-run annotate."
fi
elif [ "$ENSURE_EXIT" -eq 2 ]; then
echo "[JEO][ANNOTATE] ℹ️ Not a Node.js project — mount <Agentation endpoint='${JEO_AGENTATION_ENDPOINT}' /> manually."
else
echo "[JEO][ANNOTATE] ⚠️ ensure-agentation.sh failed (exit $ENSURE_EXIT) — mount <Agentation endpoint='${JEO_AGENTATION_ENDPOINT}' /> manually."
fi
else
echo "[JEO][ANNOTATE] ⚠️ ensure-agentation.sh not found — mount <Agentation endpoint='${JEO_AGENTATION_ENDPOINT}' /> manually."
fi
fi
echo "[JEO][ANNOTATE] ✅ agentation ready — server OK, ${S_COUNT} session(s)"
fi
```
> After passing pre-flight (`else` branch), update jeo-state.json `phase` to `"verify_ui"`, set `agentation.active` to `true`, and set `agentation.submit_gate_status` to `"waiting_for_submit"`.
> Do not call `/pending` yet. Draft annotations are not actionable until the user explicitly submits them.
**Claude Code (direct MCP tool call):**
```
# annotate keyword detected (or agentui — backward compatible)
# 1. wait for UserPromptSubmit after the user clicks Send Annotations / onSubmit
# 2. the JEO submit-gate hook records submit_gate_status="submitted"
# 3. only then run the blocking agentation watch loop
#
# batchWindowSeconds:10 — receive annotations in 10-second batches
# timeoutSeconds:120 — auto-exit after 120 seconds with no annotations
#
# Per-annotation processing loop:
# 1. agentation_acknowledge_annotation({id}) — show 'processing' in UI
# 2. navigate code via annotation.elementPath (CSS selector) → apply fix
# 3. agentation_resolve_annotation({id, summary}) — mark 'done' + save summary
#
# Loop ends when annotation count=0 or timeout
```
> **Important**: `agentation_watch_annotations` is a blocking call. Do not run with `&` background.
> Same as plannotator's `approved:true` loop: annotation count=0 or timeout = completion signal.
> `annotate` is the primary keyword. `agentui` is a backward-compatible alias and behaves identically.
**Codex / Gemini / OpenCode (HTTP REST API fallback):**
```bash
START_TIME=$(date +%s)
TIMEOUT_SECONDS=120
# Required gate: do not enter the loop until the human has clicked Send Annotations
# and the platform has opened agentation.submit_gate_status="submitted".
while true; do
# Timeout check
NOW=$(date +%s)
ELAPSED=$((NOW - START_TIME))
if [ $ELAPSED -ge $TIMEOUT_SECONDS ]; then
echo "[JEO] agentation polling timeout (${TIMEOUT_SECONDS}s) — some annotations may remain unresolved"
break
fi
SUBMIT_GATE=$(python3 -c "
import json
try:
print(json.load(open('.omc/state/jeo-state.json')).get('agentation', {}).get('submit_gate_status', 'idle'))
except Exception:
print('idle')
" 2>/dev/null || echo "idle")
if [ "$SUBMIT_GATE" != "submitted" ]; then
sleep 2
continue
fi
COUNT=$(curl -sf --connect-timeout 3 --max-time 5 http://localhost:4747/pending 2>/dev/null | python3 -c "import sys,json; data=sys.stdin.read(); d=json.loads(data) if data.strip() else {}; print(d.get('count', len(d.get('annotations', [])) if isinstance(d, dict) else 0))" 2>/dev/null || echo 0)
[ "$COUNT" -eq 0 ] && break
# Process each annotation:
# a) Acknowledge (show as in-progress)
curl -X PATCH http://localhost:4747/annotations/<id> \
-H 'Content-Type: application/json' \
-d '{"status": "acknowledged"}'
# b) Navigate code via elementPath (CSS selector) → apply fix
# c) Resolve (mark done + fix summary)
curl -X PATCH http://localhost:4747/annotations/<id> \
-H 'Content-Type: application/json' \
-d '{"status": "resolved", "resolution": "<fix summary>"}'
sleep 3
done
```
### 3.4 CLEANUP Step (automatic worktree cleanup)
```bash
# Runs automatically after all work is complete
bash scripts/worktree-cleanup.sh
# Individual commands
git worktree list # List current worktrees
git worktree prune # Clean up worktrees for deleted branches
bash scripts/worktree-cleanup.sh --force # Force cleanup including dirty worktrees
```
> Default run removes only clean extra worktrees; worktrees with changes are left with a warning.
> Use `--force` only after review.
---
## 4. Platform Plugin Configuration
### 4.1 Claude Code
```bash
# Automatic setup
bash scripts/setup-claude.sh
# Or manually:
/plugin marketplace add https://github.com/Yeachan-Heo/oh-my-claudecode
/plugin install oh-my-claudecode
/omc:omc-setup
# Add plannotator hook
bash .agent-skills/plannotator/scripts/setup-hook.sh
```
**Config file**: `~/.claude/settings.json`
```json
{
"hooks": {
"PermissionRequest": [{
"matcher": "ExitPlanMode",
"hooks": [{
"type": "command",
"command": "python3 ~/.agent-skills/jeo/scripts/claude-plan-gate.py",
"timeout": 1800
}]
}]
}
}
```
**agentation MCP config** (`~/.claude/settings.json` or `.claude/mcp.json`):
```json
{
"mcpServers": {
"agentation": {
"command": "npx",
"args": ["-y", "agentation-mcp", "server"]
}
},
"hooks": {
"UserPromptSubmit": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "python3 ~/.agent-skills/jeo/scripts/claude-agentation-submit-hook.py",
"timeout": 300
}]
}]
}
}
```
`bash ~/.agent-skills/jeo/scripts/setup-claude.sh` migrates stale `~/.agent-skills/omg/...` hook paths to the active JEO install and installs compatibility shims for legacy Claude setups.
### 4.2 Codex CLI
```bash
# Automatic setup
bash scripts/setup-codex.sh
# What gets configured:
# - developer_instructions: ~/.codex/config.toml
# - prompt file: ~/.codex/prompts/jeo.md
# - notify hook: ~/.codex/hooks/jeo-notify.py
# - [tui] notifications: agent-turn-complete
```
**agentation MCP config** (`~/.codex/config.toml`):
```toml
[mcp_servers.agentation]
command = "npx"
args = ["-y", "agentation-mcp", "server"]
```
**notify hook** (`~/.codex/hooks/jeo-notify.py`):
- Detects `PLAN_READY` signal in `last-assistant-message` when agent turn completes
- Confirms `plan.md` exists, compares the current hash against `last_reviewed_plan_hash`, and skips the gate when the plan was already reviewed
- Saves result to `/tmp/plannotator_feedback.txt`
- Detects `ANNOTATE_READY` signal (or backward-compatible `AGENTUI_READY`) only in `verify_ui`
- Opens `agentation.submit_gate_status="submitted"` first, then polls `http://localhost:4747/pending`
**`~/.codex/config.toml`** config:
```toml
developer_instructions = """
# JEO Orchestration Workflow
# ...
"""
notify = ["python3", "~/.codex/hooks/jeo-notify.py"]
[tui]
notifications = ["agent-turn-complete"]
notification_method = "osc9"
```
> `developer_instructions` must be a **top-level string**.
> Writing it as a `[developer_instructions]` table may cause Codex to fail on startup with `invalid type: map, expected a string`.
> `notify` and `[tui].notifications` must also be set correctly for the PLAN/ANNOTATE follow-up loop to actually work.
Using in Codex:
```bash
/prompts:jeo # Activate JEO workflow
# Agent writes plan.md and outputs "PLAN_READY" → notify hook runs automatically
```
### 4.3 Gemini CLI
```bash
# Automatic setup
bash scripts/setup-gemini.sh
# What gets configured:
# - AfterAgent backup hook: ~/.gemini/hooks/jeo-plannotator.sh
# - Instructions (MANDATORY loop): ~/.gemini/GEMINI.md
```
**Key principle**: The agent must call plannotator **directly in blocking mode** to receive feedback in the same turn.
The AfterAgent hook serves only as a safety net (runs after turn ends → injected in next turn).
**AfterAgent backup hook** (`~/.gemini/settings.json`):
```json
{
"hooks": {
"AfterAgent": [{
"matcher": "",
"hooks": [{
"name": "plannotator-review",
"type": "command",
"command": "bash ~/.gemini/hooks/jeo-plannotator.sh",
"description": "Run plannotator when plan.md is detected (AfterAgent backup)"
}]
}]
}
}
```
**PLAN instructions added to GEMINI.md (mandatory loop)**:
```
1. Write plan.md
2. Run plannotator blocking (no &) → /tmp/plannotator_feedback.txt
3. approved=true → EXECUTE / Not approved → revise and repeat step 2
NEVER proceed to EXECUTE without approved=true.
```
**agentation MCP config** (`~/.gemini/settings.json`):
```json
{
"mcpServers": {
"agentation": {
"command": "npx",
"args": ["-y", "agentation-mcp", "server"]
}
}
}
```
> **Note**: Gemini CLI hook events use `BeforeTool` and `AfterAgent`.
> `ExitPlanMode` is a Claude Code-only hook.
> [Hooks Official Guide](https://developers.googleblog.com/tailor-gemini-cli-to-your-workflow-with-hooks/)
### 4.4 OpenCode
```bash
# Automatic setup
bash scripts/setup-opencode.sh
# Added to opencode.json:
# "@plannotator/opencode@latest" plugin
# "@oh-my-opencode/opencode@latest" plugin (omx)
```
OpenCode slash commands:
- `/jeo-plan` — plan with ralph + plannotator
- `/jeo-exec` — execute with team/bmad
- `/jeo-annotate` — start agentation watch loop (annotate; `/jeo-agentui` is a deprecated alias)
- `/jeo-cleanup` — worktree cleanup
**plannotator integration** (MANDATORY blocking loop):
```bash
# Write plan.md then run PLAN gate (no &) — receive feedback in same turn
bash scripts/plannotator-plan-loop.sh plan.md /tmp/plannotator_feedback.txt 3
# - Must wait until approve/feedback is received
# - Auto-restart on session exit (up to 3 times)
# - After 3 exits, confirm with user whether to abort or resume
# - exit 32 if localhost bind unavailable (replace with manual gate in TTY)
# Branch based on result
# approved=true → enter EXECUTE
# not approved → apply feedback, revise plan.md → repeat above
```
**agentation MCP config** (`opencode.json`):
```json
{
"mcp": {
"agentation": {
"type": "local",
"command": ["npx", "-y", "agentation-mcp", "server"]
}
}
}
```
---
## 5. Memory & State
JEO stores state at the following paths:
```
{worktree}/.omc/state/jeo-state.json # JEO execution state
{worktree}/.omc/plans/jeo-plan.md # Approved plan
{worktree}/.omc/logs/jeo-*.log # Execution logs
```
**State file structure:**
```json
{
"mode": "jeo",
"phase": "plan|execute|verify|verify_ui|cleanup|done",
"session_id": "<uuid>",
"task": "current task description",
"plan_approved": true,
"plan_gate_status": "pending|approved|feedback_required|infrastructure_blocked|manual_approved",
"plan_current_hash": "<sha256 or null>",
"last_reviewed_plan_hash": "<sha256 or null>",
"last_reviewed_plan_at": "2026-02-24T00:00:00Z",
"plan_review_method": "plannotator|manual|null",
"team_available": true,
"retry_count": 0,
"last_error": null,
"checkpoint": "plan|execute|verify|verify_ui|cleanup",
"created_at": "2026-02-24T00:00:00Z",
"updated_at": "2026-02-24T00:00:00Z",
"agentation": {
"active": false,
"session_id": null,
"keyword_used": null,
"submit_gate_status": "idle|waiting_for_submit|submitted",
"submit_signal": "claude-user-prompt-submit|codex-notify|gemini-manual|null",
"submit_received_at": "2026-02-24T00:00:00Z",
"submitted_annotation_count": 0,
"started_at": null,
"timeout_seconds": 120,
"annotations": {
"total": 0, "acknowledged": 0, "resolved": 0, "dismissed": 0, "pending": 0
},
"completed_at": null,
"exit_reason": null
}
}
```
> **agentation fields**: `active` — whether the watch loop is running (used as hook guard), `session_id` — for resuming,
> `submit_gate_status` — prevents processing draft annotations before submit/onSubmit, `submit_signal` — which platform opened the gate,
> `submit_received_at` / `submitted_annotation_count` — audit trail for the submitted batch, `exit_reason` — `"all_resolved"` | `"timeout"` | `"user_cancelled"` | `"error"`
>
> **dismissed annotations**: When a user dismisses an annotation in the agentation UI (status becomes `"dismissed"`),
> the agent should skip code changes for that annotation, increment `annotations.dismissed`, and continue to the next pending annotation.
> Dismissed annotations are counted but not acted upon. The watch loop exits normally when `pending == 0` (resolved + dismissed covers all).
>
> **`plan_review_method`**: set to `"plannotator"` when approved via UI, `"manual"` when approved via TTY fallback gate.
>
> **`cleanup_completed`**: set to `true` by `worktree-cleanup.sh` after successful worktree prune.
> **Error recovery fields**:
> - `retry_count` — number of retries after an error. Increments +1 on each pre-flight failure. Ask user to confirm if `>= 3`.
> - `last_error` — most recent error message. Used to identify the cause on restart.
> - `checkpoint` — last phase that was started. Resume from this phase on restart (`plan|execute|verify|cleanup`).
**Checkpoint-based resume flow:**
```bash
# Check checkpoint on restart
python3 -c "
import json, os, subprocess
try:
root = subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], stderr=subprocess.DEVNULL).decode().strip()
except:
root = os.getcwd()
f = os.path.join(root, '.omc/state/jeo-state.json')
if os.path.exists(f):
d=json.load(open(f))
cp=d.get('checkpoint')
err=d.get('last_error')
rc=d.get('retry_count',0)
print(f'Resume from: {cp or \"beginning\"}')
if err: print(f'Previous error ({rc} time(s)): {err}')
if rc >= 3: print('⚠️ Retry count exceeded 3 — user confirmation required')
"
```
Restore after restart:
```bash
# Check status and resume
bash scripts/check-status.sh --resume
```
---
## 6. Recommended Workflow
```
# Step 1: Install (once)
bash scripts/install.sh --all
bash scripts/check-status.sh
# Step 2: Start work
jeo "<task description>" # Activate with keyword
# Or in Claude: Shift+Tab×2 → plan mode
# Step 3: Review plan with plannotator
# Approve or Send Feedback in browser UI
# Step 4: Automatic execution
# team or bmad handles the work
# Step 5: Cleanup after completion
bash scripts/worktree-cleanup.sh
```
---
## 7. Best Practices
1. **Plan first**: always review the plan with ralph+plannotator before executing (catches wrong approaches early)
2. **Team first**: omc team mode is most efficient in Claude Code
3. **bmad fallback**: use BMAD in environments without team (Codex, Gemini)
4. **Worktree cleanup**: run `worktree-cleanup.sh` immediately after work completes (prevents branch pollution)
5. **State persistence**: use `.omc/state/jeo-state.json` to maintain state across sessions
6. **annotate**: use the `annotate` keyword to run the agentation watch loop for complex UI changes (precise code changes via CSS selector). `agentui` is a backward-compatible alias.
---
## 8. Troubleshooting
| Issue | Solution |
|------|------|
| plannotator not running | JEO first auto-runs `bash scripts/ensure-plannotator.sh`; if it still fails, run `bash .agent-skills/plannotator/scripts/check-status.sh` |
| plannotator not opening in Claude Code | plannotator is hook-only. Do NOT call it via MCP or CLI. Use `EnterPlanMode` → write plan → `ExitPlanMode`; the hook fires automatically. Verify hook is set: `cat ~/.claude/settings.json \| python3 -c "import sys,json;h=json.load(sys.stdin).get('hooks',{});print(h.get('PermissionRequest','missing'))"` |
| plannotator feedback not received | Remove `&` background execution → run blocking, then check `/tmp/plannotator_feedback.txt` (Codex/Gemini/OpenCode only) |
| Same plan is repeatedly re-reviewed in Codex | Compare `last_reviewed_plan_hash` in `jeo-state.json` with the current `plan.md` hash. If they match and `plan_gate_status` is terminal, do not re-run |
| Codex startup failure (`invalid type: map, expected a string`) | Re-run `bash scripts/setup-codex.sh` and confirm `developer_instructions` in `~/.codex/config.toml` is a top-level string |
| Gemini feedback loop missing | Add blocking direct call instruction to `~/.gemini/GEMINI.md` |
| worktree conflict | `git worktree prune && git worktree list` |
| team mode not working | JEO requires team mode in Claude Code. Run `bash scripts/setup-claude.sh`, restart Claude Code, and verify `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` before retrying |
| omc install failed | Run `/omc:omc-doctor` |
| agent-browser error | Check `agent-browser --version` |
| annotate (agentation) not opening | Check `curl http://localhost:4747/health` and `curl http://localhost:4747/sessions`. JEO waits for explicit submit/onSubmit before polling `/pending` |
| annotation not reflected in code | Confirm `summary` field is present when calling `agentation_resolve_annotation` |
| `agentui` keyword not activating | Use the `annotate` keyword (new). `agentui` is a deprecated alias but still works. |
| MCP tool not registered (Codex/Gemini) | Re-run `bash scripts/setup-codex.sh` / `setup-gemini.sh` |
---
## 9. References
- [oh-my-claudecode](https://github.com/Yeachan-Heo/oh-my-claudecode) — Claude Code multi-agent
- [plannotator](https://plannotator.ai) — visual plan/diff review
- [BMAD Method](https://github.com/bmad-dev/BMAD-METHOD) — structured AI development workflow
- [Agent Skills Spec](https://agentskills.io/specification) — skill format specification
- [agentation](https://github.com/benjitaylor/agentation) — UI annotation → agent code fix integration (`annotate`; `agentui` backward compatible)
```
### ../agent-browser/SKILL.md
```markdown
---
name: agent-browser
description: "Browser automation CLI for AI agents. Use for website interaction, form automation, screenshots, scraping, and web app verification. Prefer snapshot refs (@e1, @e2) for deterministic actions."
allowed-tools: Read Write Bash Grep Glob
metadata:
tags: browser-automation, headless-browser, ai-agent, web-testing, web-scraping, verification
platforms: Claude, Gemini, Codex, ChatGPT
version: 1.1.0
source: vercel-labs/agent-browser
---
# agent-browser - Browser Automation for AI Agents
## When to use this skill
- Open websites and automate UI actions
- Fill forms, click controls, and verify outcomes
- Capture screenshots/PDFs or extract content
- Run deterministic web checks with accessibility refs
- Execute parallel browser tasks via isolated sessions
## Core workflow
Always use the deterministic ref loop:
1. `agent-browser open <url>`
2. `agent-browser snapshot -i`
3. interact with refs (`@e1`, `@e2`, ...)
4. `agent-browser snapshot -i` again after page/DOM changes
```bash
agent-browser open https://example.com/form
agent-browser wait --load networkidle
agent-browser snapshot -i
agent-browser fill @e1 "[email protected]"
agent-browser click @e2
agent-browser snapshot -i
```
## Command patterns
Use `&&` chaining when intermediate output is not needed.
```bash
# Good chaining: open -> wait -> snapshot
agent-browser open https://example.com && agent-browser wait --load networkidle && agent-browser snapshot -i
# Separate calls when output is needed first
agent-browser snapshot -i
# parse refs
agent-browser click @e2
```
High-value commands:
- Navigation: `open`, `close`
- Snapshot: `snapshot -i`, `snapshot -i -C`, `snapshot -s "#selector"`
- Interaction: `click`, `fill`, `type`, `select`, `check`, `press`
- Verification: `diff snapshot`, `diff screenshot --baseline <file>`
- Capture: `screenshot`, `screenshot --annotate`, `pdf`
- Wait: `wait --load networkidle`, `wait <selector|@ref|ms>`
## Verification patterns
Use explicit evidence after actions.
```bash
# Baseline -> action -> verify structure
agent-browser snapshot -i
agent-browser click @e3
agent-browser diff snapshot
# Visual regression
agent-browser screenshot baseline.png
agent-browser click @e5
agent-browser diff screenshot --baseline baseline.png
```
## Safety and reliability
- Refs are invalid after navigation or significant DOM updates; re-snapshot before next action.
- Prefer `wait --load networkidle` or selector/ref waits over fixed sleeps.
- For multi-step JS, use `eval --stdin` (or base64) to avoid shell escaping breakage.
- For concurrent tasks, isolate with `--session <name>`.
- Use output controls in long pages to reduce context flooding.
- Optional hardening in sensitive flows: domain allowlist and action policies.
Optional hardening examples:
```bash
# Wrap page content with boundaries to reduce prompt-injection risk
export AGENT_BROWSER_CONTENT_BOUNDARIES=1
# Limit output volume for long pages
export AGENT_BROWSER_MAX_OUTPUT=50000
# Restrict navigation and network to trusted domains
export AGENT_BROWSER_ALLOWED_DOMAINS="example.com,*.example.com"
# Restrict allowed action types
export AGENT_BROWSER_ACTION_POLICY=./policy.json
```
Example `policy.json`:
```json
{"default":"deny","allow":["navigate","snapshot","click","fill","scroll","wait","get"],"deny":["eval","download","upload","network","state"]}
```
CLI-flag equivalent:
```bash
agent-browser --content-boundaries --max-output 50000 --allowed-domains "example.com,*.example.com" --action-policy ./policy.json open https://example.com
```
## Troubleshooting
- `command not found`: install and run `agent-browser install`.
- Wrong element clicked: run `snapshot -i` again and use fresh refs.
- Dynamic SPA content missing: wait with `--load networkidle` or targeted `wait` selector.
- Session collisions: assign unique `--session` names and close each session.
- Large output pressure: narrow snapshots (`-i`, `-c`, `-d`, `-s`) and extract only needed text.
## References
Deep-dive docs in this skill:
- [commands](./references/commands.md)
- [snapshot-refs](./references/snapshot-refs.md)
- [session-management](./references/session-management.md)
- [authentication](./references/authentication.md)
Related resources:
- https://github.com/vercel-labs/agent-browser
- https://agent-browser.dev
Ready templates:
- `./templates/form-automation.sh`
- `./templates/capture-workflow.sh`
## Metadata
- Version: 1.1.0
- Last updated: 2026-02-26
- Scope: deterministic browser automation for agent workflows
```
---
## Skill Companion Files
> Additional files collected from the skill directory layout.
### scripts/setup-agentation-mcp.sh
```bash
#!/usr/bin/env bash
# setup-agentation-mcp.sh — Register agentation MCP server for all AI agent platforms
#
# Usage:
# bash setup-agentation-mcp.sh # all platforms
# bash setup-agentation-mcp.sh --claude # Claude Code only
# bash setup-agentation-mcp.sh --codex # Codex CLI only
# bash setup-agentation-mcp.sh --gemini # Gemini CLI only
# bash setup-agentation-mcp.sh --opencode # OpenCode only
# bash setup-agentation-mcp.sh --all # all platforms (explicit)
set -euo pipefail
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
info() { echo -e "${BLUE}ℹ $*${NC}"; }
ok() { echo -e "${GREEN}✅ $*${NC}"; }
warn() { echo -e "${YELLOW}⚠ $*${NC}"; }
err() { echo -e "${RED}✗ $*${NC}" >&2; }
# ─── Argument parsing ────────────────────────────────────────────────────────
SETUP_CLAUDE=false
SETUP_CODEX=false
SETUP_GEMINI=false
SETUP_OPENCODE=false
while [[ $# -gt 0 ]]; do
case "$1" in
--claude) SETUP_CLAUDE=true ;;
--codex) SETUP_CODEX=true ;;
--gemini) SETUP_GEMINI=true ;;
--opencode) SETUP_OPENCODE=true ;;
--all) SETUP_CLAUDE=true; SETUP_CODEX=true; SETUP_GEMINI=true; SETUP_OPENCODE=true ;;
*) warn "Unknown flag: $1" ;;
esac
shift
done
# Default: all platforms
if [[ "$SETUP_CLAUDE$SETUP_CODEX$SETUP_GEMINI$SETUP_OPENCODE" == "falsefalsefalsefalse" ]]; then
SETUP_CLAUDE=true; SETUP_CODEX=true; SETUP_GEMINI=true; SETUP_OPENCODE=true
fi
echo "╔══════════════════════════════════════════╗"
echo "║ agentation MCP Setup ║"
echo "╚══════════════════════════════════════════╝"
echo ""
# ─── Claude Code (~/.claude/claude_desktop_config.json) ───────────────────
if [[ "$SETUP_CLAUDE" == "true" ]]; then
info "Setting up Claude Code..."
CLAUDE_DIR="$HOME/.claude"
CLAUDE_CFG="$CLAUDE_DIR/claude_desktop_config.json"
mkdir -p "$CLAUDE_DIR"
MCP_JSON='{"command":"npx","args":["-y","agentation-mcp","server"]}'
if [[ -f "$CLAUDE_CFG" ]]; then
if command -v jq &>/dev/null; then
MERGED=$(jq --argjson entry "$MCP_JSON" '.mcpServers.agentation = $entry' "$CLAUDE_CFG" 2>/dev/null)
if [[ -n "$MERGED" ]]; then
echo "$MERGED" > "$CLAUDE_CFG"
ok "Claude Code: merged into $CLAUDE_CFG"
else
warn "Claude Code: jq merge failed — add manually"
fi
else
warn "Claude Code: jq not found — add manually to $CLAUDE_CFG:"
echo ' "mcpServers": { "agentation": { "command": "npx", "args": ["-y", "agentation-mcp", "server"] } }'
fi
else
cat > "$CLAUDE_CFG" <<'EOF'
{
"mcpServers": {
"agentation": {
"command": "npx",
"args": ["-y", "agentation-mcp", "server"]
}
}
}
EOF
ok "Claude Code: created $CLAUDE_CFG"
fi
echo ""
fi
# ─── Codex CLI (~/.codex/config.toml) ─────────────────────────────────────
if [[ "$SETUP_CODEX" == "true" ]]; then
info "Setting up Codex CLI..."
CODEX_DIR="$HOME/.codex"
CODEX_CFG="$CODEX_DIR/config.toml"
mkdir -p "$CODEX_DIR"
CODEX_ENTRY=$'\n# agentation MCP Server\n[[mcp_servers]]\nname = "agentation"\ncommand = "npx"\nargs = ["-y", "agentation-mcp", "server"]\n'
if [[ -f "$CODEX_CFG" ]]; then
if grep -q '"agentation"\|name = "agentation"' "$CODEX_CFG" 2>/dev/null; then
warn "Codex CLI: agentation already in $CODEX_CFG — skipping"
else
printf '%s' "$CODEX_ENTRY" >> "$CODEX_CFG"
ok "Codex CLI: appended to $CODEX_CFG"
fi
else
printf '%s' "$CODEX_ENTRY" > "$CODEX_CFG"
ok "Codex CLI: created $CODEX_CFG"
fi
echo ""
fi
# ─── Gemini CLI (~/.gemini/settings.json) ─────────────────────────────────
if [[ "$SETUP_GEMINI" == "true" ]]; then
info "Setting up Gemini CLI..."
GEMINI_DIR="$HOME/.gemini"
GEMINI_CFG="$GEMINI_DIR/settings.json"
mkdir -p "$GEMINI_DIR"
MCP_JSON='{"command":"npx","args":["-y","agentation-mcp","server"]}'
if [[ -f "$GEMINI_CFG" ]]; then
if command -v jq &>/dev/null; then
MERGED=$(jq --argjson entry "$MCP_JSON" '.mcpServers.agentation = $entry' "$GEMINI_CFG" 2>/dev/null)
if [[ -n "$MERGED" ]]; then
echo "$MERGED" > "$GEMINI_CFG"
ok "Gemini CLI: merged into $GEMINI_CFG"
else
warn "Gemini CLI: jq merge failed — add manually"
fi
else
warn "Gemini CLI: jq not found — add manually to $GEMINI_CFG:"
echo ' "mcpServers": { "agentation": { "command": "npx", "args": ["-y", "agentation-mcp", "server"] } }'
fi
else
cat > "$GEMINI_CFG" <<'EOF'
{
"mcpServers": {
"agentation": {
"command": "npx",
"args": ["-y", "agentation-mcp", "server"]
}
}
}
EOF
ok "Gemini CLI: created $GEMINI_CFG"
fi
echo ""
fi
# ─── OpenCode (~/.config/opencode/opencode.json) ──────────────────────────
if [[ "$SETUP_OPENCODE" == "true" ]]; then
info "Setting up OpenCode..."
OC_DIR="$HOME/.config/opencode"
OC_CFG="$OC_DIR/opencode.json"
mkdir -p "$OC_DIR"
MCP_ENTRY='{"type":"local","command":["npx","-y","agentation-mcp","server"]}'
if [[ -f "$OC_CFG" ]]; then
if command -v jq &>/dev/null; then
MERGED=$(jq --argjson entry "$MCP_ENTRY" '.mcp.agentation = $entry' "$OC_CFG" 2>/dev/null)
if [[ -n "$MERGED" ]]; then
echo "$MERGED" > "$OC_CFG"
ok "OpenCode: merged into $OC_CFG"
else
warn "OpenCode: jq merge failed — add manually"
fi
else
warn "OpenCode: jq not found — add manually to $OC_CFG:"
echo ' "mcp": { "agentation": { "type": "local", "command": ["npx", "-y", "agentation-mcp", "server"] } }'
fi
else
cat > "$OC_CFG" <<'EOF'
{
"mcp": {
"agentation": {
"type": "local",
"command": ["npx", "-y", "agentation-mcp", "server"]
}
}
}
EOF
ok "OpenCode: created $OC_CFG"
fi
echo ""
fi
# ─── Done ─────────────────────────────────────────────────────────────────
echo "╔══════════════════════════════════════════╗"
echo "║ Setup Complete ║"
echo "╚══════════════════════════════════════════╝"
echo ""
echo "Next steps:"
echo " 1. Restart your agent(s)"
echo " 2. Start agentation MCP server: npx agentation-mcp server"
echo " 3. Add to your app: import { Agentation } from 'agentation'"
echo " <Agentation endpoint=\"http://localhost:4747\" />"
echo ""
echo "Available MCP tools: agentation_watch_annotations, agentation_resolve, agentation_acknowledge, ..."
echo "Run 'npx agentation-mcp doctor' to verify."
```
### scripts/verify-loop.sh
```bash
#!/usr/bin/env bash
# verify-loop.sh — Integration test for agentation watch loop
# Tests: server health, annotation CRUD, ACK-RESOLVE cycle, error cases
# Usage: bash verify-loop.sh [--quick] (--quick skips phase 4 error tests)
set -euo pipefail
GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; NC='\033[0m'
pass() { echo -e "${GREEN}PASS${NC} $*"; }
fail() { echo -e "${RED}FAIL${NC} $*"; FAILURES=$((FAILURES + 1)); }
info() { echo -e "${YELLOW}....${NC} $*"; }
FAILURES=0
BASE_URL="http://localhost:4747"
QUICK=false
[[ "${1:-}" == "--quick" ]] && QUICK=true
echo ""
echo "agentation Watch Loop — Integration Test"
echo "========================================="
echo ""
# ── Phase 1: Server Health ──────────────────────────────────────────────────
echo "Phase 1: Server Health"
echo "──────────────────────"
info "1a. Health check"
if curl -sf --connect-timeout 3 "${BASE_URL}/health" >/dev/null 2>&1; then
pass "GET /health — server reachable"
else
fail "GET /health — server not reachable. Start with: npx agentation-mcp server"
echo ""
echo "Cannot continue without server. Exiting."
exit 1
fi
info "1b. Status check"
STATUS=$(curl -sf "${BASE_URL}/status" 2>/dev/null || echo "")
if [[ -n "$STATUS" ]]; then
pass "GET /status — returns server metadata"
else
fail "GET /status — empty response"
fi
info "1c. Baseline pending check"
PENDING=$(curl -sf "${BASE_URL}/pending" 2>/dev/null || echo '{"count":-1}')
BASELINE_COUNT=$(echo "$PENDING" | python3 -c "import sys,json; print(json.load(sys.stdin).get('count',-1))" 2>/dev/null || echo -1)
if [[ "$BASELINE_COUNT" -ge 0 ]]; then
pass "GET /pending — baseline count: ${BASELINE_COUNT}"
else
fail "GET /pending — invalid response"
fi
echo ""
# ── Phase 2: Session & Annotation CRUD ──────────────────────────────────────
echo "Phase 2: Annotation CRUD"
echo "────────────────────────"
info "2a. Create session"
SESSION_RESP=$(curl -sf -X POST "${BASE_URL}/sessions" -H "Content-Type: application/json" -d '{}' 2>/dev/null || echo "")
SESSION_ID=$(echo "$SESSION_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || echo "")
if [[ -n "$SESSION_ID" ]]; then
pass "POST /sessions — created session: ${SESSION_ID:0:12}..."
else
fail "POST /sessions — failed to create session"
echo "Cannot continue without session. Exiting."
exit 1
fi
info "2b. Create annotation"
ANN_RESP=$(curl -sf -X POST "${BASE_URL}/sessions/${SESSION_ID}/annotations" \
-H "Content-Type: application/json" \
-d "{\"comment\":\"Test: change button color\",\"element\":\"button\",\"elementPath\":\"body > main > button.cta\",\"x\":50,\"y\":100}" 2>/dev/null || echo "")
ANN_ID=$(echo "$ANN_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || echo "")
if [[ -n "$ANN_ID" ]]; then
pass "POST annotation — created: ${ANN_ID:0:12}..."
else
fail "POST annotation — failed"
fi
info "2c. Verify pending count = 1"
P_COUNT=$(curl -sf "${BASE_URL}/sessions/${SESSION_ID}/pending" 2>/dev/null \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('count',0))" 2>/dev/null || echo 0)
if [[ "$P_COUNT" -eq 1 ]]; then
pass "GET /sessions/:id/pending — count: 1"
else
fail "GET /sessions/:id/pending — expected 1, got ${P_COUNT}"
fi
echo ""
# ── Phase 3: ACK-RESOLVE Cycle ──────────────────────────────────────────────
echo "Phase 3: ACK-RESOLVE Cycle"
echo "──────────────────────────"
info "3a. Acknowledge annotation"
ACK_RESP=$(curl -sf -X PATCH "${BASE_URL}/annotations/${ANN_ID}" \
-H "Content-Type: application/json" \
-d '{"status":"acknowledged"}' 2>/dev/null || echo "")
ACK_STATUS=$(echo "$ACK_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status',''))" 2>/dev/null || echo "")
if [[ "$ACK_STATUS" == "acknowledged" ]]; then
pass "PATCH acknowledged — status: acknowledged"
else
fail "PATCH acknowledged — expected 'acknowledged', got '${ACK_STATUS}'"
fi
info "3b. Resolve annotation"
RES_RESP=$(curl -sf -X PATCH "${BASE_URL}/annotations/${ANN_ID}" \
-H "Content-Type: application/json" \
-d '{"status":"resolved","resolution":"Changed button color to #3b82f6"}' 2>/dev/null || echo "")
RES_STATUS=$(echo "$RES_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status',''))" 2>/dev/null || echo "")
if [[ "$RES_STATUS" == "resolved" ]]; then
pass "PATCH resolved — status: resolved"
else
fail "PATCH resolved — expected 'resolved', got '${RES_STATUS}'"
fi
info "3c. Verify pending count = 0"
FINAL_COUNT=$(curl -sf "${BASE_URL}/sessions/${SESSION_ID}/pending" 2>/dev/null \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('count',0))" 2>/dev/null || echo -1)
if [[ "$FINAL_COUNT" -eq 0 ]]; then
pass "GET pending after resolve — count: 0 (all resolved)"
else
fail "GET pending after resolve — expected 0, got ${FINAL_COUNT}"
fi
echo ""
# ── Phase 4: Error Cases ────────────────────────────────────────────────────
if ! $QUICK; then
echo "Phase 4: Error Cases"
echo "────────────────────"
info "4a. Invalid annotation ID"
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" -X PATCH "${BASE_URL}/annotations/nonexistent-id-12345" \
-H "Content-Type: application/json" -d '{"status":"acknowledged"}' 2>/dev/null || echo "000")
if [[ "$HTTP_CODE" == "404" || "$HTTP_CODE" == "400" ]]; then
pass "Invalid ID — HTTP ${HTTP_CODE} (expected 4xx)"
else
fail "Invalid ID — expected 4xx, got HTTP ${HTTP_CODE}"
fi
info "4b. GET non-existent session"
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" "${BASE_URL}/sessions/nonexistent-session-12345" 2>/dev/null || echo "000")
if [[ "$HTTP_CODE" == "404" || "$HTTP_CODE" == "400" ]]; then
pass "Non-existent session — HTTP ${HTTP_CODE} (expected 4xx)"
else
fail "Non-existent session — expected 4xx, got HTTP ${HTTP_CODE}"
fi
echo ""
fi
# ── Summary ─────────────────────────────────────────────────────────────────
echo "========================================="
if [[ "$FAILURES" -eq 0 ]]; then
echo -e "${GREEN}ALL TESTS PASSED${NC}"
echo ""
echo "agentation watch loop is working correctly."
echo "MCP tool agentation_watch_annotations should function end-to-end."
else
echo -e "${RED}${FAILURES} TEST(S) FAILED${NC}"
echo ""
echo "Fix the issues above and re-run: bash verify-loop.sh"
fi
echo ""
exit $FAILURES
```