This commit is contained in:
Rai (Michael Pokorny)
2025-06-25 03:20:15 -07:00
parent 079ee5f6e3
commit f106686146
10 changed files with 236 additions and 272 deletions

View File

@@ -33,6 +33,6 @@
- **Merged:** tasks with no branch/worktree.
- **Ready to merge:** tasks marked Done with branch commits ahead.
- **Unblocked:** tasks with no outstanding dependencies.
- The script also prints a `create-task-worktree.sh --agent --tmux <IDs>` command for all unblocked tasks.
- The script also prints a `agentydragon/tools/create_task_worktree.py --agent --tmux <IDs>` command for all unblocked tasks.
This guide centralizes the handoff workflow for all agents.

View File

@@ -7,7 +7,7 @@ tydragon-driven task workflow:
e, “Status”, “Goal”, and sections for “Acceptance Criteria”, “Implementation”, and “Notes”.
2. **Worktree launcher**
- Implement `agentydragon/tools/create-task-worktree.sh` with:
- Implement `agentydragon/tools/create_task_worktree.py` with:
- `--agent` mode to spin up a Codex agent in the worktree,
- `--tmux` to tile panes for multiple tasks in a single tmux session,
- twodigit or slug ID resolution.
@@ -26,7 +26,7 @@ e, “Status”, “Goal”, and sections for “Acceptance Criteria”, “Impl
You are the **Project Manager** Codex agent for the `codex` repository. Your responsibilities include:
- **Reading documentation**: Load and understand all relevant docs in this repo (especially those defining task, worktree, and branch conventions, as well as each task file and toplevel README files).
- **Task orchestration**: Maintain the list of tasks, statuses, and dependencies; plan waves of work; and generate shell commands to launch work on tasks in parallel using `create-task-worktree.sh` with `--agent` and `--tmux`.
- **Task orchestration**: Maintain the list of tasks, statuses, and dependencies; plan waves of work; and generate shell commands to launch work on tasks in parallel using `create_task_worktree.py` with `--agent` and `--tmux`.
- **Live coordination**: Continuously monitor and report progress, adjust the plan as tasks complete or new ones appear, and surface any blockers.
- **Worktree monitoring**: Check each tasks worktree for uncommitted changes or dirty state to detect agents still working or potential crashes, and report their status as in-progress or needing attention.
- **Background polling**: On user request, enter a sleepandscan loop (e.g. 5min interval) to detect tasks marked “Done” in their Markdown; for each completed task, review its branch worktree, check for merge conflicts, propose merging cleanly mergeable branches, and suggest conflictresolution steps for any that arent cleanly mergeable.
@@ -35,7 +35,7 @@ e, “Status”, “Goal”, and sections for “Acceptance Criteria”, “Impl
### First Actions
1. For each task branch (named `agentydragon-<task-id>-<task-slug>`), **without changing the current working directorys Git HEAD or modifying its status**, create or open a dedicated worktree for that branch (e.g. via `create-task-worktree.sh <task-slug>`) and read the tasks Markdown copy under that worktrees `agentydragon/tasks/` to extract and list the task number, title, live **Status**, and dependencies. *(Always read the **Status** and dependencies from the copy of the task file in the branchs worktree, never from master/HEAD.)*
1. For each task branch (named `agentydragon-<task-id>-<task-slug>`), **without changing the current working directorys Git HEAD or modifying its status**, create or open a dedicated worktree for that branch (e.g. via `create_task_worktree.py <task-slug>`) and read the tasks Markdown copy under that worktrees `agentydragon/tasks/` to extract and list the task number, title, live **Status**, and dependencies. *(Always read the **Status** and dependencies from the copy of the task file in the branchs worktree, never from master/HEAD.)*
2. Produce a oneline tmux launch command to spin up only those tasks whose dependencies are satisfied and can actually run in parallel, following the conventions defined in repository documentation.
3. Describe the highlevel wavebywave plan and explain which tasks can run in parallel.
@@ -58,7 +58,7 @@ entions.
- Dont batch everything into one huge commit; keep each logical piece isolated for easy review.
**Reporting**
After each commit, print a short status message (e.g. “✅ Task stubs created”, “✅ create-task-worktree.sh implemented”, etc.) and await confirmation before continuing
After each commit, print a short status message (e.g. “✅ Task stubs created”, “✅ create_task_worktree.py implemented”, etc.) and await confirmation before continuing
the next step.
---

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env python3
"""
common.py: Shared utilities for agentydragon tooling scripts.
"""
import subprocess
from pathlib import Path
def repo_root() -> Path:
"""Return the Git repository root directory."""
out = subprocess.check_output(['git', 'rev-parse', '--show-toplevel'])
return Path(out.decode().strip())
def tasks_dir() -> Path:
"""Path to the agentydragon/tasks directory."""
return repo_root() / 'agentydragon' / 'tasks'
def worktrees_dir() -> Path:
"""Path to the agentydragon/tasks/.worktrees directory."""
return tasks_dir() / '.worktrees'
def resolve_slug(input_id: str) -> str:
"""Resolve a two-digit task ID into its full slug, or return slug unchanged."""
if input_id.isdigit() and len(input_id) == 2:
matches = list(tasks_dir().glob(f"{input_id}-*.md"))
if len(matches) == 1:
return matches[0].stem
raise ValueError(f"Expected one task file for ID {input_id}, found {len(matches)}")
return input_id

