diff --git a/agentydragon/WORKFLOW.md b/agentydragon/WORKFLOW.md index a380952561..1e3651dd19 100644 --- a/agentydragon/WORKFLOW.md +++ b/agentydragon/WORKFLOW.md @@ -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 ` command for all unblocked tasks. +- The script also prints a `agentydragon/tools/create_task_worktree.py --agent --tmux ` command for all unblocked tasks. This guide centralizes the handoff workflow for all agents. diff --git a/agentydragon/prompts/scaffolding-setup.md b/agentydragon/prompts/scaffolding-setup.md index 425cb05645..0c44442e8f 100644 --- a/agentydragon/prompts/scaffolding-setup.md +++ b/agentydragon/prompts/scaffolding-setup.md @@ -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, - two‑digit 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 top‑level 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 task’s 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 sleep‑and‑scan loop (e.g. 5 min 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 conflict‑resolution steps for any that aren’t cleanly mergeable. @@ -35,7 +35,7 @@ e, “Status”, “Goal”, and sections for “Acceptance Criteria”, “Impl ### First Actions - 1. For each task branch (named `agentydragon--`), **without changing the current working directory’s Git HEAD or modifying its status**, create or open a dedicated worktree for that branch (e.g. via `create-task-worktree.sh `) and read the task’s Markdown copy under that worktree’s `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 branch’s worktree, never from master/HEAD.)* + 1. For each task branch (named `agentydragon--`), **without changing the current working directory’s Git HEAD or modifying its status**, create or open a dedicated worktree for that branch (e.g. via `create_task_worktree.py `) and read the task’s Markdown copy under that worktree’s `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 branch’s worktree, never from master/HEAD.)* 2. Produce a one‑line 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 high‑level wave‑by‑wave plan and explain which tasks can run in parallel. @@ -58,7 +58,7 @@ entions. - Don’t 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. --- diff --git a/agentydragon/tools/common.py b/agentydragon/tools/common.py new file mode 100644 index 0000000000..894f537cc6 --- /dev/null +++ b/agentydragon/tools/common.py @@ -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 diff --git a/agentydragon/tools/create-task-worktree.sh b/agentydragon/tools/create-task-worktree.sh deleted file mode 100755 index a4789d3235..0000000000 --- a/agentydragon/tools/create-task-worktree.sh +++ /dev/null @@ -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] [...] - -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] [...] - -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] - [...]" - exit 1 - fi -else - if [ "$#" -ne 1 ]; then - echo "Usage: $0 [-a|--agent] [-t|--tmux] - [...]" - 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 diff --git a/agentydragon/tools/create_task_worktree.py b/agentydragon/tools/create_task_worktree.py new file mode 100644 index 0000000000..3650b7c687 --- /dev/null +++ b/agentydragon/tools/create_task_worktree.py @@ -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() diff --git a/agentydragon/tools/launch-commit-agent.sh b/agentydragon/tools/launch-commit-agent.sh deleted file mode 100755 index 3cd58e823c..0000000000 --- a/agentydragon/tools/launch-commit-agent.sh +++ /dev/null @@ -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 [...] -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 [...]" >&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 diff --git a/agentydragon/tools/launch-project-manager.sh b/agentydragon/tools/launch-project-manager.sh deleted file mode 100755 index a13cef0da6..0000000000 --- a/agentydragon/tools/launch-project-manager.sh +++ /dev/null @@ -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")" diff --git a/agentydragon/tools/launch_commit_agent.py b/agentydragon/tools/launch_commit_agent.py new file mode 100644 index 0000000000..b07cbd7b86 --- /dev/null +++ b/agentydragon/tools/launch_commit_agent.py @@ -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() diff --git a/agentydragon/tools/launch_project_manager.py b/agentydragon/tools/launch_project_manager.py new file mode 100644 index 0000000000..25e0c14aed --- /dev/null +++ b/agentydragon/tools/launch_project_manager.py @@ -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])} ") + subprocess.check_call(cmd) + + +if __name__ == '__main__': + main() diff --git a/codex-rs/core/README.md b/codex-rs/core/README.md index a04520cb19..0178b5f3ec 100644 --- a/codex-rs/core/README.md +++ b/codex-rs/core/README.md @@ -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": ""}, + {"role": "system", "content": ""}, + {"role": "system", "content": ""}, + {"role": "user", "content": ""} + ], + ... +} +``` + The base instructions behavior can be customized with `CODEX_BASE_INSTRUCTIONS_FILE`: - If unset, the built-in prompt (`prompt.md`) is used.