File size: 15,544 Bytes
f7e1070
 
 
 
 
 
 
 
 
e36381e
f7e1070
 
 
 
 
 
e36381e
f7e1070
 
e36381e
f7e1070
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e36381e
f7e1070
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e36381e
f7e1070
 
e36381e
f7e1070
 
e36381e
f7e1070
 
e36381e
f7e1070
 
 
e36381e
f7e1070
 
 
e36381e
f7e1070
 
 
e36381e
f7e1070
7fd3e2c
f7e1070
e36381e
f7e1070
 
 
 
e36381e
f7e1070
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
#!/usr/bin/env bash
# Cloud dev worker β€” parallel team of cloud-based coders like Claude Code multi-agent.
# Each provider picks a DIFFERENT 'ready' priority (avoid duplicating work).
# Output goes to same dir/format as qwen-coder β†’ validator + reviewer + auto-commit reuses.
#
# Usage: dev-cloud-worker.sh <provider>
#   provider = github | samba | cloudflare | groq | gemini
#
# Rate-limit aware per provider (set by cron schedule, NOT inside script).
# Cross-worker coordination: lockfile per (priority, provider) in ~/.surrogate/state/dev-locks/
# Global priority lock: 30-min window, so same priority only gets fresh attempt per provider
# every 30 min (prevents redundant work, allows tournament of implementations over time).
set -u

PROVIDER="${1:?usage: dev-cloud-worker.sh <github|samba|cloudflare|groq|gemini>}"

LOG="$HOME/.surrogate/logs/dev-cloud-$PROVIDER.log"
OUT_DIR="$HOME/.hermes/workspace/dev-cloud-$PROVIDER"
SHARED="$HOME/.hermes/workspace/swarm-shared"
LOCK_DIR="$HOME/.surrogate/state/dev-locks"
mkdir -p "$(dirname "$LOG")" "$OUT_DIR" "$LOCK_DIR"

START=$(date +%s)

