diff --git a/ops/agent-state/lib-state.sh b/ops/agent-state/lib-state.sh index 1b168da66..276076486 100755 --- a/ops/agent-state/lib-state.sh +++ b/ops/agent-state/lib-state.sh @@ -14,15 +14,6 @@ _state_dir() { echo "$STATE_ROOT/$agent" } -# Atomic write: write to tmp file, then rename. Prevents partial reads. -_atomic_write() { - local filepath="$1" - local content="$2" - local tmpfile="${filepath}.tmp.$$" - echo "$content" > "$tmpfile" - mv -f "$tmpfile" "$filepath" -} - # --- Report (current status) --- state_read_report() { @@ -37,17 +28,18 @@ state_update_report() { local summary="$3" local file="$(_state_dir "$agent")/report.json" - # Read existing, merge with updates using python (available on VPS) + _STATE_FILE="$file" _STATE_AGENT="$agent" _STATE_STATUS="$status" \ + _STATE_SUMMARY="$summary" _STATE_TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ python3 -c " -import json, sys +import json, os try: - with open('$file') as f: + with open(os.environ['_STATE_FILE']) as f: data = json.load(f) except: - data = {'agent': '$agent'} -data['status'] = '$status' -data['summary'] = '''$summary''' -data['updated_at'] = '$(date -u +%Y-%m-%dT%H:%M:%SZ)' + data = {'agent': os.environ['_STATE_AGENT']} +data['status'] = os.environ['_STATE_STATUS'] +data['summary'] = os.environ['_STATE_SUMMARY'] +data['updated_at'] = os.environ['_STATE_TS'] print(json.dumps(data, indent=2)) " | _atomic_write_stdin "$file" } @@ -75,25 +67,35 @@ state_finalize_report() { local next_priority="${11:-null}" local file="$(_state_dir "$agent")/report.json" + _STATE_FILE="$file" _STATE_AGENT="$agent" _STATE_STATUS="$status" \ + _STATE_SUMMARY="$summary" _STATE_SESSION_ID="$session_id" \ + _STATE_STARTED="$started_at" _STATE_ENDED="$ended_at" \ + _STATE_OUTCOME="$outcome" _STATE_SOURCES="$sources" \ + _STATE_BRANCH="$branch" _STATE_PR="$pr_number" \ + _STATE_NEXT="$next_priority" \ python3 -c " -import json +import json, os +e = os.environ +sources = int(e['_STATE_SOURCES']) if e['_STATE_SOURCES'].isdigit() else 0 +pr = int(e['_STATE_PR']) if e['_STATE_PR'].isdigit() else None +next_p = None if e['_STATE_NEXT'] == 'null' else e['_STATE_NEXT'] data = { - 'agent': '$agent', - 'updated_at': '$ended_at', - 'status': '$status', - 'summary': '''$summary''', + 'agent': e['_STATE_AGENT'], + 'updated_at': e['_STATE_ENDED'], + 'status': e['_STATE_STATUS'], + 'summary': e['_STATE_SUMMARY'], 'current_task': None, 'last_session': { - 'id': '$session_id', - 'started_at': '$started_at', - 'ended_at': '$ended_at', - 'outcome': '$outcome', - 'sources_archived': $sources, - 'branch': '$branch', - 'pr_number': $pr_number + 'id': e['_STATE_SESSION_ID'], + 'started_at': e['_STATE_STARTED'], + 'ended_at': e['_STATE_ENDED'], + 'outcome': e['_STATE_OUTCOME'], + 'sources_archived': sources, + 'branch': e['_STATE_BRANCH'], + 'pr_number': pr }, 'blocked_by': None, - 'next_priority': $([ "$next_priority" = "null" ] && echo "None" || echo "'$next_priority'") + 'next_priority': next_p } print(json.dumps(data, indent=2)) " | _atomic_write_stdin "$file" @@ -113,19 +115,23 @@ state_start_session() { started_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)" local file="$(_state_dir "$agent")/session.json" + _STATE_FILE="$file" _STATE_AGENT="$agent" _STATE_SID="$session_id" \ + _STATE_STARTED="$started_at" _STATE_TYPE="$type" _STATE_DOMAIN="$domain" \ + _STATE_BRANCH="$branch" _STATE_MODEL="$model" _STATE_TIMEOUT="$timeout" \ python3 -c " -import json +import json, os +e = os.environ data = { - 'agent': '$agent', - 'session_id': '$session_id', - 'started_at': '$started_at', + 'agent': e['_STATE_AGENT'], + 'session_id': e['_STATE_SID'], + 'started_at': e['_STATE_STARTED'], 'ended_at': None, - 'type': '$type', - 'domain': '$domain', - 'branch': '$branch', + 'type': e['_STATE_TYPE'], + 'domain': e['_STATE_DOMAIN'], + 'branch': e['_STATE_BRANCH'], 'status': 'running', - 'model': '$model', - 'timeout_seconds': $timeout, + 'model': e['_STATE_MODEL'], + 'timeout_seconds': int(e['_STATE_TIMEOUT']), 'research_question': None, 'belief_targeted': None, 'disconfirmation_target': None, @@ -149,13 +155,18 @@ state_end_session() { local pr_number="${4:-null}" local file="$(_state_dir "$agent")/session.json" + _STATE_FILE="$file" _STATE_OUTCOME="$outcome" _STATE_SOURCES="$sources" \ + _STATE_PR="$pr_number" _STATE_TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ python3 -c " -import json -with open('$file') as f: +import json, os +e = os.environ +with open(e['_STATE_FILE']) as f: data = json.load(f) -data['ended_at'] = '$(date -u +%Y-%m-%dT%H:%M:%SZ)' -data['status'] = '$outcome' -data['sources_archived'] = $sources +data['ended_at'] = e['_STATE_TS'] +data['status'] = e['_STATE_OUTCOME'] +data['sources_archived'] = int(e['_STATE_SOURCES']) if e['_STATE_SOURCES'].isdigit() else 0 +pr = e.get('_STATE_PR', 'null') +data['pr_number'] = int(pr) if pr.isdigit() else None print(json.dumps(data, indent=2)) " | _atomic_write_stdin "$file" } @@ -168,13 +179,17 @@ state_journal_append() { shift 2 # Remaining args are key=value pairs for extra fields local file="$(_state_dir "$agent")/journal.jsonl" - local extras="" - for kv in "$@"; do - local key="${kv%%=*}" - local val="${kv#*=}" - extras="$extras, \"$key\": \"$val\"" - done - echo "{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"event\":\"$event\"$extras}" >> "$file" + + _STATE_TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)" _STATE_EVT="$event" \ + python3 -c " +import json, os, sys +entry = {'ts': os.environ['_STATE_TS'], 'event': os.environ['_STATE_EVT']} +for pair in sys.argv[1:]: + k, _, v = pair.partition('=') + if k: + entry[k] = v +print(json.dumps(entry)) +" "$@" >> "$file" } # --- Metrics --- @@ -185,25 +200,29 @@ state_update_metrics() { local sources="${3:-0}" local file="$(_state_dir "$agent")/metrics.json" + _STATE_FILE="$file" _STATE_AGENT="$agent" _STATE_OUTCOME="$outcome" \ + _STATE_SOURCES="$sources" _STATE_TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ python3 -c " -import json +import json, os +e = os.environ try: - with open('$file') as f: + with open(e['_STATE_FILE']) as f: data = json.load(f) except: - data = {'agent': '$agent', 'lifetime': {}, 'rolling_30d': {}} + data = {'agent': e['_STATE_AGENT'], 'lifetime': {}, 'rolling_30d': {}} lt = data.setdefault('lifetime', {}) lt['sessions_total'] = lt.get('sessions_total', 0) + 1 -if '$outcome' == 'completed': +outcome = e['_STATE_OUTCOME'] +if outcome == 'completed': lt['sessions_completed'] = lt.get('sessions_completed', 0) + 1 -elif '$outcome' == 'timeout': +elif outcome == 'timeout': lt['sessions_timeout'] = lt.get('sessions_timeout', 0) + 1 -elif '$outcome' == 'error': +elif outcome == 'error': lt['sessions_error'] = lt.get('sessions_error', 0) + 1 -lt['sources_archived'] = lt.get('sources_archived', 0) + $sources +lt['sources_archived'] = lt.get('sources_archived', 0) + (int(e['_STATE_SOURCES']) if e['_STATE_SOURCES'].isdigit() else 0) -data['updated_at'] = '$(date -u +%Y-%m-%dT%H:%M:%SZ)' +data['updated_at'] = e['_STATE_TS'] print(json.dumps(data, indent=2)) " | _atomic_write_stdin "$file" } @@ -227,17 +246,21 @@ state_send_message() { local file="$inbox/${msg_id}.json" mkdir -p "$inbox" + _STATE_FILE="$file" _STATE_MSGID="$msg_id" _STATE_FROM="$from" \ + _STATE_TO="$to" _STATE_TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + _STATE_TYPE="$type" _STATE_SUBJECT="$subject" _STATE_BODY="$body" \ python3 -c " -import json +import json, os +e = os.environ data = { - 'id': '$msg_id', - 'from': '$from', - 'to': '$to', - 'created_at': '$(date -u +%Y-%m-%dT%H:%M:%SZ)', - 'type': '$type', + 'id': e['_STATE_MSGID'], + 'from': e['_STATE_FROM'], + 'to': e['_STATE_TO'], + 'created_at': e['_STATE_TS'], + 'type': e['_STATE_TYPE'], 'priority': 'normal', - 'subject': '''$subject''', - 'body': '''$body''', + 'subject': e['_STATE_SUBJECT'], + 'body': e['_STATE_BODY'], 'source_ref': None, 'expires_at': None } diff --git a/ops/deploy.sh b/ops/deploy.sh index aef9475ca..31a2f6d1d 100755 --- a/ops/deploy.sh +++ b/ops/deploy.sh @@ -43,7 +43,7 @@ echo "=== Pre-deploy syntax check ===" ERRORS=0 for f in "$REPO_ROOT/ops/pipeline-v2/lib/"*.py "$REPO_ROOT/ops/pipeline-v2/"*.py "$REPO_ROOT/ops/diagnostics/"*.py; do [ -f "$f" ] || continue - if ! python3 -c "import ast; ast.parse(open('$f').read())" 2>/dev/null; then + if ! python3 -c "import ast, sys; ast.parse(open(sys.argv[1]).read())" "$f" 2>/dev/null; then echo "SYNTAX ERROR: $f" ERRORS=$((ERRORS + 1)) fi diff --git a/ops/research-session.sh b/ops/research-session.sh index c66f516d2..abc6ab857 100644 --- a/ops/research-session.sh +++ b/ops/research-session.sh @@ -69,10 +69,9 @@ if [ ! -d "$REPO_DIR/.git" ]; then fi cd "$REPO_DIR" -git config credential.helper "!f() { echo username=m3taversal; echo password=$FORGEJO_ADMIN_TOKEN; }; f" git remote set-url origin "${FORGEJO_URL}/teleo/teleo-codex.git" 2>/dev/null || true -git checkout main >> "$LOG" 2>&1 -git pull --rebase >> "$LOG" 2>&1 +git -c http.extraHeader="Authorization: token $FORGEJO_ADMIN_TOKEN" checkout main >> "$LOG" 2>&1 +git -c http.extraHeader="Authorization: token $FORGEJO_ADMIN_TOKEN" pull --rebase >> "$LOG" 2>&1 # --- Map agent to domain --- case "$AGENT" in @@ -94,13 +93,13 @@ if [ ! -f "$NETWORK_FILE" ]; then else log "Pulling tweets from ${AGENT}'s network..." ACCOUNTS=$(python3 -c " -import json -with open('$NETWORK_FILE') as f: +import json, sys +with open(sys.argv[1]) as f: data = json.load(f) for acct in data.get('accounts', []): if acct.get('tier') in ('core', 'extended'): print(acct['username']) -" 2>/dev/null || true) +" "$NETWORK_FILE" 2>/dev/null || true) TWEET_DATA="" API_CALLS=0 @@ -132,7 +131,7 @@ for acct in data.get('accounts', []): $(python3 -c " import json, sys try: - d = json.load(open('$OUTFILE')) + d = json.load(open(sys.argv[1])) tweets = d.get('tweets', d.get('data', [])) for t in tweets[:20]: text = t.get('text', '')[:500] @@ -144,7 +143,7 @@ try: print() except Exception as e: print(f'Error reading: {e}', file=sys.stderr) -" 2>/dev/null || echo "(failed to parse)")" +" "$OUTFILE" 2>/dev/null || echo "(failed to parse)")" fi done log "API usage: ${API_CALLS} calls, ${API_CACHED} cached for ${AGENT}" @@ -168,7 +167,7 @@ if [ -d "$INBOX_RAW" ] && ls "$INBOX_RAW"/*.json 2>/dev/null | head -1 > /dev/nu $(python3 -c " import json, sys try: - d = json.load(open('$RAWFILE')) + d = json.load(open(sys.argv[1])) tweets = d.get('tweets', d.get('data', [])) for t in tweets[:20]: text = t.get('text', '')[:500] @@ -180,7 +179,7 @@ try: print() except Exception as e: print(f'Error: {e}', file=sys.stderr) -" 2>/dev/null || echo "(failed to parse)")" +" "$RAWFILE" 2>/dev/null || echo "(failed to parse)")" done fi @@ -432,7 +431,7 @@ git commit -m "${AGENT}: research session ${DATE} — ${SOURCE_COUNT} sources ar Pentagon-Agent: ${AGENT_UPPER} " >> "$LOG" 2>&1 # --- Push --- -git push -u origin "$BRANCH" --force >> "$LOG" 2>&1 +git -c http.extraHeader="Authorization: token $AGENT_TOKEN" push -u origin "$BRANCH" --force >> "$LOG" 2>&1 log "Pushed $BRANCH" # --- Check for existing PR on this branch ---