diff options
| -rw-r--r-- | .envrc | 1 | ||||
| -rw-r--r-- | .tasks/tasks.jsonl | 16 | ||||
| -rw-r--r-- | Omni/Agent/WORKER_AGENT_GUIDE.md | 59 | ||||
| -rwxr-xr-x | Omni/Agent/harvest-tasks.sh | 55 | ||||
| -rwxr-xr-x | Omni/Agent/start-worker.sh | 60 | ||||
| -rw-r--r-- | Omni/Task/Core.hs | 11 |
6 files changed, 156 insertions, 46 deletions
@@ -67,6 +67,7 @@ [[ -n "${CI}" ]] && exit 0 # # create third-party tags + mkdir -p "$CODEROOT"/_/src [[ -L "$CODEROOT"/_/src/.ctags.d ]] || ln -s "$CODEROOT"/.ctags.d "$CODEROOT"/_/src/.ctags.d function MakeExternalTags { "$CODEROOT"/Omni/Ide/MakeTags.py --external $(tr ':' '\n' <<< "$ALL_SOURCES") diff --git a/.tasks/tasks.jsonl b/.tasks/tasks.jsonl index 47ad2de..50e13df 100644 --- a/.tasks/tasks.jsonl +++ b/.tasks/tasks.jsonl @@ -56,8 +56,8 @@ {"taskCreatedAt":"2025-11-13T19:38:33.674140035Z","taskDependencies":[],"taskId":"t-1fbBmXa","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Review and fix type: ignore comments - improve type safety","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:47:09.583640045Z"} {"taskCreatedAt":"2025-11-13T19:38:33.85804778Z","taskDependencies":[],"taskId":"t-1fbC8Nq","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Remove PLR2004 magic number - use constant for month check","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:47:45.120428021Z"} {"taskCreatedAt":"2025-11-13T19:38:34.035597081Z","taskDependencies":[],"taskId":"t-1fbCSZd","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Implement cancel subscription functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:22:51.709672316Z"} -{"taskCreatedAt":"2025-11-13T19:38:34.194926176Z","taskDependencies":[],"taskId":"t-1fbDyr2","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Implement delete account functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:38:34.194926176Z"} -{"taskCreatedAt":"2025-11-13T19:38:34.384489707Z","taskDependencies":[],"taskId":"t-1fbElKv","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Implement change email address functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:38:34.384489707Z"} +{"taskCreatedAt":"2025-11-13T19:38:34.194926176Z","taskDependencies":[],"taskId":"t-1fbDyr2","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Implement delete account functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T21:54:43.703079464Z"} +{"taskCreatedAt":"2025-11-13T19:38:34.384489707Z","taskDependencies":[],"taskId":"t-1fbElKv","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Implement change email address functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:38:34.384489707Z"} {"taskCreatedAt":"2025-11-13T19:38:34.561871604Z","taskDependencies":[],"taskId":"t-1fbF5Tv","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add logout button to account page","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:22:51.65796855Z"} {"taskCreatedAt":"2025-11-13T19:38:34.777721397Z","taskDependencies":[],"taskId":"t-1fbG02X","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Replace Coming Soon placeholder with full account management UI","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:22:51.606196024Z"} {"taskCreatedAt":"2025-11-13T19:38:34.962196629Z","taskDependencies":[],"taskId":"t-1fbGM2m","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add remove button to queue status items","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:20:10.941908917Z"} @@ -65,7 +65,7 @@ {"taskCreatedAt":"2025-11-13T19:38:35.311151364Z","taskDependencies":[],"taskId":"t-1fbIeOF","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Remove 'Logged in as' text from navbar","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:18:17.23552934Z"} {"taskCreatedAt":"2025-11-13T19:38:35.476139354Z","taskDependencies":[],"taskId":"t-1fbIVJL","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Left-align navbar links instead of right-aligned buttons","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:18:17.285578917Z"} {"taskCreatedAt":"2025-11-13T19:38:35.65125955Z","taskDependencies":[],"taskId":"t-1fbJFic","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Remove logout button from navbar (will be in account page)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:18:17.336546723Z"} -{"taskCreatedAt":"2025-11-13T19:54:08.34625259Z","taskDependencies":[],"taskId":"t-1gcR9RV","taskNamespace":"Omni/Bild.nix","taskParent":null,"taskPriority":"P2","taskStatus":"Review","taskTitle":"Add ruff to the developer environment, the 'env' attribute in Bild.nix","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T21:40:17.120513982Z"} +{"taskCreatedAt":"2025-11-13T19:54:08.34625259Z","taskDependencies":[],"taskId":"t-1gcR9RV","taskNamespace":"Omni/Bild.nix","taskParent":null,"taskPriority":"P2","taskStatus":"Review","taskTitle":"Add ruff to the developer environment, the 'env' attribute in Bild.nix","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T21:40:54.645065492Z"} {"taskCreatedAt":"2025-11-13T20:02:50.914482516Z","taskDependencies":[],"taskId":"t-1gMdNJK","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Fix dev mode banner styling and pre-fill login email","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:03:45.644107089Z"} {"taskCreatedAt":"2025-11-13T21:01:35.331063546Z","taskDependencies":[],"taskId":"t-1kCJTuu","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Fix Stripe portal error handling and account page padding","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T21:03:19.701792229Z"} {"taskCreatedAt":"2025-11-14T18:19:16.584321849Z","taskDependencies":[],"taskId":"t-19ZF6A8","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Parallel Target Builds - Epic","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T19:03:02.525200039Z"} @@ -120,3 +120,13 @@ {"taskCreatedAt":"2025-11-20T15:58:11.740041636Z","taskDependencies":[],"taskId":"t-10KNtTF","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Docopt flag order matters incorrectly","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T18:35:44.798128524Z"} {"taskCreatedAt":"2025-11-20T18:44:29.330834039Z","taskDependencies":[{"depId":"t-Uumhrq","depType":"DiscoveredFrom"}],"taskId":"t-1bE2r3q","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Document TASK_TEST_MODE in AGENTS.md","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T18:53:22.852670919Z"} {"taskCreatedAt":"2025-11-20T19:46:53.636713383Z","taskDependencies":[],"taskId":"t-1fJra3K","taskNamespace":"Omni/Bild.hs","taskParent":null,"taskPriority":"P1","taskStatus":"Done","taskTitle":"Fix bild --plan to output only JSON without logging","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T19:51:46.854882315Z"} +{"taskCreatedAt":"2025-11-20T21:41:12.7461675Z","taskDependencies":[],"taskId":"t-1ndDhLo","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskPriority":"P2","taskStatus":"Open","taskTitle":"PodcastItLater: Add Pricing Page UI","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T21:41:12.7461675Z"} +{"taskCreatedAt":"2025-11-20T21:41:12.764720659Z","taskDependencies":[],"taskId":"t-1ndDmAD","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskPriority":"P2","taskStatus":"Open","taskTitle":"PodcastItLater: Add Stripe Checkout Route","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T21:41:12.764720659Z"} +{"taskCreatedAt":"2025-11-20T21:41:12.783999704Z","taskDependencies":[],"taskId":"t-1ndDrBA","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskPriority":"P2","taskStatus":"Open","taskTitle":"PodcastItLater: Add Stripe Portal Route","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T21:41:12.783999704Z"} +{"taskCreatedAt":"2025-11-20T21:41:12.802988426Z","taskDependencies":[],"taskId":"t-1ndDwxQ","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskPriority":"P2","taskStatus":"Open","taskTitle":"PodcastItLater: Add Stripe Webhook Handler","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T21:41:12.802988426Z"} +{"taskCreatedAt":"2025-11-20T21:41:12.821995769Z","taskDependencies":[],"taskId":"t-1ndDBuq","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskPriority":"P2","taskStatus":"Open","taskTitle":"PodcastItLater: Enforce Paid Limits in UI","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T21:41:12.821995769Z"} +{"taskCreatedAt":"2025-11-20T21:41:20.029426381Z","taskDependencies":[],"taskId":"t-1ne7Qtj","taskNamespace":"Network/Wai/Middleware/Braid.hs","taskParent":"t-1f9QP23","taskPriority":"P2","taskStatus":"Open","taskTitle":"Implement Braid keep-alive mechanism","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T21:41:20.029426381Z"} +{"taskCreatedAt":"2025-11-20T21:41:20.048368004Z","taskDependencies":[],"taskId":"t-1ne7VoO","taskNamespace":"Biz/Que/Host.hs","taskParent":"t-1f9QP23","taskPriority":"P2","taskStatus":"Open","taskTitle":"Revive authkey authentication in Que/Host","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T21:41:20.048368004Z"} +{"taskCreatedAt":"2025-11-20T21:41:20.067644599Z","taskDependencies":[],"taskId":"t-1ne80pJ","taskNamespace":"Biz/Dragons.hs","taskParent":"t-1f9QP23","taskPriority":"P2","taskStatus":"Open","taskTitle":"Store generated JWK in persistent file","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T21:41:20.067644599Z"} +{"taskCreatedAt":"2025-11-20T21:41:32.113815607Z","taskDependencies":[],"taskId":"t-1neWyaO","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-144hCMJ","taskPriority":"P2","taskStatus":"Open","taskTitle":"Add tests for Admin dashboard","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T21:41:32.113815607Z"} +{"taskCreatedAt":"2025-11-20T21:41:32.132888832Z","taskDependencies":[],"taskId":"t-1neWD8r","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-144hCMJ","taskPriority":"P2","taskStatus":"Open","taskTitle":"Add error handling tests for Worker","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T21:41:32.132888832Z"} diff --git a/Omni/Agent/WORKER_AGENT_GUIDE.md b/Omni/Agent/WORKER_AGENT_GUIDE.md index f1f6f60..64a55f8 100644 --- a/Omni/Agent/WORKER_AGENT_GUIDE.md +++ b/Omni/Agent/WORKER_AGENT_GUIDE.md @@ -64,38 +64,45 @@ task update t-123 in-progress ### Step 5: Submit for Review -```bash -# 1. Mark for review -task update t-123 review +1. **Commit Implementation**: + ```bash + git add . + git commit -m "feat: implement t-123" + ``` -# 2. Commit implementation -git add . -git commit -m "feat: implement t-123" +2. **Signal Review Readiness**: + The Planner checks the `omni-worker-X` branch for status updates. You must switch back and update the status there. -# 3. Switch back to worker base branch to prepare for next loop -# This detaches the worker from the task branch so the Planner can check it out -git checkout omni-worker-1 -./Omni/Agent/sync-tasks.sh -``` + ```bash + # Switch to base branch + git checkout omni-worker-1 + + # Sync to get latest state (and any manual merges) + ./Omni/Agent/sync-tasks.sh + + # Mark task for review + task update t-123 review + + # Commit this status change to the worker branch + ./Omni/Agent/sync-tasks.sh --commit + ``` + + *Note: The Planner will now see 't-123' in 'Review' when it runs `harvest-tasks.sh`.* ## 3. Planner (Reviewer) Workflow The Planner Agent (running in the main repo) will: -1. See tasks in `Review` status (after checking out the worker's branch or waiting for them to be merged). - * *Note: Since workers commit to local branches, you verify work by checking out their branch.* -2. Check out the worker's branch: `git checkout task/t-123`. -3. Review code and tests. -4. Merge to `live`: - ```bash - git checkout live - git merge task/t-123 - # Conflicts in tasks.jsonl are handled automatically by .gitattributes driver - ``` -5. Mark Done: - ```bash - task update t-123 done - git commit -am "task: t-123 done" - ``` +1. **Harvest Updates**: Run `./Omni/Agent/harvest-tasks.sh` to pull "In Progress" and "Review" statuses from workers. +2. **Find Reviews**: Run `task list --status=review`. +3. **Review Code**: + * Check out the feature branch: `git checkout task/t-123`. + * Run tests and review code. +4. **Merge**: + * `git checkout live` + * `git merge task/t-123` +5. **Complete**: + * `task update t-123 done` + * `git commit -am "task: t-123 done"` ## Troubleshooting diff --git a/Omni/Agent/harvest-tasks.sh b/Omni/Agent/harvest-tasks.sh new file mode 100755 index 0000000..282beab --- /dev/null +++ b/Omni/Agent/harvest-tasks.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -e + +# Omni/Agent/harvest-tasks.sh +# Imports task updates from all worker branches into the current branch (usually live). + +REPO_ROOT="$(git rev-parse --show-toplevel)" +cd "$REPO_ROOT" + +echo "Harvesting task updates from workers..." + +# Find all worker branches (assuming naming convention omni-worker-*) +# We filter for local branches +WORKER_BRANCHES=$(git branch --list "omni-worker-*" --format="%(refname:short)") + +if [ -z "$WORKER_BRANCHES" ]; then + echo "No worker branches found." + exit 0 +fi + +UPDATED=0 + +for branch in $WORKER_BRANCHES; do + echo "Checking $branch..." + + # Extract tasks.jsonl from the worker branch + if git show "$branch:.tasks/tasks.jsonl" > .tasks/worker-tasks.jsonl 2>/dev/null; then + # Import into current DB + # The import command handles deduplication and timestamp conflict resolution + if "$REPO_ROOT/_/bin/task" import -i .tasks/worker-tasks.jsonl >/dev/null; then + echo " Imported tasks from $branch" + UPDATED=1 + fi + else + echo " Warning: Could not read .tasks/tasks.jsonl from $branch" + fi +done + +rm -f .tasks/worker-tasks.jsonl + +if [ "$UPDATED" -eq 1 ]; then + # Consolidate + "$REPO_ROOT/_/bin/task" export --flush + + # Commit if there are changes + if [[ -n $(git status --porcelain .tasks/tasks.jsonl) ]]; then + git add .tasks/tasks.jsonl + git commit -m "task: harvest updates from workers" + echo "Success: Task database updated and committed." + else + echo "No effective changes found." + fi +else + echo "No updates found." +fi diff --git a/Omni/Agent/start-worker.sh b/Omni/Agent/start-worker.sh index 2c5eee4..e3a7200 100755 --- a/Omni/Agent/start-worker.sh +++ b/Omni/Agent/start-worker.sh @@ -2,18 +2,25 @@ set -e # Omni/Agent/start-worker.sh -# Launches an Amp worker agent in the specified worktree. -# Usage: ./Omni/Agent/start-worker.sh [worker-directory-name] +# 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 -WORKER_NAME="${1:-omni-worker-1}" -REPO_ROOT="$(git rev-parse --show-toplevel)" -WORKER_PATH="$REPO_ROOT/../$WORKER_NAME" -AMP_BIN="$REPO_ROOT/node_modules/.bin/amp" +TARGET="${1:-omni-worker-1}" -if [ ! -d "$WORKER_PATH" ]; then - echo "Error: Worker directory '$WORKER_PATH' does not exist." - echo "Please run './Omni/Agent/setup-worker.sh $WORKER_NAME' first." +# 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" + +# 2. Resolve Worker Path +# If TARGET is a directory, use it. Otherwise assume it's a sibling of MAIN_REPO. +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 @@ -23,12 +30,35 @@ if [ ! -x "$AMP_BIN" ]; then exit 1 fi -echo "Starting Worker Agent in '$WORKER_PATH'..." -echo "Using Amp binary: $AMP_BIN" +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." cd "$WORKER_PATH" -# Launch Amp with the worker persona and instructions -"$AMP_BIN" -- "You are a Worker Agent. Your goal is to process tasks from the task manager. -Please read Omni/Agent/WORKER_AGENT_GUIDE.md and follow the 'Worker Loop' instructions exactly. -Start by syncing tasks and checking for ready work." +# 3. The Worker Loop +while true; do + echo "----------------------------------------------------------------" + echo "$(date): Starting new agent session..." + + mkdir -p _/llm + "$AMP_BIN" --log-file "_/llm/amp.log" --dangerously-allow-all -x "You are a Worker Agent. Your goal is to process tasks from the task manager. + + Step 1: Read 'Omni/Agent/WORKER_AGENT_GUIDE.md' to understand your workflow. + Step 2: Follow the 'Worker Loop' instructions exactly. + + Start by syncing tasks and checking for ready work. If no work is ready, exit." + + EXIT_CODE=$? + + if [ $EXIT_CODE -ne 0 ]; then + echo "Agent exited with error code $EXIT_CODE. Sleeping for 10s..." + sleep 10 + else + echo "Agent session finished. Sleeping for 5s..." + sleep 5 + fi +done diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs index af105de..f7b7915 100644 --- a/Omni/Task/Core.hs +++ b/Omni/Task/Core.hs @@ -246,15 +246,22 @@ listTasks maybeType maybeParent maybeStatus maybeNamespace = do filterByNamespace Nothing ts = ts filterByNamespace (Just ns) ts = filter (\t -> taskNamespace t == Just ns) ts --- Get ready tasks (not blocked by dependencies) +-- Get ready tasks (not blocked by dependencies and not a parent) getReadyTasks :: IO [Task] getReadyTasks = do allTasks <- loadTasks let openTasks = filter (\t -> taskStatus t /= Done) allTasks doneIds = map taskId <| filter (\t -> taskStatus t == Done) allTasks + + -- Find all tasks that act as parents + parentIds = mapMaybe taskParent allTasks + isParent tid = tid `elem` parentIds + -- Only Blocks and ParentChild dependencies block ready work blockingDepIds task = [depId dep | dep <- taskDependencies task, depType dep `elem` [Blocks, ParentChild]] - isReady task = all (`elem` doneIds) (blockingDepIds task) + isReady task = + not (isParent (taskId task)) + && all (`elem` doneIds) (blockingDepIds task) pure <| filter isReady openTasks -- Get dependency tree for a task (returns tasks) |
