feat: enhance agent provisioning by adding AUTONOMY.md and ensuring task dependencies are handled correctly
This commit is contained in:
@@ -188,6 +188,8 @@ async def create_task(
|
|||||||
if agent.board_id and agent.board_id != board.id:
|
if agent.board_id and agent.board_id != board.id:
|
||||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||||
session.add(task)
|
session.add(task)
|
||||||
|
# Ensure the task exists in the DB before inserting dependency rows.
|
||||||
|
await session.flush()
|
||||||
for dep_id in normalized_deps:
|
for dep_id in normalized_deps:
|
||||||
session.add(
|
session.add(
|
||||||
TaskDependency(
|
TaskDependency(
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ from app.models.board_memory import BoardMemory
|
|||||||
from app.models.board_onboarding import BoardOnboardingSession
|
from app.models.board_onboarding import BoardOnboardingSession
|
||||||
from app.models.boards import Board
|
from app.models.boards import Board
|
||||||
from app.models.gateways import Gateway
|
from app.models.gateways import Gateway
|
||||||
|
from app.models.task_dependencies import TaskDependency
|
||||||
from app.models.task_fingerprints import TaskFingerprint
|
from app.models.task_fingerprints import TaskFingerprint
|
||||||
from app.models.tasks import Task
|
from app.models.tasks import Task
|
||||||
from app.schemas.boards import BoardCreate, BoardRead, BoardUpdate
|
from app.schemas.boards import BoardCreate, BoardRead, BoardUpdate
|
||||||
@@ -228,21 +229,27 @@ async def delete_board(
|
|||||||
|
|
||||||
if task_ids:
|
if task_ids:
|
||||||
await session.execute(delete(ActivityEvent).where(col(ActivityEvent.task_id).in_(task_ids)))
|
await session.execute(delete(ActivityEvent).where(col(ActivityEvent.task_id).in_(task_ids)))
|
||||||
await session.execute(
|
await session.execute(delete(TaskDependency).where(col(TaskDependency.board_id) == board.id))
|
||||||
delete(TaskFingerprint).where(col(TaskFingerprint.board_id) == board.id)
|
await session.execute(delete(TaskFingerprint).where(col(TaskFingerprint.board_id) == board.id))
|
||||||
)
|
|
||||||
|
# Approvals can reference tasks and agents, so delete before both.
|
||||||
|
await session.execute(delete(Approval).where(col(Approval.board_id) == board.id))
|
||||||
|
|
||||||
|
await session.execute(delete(BoardMemory).where(col(BoardMemory.board_id) == board.id))
|
||||||
|
await session.execute(
|
||||||
|
delete(BoardOnboardingSession).where(col(BoardOnboardingSession.board_id) == board.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Tasks reference agents (assigned_agent_id) and have dependents (fingerprints/dependencies), so
|
||||||
|
# delete tasks before agents.
|
||||||
|
await session.execute(delete(Task).where(col(Task.board_id) == board.id))
|
||||||
|
|
||||||
if agents:
|
if agents:
|
||||||
agent_ids = [agent.id for agent in agents]
|
agent_ids = [agent.id for agent in agents]
|
||||||
await session.execute(
|
await session.execute(
|
||||||
delete(ActivityEvent).where(col(ActivityEvent.agent_id).in_(agent_ids))
|
delete(ActivityEvent).where(col(ActivityEvent.agent_id).in_(agent_ids))
|
||||||
)
|
)
|
||||||
await session.execute(delete(Agent).where(col(Agent.id).in_(agent_ids)))
|
await session.execute(delete(Agent).where(col(Agent.id).in_(agent_ids)))
|
||||||
await session.execute(delete(Approval).where(col(Approval.board_id) == board.id))
|
|
||||||
await session.execute(delete(BoardMemory).where(col(BoardMemory.board_id) == board.id))
|
|
||||||
await session.execute(
|
|
||||||
delete(BoardOnboardingSession).where(col(BoardOnboardingSession.board_id) == board.id)
|
|
||||||
)
|
|
||||||
await session.execute(delete(Task).where(col(Task.board_id) == board.id))
|
|
||||||
await session.delete(board)
|
await session.delete(board)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
return OkResponse()
|
return OkResponse()
|
||||||
|
|||||||
@@ -613,6 +613,8 @@ async def create_task(
|
|||||||
if blocked_by and (task.assigned_agent_id is not None or task.status != "inbox"):
|
if blocked_by and (task.assigned_agent_id is not None or task.status != "inbox"):
|
||||||
raise _blocked_task_error(blocked_by)
|
raise _blocked_task_error(blocked_by)
|
||||||
session.add(task)
|
session.add(task)
|
||||||
|
# Ensure the task exists in the DB before inserting dependency rows.
|
||||||
|
await session.flush()
|
||||||
for dep_id in normalized_deps:
|
for dep_id in normalized_deps:
|
||||||
session.add(
|
session.add(
|
||||||
TaskDependency(
|
TaskDependency(
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ DEFAULT_GATEWAY_FILES = frozenset(
|
|||||||
{
|
{
|
||||||
"AGENTS.md",
|
"AGENTS.md",
|
||||||
"SOUL.md",
|
"SOUL.md",
|
||||||
|
"SELF.md",
|
||||||
|
"AUTONOMY.md",
|
||||||
"TOOLS.md",
|
"TOOLS.md",
|
||||||
"IDENTITY.md",
|
"IDENTITY.md",
|
||||||
"USER.md",
|
"USER.md",
|
||||||
@@ -51,6 +53,10 @@ DEFAULT_GATEWAY_FILES = frozenset(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# These files are intended to evolve within the agent workspace. Provision them if missing,
|
||||||
|
# but avoid overwriting existing content during updates.
|
||||||
|
PRESERVE_AGENT_EDITABLE_FILES = frozenset({"SELF.md", "AUTONOMY.md"})
|
||||||
|
|
||||||
HEARTBEAT_LEAD_TEMPLATE = "HEARTBEAT_LEAD.md"
|
HEARTBEAT_LEAD_TEMPLATE = "HEARTBEAT_LEAD.md"
|
||||||
HEARTBEAT_AGENT_TEMPLATE = "HEARTBEAT_AGENT.md"
|
HEARTBEAT_AGENT_TEMPLATE = "HEARTBEAT_AGENT.md"
|
||||||
MAIN_TEMPLATE_MAP = {
|
MAIN_TEMPLATE_MAP = {
|
||||||
@@ -115,6 +121,25 @@ def _workspace_path(agent: Agent, workspace_root: str) -> str:
|
|||||||
return f"{root}/workspace-{_slugify(key)}"
|
return f"{root}/workspace-{_slugify(key)}"
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_workspace_file(
|
||||||
|
workspace_path: str,
|
||||||
|
name: str,
|
||||||
|
content: str,
|
||||||
|
*,
|
||||||
|
overwrite: bool = False,
|
||||||
|
) -> None:
|
||||||
|
if not workspace_path or not name:
|
||||||
|
return
|
||||||
|
root = Path(workspace_path)
|
||||||
|
path = root / name
|
||||||
|
if not overwrite and path.exists():
|
||||||
|
return
|
||||||
|
root.mkdir(parents=True, exist_ok=True)
|
||||||
|
tmp_path = path.with_suffix(f"{path.suffix}.tmp")
|
||||||
|
tmp_path.write_text(content, encoding="utf-8")
|
||||||
|
tmp_path.replace(path)
|
||||||
|
|
||||||
|
|
||||||
def _build_context(
|
def _build_context(
|
||||||
agent: Agent,
|
agent: Agent,
|
||||||
board: Board,
|
board: Board,
|
||||||
@@ -484,7 +509,7 @@ async def provision_agent(
|
|||||||
|
|
||||||
context = _build_context(agent, board, gateway, auth_token, user)
|
context = _build_context(agent, board, gateway, auth_token, user)
|
||||||
supported = set(await _supported_gateway_files(client_config))
|
supported = set(await _supported_gateway_files(client_config))
|
||||||
supported.add("USER.md")
|
supported.update({"USER.md", "SELF.md", "AUTONOMY.md"})
|
||||||
existing_files = await _gateway_agent_files_index(agent_id, client_config)
|
existing_files = await _gateway_agent_files_index(agent_id, client_config)
|
||||||
include_bootstrap = True
|
include_bootstrap = True
|
||||||
if action == "update" and not force_bootstrap:
|
if action == "update" and not force_bootstrap:
|
||||||
@@ -501,9 +526,25 @@ async def provision_agent(
|
|||||||
supported,
|
supported,
|
||||||
include_bootstrap=include_bootstrap,
|
include_bootstrap=include_bootstrap,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Ensure editable template files exist locally (best-effort) without overwriting.
|
||||||
|
for name in PRESERVE_AGENT_EDITABLE_FILES:
|
||||||
|
content = rendered.get(name)
|
||||||
|
if not content:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
_ensure_workspace_file(workspace_path, name, content, overwrite=False)
|
||||||
|
except OSError:
|
||||||
|
# Local workspace may not be writable/available; fall back to gateway API.
|
||||||
|
pass
|
||||||
for name, content in rendered.items():
|
for name, content in rendered.items():
|
||||||
if content == "":
|
if content == "":
|
||||||
continue
|
continue
|
||||||
|
if name in PRESERVE_AGENT_EDITABLE_FILES:
|
||||||
|
# Never overwrite; only provision if missing.
|
||||||
|
entry = existing_files.get(name)
|
||||||
|
if entry and entry.get("missing") is not True:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
await openclaw_call(
|
await openclaw_call(
|
||||||
"agents.files.set",
|
"agents.files.set",
|
||||||
@@ -543,7 +584,7 @@ async def provision_main_agent(
|
|||||||
|
|
||||||
context = _build_main_context(agent, gateway, auth_token, user)
|
context = _build_main_context(agent, gateway, auth_token, user)
|
||||||
supported = set(await _supported_gateway_files(client_config))
|
supported = set(await _supported_gateway_files(client_config))
|
||||||
supported.add("USER.md")
|
supported.update({"USER.md", "SELF.md", "AUTONOMY.md"})
|
||||||
existing_files = await _gateway_agent_files_index(agent_id, client_config)
|
existing_files = await _gateway_agent_files_index(agent_id, client_config)
|
||||||
include_bootstrap = action != "update" or force_bootstrap
|
include_bootstrap = action != "update" or force_bootstrap
|
||||||
if action == "update" and not force_bootstrap:
|
if action == "update" and not force_bootstrap:
|
||||||
@@ -564,6 +605,10 @@ async def provision_main_agent(
|
|||||||
for name, content in rendered.items():
|
for name, content in rendered.items():
|
||||||
if content == "":
|
if content == "":
|
||||||
continue
|
continue
|
||||||
|
if name in PRESERVE_AGENT_EDITABLE_FILES:
|
||||||
|
entry = existing_files.get(name)
|
||||||
|
if entry and entry.get("missing") is not True:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
await openclaw_call(
|
await openclaw_call(
|
||||||
"agents.files.set",
|
"agents.files.set",
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ This workspace is your home. Treat it as the source of truth.
|
|||||||
## Every session
|
## Every session
|
||||||
Before doing anything else:
|
Before doing anything else:
|
||||||
1) Read SOUL.md (identity, boundaries)
|
1) Read SOUL.md (identity, boundaries)
|
||||||
2) Read SELF.md (evolving identity, preferences) if it exists
|
2) Read AUTONOMY.md (how to decide when to act vs ask)
|
||||||
3) Read USER.md (who you serve)
|
3) Read SELF.md (evolving identity, preferences) if it exists
|
||||||
4) Read memory/YYYY-MM-DD.md for today and yesterday (create memory/ if missing)
|
4) Read USER.md (who you serve)
|
||||||
5) If this is the main or direct session, also read MEMORY.md
|
5) Read memory/YYYY-MM-DD.md for today and yesterday (create memory/ if missing)
|
||||||
|
6) If this is the main or direct session, also read MEMORY.md
|
||||||
|
|
||||||
## Memory
|
## Memory
|
||||||
- Daily log: memory/YYYY-MM-DD.md
|
- Daily log: memory/YYYY-MM-DD.md
|
||||||
|
|||||||
34
templates/AUTONOMY.md
Normal file
34
templates/AUTONOMY.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# AUTONOMY.md
|
||||||
|
|
||||||
|
This file defines how you decide when to act vs when to ask.
|
||||||
|
|
||||||
|
## Current settings (from onboarding, if provided)
|
||||||
|
- Autonomy level: {{ identity_autonomy_level or "balanced" }}
|
||||||
|
- Update cadence: {{ identity_update_cadence or "n/a" }}
|
||||||
|
- Verbosity: {{ identity_verbosity or "n/a" }}
|
||||||
|
- Output format: {{ identity_output_format or "n/a" }}
|
||||||
|
|
||||||
|
## Safety gates (always)
|
||||||
|
- No external side effects (emails, posts, purchases, production changes) without explicit human approval.
|
||||||
|
- No destructive actions without asking first (or an approval), unless the task explicitly instructs it and rollback is trivial.
|
||||||
|
- If requirements are unclear or info is missing and you cannot proceed reliably: do not guess. Ask for clarification (use board chat, approvals, or tag `@lead`).
|
||||||
|
- Prefer reversible steps and small increments. Keep a paper trail in task comments.
|
||||||
|
|
||||||
|
## Autonomy levels
|
||||||
|
|
||||||
|
### ask_first
|
||||||
|
- Do analysis + propose a plan, but ask before taking any non-trivial action.
|
||||||
|
- Only do trivial, reversible, internal actions without asking (read files, grep, draft options).
|
||||||
|
|
||||||
|
### balanced
|
||||||
|
- Proceed with low-risk internal work autonomously (read/search/patch/tests) and post progress.
|
||||||
|
- Ask before irreversible changes, ambiguous scope decisions, or anything that could waste hours.
|
||||||
|
|
||||||
|
### autonomous
|
||||||
|
- Move fast on internal work: plan, execute, validate, and report results without waiting.
|
||||||
|
- Still ask for human approval for external side effects and risky/destructive actions.
|
||||||
|
|
||||||
|
## Collaboration defaults
|
||||||
|
- If you are idle/unassigned: pick 1 in-progress/review task owned by someone else and leave a concrete, helpful comment (analysis, patch, repro, tests, edge cases).
|
||||||
|
- If you notice duplicate work: flag it and propose a merge/split so there is one clear DRI per deliverable.
|
||||||
|
|
||||||
@@ -7,10 +7,11 @@ There is no memory yet. Create what is missing and proceed without blocking.
|
|||||||
## Non‑interactive bootstrap (default)
|
## Non‑interactive bootstrap (default)
|
||||||
1) Create `memory/` if missing.
|
1) Create `memory/` if missing.
|
||||||
2) Ensure `MEMORY.md` exists (create if missing).
|
2) Ensure `MEMORY.md` exists (create if missing).
|
||||||
3) Ensure either `SELF.md` exists (create if missing) or `MEMORY.md` contains an up-to-date `## SELF` section.
|
3) Ensure `AUTONOMY.md` exists (create if missing).
|
||||||
4) Read `IDENTITY.md`, `SOUL.md`, `SELF.md` (if present), and `USER.md`.
|
4) Ensure either `SELF.md` exists (create if missing) or `MEMORY.md` contains an up-to-date `## SELF` section.
|
||||||
5) If any fields are blank, leave them blank. Do not invent values.
|
5) Read `IDENTITY.md`, `SOUL.md`, `AUTONOMY.md`, `SELF.md` (if present), and `USER.md`.
|
||||||
6) If `BASE_URL`, `AUTH_TOKEN`, and `BOARD_ID` are set in `TOOLS.md`, check in
|
6) If any fields are blank, leave them blank. Do not invent values.
|
||||||
|
7) If `BASE_URL`, `AUTH_TOKEN`, and `BOARD_ID` are set in `TOOLS.md`, check in
|
||||||
to Mission Control to mark the agent online:
|
to Mission Control to mark the agent online:
|
||||||
```bash
|
```bash
|
||||||
curl -s -X POST "$BASE_URL/api/v1/agent/heartbeat" \
|
curl -s -X POST "$BASE_URL/api/v1/agent/heartbeat" \
|
||||||
@@ -18,9 +19,9 @@ curl -s -X POST "$BASE_URL/api/v1/agent/heartbeat" \
|
|||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"name": "'$AGENT_NAME'", "board_id": "'$BOARD_ID'", "status": "online"}'
|
-d '{"name": "'$AGENT_NAME'", "board_id": "'$BOARD_ID'", "status": "online"}'
|
||||||
```
|
```
|
||||||
7) Write a short note to `MEMORY.md` that bootstrap completed and list any
|
8) Write a short note to `MEMORY.md` that bootstrap completed and list any
|
||||||
missing fields (e.g., user name, timezone).
|
missing fields (e.g., user name, timezone).
|
||||||
8) Delete this file.
|
9) Delete this file.
|
||||||
|
|
||||||
## Optional: if a human is already present
|
## Optional: if a human is already present
|
||||||
You may ask a short, single message to fill missing fields. If no reply arrives
|
You may ask a short, single message to fill missing fields. If no reply arrives
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ This workspace belongs to the **Main Agent** for this gateway. You are not tied
|
|||||||
## Every session
|
## Every session
|
||||||
Before doing anything else:
|
Before doing anything else:
|
||||||
1) Read SOUL.md (identity, boundaries)
|
1) Read SOUL.md (identity, boundaries)
|
||||||
2) Read SELF.md (evolving identity, preferences) if it exists
|
2) Read AUTONOMY.md (how to decide when to act vs ask)
|
||||||
3) Read USER.md (who you serve)
|
3) Read SELF.md (evolving identity, preferences) if it exists
|
||||||
4) Read memory/YYYY-MM-DD.md for today and yesterday (create memory/ if missing)
|
4) Read USER.md (who you serve)
|
||||||
5) If this is the main or direct session, also read MEMORY.md
|
5) Read memory/YYYY-MM-DD.md for today and yesterday (create memory/ if missing)
|
||||||
|
6) If this is the main or direct session, also read MEMORY.md
|
||||||
|
|
||||||
## Mission Control API (required)
|
## Mission Control API (required)
|
||||||
- All work outputs must be sent to Mission Control via HTTP using:
|
- All work outputs must be sent to Mission Control via HTTP using:
|
||||||
|
|||||||
@@ -34,10 +34,11 @@ Each session, you wake up fresh. These files _are_ your memory. Read them. Updat
|
|||||||
|
|
||||||
Read order (recommended):
|
Read order (recommended):
|
||||||
1) `SOUL.md` - stable core (this file)
|
1) `SOUL.md` - stable core (this file)
|
||||||
2) `SELF.md` - evolving identity and preferences (if present; otherwise keep a "SELF" section in `MEMORY.md`)
|
2) `AUTONOMY.md` - decision policy (when to act vs ask)
|
||||||
3) `USER.md` - who you serve, plus board context
|
3) `SELF.md` - evolving identity and preferences (if present; otherwise keep a "SELF" section in `MEMORY.md`)
|
||||||
4) `memory/YYYY-MM-DD.md` - recent raw logs (today + yesterday)
|
4) `USER.md` - who you serve, plus board context
|
||||||
5) `MEMORY.md` - curated long-term knowledge (main/direct sessions)
|
5) `memory/YYYY-MM-DD.md` - recent raw logs (today + yesterday)
|
||||||
|
6) `MEMORY.md` - curated long-term knowledge (main/direct sessions)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user