feat: enhance agent provisioning by adding AUTONOMY.md and ensuring task dependencies are handled correctly

This commit is contained in:
Abhimanyu Saharan
2026-02-07 02:42:33 +05:30
parent c1d63f8178
commit a4442eb9d5
9 changed files with 123 additions and 29 deletions

View File

@@ -188,6 +188,8 @@ async def create_task(
if agent.board_id and agent.board_id != board.id:
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
session.add(task)
# Ensure the task exists in the DB before inserting dependency rows.
await session.flush()
for dep_id in normalized_deps:
session.add(
TaskDependency(

View File

@@ -28,6 +28,7 @@ from app.models.board_memory import BoardMemory
from app.models.board_onboarding import BoardOnboardingSession
from app.models.boards import Board
from app.models.gateways import Gateway
from app.models.task_dependencies import TaskDependency
from app.models.task_fingerprints import TaskFingerprint
from app.models.tasks import Task
from app.schemas.boards import BoardCreate, BoardRead, BoardUpdate
@@ -228,21 +229,27 @@ async def delete_board(
if task_ids:
await session.execute(delete(ActivityEvent).where(col(ActivityEvent.task_id).in_(task_ids)))
await session.execute(
delete(TaskFingerprint).where(col(TaskFingerprint.board_id) == board.id)
)
await session.execute(delete(TaskDependency).where(col(TaskDependency.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:
agent_ids = [agent.id for agent in agents]
await session.execute(
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(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.commit()
return OkResponse()

View File

@@ -613,6 +613,8 @@ async def create_task(
if blocked_by and (task.assigned_agent_id is not None or task.status != "inbox"):
raise _blocked_task_error(blocked_by)
session.add(task)
# Ensure the task exists in the DB before inserting dependency rows.
await session.flush()
for dep_id in normalized_deps:
session.add(
TaskDependency(

View File

@@ -41,6 +41,8 @@ DEFAULT_GATEWAY_FILES = frozenset(
{
"AGENTS.md",
"SOUL.md",
"SELF.md",
"AUTONOMY.md",
"TOOLS.md",
"IDENTITY.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_AGENT_TEMPLATE = "HEARTBEAT_AGENT.md"
MAIN_TEMPLATE_MAP = {
@@ -115,6 +121,25 @@ def _workspace_path(agent: Agent, workspace_root: str) -> str:
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(
agent: Agent,
board: Board,
@@ -484,7 +509,7 @@ async def provision_agent(
context = _build_context(agent, board, gateway, auth_token, user)
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)
include_bootstrap = True
if action == "update" and not force_bootstrap:
@@ -501,9 +526,25 @@ async def provision_agent(
supported,
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():
if content == "":
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:
await openclaw_call(
"agents.files.set",
@@ -543,7 +584,7 @@ async def provision_main_agent(
context = _build_main_context(agent, gateway, auth_token, user)
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)
include_bootstrap = action != "update" or force_bootstrap
if action == "update" and not force_bootstrap:
@@ -564,6 +605,10 @@ async def provision_main_agent(
for name, content in rendered.items():
if content == "":
continue
if name in PRESERVE_AGENT_EDITABLE_FILES:
entry = existing_files.get(name)
if entry and entry.get("missing") is not True:
continue
try:
await openclaw_call(
"agents.files.set",

View File

@@ -8,10 +8,11 @@ This workspace is your home. Treat it as the source of truth.
## Every session
Before doing anything else:
1) Read SOUL.md (identity, boundaries)
2) Read SELF.md (evolving identity, preferences) if it exists
3) Read USER.md (who you serve)
4) Read memory/YYYY-MM-DD.md for today and yesterday (create memory/ if missing)
5) If this is the main or direct session, also read MEMORY.md
2) Read AUTONOMY.md (how to decide when to act vs ask)
3) Read SELF.md (evolving identity, preferences) if it exists
4) Read USER.md (who you serve)
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
- Daily log: memory/YYYY-MM-DD.md

34
templates/AUTONOMY.md Normal file
View 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.

View File

@@ -7,10 +7,11 @@ There is no memory yet. Create what is missing and proceed without blocking.
## Noninteractive bootstrap (default)
1) Create `memory/` 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.
4) Read `IDENTITY.md`, `SOUL.md`, `SELF.md` (if present), and `USER.md`.
5) If any fields are blank, leave them blank. Do not invent values.
6) If `BASE_URL`, `AUTH_TOKEN`, and `BOARD_ID` are set in `TOOLS.md`, check in
3) Ensure `AUTONOMY.md` exists (create if missing).
4) Ensure either `SELF.md` exists (create if missing) or `MEMORY.md` contains an up-to-date `## SELF` section.
5) Read `IDENTITY.md`, `SOUL.md`, `AUTONOMY.md`, `SELF.md` (if present), and `USER.md`.
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:
```bash
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" \
-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).
8) Delete this file.
9) Delete this file.
## Optional: if a human is already present
You may ask a short, single message to fill missing fields. If no reply arrives

View File

@@ -8,10 +8,11 @@ This workspace belongs to the **Main Agent** for this gateway. You are not tied
## Every session
Before doing anything else:
1) Read SOUL.md (identity, boundaries)
2) Read SELF.md (evolving identity, preferences) if it exists
3) Read USER.md (who you serve)
4) Read memory/YYYY-MM-DD.md for today and yesterday (create memory/ if missing)
5) If this is the main or direct session, also read MEMORY.md
2) Read AUTONOMY.md (how to decide when to act vs ask)
3) Read SELF.md (evolving identity, preferences) if it exists
4) Read USER.md (who you serve)
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)
- All work outputs must be sent to Mission Control via HTTP using:

View File

@@ -34,10 +34,11 @@ Each session, you wake up fresh. These files _are_ your memory. Read them. Updat
Read order (recommended):
1) `SOUL.md` - stable core (this file)
2) `SELF.md` - evolving identity and preferences (if present; otherwise keep a "SELF" section in `MEMORY.md`)
3) `USER.md` - who you serve, plus board context
4) `memory/YYYY-MM-DD.md` - recent raw logs (today + yesterday)
5) `MEMORY.md` - curated long-term knowledge (main/direct sessions)
2) `AUTONOMY.md` - decision policy (when to act vs ask)
3) `SELF.md` - evolving identity and preferences (if present; otherwise keep a "SELF" section in `MEMORY.md`)
4) `USER.md` - who you serve, plus board context
5) `memory/YYYY-MM-DD.md` - recent raw logs (today + yesterday)
6) `MEMORY.md` - curated long-term knowledge (main/direct sessions)
---