← back to Chief Of Operations
auto-claim one DW-priority open ticket
585906585091c1da01abf0a552d19ced40c5012f · 2026-09-17 09:01:07 -0700 · Steve Abrams
Files touched
M README.mdM chief_ops.pyM supervisor_loop.sh
Diff
commit 585906585091c1da01abf0a552d19ced40c5012f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Sep 17 09:01:07 2026 -0700
auto-claim one DW-priority open ticket
---
README.md | 3 ++-
chief_ops.py | 29 +++++++++++++++++++++++++++--
supervisor_loop.sh | 1 +
3 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 8e2b9fe..eac96c6 100644
--- a/README.md
+++ b/README.md
@@ -14,8 +14,9 @@ The Arrange action delegates to the existing master router through the desktop b
## Continuous ticket supervision
-`supervisor_loop.sh` refreshes `state/queue.json` every 60 seconds under launchd (`com.steve.chief-of-operations`). The queue includes open, blocked, and doing tickets, scores Designer Wallcoverings/client-impact work first, and records the next safe step. It does not auto-claim tickets or bypass approval gates.
+`supervisor_loop.sh` refreshes `state/queue.json` every 60 seconds under launchd (`com.steve.chief-of-operations`). The queue includes open, blocked, and doing tickets, scores Designer Wallcoverings/client-impact work first, and records the next safe step. It automatically claims one highest-priority open ticket at a time as `chief-of-operations`; blocked tickets remain surfaced for blocker review.
```sh
./chief_ops.py queue
+./chief_ops.py claim
```
diff --git a/chief_ops.py b/chief_ops.py
index 2df3443..c9ea583 100755
--- a/chief_ops.py
+++ b/chief_ops.py
@@ -9,7 +9,9 @@ HOME = Path.home()
ROUTER = HOME / '.claude/skills/dot-screen-router'
DOTBAR = HOME / 'Projects/desktop-dotbar'
TICKET_LIB = HOME / 'Projects/ticket-system/lib.js'
+TK = HOME / 'Projects/ticket-system/tk'
STATE = Path(__file__).with_name('state')
+OPS_AGENT = 'chief-of-operations'
STATUS = HOME / '.local/state/abrams-terminal-status/ttys016.json'
TICKET = 'TK-11882-chief-of-operations-harness-for-terminal'
SCREENSHOTS = [HOME/'Desktop/arrange-click-screen1.png', HOME/'Desktop/arrange-click-screen2.png']
@@ -101,6 +103,28 @@ def ticket_queue(write=False):
STATE.mkdir(exist_ok=True); (STATE/'queue.json').write_text(json.dumps(result,indent=2)+'\n')
return result
+def auto_claim():
+ q=ticket_queue(write=True)
+ if not q.get('ok'): return {'ok':False,'error':q.get('error','queue unavailable')}
+ claimed_path=STATE/'claimed.json'
+ current=None
+ if claimed_path.exists():
+ try: current=json.loads(claimed_path.read_text()).get('ticket')
+ except Exception: current=None
+ if current:
+ active=next((x for x in q['items'] if x['id']==current), None)
+ if active and active.get('status') in ('open','doing') and active.get('assignee')==OPS_AGENT:
+ return {'ok':True,'action':'hold','ticket':current,'reason':'already claimed and active'}
+ candidate=next((x for x in q['items'] if x.get('status')=='open'), None)
+ if not candidate:
+ return {'ok':True,'action':'none','reason':'no open ticket; blocked work remains surfaced','blocked_count':q.get('blocked_count',0)}
+ rc,out,err=cmd([str(TK),'take',candidate['id'],'-a',OPS_AGENT], timeout=10)
+ if rc:
+ return {'ok':False,'action':'claim-failed','ticket':candidate['id'],'error':err or out}
+ STATE.mkdir(exist_ok=True)
+ claimed_path.write_text(json.dumps({'ticket':candidate['id'],'claimed_at':time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime()),'title':candidate['title']},indent=2)+'\n')
+ return {'ok':True,'action':'claimed','ticket':candidate['id'],'title':candidate['title'],'score':candidate['score']}
+
def check():
status={}
@@ -152,9 +176,10 @@ class Handler(BaseHTTPRequestHandler):
b=json.dumps(data, indent=2).encode(); self.send_response(200); self.send_header('Content-Type','application/json'); self.send_header('Content-Length',str(len(b))); self.end_headers(); self.wfile.write(b)
def main():
- ap=argparse.ArgumentParser(); ap.add_argument('action', choices=['check','arrange','queue','serve'], nargs='?', default='check'); ap.add_argument('--port',type=int,default=9896); ap.add_argument('--write',action='store_true'); a=ap.parse_args()
+ ap=argparse.ArgumentParser(); ap.add_argument('action', choices=['check','arrange','queue','claim','serve'], nargs='?', default='check'); ap.add_argument('--port',type=int,default=9896); ap.add_argument('--write',action='store_true'); a=ap.parse_args()
if a.action=='check': print(json.dumps(check(), indent=2)); return
if a.action=='arrange': print(json.dumps(arrange(), indent=2)); return
- if a.action=='queue': print(json.dumps(ticket_queue(write=a.write or True), indent=2)); return
+ if a.action=='queue': print(json.dumps(ticket_queue(write=True), indent=2)); return
+ if a.action=='claim': print(json.dumps(auto_claim(), indent=2)); return
print(f'Chief of Operations on http://127.0.0.1:{a.port}', flush=True); ThreadingHTTPServer(('127.0.0.1',a.port), Handler).serve_forever()
if __name__=='__main__': main()
diff --git a/supervisor_loop.sh b/supervisor_loop.sh
index 6060911..9f500e3 100755
--- a/supervisor_loop.sh
+++ b/supervisor_loop.sh
@@ -3,6 +3,7 @@ set -u
cd "$(dirname "$0")"
INTERVAL="${CHIEF_OPS_INTERVAL:-60}"
while true; do
+ ./chief_ops.py claim >> state/supervisor.log 2>&1 || true
./chief_ops.py queue --write >> state/supervisor.log 2>&1 || true
sleep "$INTERVAL"
done
← 030f843 report supervisor launchd health
·
back to Chief Of Operations
·
monitor all configured exo hosts 777f586 →