# -------- Pick a priority --------
# Two modes:
#   (A) Daemon-driven (queue mode): HERMES_PRIO_ID env var is set by the daemon
#       after BLPOP + redis lock. We trust the daemon's lock and just pick that
#       priority from priority.json β€” skipping the file-lock check that was
#       designed for cron mode and now rejects daemon-driven work.
#   (B) Cron-driven (legacy): no env var. Use file-lock selection: 30-min
#       self-skip + 15-min any-provider skip. Still useful when cron fires N
#       providers simultaneously.
PRIORITY=$(python3 -c "
import json, os, time, sys
try:
    with open('$SHARED/priority.json') as f: d = json.load(f)
except Exception as e:
    print('', file=sys.stderr); sys.exit(0)

now = time.time()
PROVIDER = '$PROVIDER'
LOCK_DIR = '$LOCK_DIR'
PINNED = os.environ.get('HERMES_PRIO_ID', '').strip()

# Mode A: daemon pinned this priority β€” no file-lock gating, trust redis lock.
if PINNED:
    for p in d.get('priorities', []):
        if p.get('id') == PINNED and p.get('status') == 'ready':
            print(json.dumps(p))
            # Still touch the file lock (useful for heuristics elsewhere, e.g.
            # the multi-priority batch collector below which reads lock mtimes).
            open(os.path.join(LOCK_DIR, f'{PINNED}_{PROVIDER}'), 'w').close()
            sys.exit(0)
    # Pinned priority missing/not ready β€” fall through to generic selection
    print(f'pinned {PINNED} not found/not-ready β€” falling back to selection', file=sys.stderr)

# Mode B: cron-driven selection via file locks.
recent_any = set()
recent_self = set()
try:
    for fn in os.listdir(LOCK_DIR):
        parts = fn.rsplit('_', 1)
        if len(parts) != 2: continue
        prio_id, prov = parts
        try:
            mtime = os.path.getmtime(os.path.join(LOCK_DIR, fn))
        except OSError: continue
        age = now - mtime
        if age < 900:  # 15 min β€” any provider blocks
            recent_any.add(prio_id)
        if prov == PROVIDER and age < 1800:  # 30 min β€” this provider re-skip
            recent_self.add(prio_id)
except FileNotFoundError: pass

prov_offset = {'github':0,'samba':1,'cloudflare':2,'groq':3,'gemini':4,'qwen-local':5}.get(PROVIDER, 0)

priorities = [p for p in d.get('priorities', []) if p.get('status') == 'ready']
rotated = priorities[prov_offset:] + priorities[:prov_offset]

for p in rotated:
    pid = p.get('id','')
    if not pid: continue
    if pid in recent_any: continue
    if pid in recent_self: continue
    print(json.dumps(p))
    open(os.path.join(LOCK_DIR, f'{pid}_{PROVIDER}'), 'w').close()
    sys.exit(0)
sys.exit(0)
" 2>>"$LOG")

if [[ -z "$PRIORITY" ]]; then
    echo "[$(date '+%H:%M:%S')] no free priority (all locked or none ready)" >> "$LOG"
    exit 0
fi

# Multi-priority per run: each worker handles up to 3 priorities in parallel subshells
# Env var MULTI_PRIORITY_COUNT=3 (override via env if needed)
MULTI_COUNT=${MULTI_PRIORITY_COUNT:-3}

# Collect up to MULTI_COUNT priorities (first one already picked above)
PRIORITIES_JSON=$(python3 -c "
import json, os, time
ps = json.loads('''$PRIORITY'''.replace('\n', ' '))
# Already picked one β€” fetch more from priority.json
priorities = [ps]
try:
    with open('$SHARED/priority.json') as f: d = json.load(f)
    ready = [p for p in d.get('priorities', []) if p.get('status') == 'ready' and p.get('id') != ps.get('id')]
    # Check locks
    lock_dir = '$LOCK_DIR'
    now = time.time()
    for extra in ready:
        pid = extra.get('id','')
        if not pid: continue
        # Skip if any provider locked in last 15 min
        blocked = False
        try:
            for fn in os.listdir(lock_dir):
                if fn.startswith(pid + '_') and now - os.path.getmtime(os.path.join(lock_dir, fn)) < 900:
                    blocked = True
                    break
        except FileNotFoundError: pass
        if blocked: continue
        priorities.append(extra)
        # Touch lock
        open(os.path.join(lock_dir, f'{pid}_$PROVIDER'), 'w').close()
        if len(priorities) >= $MULTI_COUNT: break
except Exception: pass
print(json.dumps(priorities))
" 2>/dev/null || echo "[$PRIORITY]")

echo "[$(date '+%H:%M:%S')] $PROVIDER batch: $(echo "$PRIORITIES_JSON" | python3 -c "import json,sys; print(len(json.loads(sys.stdin.read())))") priorities" >> "$LOG"

# Work on first one (serially inside this script β€” parallel at provider level via cron)
# Future: subshell per priority β€” for now, 1 per invocation but worker fires multiple
PRIORITY=$(echo "$PRIORITIES_JSON" | python3 -c "import json,sys; p = json.loads(sys.stdin.read())[0]; print(json.dumps(p))")

PRIO_ID=$(echo "$PRIORITY" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['id'])")
PRIO_TITLE=$(echo "$PRIORITY" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['title'])")
PRIO_PROJECT=$(echo "$PRIORITY" | python3 -c "import json,sys; print(json.loads(sys.stdin.read()).get('project','?'))")

echo "[$(date '+%H:%M:%S')] $PROVIDER picked $PRIO_ID ($PRIO_PROJECT: ${PRIO_TITLE:0:60})" >> "$LOG"

# -------- Rich context injection (B: enrich with repo + similar funcs + few-shot + deltas) --------
source "$HOME/.surrogate/bin/lib/context_builder.sh"
build_rich_context "$PRIO_PROJECT" "$PRIO_ID" "$PRIO_TITLE"
# Sets: REPO_MAP, SIMILAR_FUNCS, RAG_EXAMPLES, SEMANTIC_RAG, FEWSHOT_ACCEPTED, ANTI_PATTERNS, PROMPT_DELTAS, PRIO_SPEC

# Build prompt via temp file (avoids bash double-interpretation of backticks in command substitution)
PROMPT_FILE=$(/usr/bin/mktemp)
trap "rm -f $PROMPT_FILE" EXIT

# Build prompt. When a detailed spec exists (>500 chars), it's authoritative β€”
# drop the fuzzy RAG/SIMILAR/FEWSHOT signals that would bloat the prompt past
# the tightest provider's request-size cap (Groq free tier ~20KB).
HAS_SPEC=0
[[ ${#PRIO_SPEC} -gt 500 ]] && HAS_SPEC=1

cat > "$PROMPT_FILE" <<EOF
You are $PROVIDER-cloud-dev. Team: github(Codestral), samba(DeepSeek-V3.1), cloudflare(R1-distill-32B), groq(Qwen3-32B), cerebras(Qwen3-235B), nvidia(Llama-3.3-70B), qwen-local(Qwen-Coder-7B). Output goes to tournament where best-of-N is synthesized by Sonnet.

⚠️ HARD QUALITY RULES (reviewer blocks if violated β€” no exceptions):

### Code completeness
- NEVER truncate lines. If \`raise HTTPException(...\` appears, write it completely with status_code, detail, every arg.
- NEVER use \`# TODO\` or \`# ...\` in production code. If unsure, write the simplest working version.
- EVERY function has a complete body; no stubs unless the spec explicitly says "scaffold".

### Imports (most-frequent reviewer bug)
- Use ABSOLUTE imports matching the project's layout: \`from app.models.finding import Finding\`, NOT \`from .models import Finding\` (relative imports break when tests run from repo root).
- Verify every imported symbol EXISTS in REPO MAP below. If the symbol is not mapped, do NOT import it β€” ask via comment "# need to verify <symbol> exists" or pick an alternate.
- Common name collisions: never shadow stdlib (\`uuid.py\`, \`logging.py\`, \`typing.py\` as module names).

### Async patterns (second-most-frequent bug)
- \`boto3\` is SYNC. To call it from async code, wrap with \`await asyncio.to_thread(client.method, ...)\`. The callable is \`client.method\`; the args come AFTER (do NOT do \`asyncio.to_thread(client.method(...))\`).
- Paginators: \`paginator = client.get_paginator('op'); async for page in aiter(paginator.paginate(...))\` OR manually loop pages inside a to_thread wrapper. Do NOT wrap \`.paginate(...)\` itself in to_thread β€” \`.paginate()\` returns an iterator, not a coroutine.
- \`await\` only on awaitables. \`async for\` only on async iterators. Use \`aiter()\` to bridge sync→async iterators.

### Pydantic / SQLAlchemy
- Pydantic v2: fields with enums MUST be typed as the Enum, not \`str\` (type hints drive validation).
- SQLAlchemy models: every column has explicit type + nullable/default. Use \`Mapped[str | None]\` syntax (v2), not \`Column(...)\`.
- ORM β†’ Pydantic: use \`model_config = ConfigDict(from_attributes=True)\` (v2) not deprecated \`from_orm\`.

### Test files
- Absolute imports in tests: \`from app.services.X import Y\` (tests run via \`pytest backend/tests/\` from project root).
- Use existing fixtures from \`conftest.py\` β€” do NOT re-define \`async_session\`, \`test_client\`, etc.
- Each test independent + self-seeding. NO \`pytest.mark.order\` chaining unless spec says so.

### Validator gates (enforced automatically, 0/100 if fail)
1. \`ast.parse\` β€” syntax OK
2. \`import <top-level-dep>\` β€” only stdlib + real deps (boto3, pydantic, sqlalchemy, fastapi, etc.)
3. \`pytest -x\` on embedded tests β€” must PASS (not just collect)
4. No secrets (AWS keys, tokens) in output

### Anti-hallucination
- Imports: ONLY stdlib OR packages actually in the project's pyproject.toml/package.json OR ones you verified via WebFetch
- APIs: every function/class must exist in REPO MAP or be a well-known library method
- If unsure whether an API exists β†’ write a stub with \`raise NotImplementedError("verify <api>")\` so reviewer sees it explicitly, rather than hallucinating a signature

$([ -n "$PROMPT_DELTAS" ] && echo "=== ACTIVE-LEARNED RULES (avoid these past mistakes) ===" && echo "$PROMPT_DELTAS")

=== TASK ===
PROJECT: $PRIO_PROJECT
PRIORITY_ID: $PRIO_ID
TITLE: $PRIO_TITLE
EOF

if [[ $HAS_SPEC -eq 1 ]]; then
    # Authoritative path: spec + repo map + authoritative sources (NEW) + anti-patterns.
    cat >> "$PROMPT_FILE" <<EOF

=== FULL SPEC (AUTHORITATIVE β€” follow exactly) ===
$PRIO_SPEC

=== REPO MAP (existing files/symbols β€” do not invent new modules) ===
$REPO_MAP

$([ -n "$AUTHORITATIVE_CONTEXT" ] && echo "=== AUTHORITATIVE KNOWLEDGE (from task-routed sources β€” use as ground truth) ===" && echo "$AUTHORITATIVE_CONTEXT")

$([ -n "$HERMES_RECALL" ] && echo "=== HERMES HANDLED SIMILAR BEFORE (learn from past decisions) ===" && echo "$HERMES_RECALL")

$([ -n "$GRAPH_CONTEXT" ] && echo "=== RELATED PRIORITIES + LEARNED RULES (graph) ===" && echo "$GRAPH_CONTEXT")

$([ -n "$ANTI_PATTERNS" ] && echo "=== DO NOT REPEAT THESE BUGS (from recent rejections) ===" && echo "$ANTI_PATTERNS")
EOF
else
    # No spec β€” use full RAG scaffolding to give the model grounding.
    cat >> "$PROMPT_FILE" <<EOF

=== REPO MAP (all files + exported symbols) ===
$REPO_MAP

=== EXISTING SIMILAR FUNCTIONS IN THIS PROJECT (match their style) ===
$SIMILAR_FUNCS

=== RAG (FTS keyword-matched project patterns) ===
$RAG_EXAMPLES

$([ -n "$AUTHORITATIVE_CONTEXT" ] && echo "=== AUTHORITATIVE KNOWLEDGE (task-routed: scraped experts/docs) ===" && echo "$AUTHORITATIVE_CONTEXT")

$([ -n "$HERMES_RECALL" ] && echo "=== HERMES HANDLED SIMILAR BEFORE ===" && echo "$HERMES_RECALL")

=== SEMANTIC RAG (embedding-matched related knowledge) ===
$SEMANTIC_RAG

$([ -n "$FEWSHOT_ACCEPTED" ] && echo "=== GOLD EXAMPLE (previously accepted output, quality β‰₯ 7) ===" && echo "$FEWSHOT_ACCEPTED")

$([ -n "$ANTI_PATTERNS" ] && echo "=== DO NOT REPEAT THESE BUGS (from recent rejections) ===" && echo "$ANTI_PATTERNS")
EOF
fi

if [[ -n "$ANTI_PATTERNS" ]]; then
    echo "=== ANTI-PATTERNS (DO NOT repeat these bugs from recent rejections) ===" >> "$PROMPT_FILE"
    echo "$ANTI_PATTERNS" >> "$PROMPT_FILE"
fi

cat >> "$PROMPT_FILE" <<'OUTPUT_SPEC'

=== OUTPUT (strict structure) ===

## Implementation Plan
- 3-5 bullets in dependency order

## Code
```<language>
# complete runnable, verified imports, no TODO stubs
```

## Tests
```<language>
# 3 cases: happy + edge + error
```

## Acceptance Criteria
- 3 bullets with exact commands/assertions

Total under 2000 tokens.
OUTPUT_SPEC

PROMPT=$(cat "$PROMPT_FILE")

# -------- Route to appropriate cloud bridge --------
case "$PROVIDER" in
    github)
        # Codestral-2501 is Mistral's dedicated code model β€” free via PAT, top-tier for code tasks.
        # Better than gpt-4o-mini for coding specifically. Budget-aware: falls through if HALT.
        RESULT=$(echo "$PROMPT" | "$HOME/.surrogate/bin/github-bridge.sh" --model codestral 2>>"$LOG")
        ;;
    samba|sambanova)
        RESULT=$(echo "$PROMPT" | "$HOME/.surrogate/bin/sambanova-bridge.sh" --model deepseek 2>>"$LOG")
        ;;
    cloudflare|cf)
        RESULT=$(echo "$PROMPT" | "$HOME/.surrogate/bin/cloudflare-bridge.sh" --model deepseek 2>>"$LOG")
        ;;
    groq)
        RESULT=$(echo "$PROMPT" | "$HOME/.surrogate/bin/groq-bridge.sh" --model qwen 2>>"$LOG")
        ;;
    gemini)
        # Use ai-fallback's gemini path
        RESULT=$(echo "$PROMPT" | "$HOME/.surrogate/bin/ai-fallback.sh" --force gemini 2>>"$LOG")
        ;;
    cerebras)
        # Wafer-scale β€” fastest inference on planet (~2000 tok/s). Qwen3 235B excellent for code.
        RESULT=$(echo "$PROMPT" | "$HOME/.surrogate/bin/cerebras-bridge.sh" --model big 2>>"$LOG")
        ;;
    nvidia|nim)
        # NVIDIA NIM β€” Llama 3.3 70B, diverse model pool (Nemotron, DeepSeek-R1, Qwen-Coder)
        RESULT=$(echo "$PROMPT" | "$HOME/.surrogate/bin/nvidia-bridge.sh" --model qwen 2>>"$LOG")
        ;;
    chutes_DISABLED_402)
        # Chutes.ai aggregator β€” free tier needs activation; currently may 402
        RESULT=$(echo "$PROMPT" | "$HOME/.surrogate/bin/chutes-bridge.sh" --model deepseek 2>>"$LOG")
        ;;
    surrogate|surrogate-1)
        # ΰΈ™ΰΉ‰ΰΈ­ΰΈ‡ β€” local Ollama, Ashira-personalized (Qwen2.5-Coder-7B + Thai/DevSecOps prompt)
        # Will be upgraded with LoRA adapter after RunPod training.
        RESULT=$(echo "$PROMPT" | "$HOME/.surrogate/bin/surrogate-bridge.sh" 2>>"$LOG")
        ;;
    *)
        echo "[$(date '+%H:%M:%S')] unknown provider $PROVIDER" >> "$LOG"
        exit 1
        ;;
esac

DUR=$(( $(date +%s) - START ))
if [[ -z "$RESULT" ]]; then
    echo "[$(date '+%H:%M:%S')] $PROVIDER failed after ${DUR}s" >> "$LOG"
    # Remove lock so another provider can try
    /bin/rm -f "$LOCK_DIR/${PRIO_ID}_${PROVIDER}" 2>/dev/null
    exit 1
fi

# -------- Save output (same schema as qwen-coder for unified review pipeline) --------
DATE=$(date +%Y-%m-%d_%H-%M)
OUT="$OUT_DIR/${PRIO_ID}_${DATE}.md"

cat > "$OUT" <<EOF
---
priority_id: $PRIO_ID
project: $PRIO_PROJECT
title: $PRIO_TITLE
model: $PROVIDER-cloud
worker: dev-cloud-$PROVIDER
ran_at: $(date -u +%Y-%m-%dT%H:%M:%SZ)
duration_s: $DUR
reviewed: false
---

$RESULT
EOF

echo "[$(date '+%H:%M:%S')] βœ… $PROVIDER β†’ $OUT (${DUR}s, $(wc -c < "$OUT") bytes)" >> "$LOG"