From a4442eb9d54138a596f28254375c9b34476e7abd Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Sat, 7 Feb 2026 02:42:33 +0530 Subject: [PATCH] feat: enhance agent provisioning by adding AUTONOMY.md and ensuring task dependencies are handled correctly --- backend/app/api/agent.py | 2 + backend/app/api/boards.py | 25 +++++++---- backend/app/api/tasks.py | 2 + backend/app/services/agent_provisioning.py | 49 +++++++++++++++++++++- templates/AGENTS.md | 9 ++-- templates/AUTONOMY.md | 34 +++++++++++++++ templates/BOOTSTRAP.md | 13 +++--- templates/MAIN_AGENTS.md | 9 ++-- templates/SOUL.md | 9 ++-- 9 files changed, 123 insertions(+), 29 deletions(-) create mode 100644 templates/AUTONOMY.md diff --git a/backend/app/api/agent.py b/backend/app/api/agent.py index 1aacb60b..83cd1c49 100644 --- a/backend/app/api/agent.py +++ b/backend/app/api/agent.py @@ -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( diff --git a/backend/app/api/boards.py b/backend/app/api/boards.py index be50d27b..e197b47b 100644 --- a/backend/app/api/boards.py +++ b/backend/app/api/boards.py @@ -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() diff --git a/backend/app/api/tasks.py b/backend/app/api/tasks.py index 3cd5ef21..fdacdd19 100644 --- a/backend/app/api/tasks.py +++ b/backend/app/api/tasks.py @@ -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( diff --git a/backend/app/services/agent_provisioning.py b/backend/app/services/agent_provisioning.py index d58c4e5f..a1214d3d 100644 --- a/backend/app/services/agent_provisioning.py +++ b/backend/app/services/agent_provisioning.py @@ -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", diff --git a/templates/AGENTS.md b/templates/AGENTS.md index c0c853db..96b77b5c 100644 --- a/templates/AGENTS.md +++ b/templates/AGENTS.md @@ -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 diff --git a/templates/AUTONOMY.md b/templates/AUTONOMY.md new file mode 100644 index 00000000..e96121ae --- /dev/null +++ b/templates/AUTONOMY.md @@ -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. + diff --git a/templates/BOOTSTRAP.md b/templates/BOOTSTRAP.md index fd185871..2f54934d 100644 --- a/templates/BOOTSTRAP.md +++ b/templates/BOOTSTRAP.md @@ -7,10 +7,11 @@ There is no memory yet. Create what is missing and proceed without blocking. ## Non‑interactive 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 diff --git a/templates/MAIN_AGENTS.md b/templates/MAIN_AGENTS.md index 3639fa9a..b03c17a1 100644 --- a/templates/MAIN_AGENTS.md +++ b/templates/MAIN_AGENTS.md @@ -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: diff --git a/templates/SOUL.md b/templates/SOUL.md index 8b7a934b..bd73f74f 100644 --- a/templates/SOUL.md +++ b/templates/SOUL.md @@ -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) ---