View File

@@ -1,180 +0,0 @@
#!/usr/bin/env bash
#
# create-task-worktree.sh
#
# Create or reuse a git worktree for a specific task branch under agentydragon/tasks/.worktrees.
# Usage: create-task-worktree.sh [-a|--agent] [-t|--tmux] <task-slug|NN> [<task-slug|NN>...]
set -euo pipefail
agent_mode=false
tmux_mode=false
interactive_mode=false
shell_mode=false
while [[ $# -gt 0 ]]; do
case "$1" in
-a|--agent)
agent_mode=true
shift
;;
-t|--tmux)
tmux_mode=true
shift
;;
-h|--help)
cat << EOF
Usage: $0 [-a|--agent] [-s|--shell] [-i|--interactive] [-t|--tmux] <task-slug|NN> [<task-slug|NN>...]
Options:
-a, --agent create/reuse worktree, run pre-commit checks (aborting on failure),
and launch a Codex agent with prompt injection; when the agent exits,
auto-run commit helper
-s, --shell create/reuse worktree and launch an interactive Codex shell
(no prompt injection, skip auto-commit)
-i, --interactive run the agent in interactive mode (no 'exec'); implies --agent
-t, --tmux open each task in its own tmux window; implies --agent
-h, --help show this help message and exit
EOF
exit 0
;;
-i|--interactive)
interactive_mode=true
agent_mode=true
shift
;;
-s|--shell)
# Launch agent in an interactive shell (no prompt injection, no auto-commit)
shell_mode=true
agent_mode=true
shift
;;
*)
break
;;
esac
done
# Validate number of task arguments based on mode
if [ "$tmux_mode" = true ]; then
if [ "$#" -lt 1 ]; then
echo "Usage: $0 [-a|--agent] [-t|--tmux] <task-id>-<task-slug> [<more-task-ids>...]"
exit 1
fi
else
if [ "$#" -ne 1 ]; then
echo "Usage: $0 [-a|--agent] [-t|--tmux] <task-id>-<task-slug> [<more-task-ids>...]"
exit 1
fi
fi
# Capture raw input so we can accept just a two-digit task ID
task_inputs=("$@")
# If tmux mode, batch-create worktrees and launch in tmux
if [ "$tmux_mode" = true ]; then
# Implicitly enable agent mode in tmux panes
agent_mode=true
# Build a unique session name from task IDs
session="agentydragon-${task_inputs[*]// /_}"
cmd="$0"
# First pane: first task
first="${task_inputs[0]}"
pane_cmd="$cmd${agent_mode:+ -a} $first"
tmux new-session -d -s "$session" "$pane_cmd"
# Open each additional task in its own tmux window
for task in "${task_inputs[@]:1}"; do
tmux new-window -t "$session" "$cmd${agent_mode:+ -a} $task"
done
tmux attach -t "$session"
exit 0
fi
task_input="${task_inputs[0]}"
# Determine repository root and tasks directory
repo_root=$(git rev-parse --show-toplevel)
tasks_dir="$repo_root/agentydragon/tasks"
# If given only a two-digit ID, resolve to the full task slug
if [[ "$task_input" =~ ^[0-9]{2}$ ]]; then
matches=( "$tasks_dir/${task_input}-"*.md )
if [ "${#matches[@]}" -eq 1 ]; then
task_slug="$(basename "${matches[0]}" .md)"
echo "Resolved task ID '$task_input' to slug '$task_slug'"
else
echo "Error: expected exactly one task file matching '${task_input}-*.md', found ${#matches[@]}" >&2
exit 1
fi
else
task_slug="$task_input"
fi
# Use dash-separated branch name to avoid ref-dir conflicts
branch="agentydragon-$task_slug"
worktrees_dir="$tasks_dir/.worktrees"
worktree_path="$worktrees_dir/$task_slug"
mkdir -p "$worktrees_dir"
# Create branch if it does not exist
if ! git show-ref --verify --quiet "refs/heads/$branch"; then
echo "Creating branch $branch from agentydragon branch..."
git branch --track "$branch" agentydragon
fi
# Create worktree if it does not exist
if [ ! -d "$worktree_path" ]; then
echo "Creating worktree for $branch at $worktree_path"
git worktree add "$worktree_path" "$branch"
# Install pre-commit hooks in the new worktree if pre-commit is available
if command -v pre-commit >/dev/null 2>&1; then
(cd "$worktree_path" && pre-commit install)
else
echo "Warning: pre-commit not found; skipping hook install in $worktree_path"
fi
else
echo "Worktree for $branch already exists at $worktree_path"
fi
echo "Done."
if [ "$agent_mode" = true ]; then
echo "Launching Developer Codex agent for task $task_slug in sandboxed worktree"
cd "${worktree_path}"
# Before launching the developer agent, run pre-commit checks and abort if hooks fail
if command -v pre-commit >/dev/null 2>&1; then
echo "Running pre-commit checks in $(pwd)"
if ! pre-commit run --all-files; then
echo "Error: pre-commit checks failed. Fix issues before launching the developer agent." >&2
exit 1
fi
else
echo "Warning: pre-commit is not installed; skipping pre-commit checks." >&2
fi
echo "Launching Developer Codex agent for task $task_slug in sandboxed worktree"
if [ "$shell_mode" = true ]; then
# Interactive shell mode: no prompt, skip commit helper
cmd=(codex --full-auto)
echo "${cmd[*]}"
exec "${cmd[@]}"
fi
prompt_file="$repo_root/agentydragon/prompts/developer.md"
task_file="$repo_root/agentydragon/tasks/$task_slug.md"
if [ ! -f "$prompt_file" ]; then
echo "Error: developer prompt file not found at $prompt_file" >&2
exit 1
fi
# Launch the agent under Landlock+seccomp sandbox: writable only in cwd and TMPDIR, network disabled
cmd=(codex --full-auto)
if [ "${interactive_mode:-}" != true ]; then
cmd+=(exec)
fi
echo "${cmd[*]}"
# Run Developer agent (non-interactive by default) to implement the task
"${cmd[@]}" "$(<"$prompt_file")"$'\n\n'"$(<"$task_file")"
# After the Developer agent exits, stage and commit via the Commit agent helper
echo "Running Commit agent to finalize task $task_slug"
"$repo_root/agentydragon/tools/launch-commit-agent.sh" "$task_slug"
fi

