feat: split heartbeat templates and allow lead agent creation

This commit is contained in:
Abhimanyu Saharan
2026-02-05 15:11:27 +05:30
parent d29a3f5cda
commit e02e1eeca2
6 changed files with 317 additions and 24 deletions

View File

@@ -189,8 +189,26 @@ def list_agents(
async def create_agent(
payload: AgentCreate,
session: Session = Depends(get_session),
auth: AuthContext = Depends(require_admin_auth),
actor: ActorContext = Depends(require_admin_or_agent),
) -> Agent:
if actor.actor_type == "agent":
if not actor.agent or not actor.agent.is_board_lead:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Only board leads can create agents",
)
if not actor.agent.board_id:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Board lead must be assigned to a board",
)
if payload.board_id and payload.board_id != actor.agent.board_id:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Board leads can only create agents in their own board",
)
payload = AgentCreate(**{**payload.model_dump(), "board_id": actor.agent.board_id})
board = _require_board(session, payload.board_id)
gateway, client_config = _require_gateway(session, board)
data = payload.model_dump()
@@ -230,7 +248,14 @@ async def create_agent(
)
session.commit()
try:
await provision_agent(agent, board, gateway, raw_token, auth.user, action="provision")
await provision_agent(
agent,
board,
gateway,
raw_token,
actor.user if actor.actor_type == "user" else None,
action="provision",
)
await _send_wakeup_message(agent, client_config, verb="provisioned")
agent.provision_confirm_token_hash = None
agent.provision_requested_at = None

View File

