summaryrefslogtreecommitdiff
path: root/Omni/Agent/start-worker.sh
blob: 37c3760409e2042561cbe7c6f3142211e235d3da (plain)
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
#!/usr/bin/env bash
set -e

# Omni/Agent/start-worker.sh
# Launches an Amp worker agent in the specified worktree in a loop.
# Usage: ./Omni/Agent/start-worker.sh [worker-directory-name-or-path]
# Example: ./Omni/Agent/start-worker.sh omni-worker-1

TARGET="${1:-omni-worker-1}"

# 1. Find the Main Repo (where node_modules lives)
# The first worktree listed is always the main one
MAIN_REPO="$(git worktree list --porcelain | grep '^worktree ' | head -n 1 | cut -d ' ' -f 2)"
AMP_BIN="$MAIN_REPO/node_modules/.bin/amp"
TASK_BIN="$MAIN_REPO/_/bin/task"

# 2. Resolve Worker Path
if [ -d "$TARGET" ]; then
    WORKER_PATH="$(realpath "$TARGET")"
elif [ -d "$MAIN_REPO/../$TARGET" ]; then
    WORKER_PATH="$(realpath "$MAIN_REPO/../$TARGET")"
else
    echo "Error: Worker directory for '$TARGET' not found."
    exit 1
fi

if [ ! -x "$AMP_BIN" ]; then
    echo "Error: Amp binary not found at '$AMP_BIN'."
    exit 1
fi

# Ensure task binary is built/available
if [ ! -x "$TASK_BIN" ]; then
    echo "Warning: Task binary not found at '$TASK_BIN'. Assuming it's in path or build it first."
fi

# Ensure worker has local task binary (required for sync-tasks.sh)
mkdir -p "$WORKER_PATH/_/bin"
echo "Building 'task' in worker..."
if ! (cd "$WORKER_PATH" && bild Omni/Task.hs); then
    echo "Error: Failed to build 'task' in worker directory."
    exit 1
fi

echo "Starting Worker Agent Loop"
echo "  Worker Path: $WORKER_PATH"
echo "  Amp Binary:  $AMP_BIN"
echo "  Log File:    $WORKER_PATH/_/llm/amp.log"
echo "  Monitor:     tail -f $WORKER_PATH/_/llm/amp.log"
echo "  Press Ctrl+C to stop."

# Function to sync tasks safely
sync_tasks() {
    "$MAIN_REPO/Omni/Agent/sync-tasks.sh" "$@"
}

cd "$WORKER_PATH"