View File

@@ -0,0 +1,109 @@
#!/usr/bin/env python3
"""
create_task_worktree.py: Create or reuse a git worktree for a specific task and optionally launch a Developer Codex agent.
"""
import os
import subprocess
import sys
from pathlib import Path
import click
from common import repo_root, tasks_dir, worktrees_dir, resolve_slug
def run(cmd, cwd=None):
click.echo(f"Running: {' '.join(cmd)}")
subprocess.check_call(cmd, cwd=cwd)
def resolve_slug(input_id: str) -> str:
if input_id.isdigit() and len(input_id) == 2:
matches = list(tasks_dir().glob(f"{input_id}-*.md"))
if len(matches) == 1:
return matches[0].stem
click.echo(f"Error: expected one task file for ID {input_id}, found {len(matches)}", err=True)
sys.exit(1)
return input_id
@click.command()
@click.option('-a', '--agent', is_flag=True,
help='Launch Developer Codex agent after setting up worktree.')
@click.option('-t', '--tmux', 'tmux_mode', is_flag=True,
help='Open each task in its own tmux pane; implies --agent.')
@click.option('-i', '--interactive', is_flag=True,
help='Run agent in interactive mode (no exec); implies --agent.')
@click.option('-s', '--shell', 'shell_mode', is_flag=True,
help='Launch an interactive Codex shell (skip auto-commit); implies --agent.')
@click.argument('task_inputs', nargs=-1, required=True)
def main(agent, tmux_mode, interactive, shell_mode, task_inputs):
"""Create/reuse a task worktree and optionally launch a Dev agent or tmux session."""
if interactive or shell_mode:
agent = True
if tmux_mode:
agent = True
session = 'agentydragon_' + '_'.join(task_inputs)
for idx, inp in enumerate(task_inputs):
slug = resolve_slug(inp)
cmd = [sys.executable, '-u', __file__]
if agent:
cmd.append('--agent')
cmd.append(slug)
if idx == 0:
run(['tmux', 'new-session', '-d', '-s', session] + cmd)
else:
run(['tmux', 'new-window', '-t', session] + cmd)
run(['tmux', 'attach', '-t', session])
return
# Single task
slug = resolve_slug(task_inputs[0])
branch = f"agentydragon-{slug}"
wt_root = worktrees_dir()
wt_path = wt_root / slug
# Ensure branch exists
if subprocess.call(['git', 'show-ref', '--verify', '--quiet', f'refs/heads/{branch}']) != 0:
run(['git', 'branch', '--track', branch, 'agentydragon'])
wt_root.mkdir(parents=True, exist_ok=True)
if not wt_path.exists():
# Create worktree without checkout, then hydrate via reflink/rsync for COW performance
run(['git', 'worktree', 'add', '--no-checkout', str(wt_path), branch])
src = str(repo_root())
dst = str(wt_path)
try:
run(['cp', '-cRp', f'{src}/.', f'{dst}/', '--exclude=.git', '--exclude=.gitlink'])
except subprocess.CalledProcessError:
run(['rsync', '-a', '--delete', f'{src}/', f'{dst}/', '--exclude=.git*'])
if shutil.which('pre-commit'):
run(['pre-commit', 'install'], cwd=dst)
else:
click.echo('Warning: pre-commit not found; skipping hook install', err=True)
else:
click.echo(f'Worktree already exists at {wt_path}')
if not agent:
return
# Pre-commit checks
if shutil.which('pre-commit'):
run(['pre-commit', 'run', '--all-files'], cwd=str(wt_path))
else:
click.echo('Warning: pre-commit not installed; skipping checks', err=True)
click.echo(f'Launching Developer Codex agent for task {slug} in sandboxed worktree')
os.chdir(wt_path)
cmd = ['codex', '--full-auto']
if not interactive:
cmd.append('exec')
prompt = (repo_root() / 'agentydragon' / 'prompts' / 'developer.md').read_text()
taskfile = (tasks_dir() / f'{slug}.md').read_text()
run(cmd + [prompt + '\n\n' + taskfile])
if __name__ == '__main__':
import shutil
main()