@@ -42,6 +42,9 @@ DEFAULT_GATEWAY_FILES = frozenset(
}
)
HEARTBEAT_LEAD_TEMPLATE = "HEARTBEAT_LEAD.md"
HEARTBEAT_AGENT_TEMPLATE = "HEARTBEAT_AGENT.md"
def _repo_root() -> Path:
return Path(__file__).resolve().parents[3]
@@ -80,6 +83,10 @@ def _template_env() -> Environment:
)
def _heartbeat_template_name(agent: Agent) -> str:
return HEARTBEAT_LEAD_TEMPLATE if agent.is_board_lead else HEARTBEAT_AGENT_TEMPLATE
def _workspace_path(agent_name: str, workspace_root: str) -> str:
if not workspace_root:
raise ValueError("gateway_workspace_root is required")
@@ -219,6 +226,14 @@ def _render_agent_files(
if name == "MEMORY.md":
rendered[name] = "# MEMORY\n\nBootstrap pending.\n"
continue
if name == "HEARTBEAT.md":
heartbeat_template = _heartbeat_template_name(agent)
heartbeat_path = _templates_root() / heartbeat_template
if heartbeat_path.exists():
rendered[name] = (
env.get_template(heartbeat_template).render(**context).strip()
)
continue
override = overrides.get(name)
if override:
rendered[name] = env.from_string(override).render(**context).strip()

View File

@@ -29,7 +29,7 @@ Write things down. Do not rely on short-term context.
## Heartbeats
- HEARTBEAT.md defines what to do on each heartbeat.
- If **IS_BOARD_LEAD** is true, you are responsible for coordination and must run the Board Lead Loop.
- Lead agents receive a lead-specific HEARTBEAT.md. Follow it exactly.
## Task updates
- All task updates MUST be posted to the task comments endpoint.

View File

@@ -1,7 +1,9 @@
# HEARTBEAT.md
> This file is provisioned from HEARTBEAT_LEAD.md or HEARTBEAT_AGENT.md. If you see this template directly, follow the agent loop below.
## Purpose
This file defines the single, authoritative heartbeat loop. Follow it exactly.
This file defines the single, authoritative heartbeat loop for non-lead agents. Follow it exactly.
## Required inputs
- BASE_URL (e.g. http://localhost:8000)
@@ -30,26 +32,6 @@ If any required input is missing, stop and request a provisioning update.
- GET $BASE_URL/api/v1/boards/{BOARD_ID}/tasks must succeed.
- If any check fails, stop and retry next heartbeat.
## Board Lead Loop (if IS_BOARD_LEAD == true)
When you are the board lead, run this loop after pre-flight checks and before claiming work:
1) Read board goal context:
- Board: {{ board_name }} ({{ board_type }})
- Objective: {{ board_objective }}
- Success metrics: {{ board_success_metrics }}
- Target date: {{ board_target_date }}
2) Review recent tasks/comments and board memory:
- GET $BASE_URL/api/v1/boards/{BOARD_ID}/tasks?limit=50
- GET $BASE_URL/api/v1/boards/{BOARD_ID}/memory?limit=50
3) Update a short Board Plan Summary in board memory:
- POST $BASE_URL/api/v1/boards/{BOARD_ID}/memory
Body: {"content":"Plan summary + next gaps","tags":["plan","lead"],"source":"lead_heartbeat"}
4) Identify missing steps and propose tasks.
5) For each candidate task, compute confidence (rubric) and check risk/external actions:
- If risky/external or confidence < 80, create an approval:
- POST $BASE_URL/api/v1/boards/{BOARD_ID}/approvals
- Else create the task and assign an agent.
6) Post a brief status update in board memory (1-3 bullets).
## Heartbeat checklist (run in order)
1) Check in:
```bash

View File

@@ -0,0 +1,107 @@
# HEARTBEAT_AGENT.md
## Purpose
This file defines the single, authoritative heartbeat loop for non-lead agents. Follow it exactly.
## Required inputs
- BASE_URL (e.g. http://localhost:8000)
- AUTH_TOKEN (agent token)
- AGENT_NAME
- AGENT_ID
- BOARD_ID
If any required input is missing, stop and request a provisioning update.
## Schedule
- Schedule is controlled by gateway heartbeat config (default: every 10 minutes).
- On first boot, send one immediate check-in before the schedule starts.
## Nonnegotiable rules
- Task updates go only to task comments (never chat/web).
- Comments must be markdown. Write naturally; be clear and concise.
- Every status change must have a comment within 30 seconds.
- Do not claim a new task if you already have one in progress.
## Preflight checks (before each heartbeat)
- Confirm BASE_URL, AUTH_TOKEN, and BOARD_ID are set.
- Verify API access:
- GET $BASE_URL/healthz must succeed.
- GET $BASE_URL/api/v1/boards must succeed.
- GET $BASE_URL/api/v1/boards/{BOARD_ID}/tasks must succeed.
- If any check fails, stop and retry next heartbeat.
## Heartbeat checklist (run in order)
1) Check in:
```bash
curl -s -X POST "$BASE_URL/api/v1/agents/heartbeat" \
-H "X-Agent-Token: $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "'$AGENT_NAME'", "board_id": "'$BOARD_ID'", "status": "online"}'
```
2) List boards:
```bash
curl -s "$BASE_URL/api/v1/boards" \
-H "X-Agent-Token: $AUTH_TOKEN"
```
3) For the assigned board, list tasks (use filters to avoid large responses):
```bash
curl -s "$BASE_URL/api/v1/boards/{BOARD_ID}/tasks?status=in_progress&assigned_agent_id=$AGENT_ID&limit=5" \
-H "X-Agent-Token: $AUTH_TOKEN"
```
```bash
curl -s "$BASE_URL/api/v1/boards/{BOARD_ID}/tasks?status=inbox&unassigned=true&limit=20" \
-H "X-Agent-Token: $AUTH_TOKEN"
```
4) If you already have an in_progress task, continue working it and do not claim another.
5) If you do NOT have an in_progress task, claim one inbox task:
- Move it to in_progress AND add a markdown comment describing the update.
6) Work the task:
- Post progress comments as you go.
- Completion is a twostep sequence:
6a) Post the full response as a markdown comment using:
POST $BASE_URL/api/v1/boards/{BOARD_ID}/tasks/{TASK_ID}/comments
Example:
```bash
curl -s -X POST "$BASE_URL/api/v1/boards/$BOARD_ID/tasks/$TASK_ID/comments" \
-H "X-Agent-Token: $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"message":"- Update: ...\n- Result: ..."}'
```
6b) Move the task to review.
6b) Move the task to "review":
```bash
curl -s -X PATCH "$BASE_URL/api/v1/boards/{BOARD_ID}/tasks/{TASK_ID}" \
-H "X-Agent-Token: $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status": "review"}'
```
## Definition of Done
- A task is not complete until the draft/response is posted as a task comment.
- Comments must be markdown.
## Common mistakes (avoid)
- Changing status without posting a comment.
- Posting updates in chat/web instead of task comments.
- Claiming a second task while one is already in progress.
- Moving to review before posting the full response.
- Sending Authorization header instead of X-Agent-Token.
## Success criteria (when to say HEARTBEAT_OK)
- Checkin succeeded.
- Tasks were listed successfully.
- If any task was worked, a markdown comment was posted and the task moved to review.
- If any task is inbox or in_progress, do NOT say HEARTBEAT_OK.
## Status flow
```
inbox -> in_progress -> review -> done
```
Do not say HEARTBEAT_OK if there is inbox work or active in_progress work.

164
templates/HEARTBEAT_LEAD.md Normal file
View File

@@ -0,0 +1,164 @@
# HEARTBEAT_LEAD.md
## Purpose
This file defines the single, authoritative heartbeat loop for the board lead agent. Follow it exactly.
## Required inputs
- BASE_URL (e.g. http://localhost:8000)
- AUTH_TOKEN (agent token)
- AGENT_NAME
- AGENT_ID
- BOARD_ID
If any required input is missing, stop and request a provisioning update.
## Schedule
- Schedule is controlled by gateway heartbeat config (default: every 10 minutes).
- On first boot, send one immediate check-in before the schedule starts.
## Nonnegotiable rules
- Task updates go only to task comments (never chat/web).
- Comments must be markdown. Write naturally; be clear and concise.
- Every status change must have a comment within 30 seconds.
- Do not claim a new task if you already have one in progress.
## Preflight checks (before each heartbeat)
- Confirm BASE_URL, AUTH_TOKEN, and BOARD_ID are set.
- Verify API access:
- GET $BASE_URL/healthz must succeed.
- GET $BASE_URL/api/v1/boards must succeed.
- GET $BASE_URL/api/v1/boards/{BOARD_ID}/tasks must succeed.
- If any check fails, stop and retry next heartbeat.
## Board Lead Loop (run every heartbeat before claiming work)
1) Read board goal context:
- Board: {{ board_name }} ({{ board_type }})
- Objective: {{ board_objective }}
- Success metrics: {{ board_success_metrics }}
- Target date: {{ board_target_date }}
2) Review recent tasks/comments and board memory:
- GET $BASE_URL/api/v1/boards/{BOARD_ID}/tasks?limit=50
- GET $BASE_URL/api/v1/boards/{BOARD_ID}/memory?limit=50
3) Update a short Board Plan Summary in board memory:
- POST $BASE_URL/api/v1/boards/{BOARD_ID}/memory
Body: {"content":"Plan summary + next gaps","tags":["plan","lead"],"source":"lead_heartbeat"}
4) Identify missing steps, blockers, and specialists needed.
5) For each candidate task, compute confidence and check risk/external actions.
Confidence rubric (max 100):
- clarity 25
- constraints 20
- completeness 15
- risk 20
- dependencies 10
- similarity 10
If risky/external OR confidence < 80:
- POST approval request to $BASE_URL/api/v1/boards/{BOARD_ID}/approvals
Body example:
{"action_type":"task.create","confidence":75,"payload":{"title":"..."},"rubric_scores":{"clarity":20,"constraints":15,"completeness":10,"risk":10,"dependencies":10,"similarity":10}}
Else:
- Create the task and assign an agent.
6) If workload or skills coverage is insufficient, create new agents.
Rule: you may autocreate agents only when confidence >= 80 and the action is not risky/external.
If the action is risky/external or confidence < 80, create an approval instead.
Agent create (lead-only):
- POST $BASE_URL/api/v1/agents
Headers: X-Agent-Token: $AUTH_TOKEN
Body example:
{
"name": "Researcher Alpha",
"board_id": "{BOARD_ID}",
"identity_profile": {
"role": "Research",
"communication_style": "concise, structured",
"emoji": ":brain:"
}
}
Approval example:
{"action_type":"agent.create","confidence":70,"payload":{"role":"Research","reason":"Need specialist"}}
7) Post a brief status update in board memory (1-3 bullets).
## Heartbeat checklist (run in order)
1) Check in:
```bash
curl -s -X POST "$BASE_URL/api/v1/agents/heartbeat" \
-H "X-Agent-Token: $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "'$AGENT_NAME'", "board_id": "'$BOARD_ID'", "status": "online"}'
```
2) List boards:
```bash
curl -s "$BASE_URL/api/v1/boards" \
-H "X-Agent-Token: $AUTH_TOKEN"
```
3) For the assigned board, list tasks (use filters to avoid large responses):
```bash
curl -s "$BASE_URL/api/v1/boards/{BOARD_ID}/tasks?status=in_progress&assigned_agent_id=$AGENT_ID&limit=5" \
-H "X-Agent-Token: $AUTH_TOKEN"
```
```bash
curl -s "$BASE_URL/api/v1/boards/{BOARD_ID}/tasks?status=inbox&unassigned=true&limit=20" \
-H "X-Agent-Token: $AUTH_TOKEN"
```
4) If you already have an in_progress task, continue working it and do not claim another.
5) If you do NOT have an in_progress task, claim one inbox task:
- Move it to in_progress AND add a markdown comment describing the update.
6) Work the task:
- Post progress comments as you go.
- Completion is a twostep sequence:
6a) Post the full response as a markdown comment using:
POST $BASE_URL/api/v1/boards/{BOARD_ID}/tasks/{TASK_ID}/comments
Example:
```bash
curl -s -X POST "$BASE_URL/api/v1/boards/$BOARD_ID/tasks/$TASK_ID/comments" \
-H "X-Agent-Token: $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"message":"- Update: ...\n- Result: ..."}'
```
6b) Move the task to review.
6b) Move the task to "review":
```bash
curl -s -X PATCH "$BASE_URL/api/v1/boards/{BOARD_ID}/tasks/{TASK_ID}" \
-H "X-Agent-Token: $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status": "review"}'
```
## Definition of Done
- A task is not complete until the draft/response is posted as a task comment.
- Comments must be markdown.
## Common mistakes (avoid)
- Changing status without posting a comment.
- Posting updates in chat/web instead of task comments.
- Claiming a second task while one is already in progress.
- Moving to review before posting the full response.
- Sending Authorization header instead of X-Agent-Token.
## Success criteria (when to say HEARTBEAT_OK)
- Checkin succeeded.
- Tasks were listed successfully.
- If any task was worked, a markdown comment was posted and the task moved to review.
- If any task is inbox or in_progress, do NOT say HEARTBEAT_OK.
## Status flow
```
inbox -> in_progress -> review -> done
```
Do not say HEARTBEAT_OK if there is inbox work or active in_progress work.