feat(agents): Move templates into dedicated folder
Provisioning now reads template files from templates/ and includes the\nbase URL for agent runtime setup. Remove unused root orchestration\ndocs to keep the repo tidy.\n\nCo-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
107
HEARTBEAT.md
107
HEARTBEAT.md
@@ -1,107 +0,0 @@
|
||||
# Mission Control Orchestrator Instructions
|
||||
|
||||
You are the Mission Control orchestrator. Your job is to:
|
||||
1. Claim unassigned tasks (FIFO)
|
||||
2. Execute work (optionally spawn sub-agents)
|
||||
3. Log progress and deliverables
|
||||
4. Move tasks to review when complete
|
||||
|
||||
## CRITICAL: You MUST call Mission Control APIs
|
||||
|
||||
Every action you take MUST be reflected in Mission Control via API calls. The dashboard shows task status in real-time.
|
||||
|
||||
## Required Inputs
|
||||
|
||||
- `BASE_URL` (e.g., http://localhost:8000)
|
||||
- `ORG_ID`
|
||||
- `WORKSPACE_ID`
|
||||
- `AGENT_TOKEN` (Authorization Bearer token)
|
||||
|
||||
## On Every Heartbeat
|
||||
|
||||
### Step 1: Claim next task (FIFO)
|
||||
```bash
|
||||
curl -s -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/claim-next" \
|
||||
-H "Authorization: Bearer $AGENT_TOKEN"
|
||||
```
|
||||
|
||||
- If response is **204**, there is no work. Wait and retry.
|
||||
- If response returns a task, process it.
|
||||
|
||||
### Step 2: Check your in-progress tasks
|
||||
```bash
|
||||
curl -s "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks?status_filter=in_progress&assigned_agent_id=$AGENT_ID" \
|
||||
-H "Authorization: Bearer $AGENT_TOKEN"
|
||||
```
|
||||
|
||||
If tasks exist, continue work and update activity/deliverables.
|
||||
|
||||
## When Processing a New Task
|
||||
|
||||
### 1) Log that you're starting
|
||||
```bash
|
||||
curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/{TASK_ID}/activities" \
|
||||
-H "Authorization: Bearer $AGENT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"activity_type": "updated", "message": "Starting work on task"}'
|
||||
```
|
||||
|
||||
### 2) Register a sub-agent (if you spawn one)
|
||||
```bash
|
||||
curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/{TASK_ID}/subagents" \
|
||||
-H "Authorization: Bearer $AGENT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"openclaw_session_id": "optional-session-id",
|
||||
"agent_name": "Designer"
|
||||
}'
|
||||
```
|
||||
|
||||
### 3) Register deliverables
|
||||
```bash
|
||||
curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/{TASK_ID}/deliverables" \
|
||||
-H "Authorization: Bearer $AGENT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"title": "Homepage Design",
|
||||
"markdown_content": "## Summary\n- Implemented layout\n- Added responsive styles"
|
||||
}'
|
||||
```
|
||||
|
||||
### 4) Log completion
|
||||
```bash
|
||||
curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/{TASK_ID}/activities" \
|
||||
-H "Authorization: Bearer $AGENT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"activity_type": "completed", "message": "Task completed successfully"}'
|
||||
```
|
||||
|
||||
### 5) Move task to REVIEW
|
||||
```bash
|
||||
curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/{TASK_ID}/transition" \
|
||||
-H "Authorization: Bearer $AGENT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"to_status": "review"}'
|
||||
```
|
||||
|
||||
## Task Statuses
|
||||
|
||||
```
|
||||
inbox → in_progress → review → done
|
||||
```
|
||||
|
||||
Other statuses may be used if configured (`assigned`, `testing`), but the default flow above is expected.
|
||||
|
||||
## Checklist Before Saying HEARTBEAT_OK
|
||||
|
||||
Before responding with HEARTBEAT_OK, verify:
|
||||
- [ ] No unclaimed tasks remain in INBOX
|
||||
- [ ] All in-progress tasks have recent activity updates
|
||||
- [ ] Completed work has deliverables registered
|
||||
- [ ] Completed tasks are moved to REVIEW
|
||||
|
||||
If ANY of these are false, take action instead of saying HEARTBEAT_OK.
|
||||
|
||||
## Reference
|
||||
|
||||
Full API documentation: See ORCHESTRATION.md in this project.
|
||||
176
ORCHESTRATION.md
176
ORCHESTRATION.md
@@ -1,176 +0,0 @@
|
||||
# Mission Control Orchestration Guide
|
||||
|
||||
This document explains how to orchestrate tasks in Mission Control, including how to:
|
||||
- Register sub-agents
|
||||
- Log activities
|
||||
- Track deliverables
|
||||
- Update task status
|
||||
|
||||
## API Base URL
|
||||
|
||||
```
|
||||
http://localhost:8000
|
||||
```
|
||||
|
||||
Or use the `BASE_URL` environment variable.
|
||||
|
||||
## Task Lifecycle
|
||||
|
||||
```
|
||||
INBOX → IN_PROGRESS → REVIEW → DONE
|
||||
```
|
||||
|
||||
**Status Descriptions:**
|
||||
- **INBOX**: New tasks awaiting processing
|
||||
- **IN_PROGRESS**: Agent actively working on the task
|
||||
- **REVIEW**: Agent finished, awaiting human approval
|
||||
- **DONE**: Task completed and approved
|
||||
|
||||
Optional statuses may be enabled (`ASSIGNED`, `TESTING`) but are not required by default.
|
||||
|
||||
## When You Receive a Task
|
||||
|
||||
When a task is claimed, the response includes:
|
||||
- Task ID
|
||||
- Title, description, priority
|
||||
- Project ID
|
||||
|
||||
## Required API Calls
|
||||
|
||||
### 1. Register Sub-Agent (when spawning a worker)
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/{TASK_ID}/subagents" \
|
||||
-H "Authorization: Bearer $AGENT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"openclaw_session_id": "unique-session-id",
|
||||
"agent_name": "Designer"
|
||||
}'
|
||||
```
|
||||
|
||||
### 2. Log Activity (for each significant action)
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/{TASK_ID}/activities" \
|
||||
-H "Authorization: Bearer $AGENT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"activity_type": "updated",
|
||||
"message": "Started working on design mockups"
|
||||
}'
|
||||
```
|
||||
|
||||
Activity types:
|
||||
- `spawned` - When sub-agent starts
|
||||
- `updated` - Progress update
|
||||
- `completed` - Work finished
|
||||
- `file_created` - Created a deliverable
|
||||
- `status_changed` - Task moved to new status
|
||||
|
||||
### 3. Register Deliverable (for each output)
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/{TASK_ID}/deliverables" \
|
||||
-H "Authorization: Bearer $AGENT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"title": "Homepage Design",
|
||||
"markdown_content": "## Summary\n- Implemented layout\n- Added responsive styles"
|
||||
}'
|
||||
```
|
||||
|
||||
### 4. Update Task Status
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/{TASK_ID}/transition" \
|
||||
-H "Authorization: Bearer $AGENT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "to_status": "review" }'
|
||||
```
|
||||
|
||||
## Complete Example Workflow
|
||||
|
||||
```bash
|
||||
TASK_ID="abc-123"
|
||||
BASE_URL="http://localhost:8000"
|
||||
ORG_ID="org-uuid"
|
||||
WORKSPACE_ID="workspace-uuid"
|
||||
AGENT_TOKEN="agent-token"
|
||||
|
||||
# 1) Log that you're starting
|
||||
curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/$TASK_ID/activities" \
|
||||
-H "Authorization: Bearer $AGENT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"activity_type": "updated", "message": "Starting work on task"}'
|
||||
|
||||
# 2) Spawn a sub-agent
|
||||
curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/$TASK_ID/subagents" \
|
||||
-H "Authorization: Bearer $AGENT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"openclaw_session_id": "subagent-'$(date +%s)'", "agent_name": "Designer"}'
|
||||
|
||||
# 3) Register the deliverable
|
||||
curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/$TASK_ID/deliverables" \
|
||||
-H "Authorization: Bearer $AGENT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"title": "Completed Design",
|
||||
"markdown_content": "## Deliverable\n- Final design with all requested features"
|
||||
}'
|
||||
|
||||
# 4) Log completion
|
||||
curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/$TASK_ID/activities" \
|
||||
-H "Authorization: Bearer $AGENT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"activity_type": "completed", "message": "Design completed successfully"}'
|
||||
|
||||
# 5) Move to review
|
||||
curl -X POST "$BASE_URL/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/tasks/$TASK_ID/transition" \
|
||||
-H "Authorization: Bearer $AGENT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"to_status": "review"}'
|
||||
```
|
||||
|
||||
## Endpoints Reference
|
||||
|
||||
| Endpoint | Method | Purpose |
|
||||
|----------|--------|---------|
|
||||
| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks` | GET | List tasks |
|
||||
| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks` | POST | Create task |
|
||||
| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks/{task_id}` | PATCH | Update task |
|
||||
| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks/{task_id}/activities` | GET | List activities |
|
||||
| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks/{task_id}/activities` | POST | Log activity |
|
||||
| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks/{task_id}/deliverables` | GET | List deliverables |
|
||||
| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks/{task_id}/deliverables` | POST | Add deliverable |
|
||||
| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks/{task_id}/subagents` | GET | List sub-agents |
|
||||
| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks/{task_id}/subagents` | POST | Register sub-agent |
|
||||
| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/tasks/claim-next` | POST | Claim next task (FIFO) |
|
||||
| `/api/v1/orgs/{org_id}/workspaces/{workspace_id}/events/activities` | GET | SSE activity stream |
|
||||
|
||||
## Activity Body Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"activity_type": "spawned|updated|completed|file_created|status_changed",
|
||||
"message": "Human-readable description of what happened"
|
||||
}
|
||||
```
|
||||
|
||||
## Deliverable Body Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "Display name for the deliverable",
|
||||
"markdown_content": "Markdown content for the deliverable"
|
||||
}
|
||||
```
|
||||
|
||||
## Sub-Agent Body Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"openclaw_session_id": "unique-identifier-for-session",
|
||||
"agent_name": "Designer|Developer|Researcher|Writer"
|
||||
}
|
||||
```
|
||||
@@ -3,6 +3,7 @@ LOG_LEVEL=INFO
|
||||
DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:5432/openclaw_agency
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
CORS_ORIGINS=http://localhost:3000
|
||||
BASE_URL=
|
||||
|
||||
# Clerk (auth only)
|
||||
CLERK_JWKS_URL=
|
||||
@@ -12,6 +13,8 @@ CLERK_LEEWAY=10.0
|
||||
# OpenClaw Gateway
|
||||
OPENCLAW_GATEWAY_URL=ws://127.0.0.1:18789
|
||||
OPENCLAW_GATEWAY_TOKEN=
|
||||
OPENCLAW_MAIN_SESSION_KEY=agent:main:main
|
||||
OPENCLAW_WORKSPACE_ROOT=~/.openclaw/workspaces
|
||||
|
||||
# Database
|
||||
DB_AUTO_MIGRATE=false
|
||||
|
||||
@@ -20,11 +20,12 @@ from app.schemas.agents import (
|
||||
AgentUpdate,
|
||||
)
|
||||
from app.services.admin_access import require_admin
|
||||
from app.services.agent_provisioning import send_provisioning_message
|
||||
|
||||
router = APIRouter(prefix="/agents", tags=["agents"])
|
||||
|
||||
OFFLINE_AFTER = timedelta(minutes=10)
|
||||
DEFAULT_GATEWAY_CHANNEL = "openclaw-agency"
|
||||
AGENT_SESSION_PREFIX = "agent"
|
||||
|
||||
|
||||
def _slugify(value: str) -> str:
|
||||
@@ -32,17 +33,17 @@ def _slugify(value: str) -> str:
|
||||
return slug or uuid4().hex
|
||||
|
||||
|
||||
def _build_session_label(agent_name: str) -> str:
|
||||
return f"{DEFAULT_GATEWAY_CHANNEL}-{_slugify(agent_name)}"
|
||||
def _build_session_key(agent_name: str) -> str:
|
||||
return f"{AGENT_SESSION_PREFIX}:{_slugify(agent_name)}:main"
|
||||
|
||||
|
||||
async def _create_gateway_session(agent_name: str) -> str:
|
||||
label = _build_session_label(agent_name)
|
||||
async def _ensure_gateway_session(agent_name: str) -> tuple[str, str | None]:
|
||||
session_key = _build_session_key(agent_name)
|
||||
try:
|
||||
await openclaw_call("sessions.patch", {"key": label, "label": agent_name})
|
||||
await openclaw_call("sessions.patch", {"key": session_key, "label": agent_name})
|
||||
return session_key, None
|
||||
except OpenClawGatewayError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail=str(exc)) from exc
|
||||
return label
|
||||
return session_key, str(exc)
|
||||
|
||||
|
||||
def _with_computed_status(agent: Agent) -> Agent:
|
||||
@@ -79,18 +80,48 @@ async def create_agent(
|
||||
) -> Agent:
|
||||
require_admin(auth)
|
||||
agent = Agent.model_validate(payload)
|
||||
agent.openclaw_session_id = await _create_gateway_session(agent.name)
|
||||
session_key, session_error = await _ensure_gateway_session(agent.name)
|
||||
agent.openclaw_session_id = session_key
|
||||
session.add(agent)
|
||||
session.commit()
|
||||
session.refresh(agent)
|
||||
session.add(
|
||||
ActivityEvent(
|
||||
event_type="agent.session.created",
|
||||
message=f"Session created for {agent.name}.",
|
||||
agent_id=agent.id,
|
||||
if session_error:
|
||||
session.add(
|
||||
ActivityEvent(
|
||||
event_type="agent.session.failed",
|
||||
message=f"Session sync failed for {agent.name}: {session_error}",
|
||||
agent_id=agent.id,
|
||||
)
|
||||
)
|
||||
else:
|
||||
session.add(
|
||||
ActivityEvent(
|
||||
event_type="agent.session.created",
|
||||
message=f"Session created for {agent.name}.",
|
||||
agent_id=agent.id,
|
||||
)
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
try:
|
||||
await send_provisioning_message(agent)
|
||||
except OpenClawGatewayError as exc:
|
||||
session.add(
|
||||
ActivityEvent(
|
||||
event_type="agent.provision.failed",
|
||||
message=f"Provisioning message failed: {exc}",
|
||||
agent_id=agent.id,
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
except Exception as exc: # pragma: no cover - unexpected provisioning errors
|
||||
session.add(
|
||||
ActivityEvent(
|
||||
event_type="agent.provision.failed",
|
||||
message=f"Provisioning message failed: {exc}",
|
||||
agent_id=agent.id,
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
return agent
|
||||
|
||||
|
||||
@@ -160,26 +191,88 @@ async def heartbeat_or_create_agent(
|
||||
agent = session.exec(select(Agent).where(Agent.name == payload.name)).first()
|
||||
if agent is None:
|
||||
agent = Agent(name=payload.name, status=payload.status or "online")
|
||||
agent.openclaw_session_id = await _create_gateway_session(agent.name)
|
||||
session_key, session_error = await _ensure_gateway_session(agent.name)
|
||||
agent.openclaw_session_id = session_key
|
||||
session.add(agent)
|
||||
session.commit()
|
||||
session.refresh(agent)
|
||||
session.add(
|
||||
ActivityEvent(
|
||||
event_type="agent.session.created",
|
||||
message=f"Session created for {agent.name}.",
|
||||
agent_id=agent.id,
|
||||
if session_error:
|
||||
session.add(
|
||||
ActivityEvent(
|
||||
event_type="agent.session.failed",
|
||||
message=f"Session sync failed for {agent.name}: {session_error}",
|
||||
agent_id=agent.id,
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
session.add(
|
||||
ActivityEvent(
|
||||
event_type="agent.session.created",
|
||||
message=f"Session created for {agent.name}.",
|
||||
agent_id=agent.id,
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
try:
|
||||
await send_provisioning_message(agent)
|
||||
except OpenClawGatewayError as exc:
|
||||
session.add(
|
||||
ActivityEvent(
|
||||
event_type="agent.provision.failed",
|
||||
message=f"Provisioning message failed: {exc}",
|
||||
agent_id=agent.id,
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
except Exception as exc: # pragma: no cover - unexpected provisioning errors
|
||||
session.add(
|
||||
ActivityEvent(
|
||||
event_type="agent.provision.failed",
|
||||
message=f"Provisioning message failed: {exc}",
|
||||
agent_id=agent.id,
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
elif not agent.openclaw_session_id:
|
||||
agent.openclaw_session_id = await _create_gateway_session(agent.name)
|
||||
session.add(
|
||||
ActivityEvent(
|
||||
event_type="agent.session.created",
|
||||
message=f"Session created for {agent.name}.",
|
||||
agent_id=agent.id,
|
||||
session_key, session_error = await _ensure_gateway_session(agent.name)
|
||||
agent.openclaw_session_id = session_key
|
||||
if session_error:
|
||||
session.add(
|
||||
ActivityEvent(
|
||||
event_type="agent.session.failed",
|
||||
message=f"Session sync failed for {agent.name}: {session_error}",
|
||||
agent_id=agent.id,
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
session.add(
|
||||
ActivityEvent(
|
||||
event_type="agent.session.created",
|
||||
message=f"Session created for {agent.name}.",
|
||||
agent_id=agent.id,
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
try:
|
||||
await send_provisioning_message(agent)
|
||||
except OpenClawGatewayError as exc:
|
||||
session.add(
|
||||
ActivityEvent(
|
||||
event_type="agent.provision.failed",
|
||||
message=f"Provisioning message failed: {exc}",
|
||||
agent_id=agent.id,
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
except Exception as exc: # pragma: no cover - unexpected provisioning errors
|
||||
session.add(
|
||||
ActivityEvent(
|
||||
event_type="agent.provision.failed",
|
||||
message=f"Provisioning message failed: {exc}",
|
||||
agent_id=agent.id,
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
if payload.status:
|
||||
agent.status = payload.status
|
||||
agent.last_seen_at = datetime.utcnow()
|
||||
|
||||
@@ -22,8 +22,11 @@ class Settings(BaseSettings):
|
||||
# OpenClaw Gateway
|
||||
openclaw_gateway_url: str = ""
|
||||
openclaw_gateway_token: str = ""
|
||||
openclaw_main_session_key: str = "agent:main:main"
|
||||
openclaw_workspace_root: str = "~/.openclaw/workspaces"
|
||||
|
||||
cors_origins: str = ""
|
||||
base_url: str = ""
|
||||
|
||||
# Database lifecycle
|
||||
db_auto_migrate: bool = False
|
||||
|
||||
93
backend/app/services/agent_provisioning.py
Normal file
93
backend/app/services/agent_provisioning.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
from app.core.config import settings
|
||||
from app.integrations.openclaw_gateway import send_message
|
||||
from app.models.agents import Agent
|
||||
|
||||
TEMPLATE_FILES = [
|
||||
"AGENTS.md",
|
||||
"BOOT.md",
|
||||
"BOOTSTRAP.md",
|
||||
"HEARTBEAT.md",
|
||||
"IDENTITY.md",
|
||||
"SOUL.md",
|
||||
"TOOLS.md",
|
||||
"USER.md",
|
||||
]
|
||||
|
||||
|
||||
def _repo_root() -> Path:
|
||||
return Path(__file__).resolve().parents[3]
|
||||
|
||||
|
||||
def _templates_root() -> Path:
|
||||
return _repo_root() / "templates"
|
||||
|
||||
|
||||
def _slugify(value: str) -> str:
|
||||
slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
|
||||
return slug or uuid4().hex
|
||||
|
||||
|
||||
def _read_templates() -> dict[str, str]:
|
||||
root = _templates_root()
|
||||
templates: dict[str, str] = {}
|
||||
for name in TEMPLATE_FILES:
|
||||
path = root / name
|
||||
if path.exists():
|
||||
templates[name] = path.read_text(encoding="utf-8").strip()
|
||||
else:
|
||||
templates[name] = ""
|
||||
return templates
|
||||
|
||||
|
||||
def _render_file_block(name: str, content: str) -> str:
|
||||
body = content if content else f"# {name}\n\nTODO: add content\n"
|
||||
return f"\n{name}\n```md\n{body}\n```\n"
|
||||
|
||||
|
||||
def _workspace_path(agent_name: str) -> str:
|
||||
root = settings.openclaw_workspace_root or "~/.openclaw/workspaces"
|
||||
root = root.rstrip("/")
|
||||
return f"{root}/{_slugify(agent_name)}"
|
||||
|
||||
|
||||
def build_provisioning_message(agent: Agent) -> str:
|
||||
templates = _read_templates()
|
||||
agent_id = _slugify(agent.name)
|
||||
workspace_path = _workspace_path(agent.name)
|
||||
session_key = agent.openclaw_session_id or ""
|
||||
base_url = settings.base_url or ""
|
||||
|
||||
file_blocks = "".join(
|
||||
_render_file_block(name, templates.get(name, "")) for name in TEMPLATE_FILES
|
||||
)
|
||||
|
||||
return (
|
||||
"Provision a new OpenClaw agent workspace.\n\n"
|
||||
f"Agent name: {agent.name}\n"
|
||||
f"Agent id: {agent_id}\n"
|
||||
f"Session key: {session_key}\n"
|
||||
f"Workspace path: {workspace_path}\n\n"
|
||||
f"Base URL: {base_url or 'UNSET'}\n\n"
|
||||
"Steps:\n"
|
||||
"1) Create the workspace directory.\n"
|
||||
"2) Write the files below with the exact contents.\n"
|
||||
f"3) Set BASE_URL to {base_url or '{{BASE_URL}}'} for the agent runtime.\n"
|
||||
"4) Replace placeholders like {{AGENT_NAME}}, {{AGENT_ID}}, {{BASE_URL}}, {{AUTH_TOKEN}}.\n"
|
||||
"5) Leave BOOTSTRAP.md in place; the agent should run it on first start and delete it.\n"
|
||||
"6) Register agent id in OpenClaw so it uses this workspace path.\n\n"
|
||||
"Files:" + file_blocks
|
||||
)
|
||||
|
||||
|
||||
async def send_provisioning_message(agent: Agent) -> None:
|
||||
main_session = settings.openclaw_main_session_key
|
||||
if not main_session:
|
||||
return
|
||||
message = build_provisioning_message(agent)
|
||||
await send_message(message, session_key=main_session, deliver=False)
|
||||
30
templates/AGENTS.md
Normal file
30
templates/AGENTS.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# AGENTS.md
|
||||
|
||||
This workspace is your home. Treat it as the source of truth.
|
||||
|
||||
## First run
|
||||
- If BOOTSTRAP.md exists, follow it once and delete it when finished.
|
||||
|
||||
## Every session
|
||||
Before doing anything else:
|
||||
1) Read SOUL.md (identity, boundaries)
|
||||
2) Read USER.md (who you serve)
|
||||
3) Read memory/YYYY-MM-DD.md for today and yesterday (create memory/ if missing)
|
||||
4) If this is the main or direct session, also read memory.md
|
||||
|
||||
## Memory
|
||||
- Daily log: memory/YYYY-MM-DD.md
|
||||
- Long-term: memory.md (main session only)
|
||||
Write things down. Do not rely on short-term context.
|
||||
|
||||
## Safety
|
||||
- Ask before destructive actions.
|
||||
- Prefer reversible steps.
|
||||
- Do not exfiltrate private data.
|
||||
|
||||
## Tools
|
||||
- Skills are authoritative. Follow SKILL.md instructions exactly.
|
||||
- Use TOOLS.md for environment-specific notes.
|
||||
|
||||
## Heartbeats
|
||||
- HEARTBEAT.md defines what to do on each heartbeat.
|
||||
6
templates/BOOT.md
Normal file
6
templates/BOOT.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# BOOT.md
|
||||
|
||||
On startup:
|
||||
1) Verify API reachability (GET {{BASE_URL}}/api/v1/gateway/status).
|
||||
2) If you send a boot message, end with NO_REPLY.
|
||||
3) If BOOTSTRAP.md exists in this workspace, the agent should run it once and delete it.
|
||||
8
templates/BOOTSTRAP.md
Normal file
8
templates/BOOTSTRAP.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# BOOTSTRAP.md
|
||||
|
||||
First run checklist:
|
||||
1) Fill IDENTITY.md (name, vibe, emoji, avatar).
|
||||
2) Fill USER.md with the human context.
|
||||
3) Create memory/ and memory.md if missing.
|
||||
4) Read SOUL.md and update if needed.
|
||||
5) Delete this file when done.
|
||||
56
templates/HEARTBEAT.md
Normal file
56
templates/HEARTBEAT.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# HEARTBEAT.md
|
||||
|
||||
If this file is empty, skip heartbeat work.
|
||||
|
||||
## Required inputs
|
||||
- BASE_URL (e.g. http://localhost:8000)
|
||||
- AUTH_TOKEN (Bearer token)
|
||||
- AGENT_NAME
|
||||
|
||||
## On every heartbeat
|
||||
1) Check in:
|
||||
```bash
|
||||
curl -s -X POST "$BASE_URL/api/v1/agents/heartbeat" \
|
||||
-H "Authorization: Bearer $AUTH_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "'$AGENT_NAME'", "status": "online"}'
|
||||
```
|
||||
|
||||
2) List boards:
|
||||
```bash
|
||||
curl -s "$BASE_URL/api/v1/boards" \
|
||||
-H "Authorization: Bearer $AUTH_TOKEN"
|
||||
```
|
||||
|
||||
3) For each board, list tasks:
|
||||
```bash
|
||||
curl -s "$BASE_URL/api/v1/boards/{BOARD_ID}/tasks" \
|
||||
-H "Authorization: Bearer $AUTH_TOKEN"
|
||||
```
|
||||
|
||||
4) Claim next task (FIFO):
|
||||
- Find the oldest task with status "inbox" across all boards.
|
||||
- Claim it by moving it to "in_progress":
|
||||
```bash
|
||||
curl -s -X PATCH "$BASE_URL/api/v1/boards/{BOARD_ID}/tasks/{TASK_ID}" \
|
||||
-H "Authorization: Bearer $AUTH_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"status": "in_progress"}'
|
||||
```
|
||||
|
||||
5) Work the task:
|
||||
- Update status as you progress.
|
||||
- When complete, move to "review":
|
||||
```bash
|
||||
curl -s -X PATCH "$BASE_URL/api/v1/boards/{BOARD_ID}/tasks/{TASK_ID}" \
|
||||
-H "Authorization: Bearer $AUTH_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"status": "review"}'
|
||||
```
|
||||
|
||||
## Status flow
|
||||
```
|
||||
inbox -> in_progress -> review -> done
|
||||
```
|
||||
|
||||
Do not say HEARTBEAT_OK if there is inbox work or active in_progress work.
|
||||
8
templates/IDENTITY.md
Normal file
8
templates/IDENTITY.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# IDENTITY.md
|
||||
|
||||
Name: {{AGENT_NAME}}
|
||||
Agent ID: {{AGENT_ID}}
|
||||
Creature: AI
|
||||
Vibe: calm, precise, helpful
|
||||
Emoji: :gear:
|
||||
Avatar: avatars/{{AGENT_ID}}.png
|
||||
16
templates/SOUL.md
Normal file
16
templates/SOUL.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# SOUL.md
|
||||
|
||||
You are a Mission Control agent for the openclaw-agency app.
|
||||
|
||||
Core truths:
|
||||
- Keep tasks moving and statuses accurate.
|
||||
- Write concise, factual updates.
|
||||
- Prefer small, reversible steps.
|
||||
|
||||
Boundaries:
|
||||
- Ask before destructive actions.
|
||||
- Do not invent APIs or data.
|
||||
- Avoid leaking sensitive information.
|
||||
|
||||
Continuity:
|
||||
- Record decisions and conventions in memory files.
|
||||
10
templates/TOOLS.md
Normal file
10
templates/TOOLS.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# TOOLS.md
|
||||
|
||||
BASE_URL={{BASE_URL}}
|
||||
AUTH_TOKEN={{AUTH_TOKEN}}
|
||||
MAIN_SESSION_KEY=agent:main:main
|
||||
WORKSPACE_ROOT=~/.openclaw/workspaces
|
||||
|
||||
Notes:
|
||||
- Use curl for API calls.
|
||||
- Keep outputs short and log progress via task status changes.
|
||||
7
templates/USER.md
Normal file
7
templates/USER.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# USER.md
|
||||
|
||||
Name: {{USER_NAME}}
|
||||
Preferred name: {{USER_PREFERRED_NAME}}
|
||||
Timezone: {{USER_TIMEZONE}}
|
||||
Notes:
|
||||
- {{USER_NOTES}}
|
||||
Reference in New Issue
Block a user