# 3. The Worker Loop
while true; do
    echo "----------------------------------------------------------------"
    echo "$(date): Syncing and checking for work..."
    
    # A. Sync with Live
    # We use 'git rebase' to keep history linear
    # Force checkout to clean up any untracked files from previous runs
    git checkout -f "$TARGET" >/dev/null 2>&1
    
    # Rebase directly on local live branch (shared repo)
    if ! git rebase live >/dev/null 2>&1; then
        echo "Warning: Rebase conflict at start of loop. Aborting rebase and proceeding with local state."
        git rebase --abort || true
    fi
    
    # B. Sync Tasks
    sync_tasks
    
    # C. Find Ready Work
    # We use jq to parse the first task
    # Note: task ready --json returns an array [...]
    TASK_JSON=$("$TASK_BIN" ready --json 2>/dev/null | jq -r '.[0] // empty')
    
    if [ -z "$TASK_JSON" ]; then
        echo "$(date): No ready tasks. Sleeping for 60s..."
        sleep 60
        continue
    fi
    
    TASK_ID=$(echo "$TASK_JSON" | jq -r '.taskId')
    TASK_TITLE=$(echo "$TASK_JSON" | jq -r '.taskTitle')
    TASK_NS=$(echo "$TASK_JSON" | jq -r '.taskNamespace // "root"')
    
    # Verify against live state to prevent re-claiming completed work
    # (This handles cases where local 'InProgress' timestamp > live 'Review' timestamp due to retries)
    git show live:.tasks/tasks.jsonl > .tasks/temp-live-tasks.jsonl 2>/dev/null
    LIVE_TASK=$(grep "\"taskId\":\"$TASK_ID\"" .tasks/temp-live-tasks.jsonl || true)
    LIVE_STATUS=$(echo "$LIVE_TASK" | jq -r '.taskStatus // empty')
    rm -f .tasks/temp-live-tasks.jsonl
    
    if [[ "$LIVE_STATUS" == "Review" ]] || [[ "$LIVE_STATUS" == "Done" ]]; then
        echo "Task $TASK_ID is already $LIVE_STATUS in live. Skipping and updating local state."
        # Force update local DB to match live for this task
        # We can't easily use 'task update' because it updates timestamp.
        # Instead, we just rely on the loop continuing and hopefully 'task import' eventually winning 
        # if we stop touching it. Or we could force import again.
        sleep 60
        continue
    fi

    echo "$(date): Claiming task $TASK_ID: $TASK_TITLE"
    
    # D. Claim Task
    "$TASK_BIN" update "$TASK_ID" in-progress >/dev/null
    sync_tasks --commit >/dev/null
    
    # E. Prepare Branch
    BRANCH_NAME="task/$TASK_ID"
    if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then
        echo "Resuming existing branch $BRANCH_NAME"
        # Force checkout to overwrite untracked files (like .tasks/counters.jsonl)
        # that may have been generated by sync tools but are tracked in the branch.
        git checkout -f "$BRANCH_NAME" >/dev/null
    else
        echo "Creating new branch $BRANCH_NAME from origin/live"
        git fetch origin live >/dev/null 2>&1
        git checkout -b "$BRANCH_NAME" origin/live >/dev/null
    fi
    
    # F. Execute Agent
    echo "Launching Amp to implement task..."
    
    TASK_DETAILS=$("$TASK_BIN" show "$TASK_ID")
    
    echo "----------------------------------------------------------------"
    echo "Task Details:"
    echo "$TASK_DETAILS"
    echo "----------------------------------------------------------------"
    
    # We construct a specific prompt for the agent
    PROMPT="You are a Worker Agent. 
Your goal is to implement the following task:

$TASK_DETAILS

INSTRUCTIONS:
1. Analyze the codebase (use finder/Grep) to understand where to make changes.
2. Implement the changes by editing files.
3. Run tests to verify your work (e.g., 'bild --test Omni/Namespace').
4. Fix any errors found during testing.
5. Do NOT update the task status or manage git branches (the system handles that).
6. When finished and tested, exit.

Context:
- You are working in '$WORKER_PATH'.
- The task is in namespace '$TASK_NS'.
"

    mkdir -p _/llm
    "$AMP_BIN" --log-level debug --log-file "_/llm/amp.log" --dangerously-allow-all -x "$PROMPT"
    
    AGENT_EXIT_CODE=$?
    
    if [ $AGENT_EXIT_CODE -eq 0 ]; then
        echo "Agent finished successfully."
        
        # Update status to review (bundled in commit)
        echo "Marking task $TASK_ID as Review..."
        "$TASK_BIN" update "$TASK_ID" review
        
        # G. Submit Work
        if [ -n "$(git status --porcelain)" ]; then
            echo "Committing changes..."
            git add .
            git commit -m "feat: implement $TASK_ID" || true
        else
            echo "No changes to commit."
        fi
        
        echo "Submitting for review..."
        # Switch back to base
        git checkout "$TARGET" >/dev/null
        
        # Sync again (rebase on latest live)
        # If rebase fails, we MUST abort to avoid leaving the repo in a broken state
        if ! git rebase live >/dev/null 2>&1; then
            echo "Warning: Rebase conflict. Aborting rebase and proceeding with local state."
            git rebase --abort || true
        fi
        
        sync_tasks
        
        # Update status again for signaling
        if "$TASK_BIN" update "$TASK_ID" review; then
            sync_tasks --commit >/dev/null
            echo "Task $TASK_ID submitted for review."
        else
            echo "Error: Failed to update task status to Review."
        fi
        
    else
        echo "Agent failed (exit code $AGENT_EXIT_CODE). Sleeping for 10s before retrying..."
        sleep 10
    fi
    
    echo "Cooldown..."
    sleep 5
done