View File

@@ -1,69 +0,0 @@
#!/usr/bin/env bash
#
# launch-commit-agent.sh
#
# Launch the non-interactive Codex Commit agent for a given task worktree,
# using the prompt in prompts/commit.md and the task's Markdown file.
#
# Usage: launch-commit-agent.sh <task-slug|NN> [<task-slug|NN>...]
set -euo pipefail
# Allocate a temporary file to capture the agent's commit message
last_message_file="$(mktemp)"
trap 'rm -f "$last_message_file"' EXIT
# Usage: one or more task IDs or slugs
if [ "$#" -lt 1 ]; then
echo "Usage: $0 <task-slug|NN> [<task-slug|NN>...]" >&2
exit 1
fi
# Locate repository and tasks directory; commit prompt is constant
repo_root=$(git rev-parse --show-toplevel)
tasks_dir="$repo_root/agentydragon/tasks"
prompt_file="$repo_root/agentydragon/prompts/commit.md"
for input in "$@"; do
# Resolve numeric ID to full slug if needed
if [[ "$input" =~ ^[0-9]{2}$ ]]; then
matches=("$tasks_dir/${input}-"*.md)
if [ "${#matches[@]}" -eq 1 ]; then
task_slug="$(basename "${matches[0]}" .md)"
echo "Resolved task ID '$input' to slug '$task_slug'"
else
echo "Error: expected exactly one task file matching '${input}-*.md', found ${#matches[@]}" >&2
exit 1
fi
else
task_slug="$input"
fi
# Paths for worktree and task file
worktrees_dir="$tasks_dir/.worktrees"
worktree_path="$worktrees_dir/$task_slug"
task_file="$tasks_dir/$task_slug.md"
# Preconditions
if [ ! -d "$worktree_path" ]; then
echo "Error: worktree for '$task_slug' not found; run create-task-worktree.sh first" >&2
exit 1
fi
if [ ! -f "$prompt_file" ]; then
echo "Error: commit prompt not found at $prompt_file" >&2
exit 1
fi
if [ ! -f "$task_file" ]; then
echo "Error: task file not found at $task_file" >&2
exit 1
fi
# Perform commit in the task worktree
echo "--- Processing task $task_slug ---"
cd "$worktree_path"
cmd=(codex --full-auto exec --output-last-message "$last_message_file")
echo "Running: ${cmd[*]}"
# Write the agent's final message to file, then commit using that file
"${cmd[@]}" "$(<"$prompt_file")"$'\n\n'"$(<"$task_file")"
git add -u
git commit -F "$last_message_file"
done

