---
name: agent-loop-mcp
description: "Use when starting, running, or completing a HOS agent cycle. Teaches the AgentLoopMCP lifecycle with exact tool signatures for company-workspace pair identity. Mandatory for all HOS agents. Triggers on 'agent loop', 'loop start', 'loop complete', 'persist state', 'handoff state', 'checkpoint', 'workspace_name', 'agent_name'."
---

# Agent Loop MCP — HOS Agent Lifecycle Skill

## What this is

AgentLoopMCP is the **transactional loop-state and coordination service** for recurring
HOS agents. It replaces file-based daily logs as the authoritative persistence layer
for agent continuity. Every HOS agent cycle MUST be wrapped in a loop run.

## Identity (DEV-28 shipped — pair model)

Identity is **caller-declared**. There is **no** process-wide `AGENT_LOOP_DEV_*` and
**no** `loop_self_inspect` tool.

Every agent-scoped call requires **both**:

| Field | Rule |
|-------|------|
| `workspace_name` | Globally unique company workspace (HOS uses `chrilan`) |
| `agent_name` | Unique within that workspace (this agent's registered name) |

### One-time registration (before first cycle on a fresh DB)

```
agent-loop_loop_workspace_register(payload={
  "workspace_name": "chrilan",
  "company_name": "Chrilan"
})

agent-loop_loop_agent_register(payload={
  "workspace_name": "chrilan",
  "agent_name": "<this-agent-name>"   // e.g. "hos-whatsapp-overview"
})
```

- Unknown pair → `NOT_FOUND` naming `loop_workspace_register` or `loop_agent_register`.
- Metadata conflict on re-register → `REGISTRY_CONFLICT` (409).
- Wrong-pair run ownership → `FORBIDDEN` (no owner disclosure).

### Inspect

Tool (pair-scoped continuity metadata):

```
agent-loop_loop_agent_inspect(payload={
  "workspace_name": "chrilan",
  "agent_name": "<this-agent-name>"
})
```

Resource URI:

```
agent-loop://workspaces/{workspace_name}/agents/{agent_name}
```

## The mandatory lifecycle (every cycle, no exceptions)

```
E1 step → agent-loop_loop_run_start(payload={workspace_name, agent_name, schedule_key, trigger_id})
  ↓ get run_id + fencing_token from response
  ... domain work ...
  ↓ optionally: loop_shared_publish_now / loop_shared_read_more (same pair + run_id + fencing_token)
last step → agent-loop_loop_run_complete(payload={workspace_name, agent_name, run_id, fencing_token, completion_id, result_summary, private_state})
```

## CRITICAL: all tools take a `payload` object

Every tool wraps its arguments inside a single `payload` parameter.
You MUST pass `{payload: {...}}` not the fields directly.

## Tool reference (exact signatures)

### loop_run_start

Called at the **first domain step** (E1), after identity/capability check.

```
agent-loop_loop_run_start(payload={
  "workspace_name": "chrilan",
  "agent_name": "<this-agent-name>",
  "schedule_key": "<agent-name>:<cadence>",     // e.g. "hos-whatsapp-overview:5min"
  "trigger_id": "<unique-id>",                   // e.g. "manual-2026-08-12T10:00:00Z"
  "triggered_at": null,                          // optional ISO datetime
  "requested_lease_seconds": 300,                // default 300
  "max_shared_notes": 100                         // default 100
})
```

Returns: `run_id`, `fencing_token`, `status`, `server_now`, `lease_expires_at`,
`previous_successful_run`, `private_state`, `shared_inbox`, `recovery`.

**SAVE `run_id` and `fencing_token`** — every subsequent call needs them.

### loop_run_heartbeat

```
agent-loop_loop_run_heartbeat(payload={
  "workspace_name": "chrilan",
  "agent_name": "<this-agent-name>",
  "run_id": "<from start>",
  "fencing_token": <int from start>,
  "extend_seconds": 300
})
```

### loop_run_complete

```
agent-loop_loop_run_complete(payload={
  "workspace_name": "chrilan",
  "agent_name": "<this-agent-name>",
  "run_id": "<from start>",
  "fencing_token": <int from start>,
  "completion_id": "<unique-id>",
  "result_summary": "One-line human-readable summary",
  "private_state": {
    "objective": "What this agent does",
    "summary_for_next_run": "What the next cycle needs to know",
    "unresolved_items": [],
    "decisions": [],
    "known_facts": [],
    "next_run_actions": [],
    "suppressed_items": []
  },
  "metrics": {
    "source_items_read": 0,
    "source_items_processed": 0,
    "business_actions_attempted": 0,
    "business_actions_succeeded": 0
  }
})
```

**private_state.objective** and **private_state.summary_for_next_run** are required (min_length=1).

### loop_run_fail

```
agent-loop_loop_run_fail(payload={
  "workspace_name": "chrilan",
  "agent_name": "<this-agent-name>",
  "run_id": "<from start>",
  "fencing_token": <int from start>,
  "failure_id": "<unique-id>",
  "category": "AGENT_EXECUTION_ERROR",
  "summary": "What went wrong"
})
```

### loop_shared_publish_now

```
agent-loop_loop_shared_publish_now(payload={
  "workspace_name": "chrilan",
  "agent_name": "<this-agent-name>",
  "run_id": "<from start>",
  "fencing_token": <int from start>,
  "dedupe_key": "<unique-key>",
  "note": {
    "kind": "status",
    "priority": "normal",
    "subject": "Short subject line",
    "summary": "What happened and what's needed",
    "audience": {
      "type": "channel",
      "channel_key": "hos-communications"
    },
    "requires_ack": true
  }
})
```

Audience may also target agents by **name** (`agent_names: ["hos-tickets-overview"]`), not internal keys.

### loop_shared_read_more

```
agent-loop_loop_shared_read_more(payload={
  "workspace_name": "chrilan",
  "agent_name": "<this-agent-name>",
  "run_id": "<from start>",
  "fencing_token": <int from start>,
  "limit": 50,
  "after_delivery_id": null
})
```

### loop_agent_inspect

```
agent-loop_loop_agent_inspect(payload={
  "workspace_name": "chrilan",
  "agent_name": "<this-agent-name>"
})
```

### Registration tools

```
agent-loop_loop_workspace_register(payload={"workspace_name": "chrilan", "company_name": "Chrilan"})
agent-loop_loop_agent_register(payload={"workspace_name": "chrilan", "agent_name": "<name>"})
```

## Complete cycle example (copy-paste pattern)

```
# Once per environment (idempotent if metadata matches)
agent-loop_loop_workspace_register(payload={"workspace_name": "chrilan", "company_name": "Chrilan"})
agent-loop_loop_agent_register(payload={"workspace_name": "chrilan", "agent_name": "hos-whatsapp-overview"})

# E1: Start the run
start = agent-loop_loop_run_start(payload={
  "workspace_name": "chrilan",
  "agent_name": "hos-whatsapp-overview",
  "schedule_key": "hos-whatsapp-overview:5min",
  "trigger_id": "manual-2026-08-12T10:00:00Z"
})
run_id = start.run_id
fencing_token = start.fencing_token

# ... domain work; use start.private_state and start.shared_inbox ...

agent-loop_loop_shared_publish_now(payload={
  "workspace_name": "chrilan",
  "agent_name": "hos-whatsapp-overview",
  "run_id": run_id,
  "fencing_token": fencing_token,
  "dedupe_key": "cycle-2026-08-12-10-comms",
  "note": {
    "kind": "status",
    "subject": "WhatsApp overview cycle complete",
    "summary": "30 inbound conversations processed, 0 P1",
    "audience": {"type": "channel", "channel_key": "hos-communications"}
  }
})

agent-loop_loop_run_complete(payload={
  "workspace_name": "chrilan",
  "agent_name": "hos-whatsapp-overview",
  "run_id": run_id,
  "fencing_token": fencing_token,
  "completion_id": "complete-2026-08-12T10:05:00Z",
  "result_summary": "Cycle complete: 1081 chats, 30 inbound, 0 P1",
  "private_state": {
    "objective": "Monitor Chrilan Support WhatsApp line every 5 minutes",
    "summary_for_next_run": "Watermark at 2026-08-12T10:00Z. 12 ticket-worthy conversations. No P1.",
    "unresolved_items": [
      {"key": "compliance-login-260977486690", "description": "Customer login failure handoff from Compliance", "status": "open", "priority": "high"}
    ]
  }
})
```

## Integration with existing patterns

- **task_note** remains for in-task audit during the current run
- **memory_remember** remains for long-term semantic memory
- **loop_run_complete.private_state** is the authoritative cycle-to-cycle handoff
- **loop_shared_publish_now** replaces the communication radar file
- File-based private_state is not required

## Anti-patterns (banned)

- NEVER skip `loop_run_start`. A cycle without a run has no persistence.
- NEVER skip `loop_run_complete`. An incomplete run holds a lease.
- NEVER omit `workspace_name` or `agent_name` on any agent-scoped call.
- NEVER use another agent's `agent_name` (cross-agent spoofing is a guardrail violation).
- NEVER pass fields directly — always wrap in `payload: {...}`.
- NEVER call `loop_self_inspect` — it does not exist; use `loop_agent_inspect` or the pair-scoped resource.
- NEVER rely on `AGENT_LOOP_DEV_AGENT_ID` / `AGENT_LOOP_DEV_WORKSPACE_ID` — removed.
- NEVER use `loop_run_fail` for ordinary CYCLE_COMPLETE.
- NEVER forget `fencing_token` on heartbeat/complete/publish/read calls.
