Consultant escalation policy + milestone delivery checks
Manager policy, active only when a company employs a consultant: - a task that blows its wall-clock budget or blocks on machine failure (crashed worker, capability gap — anything except needs_input) is reassigned to the consultant with the failure context appended - every objective rollup spawns a consultant review task: verify each deliverable against acceptance criteria, write milestone-check.md, verdict per deliverable, fix nothing Also: employee_role_check widened to include consultant (db + live); consultant sprite shirt color in the office. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4796274ea5
commit
7ab6db469d
3 files changed
+56
-2
No files matched your search
+1
-1
@@ -19,7 +19,7 @@ CREATE TABLE employee (
|
||||
name text NOT NULL,
|
||||
title text NOT NULL,
|
||||
role text NOT NULL DEFAULT 'engineer'
|
||||
CHECK (role IN ('manager','engineer','qa')),
|
||||
CHECK (role IN ('manager','engineer','qa','consultant')),
|
||||
department text NOT NULL DEFAULT '',
|
||||
manager_id uuid REFERENCES employee(id),
|
||||
hermes_profile text, -- Hermes profile name (ADR-001)
|
||||
|
||||
@@ -163,6 +163,25 @@ class Dispatcher:
|
||||
event_type="milestone.update",
|
||||
company_id=row["company_id"], work_item_id=row["id"],
|
||||
payload={"objective_title": row["title"], "summary": update})
|
||||
# milestone hit + consultant employed -> independent delivery check
|
||||
consultant = await self._consultant_for(row["company_id"])
|
||||
if consultant:
|
||||
await conn.execute(
|
||||
"INSERT INTO work_item (company_id, parent_id, type, title, "
|
||||
"description, status, owner_id, acceptance_criteria) "
|
||||
"VALUES (%s, %s, 'review', %s, %s, 'ready', %s, %s)",
|
||||
(row["company_id"], row["id"],
|
||||
f"Milestone delivery check: {row['title'][:60]}",
|
||||
"The objective just completed. Independently verify delivery: "
|
||||
"read the deliverable files in the workspace produced by its "
|
||||
"tasks (their reports are appended below), check each against "
|
||||
"the stated acceptance criteria, and write "
|
||||
"milestone-check.md (overwrite if present) with a verdict per "
|
||||
"deliverable — delivered / partial / missing — plus any "
|
||||
"defects found. Do not fix anything yourself.",
|
||||
consultant["id"],
|
||||
db.Json(["milestone-check.md exists with a verdict per "
|
||||
"deliverable and evidence for each verdict"])))
|
||||
log.info("objective %s rolled up to done", row["id"])
|
||||
|
||||
async def _dispatch_ready(self) -> None:
|
||||
@@ -284,10 +303,45 @@ class Dispatcher:
|
||||
elif status == "blocked":
|
||||
await self._block(item, kt.get("block_reason") or "hermes reported blocked",
|
||||
event_type="task.blocked")
|
||||
# machine failure (crashed worker, capability gap) -> consultant;
|
||||
# needs_input stays blocked for the shareholder answer button
|
||||
if kt.get("block_kind") != "needs_input":
|
||||
await self._escalate(item, "local worker failed "
|
||||
f"({kt.get('last_failure_error') or kt.get('block_kind') or 'blocked'})")
|
||||
elif await self._over_budget(item):
|
||||
await self._cap(item, kt)
|
||||
await self._escalate(item, "wall-clock budget exceeded")
|
||||
# todo/scheduled/ready/running: still waiting
|
||||
|
||||
async def _consultant_for(self, company_id) -> dict | None:
|
||||
return await db.fetch_one(
|
||||
"SELECT id, name FROM employee WHERE company_id = %s AND role = 'consultant' "
|
||||
"ORDER BY created_at LIMIT 1", company_id)
|
||||
|
||||
async def _escalate(self, item: dict, reason: str) -> bool:
|
||||
"""Manager policy: hand a failing task to the company consultant.
|
||||
Only fires when a consultant is employed (hiring one = opting in to
|
||||
external billing) and the failing owner isn't already the consultant."""
|
||||
consultant = await self._consultant_for(item["company_id"])
|
||||
if not consultant or consultant["id"] == item["owner_id"]:
|
||||
return False
|
||||
note = (f"\n\n[Escalated to consultant {consultant['name']}: {reason}. "
|
||||
"A previous attempt by local staff did not deliver; produce the "
|
||||
"deliverable yourself, completely.]")
|
||||
async with db.pool.connection() as conn:
|
||||
async with conn.transaction():
|
||||
await conn.execute(
|
||||
"UPDATE work_item SET owner_id=%s, status='ready', hermes_task_id=NULL, "
|
||||
"description=description || %s, updated_at=now() WHERE id=%s",
|
||||
(consultant["id"], note, item["id"]))
|
||||
await conn.execute(
|
||||
"UPDATE employee SET status='idle' WHERE id=%s", (item["employee_id"],))
|
||||
await db.append_event(conn, actor="system", event_type="task.escalated",
|
||||
company_id=item["company_id"], work_item_id=item["id"],
|
||||
payload={"to": consultant["name"], "reason": reason})
|
||||
log.info("escalated %s to consultant (%s)", item["id"], reason)
|
||||
return True
|
||||
|
||||
async def _resume(self, item: dict) -> None:
|
||||
async with db.pool.connection() as conn:
|
||||
async with conn.transaction():
|
||||
|
||||
@@ -63,7 +63,7 @@ const ROOMS = {
|
||||
conf: { test:(x,y)=> x>18*T && y<8*T, W:[17*T+8, 4*T+8], D:[19*T+8, 4*T+8] },
|
||||
lab: { test:(x,y)=> x>20*T && y>12*T, W:[24*T+8, 11*T+8], D:[24*T+8, 13*T+8] },
|
||||
};
|
||||
const ROLE_SHIRT = { manager:'#e0b354', engineer:'#5c9de0', qa:'#b48ce0' };
|
||||
const ROLE_SHIRT = { manager:'#e0b354', engineer:'#5c9de0', qa:'#b48ce0', consultant:'#e6e2d8' };
|
||||
const HAIR = ['#3a2a1c','#151412','#c9a24b','#8a3b22','#4a4e57'];
|
||||
const px = (x,y,w,h,c) => { ctx.fillStyle=c; ctx.fillRect(x|0,y|0,w,h); };
|
||||
const hash = s => [...s].reduce((a,ch)=>(a*31+ch.charCodeAt(0))>>>0, 7);
|
||||
|
||||
Reference in new issue
Block a user