feat(autonomy): add followup.create primitive and tool-continuation directives to enforce silence-kills-work

This commit is contained in:
operator
2026-10-05 05:24:45 +00:00
parent 03374795d8
commit c90e096192
5 changed files with 172 additions and 16 deletions
+40 -1
View File
@@ -733,6 +733,40 @@ def _service_restart_build(a):
return [sys.executable, os.path.join(BIN_DIR, 'box-sys-op.py'), 'service.restart']
def _followup_create_validate(raw):
if not isinstance(raw, dict):
raise OpError('args must be an object')
allowed = {'agent', 'in_m', 'prompt', 'thread', 'sidechat', 'sender'}
for k in raw:
if k not in allowed:
raise OpError(f'unknown arg: {k}')
agent = _agent(raw.get('agent', 'opm'))
sender = raw.get('sender')
if sender:
sender = _agent(sender)
try:
in_m = float(raw.get('in_m', 1))
except (TypeError, ValueError):
raise OpError('in_m must be a number')
if in_m < 0.05 or in_m > 1440:
raise OpError('in_m must be between 0.05 and 1440 minutes')
prompt = _clean_message(raw.get('prompt'))
sidechat = raw.get('thread') or raw.get('sidechat')
if sidechat and not TARGET_RE.fullmatch(str(sidechat)):
raise OpError('thread/sidechat must match safe identifier')
return {
'agent': agent,
'sender': sender or agent,
'in_m': in_m,
'prompt': prompt,
'thread': str(sidechat) if sidechat else None
}
def _followup_create_build(a):
return [sys.executable, os.path.join(BIN_DIR, 'box-sys-op.py'), 'followup.create']
def _swarm_spawn_validate(raw):
if not isinstance(raw, dict):
raise OpError('args must be an object')
@@ -924,6 +958,11 @@ OPS = {
'timeout': 30, 'side_effecting': False,
'desc': 'List all subagent swarms and their counts',
},
'followup.create': {
'validate': _followup_create_validate, 'build': _followup_create_build,
'timeout': 30, 'side_effecting': True,
'desc': 'Schedule a delayed autonomous self-followup reminder via transient systemd timer',
},
'exec.ping': {
'validate': _health_validate,
'build': lambda a: ['/bin/echo', 'PONG'],
@@ -1114,7 +1153,7 @@ class Handler(BaseHTTPRequestHandler):
).hexdigest()[:16]})
t0 = time.monotonic()
try:
stdin_input = json.dumps(clean) if op.startswith(('files.', 'web.', 'service.')) else None
stdin_input = json.dumps(clean) if op.startswith(('files.', 'web.', 'service.', 'followup.')) else None
p = subprocess.run(argv, input=stdin_input, capture_output=True, text=True,
timeout=spec['timeout'], cwd=WORK_DIR)
rc = p.returncode