View File

@@ -1,17 +0,0 @@
#!/usr/bin/env bash
#
# launch-project-manager.sh
#
# Launch the Codex Project Manager agent using the prompt in prompts/manager.md.
set -euo pipefail
repo_root=$(git rev-parse --show-toplevel)
prompt_file="$repo_root/agentydragon/prompts/manager.md"
if [ ! -f "$prompt_file" ]; then
echo "Error: manager prompt file not found at $prompt_file" >&2
exit 1
fi
codex "$(<"$prompt_file")"

View File

@@ -0,0 +1,47 @@
#!/usr/bin/env python3
"""
launch_commit_agent.py: Run the non-interactive Commit agent for completed tasks.
"""
import os
import subprocess
import sys
from pathlib import Path
import click
from common import repo_root, tasks_dir, worktrees_dir, resolve_slug
@click.command()
@click.argument('task_input', required=True)
def main(task_input):
"""Resolve TASK_INPUT to slug, run the Commit agent, and commit changes."""
slug = resolve_slug(task_input)
wt = worktrees_dir() / slug
if not wt.exists():
click.echo(f"Error: worktree for '{slug}' not found; run create_task_worktree.py first", err=True)
sys.exit(1)
prompt_file = repo_root() / 'agentydragon' / 'prompts' / 'commit.md'
task_file = tasks_dir() / f'{slug}.md'
for f in (prompt_file, task_file):
if not f.exists():
click.echo(f"Error: file not found: {f}", err=True)
sys.exit(1)
msg_file = Path(subprocess.check_output(['mktemp']).decode().strip())
try:
os.chdir(wt)
cmd = ['codex', '--full-auto', 'exec', '--output-last-message', str(msg_file)]
click.echo(f"Running: {' '.join(cmd)}")
prompt_content = prompt_file.read_text(encoding='utf-8')
task_content = task_file.read_text(encoding='utf-8')
subprocess.check_call(cmd + [prompt_content + '\n\n' + task_content])
subprocess.check_call(['git', 'add', '-u'])
subprocess.check_call(['git', 'commit', '-F', str(msg_file)])
finally:
msg_file.unlink()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python3
"""
launch_project_manager.py: Launch the Codex Project Manager agent prompt.
"""
import subprocess
import sys
import click
from common import repo_root
@click.command()
def main():
"""Read manager.md prompt and invoke Codex Project Manager agent."""
prompt_file = repo_root() / 'agentydragon' / 'prompts' / 'manager.md'
if not prompt_file.exists():
click.echo(f"Error: manager prompt not found at {prompt_file}", err=True)
sys.exit(1)
prompt = prompt_file.read_text(encoding='utf-8')
cmd = ['codex', prompt]
click.echo(f"Running: {' '.join(cmd[:1])} <prompt>")
subprocess.check_call(cmd)
if __name__ == '__main__':
main()

View File

@@ -6,12 +6,26 @@ This crate implements the business logic for Codex. It is designed to be used by
Codex composes the initial system message that seeds every chat completion turn as follows:
1. Load the built-in system prompt from `prompt.md` (unless disabled).
1. Load the built-in system prompt from `prompt.md` (unless overridden/disabled).
2. If the `CODEX_BASE_INSTRUCTIONS_FILE` env var is set, use that file instead of `prompt.md`.
3. Append any user instructions (e.g. from `instructions.md` and merged `AGENTS.md`).
4. Append the apply-patch tool instructions when using GPT-4.1 models.
5. Finally, the user's command or prompt is sent as the first user message.
This “system” prompt is delivered to the OpenAI Chat Completions API as the very first message with role `system` in the JSON `messages` array, e.g.:
```json
{
"model": "gpt-4.1",
"messages": [
{"role": "system", "content": "<base instructions here>"},
{"role": "system", "content": "<user_instructions here>"},
{"role": "system", "content": "<apply-patch tool instructions>"},
{"role": "user", "content": "<your prompt>"}
],
...
}
```
The base instructions behavior can be customized with `CODEX_BASE_INSTRUCTIONS_FILE`:
- If unset, the built-in prompt (`prompt.md`) is used.