feat(work): implement synchronous readback gate and cognitive true loopback engine
This commit is contained in:
+69
-12
@@ -31,6 +31,11 @@ try:
|
||||
except ImportError:
|
||||
acp = None
|
||||
|
||||
try:
|
||||
import box_readback_loopback as brl
|
||||
except ImportError:
|
||||
brl = None
|
||||
|
||||
# Color helpers
|
||||
USE_COLOR = sys.stdout.isatty() or os.environ.get("CLICOLOR_FORCE") == "1"
|
||||
|
||||
@@ -741,18 +746,47 @@ def cmd_start(args):
|
||||
pass
|
||||
s.close()
|
||||
|
||||
# 4. Notify agent via muse-chat-api if available
|
||||
chat_script = REPO_ROOT / "bin" / "muse-chat-api.py"
|
||||
if chat_script.exists():
|
||||
msg = f"New build ticket #{issue_num} assigned to you: {title}. Clone/pull ~/workspace/box, checkout dev/{agent}/{issue_num}-work, commit citing 'Fixes #{issue_num}', and push."
|
||||
try:
|
||||
cmd = f"python3 {chat_script} --account {agent} send '{msg}'"
|
||||
os.system(f"{cmd} >/dev/null 2>&1")
|
||||
print(c_green(f"✓ Delivered briefing to {agent} chat session"))
|
||||
except Exception:
|
||||
pass
|
||||
# 4. Notify agent via direct chat with Readback requirement
|
||||
prompt_msg = (
|
||||
f"New build ticket #{issue_num} assigned to you: {title}.\n"
|
||||
f"Goal: {body}\n\n"
|
||||
f"Please confirm with Readback:\n"
|
||||
f"[READBACK] Ticket #{issue_num} | Branch: dev/{agent}/{issue_num}-work | Plan: <short summary>"
|
||||
)
|
||||
|
||||
print(c_bold(f"\nWork ticket #{issue_num} is active and assigned to {agent}.\n"))
|
||||
initial_seq = brl.get_latest_chat_seq(agent) if brl else 0
|
||||
delivered = brl.send_agent_chat(agent, prompt_msg) if brl else False
|
||||
if delivered:
|
||||
print(c_green(f"✓ Delivered briefing to @{agent} chat session"))
|
||||
else:
|
||||
chat_script = REPO_ROOT / "bin" / "muse-chat-api.py"
|
||||
if chat_script.exists():
|
||||
os.system(f"python3 {chat_script} --account {agent} send '{prompt_msg}' >/dev/null 2>&1")
|
||||
print(c_green(f"✓ Delivered briefing to @{agent} chat session (via muse-chat-api)"))
|
||||
else:
|
||||
print(c_yellow(f"⚠️ Direct chat delivery unconfirmed for @{agent}"))
|
||||
|
||||
# 5. Synchronous Readback Gate (Wait up to 45s)
|
||||
if getattr(args, "no_readback", False):
|
||||
print(c_yellow(f"[OVERRIDE] Skipping readback gate (--no-readback specified)."))
|
||||
print(c_bold(f"\nWork ticket #{issue_num} is active and assigned to {agent}.\n"))
|
||||
elif brl:
|
||||
print(c_bold(f"\nEngaging Synchronous Readback Gate (timeout: 45s)..."))
|
||||
rb_res = brl.wait_for_readback(agent, issue_num, initial_seq, timeout_s=45)
|
||||
if rb_res["success"]:
|
||||
brl.handle_readback_success(agent, issue_num, rb_res["snippet"])
|
||||
print(c_green(f"\n🎉 Readback Confirmed from @{agent} in {rb_res['elapsed']}s!"))
|
||||
print(c_dim(f" Excerpt: {rb_res['snippet'][:120]}..."))
|
||||
print(c_bold(f"\nWork ticket #{issue_num} is confirmed in-progress and assigned to {agent}.\n"))
|
||||
else:
|
||||
brl.handle_readback_timeout(agent, issue_num, title)
|
||||
print(c_red(f"\n❌ [READBACK TIMEOUT] Agent @{agent} failed to confirm Ticket #{issue_num} within 45s."))
|
||||
print(c_yellow(f" • Ticket marked 'blocked' in Gitea"))
|
||||
print(c_yellow(f" • Assignment released so @{agent} returns to IDLE"))
|
||||
print(c_dim(f"\nTo retry: box work start '{title}' --to {agent}\nTo bypass: box work start '{title}' --to {agent} --no-readback\n"))
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(c_bold(f"\nWork ticket #{issue_num} is active and assigned to {agent}.\n"))
|
||||
|
||||
def cmd_assign(args):
|
||||
issue_num = args.issue
|
||||
@@ -863,18 +897,34 @@ def cmd_cognitive(args):
|
||||
sys.exit(1)
|
||||
acp.cmd_status(args)
|
||||
|
||||
def cmd_loopback(args):
|
||||
if not brl:
|
||||
print(c_red("Error: box_readback_loopback module not found."))
|
||||
sys.exit(1)
|
||||
dry_run = getattr(args, "dry_run", False)
|
||||
as_json = getattr(args, "json", False)
|
||||
actions = brl.run_loopback_sweep(dry_run=dry_run, verbose=not as_json)
|
||||
if as_json:
|
||||
print(json.dumps({"ok": True, "actions": actions}, indent=2))
|
||||
|
||||
WORK_COMMAND_EXAMPLES = {
|
||||
"box work": [
|
||||
"box work # View fleet workspace dashboard & signals",
|
||||
"box work loopback [--dry-run] # Sweep open tickets with 3-tier loopback escalation",
|
||||
"box work cognitive [agent...] # Live zero-click cognitive sensor probe across fleet",
|
||||
"box work menu <agent> [tab] # Inspect agent profile menu (tasks, timers, approvals)",
|
||||
"box work check [agent] # Audit pre-flight health gates",
|
||||
"box work heal <agent> # Automated remediation & chat nudge",
|
||||
"box work start \"<title>\" --to <agent> # Start & dispatch new build ticket",
|
||||
"box work start \"<title>\" --to <agent> # Start & dispatch new build ticket with readback gate",
|
||||
"box work assign <issue#> --to <agent> # Assign existing ticket",
|
||||
"box work merge <pr#> # Verify tests and merge PR to master",
|
||||
"box work chats --agent <name> # View live multi-agent chat feed",
|
||||
],
|
||||
"box work loopback": [
|
||||
"box work loopback # Sweep active tickets and escalate inactivity",
|
||||
"box work loopback --dry-run # Dry-run inspect without sending pings or comments",
|
||||
"box work loopback --json # Machine-readable output for systemd cron",
|
||||
],
|
||||
"box work menu": [
|
||||
"box work menu muse upcoming # Inspect timers & recurring cron loops",
|
||||
"box work menu 646 activity # Inspect recent tasks & active processes",
|
||||
@@ -1000,6 +1050,7 @@ def main():
|
||||
p_start.add_argument("--goal", help="Optional detailed goal description")
|
||||
p_start.add_argument("--force", action="store_true", help="Bypass pre-flight health gate")
|
||||
p_start.add_argument("--no-heal", action="store_true", help="Fail immediately without attempting auto-heal if pre-flight checks fail")
|
||||
p_start.add_argument("--no-readback", action="store_true", help="Bypass synchronous 45s readback gate")
|
||||
|
||||
p_assign = sub.add_parser("assign", help="Assign existing ticket to an agent")
|
||||
p_assign.add_argument("issue", type=int, help="Issue number (e.g. 215)")
|
||||
@@ -1026,6 +1077,10 @@ def main():
|
||||
p_cog.add_argument("agents", nargs="*", help="Optional agent usernames")
|
||||
p_cog.add_argument("--json", action="store_true", help="Output JSON")
|
||||
|
||||
p_loop = sub.add_parser("loopback", help="Sweep active tickets with 3-tier loopback escalation")
|
||||
p_loop.add_argument("--dry-run", action="store_true", help="Inspect without modifying tickets or sending DMs")
|
||||
p_loop.add_argument("--json", action="store_true", help="Output actions as JSON")
|
||||
|
||||
args = parser.parse_args()
|
||||
action = args.work_action
|
||||
|
||||
@@ -1047,6 +1102,8 @@ def main():
|
||||
cmd_menu(args)
|
||||
elif action == "cognitive":
|
||||
cmd_cognitive(args)
|
||||
elif action == "loopback":
|
||||
cmd_loopback(args)
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user