fix(issue-36): registry and wrapper accept --prompt-file; the wrapper hands the same path on
One argv string is capped at MAX_ARG_STRLEN (131,072 bytes on Linux), so a prompt passed as --prompt could not reach the registry once it carried a large diff. submit now takes --prompt-file and passes the same path to `registry.py register --prompt-file`; the loop type's per-iteration update goes by file too. --prompt keeps working unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1debc7d924
commit
68a5d31381
3 files changed
+46
-11
No files matched your search
@@ -323,7 +323,11 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||||||
sub = parser.add_subparsers(dest="command", required=True)
|
sub = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
p_reg = sub.add_parser("register", help="create a pending job; prints the job id")
|
p_reg = sub.add_parser("register", help="create a pending job; prints the job id")
|
||||||
p_reg.add_argument("--prompt", required=True)
|
# Issue #36: a long prompt goes by file - one argv string is capped at
|
||||||
|
# MAX_ARG_STRLEN (131,072 bytes on Linux).
|
||||||
|
reg_prompt = p_reg.add_mutually_exclusive_group(required=True)
|
||||||
|
reg_prompt.add_argument("--prompt")
|
||||||
|
reg_prompt.add_argument("--prompt-file", help="read the prompt from this file (utf-8)")
|
||||||
p_reg.add_argument("--agent", default="claude-code")
|
p_reg.add_argument("--agent", default="claude-code")
|
||||||
p_reg.add_argument("--agent-session", default="herdr:claude")
|
p_reg.add_argument("--agent-session", default="herdr:claude")
|
||||||
p_reg.add_argument("--role", default="Worker", help="logical role for the delegated agent (e.g. Worker, Planner, Reviewer)")
|
p_reg.add_argument("--role", default="Worker", help="logical role for the delegated agent (e.g. Worker, Planner, Reviewer)")
|
||||||
@@ -365,7 +369,9 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||||||
p_update.add_argument("--job", required=True)
|
p_update.add_argument("--job", required=True)
|
||||||
p_update.add_argument("--status", default=None)
|
p_update.add_argument("--status", default=None)
|
||||||
p_update.add_argument("--agent-session", default=None)
|
p_update.add_argument("--agent-session", default=None)
|
||||||
p_update.add_argument("--prompt", default=None)
|
upd_prompt = p_update.add_mutually_exclusive_group()
|
||||||
|
upd_prompt.add_argument("--prompt", default=None)
|
||||||
|
upd_prompt.add_argument("--prompt-file", default=None, help="read the prompt from this file (utf-8)")
|
||||||
p_update.add_argument("--iteration", type=int, default=None)
|
p_update.add_argument("--iteration", type=int, default=None)
|
||||||
p_update.add_argument("--attempt-id", type=int, default=None)
|
p_update.add_argument("--attempt-id", type=int, default=None)
|
||||||
p_update.add_argument("--role", default=None)
|
p_update.add_argument("--role", default=None)
|
||||||
@@ -473,15 +479,26 @@ def _operator_write(job_id: str, registry_dir: str, **fields) -> int:
|
|||||||
return 4
|
return 4
|
||||||
|
|
||||||
|
|
||||||
|
def _prompt_from_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> Optional[str]:
|
||||||
|
"""The prompt text: from --prompt-file (read verbatim) or from --prompt."""
|
||||||
|
if getattr(args, "prompt_file", None) is None:
|
||||||
|
return args.prompt
|
||||||
|
try:
|
||||||
|
return Path(args.prompt_file).read_text(encoding="utf-8")
|
||||||
|
except (OSError, UnicodeDecodeError) as exc:
|
||||||
|
parser.error(f"cannot read --prompt-file {args.prompt_file}: {exc}")
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Optional[List[str]] = None) -> int:
|
def main(argv: Optional[List[str]] = None) -> int:
|
||||||
mqtt_common.ensure_env_loaded()
|
mqtt_common.ensure_env_loaded()
|
||||||
mqtt_common.setup_logging(logging.INFO)
|
mqtt_common.setup_logging(logging.INFO)
|
||||||
args = _build_parser().parse_args(argv)
|
parser = _build_parser()
|
||||||
|
args = parser.parse_args(argv)
|
||||||
rd = args.registry_dir
|
rd = args.registry_dir
|
||||||
|
|
||||||
if args.command == "register":
|
if args.command == "register":
|
||||||
job_id = register_job(
|
job_id = register_job(
|
||||||
prompt=args.prompt,
|
prompt=_prompt_from_args(parser, args),
|
||||||
agent=args.agent,
|
agent=args.agent,
|
||||||
agent_session=args.agent_session,
|
agent_session=args.agent_session,
|
||||||
role=args.role,
|
role=args.role,
|
||||||
@@ -540,8 +557,9 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|||||||
fields["status"] = args.status
|
fields["status"] = args.status
|
||||||
if args.agent_session is not None:
|
if args.agent_session is not None:
|
||||||
fields["agent_session"] = args.agent_session
|
fields["agent_session"] = args.agent_session
|
||||||
if args.prompt is not None:
|
prompt = _prompt_from_args(parser, args)
|
||||||
fields["prompt"] = args.prompt
|
if prompt is not None:
|
||||||
|
fields["prompt"] = prompt
|
||||||
if args.iteration is not None:
|
if args.iteration is not None:
|
||||||
fields["iteration"] = args.iteration
|
fields["iteration"] = args.iteration
|
||||||
if args.attempt_id is not None:
|
if args.attempt_id is not None:
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ multi-agent-mux-delegate-job submit \
|
|||||||
# Liveness contract (Issue #18): the agent heartbeats every N s (automatic),
|
# Liveness contract (Issue #18): the agent heartbeats every N s (automatic),
|
||||||
# the job is unresponsive after K x N s (--idle-timeout overrides), `started`
|
# the job is unresponsive after K x N s (--idle-timeout overrides), `started`
|
||||||
# must arrive within T s of the injection or the inject fails closed.
|
# must arrive within T s of the injection or the inject fails closed.
|
||||||
|
# --prompt-file <path> replaces --prompt for long prompts (Issue #36): one
|
||||||
|
# argv string is capped at 131,072 bytes on Linux. The loop always uses it.
|
||||||
|
|
||||||
# 1b) Same, for an agent on another host (no herdr pane, no shared disk):
|
# 1b) Same, for an agent on another host (no herdr pane, no shared disk):
|
||||||
# the brief travels over the broker as a signed retained dispatch and a
|
# the brief travels over the broker as a signed retained dispatch and a
|
||||||
|
|||||||
@@ -109,13 +109,15 @@ usage() {
|
|||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
multi-agent-mux-delegate-job <command> [options]
|
multi-agent-mux-delegate-job <command> [options]
|
||||||
|
|
||||||
submit --agent <name> --prompt <text> [--workdir <dir>] [--agent-session <label>]
|
submit --agent <name> (--prompt <text> | --prompt-file <path>) [--workdir <dir>] [--agent-session <label>]
|
||||||
[--timeout <sec>] [--idle-timeout <sec>] [--validate <script>]
|
[--timeout <sec>] [--idle-timeout <sec>] [--validate <script>]
|
||||||
[--heartbeat <sec>] [--heartbeat-misses <count>] [--ack-timeout <sec>]
|
[--heartbeat <sec>] [--heartbeat-misses <count>] [--ack-timeout <sec>]
|
||||||
[--registry-dir <dir>] [--dry-run] [--role <role_name>]
|
[--registry-dir <dir>] [--dry-run] [--role <role_name>]
|
||||||
[--type <direct|loop|discuss>] [--reviewer <reviewer_agent>]
|
[--type <direct|loop|discuss>] [--reviewer <reviewer_agent>]
|
||||||
[--reviewer-session <reviewer_session>] [--max-iterations <count>]
|
[--reviewer-session <reviewer_session>] [--max-iterations <count>]
|
||||||
[--counterpart-role <role_name>] [--strict-role-check]
|
[--counterpart-role <role_name>] [--strict-role-check]
|
||||||
|
# --prompt-file passes the prompt by file: one argv string is capped
|
||||||
|
# at 131,072 bytes on Linux (Issue #36). The file is read, not kept.
|
||||||
# --agent-session herdr:<name> injects into a local herdr pane;
|
# --agent-session herdr:<name> injects into a local herdr pane;
|
||||||
# --agent-session remote:<name> dispatches over the broker (Issue #18).
|
# --agent-session remote:<name> dispatches over the broker (Issue #18).
|
||||||
# Liveness contract: the agent heartbeats every --heartbeat N s
|
# Liveness contract: the agent heartbeats every --heartbeat N s
|
||||||
@@ -131,7 +133,7 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ---- arg parsing helpers --------------------------------------------------
|
# ---- arg parsing helpers --------------------------------------------------
|
||||||
AGENT="claude-code"; PROMPT=""; WORKDIR="$(pwd)"; AGENT_SESSION="herdr:claude"
|
AGENT="claude-code"; PROMPT=""; PROMPT_FILE=""; WORKDIR="$(pwd)"; AGENT_SESSION="herdr:claude"
|
||||||
TIMEOUT=3600; IDLE_TIMEOUT=""; VALIDATE=""; DRY_RUN=0
|
TIMEOUT=3600; IDLE_TIMEOUT=""; VALIDATE=""; DRY_RUN=0
|
||||||
# Issue #18 liveness contract (N / K / T). An unset --idle-timeout derives K x N.
|
# Issue #18 liveness contract (N / K / T). An unset --idle-timeout derives K x N.
|
||||||
HEARTBEAT_SEC="${MAM_HEARTBEAT_SEC:-30}"; HEARTBEAT_MISSES="${MAM_HEARTBEAT_MISSES:-4}"; ACK_TIMEOUT="${MAM_ACK_TIMEOUT_S:-180}"
|
HEARTBEAT_SEC="${MAM_HEARTBEAT_SEC:-30}"; HEARTBEAT_MISSES="${MAM_HEARTBEAT_MISSES:-4}"; ACK_TIMEOUT="${MAM_ACK_TIMEOUT_S:-180}"
|
||||||
@@ -148,6 +150,7 @@ parse_opts() {
|
|||||||
case "$1" in
|
case "$1" in
|
||||||
--agent) AGENT="$2"; shift 2;;
|
--agent) AGENT="$2"; shift 2;;
|
||||||
--prompt) PROMPT="$2"; shift 2;;
|
--prompt) PROMPT="$2"; shift 2;;
|
||||||
|
--prompt-file) PROMPT_FILE="$2"; shift 2;;
|
||||||
--workdir) WORKDIR="$2"; shift 2;;
|
--workdir) WORKDIR="$2"; shift 2;;
|
||||||
--agent-session) AGENT_SESSION="$2"; shift 2;;
|
--agent-session) AGENT_SESSION="$2"; shift 2;;
|
||||||
--timeout) TIMEOUT="$2"; shift 2;;
|
--timeout) TIMEOUT="$2"; shift 2;;
|
||||||
@@ -183,7 +186,17 @@ _to_lower() {
|
|||||||
|
|
||||||
cmd_submit() {
|
cmd_submit() {
|
||||||
parse_opts "$@"
|
parse_opts "$@"
|
||||||
[[ -n "$PROMPT" ]] || { echo "submit requires --prompt" >&2; exit 1; }
|
if [[ -n "$PROMPT_FILE" ]]; then
|
||||||
|
[[ -z "$PROMPT" ]] || { echo "submit takes --prompt or --prompt-file, not both" >&2; exit 1; }
|
||||||
|
[[ "$PROMPT_FILE" == /* ]] || PROMPT_FILE="$(pwd)/$PROMPT_FILE"
|
||||||
|
[[ -f "$PROMPT_FILE" && -r "$PROMPT_FILE" ]] || { echo "submit: cannot read --prompt-file $PROMPT_FILE" >&2; exit 1; }
|
||||||
|
PROMPT="$(<"$PROMPT_FILE")"
|
||||||
|
fi
|
||||||
|
[[ -n "$PROMPT" || -n "$PROMPT_FILE" ]] || { echo "submit requires --prompt or --prompt-file" >&2; exit 1; }
|
||||||
|
# Issue #36: the prompt reaches the registry by file when it came by file,
|
||||||
|
# never as one argv string (capped at 131,072 bytes).
|
||||||
|
local reg_prompt=(--prompt "$PROMPT")
|
||||||
|
[[ -n "$PROMPT_FILE" ]] && reg_prompt=(--prompt-file "$PROMPT_FILE")
|
||||||
PY="$(pick_python)"
|
PY="$(pick_python)"
|
||||||
[[ "$PY" == */* && "$PY" != /* ]] && PY="$(pwd)/$PY"
|
[[ "$PY" == */* && "$PY" != /* ]] && PY="$(pwd)/$PY"
|
||||||
cd "$WORKDIR"
|
cd "$WORKDIR"
|
||||||
@@ -192,7 +205,7 @@ cmd_submit() {
|
|||||||
|
|
||||||
# 1) register job (prints the new job id)
|
# 1) register job (prints the new job id)
|
||||||
JOB_ID="$("$PY" "$SCRIPT_DIR/scripts/registry.py" --registry-dir "$REGISTRY_DIR" register \
|
JOB_ID="$("$PY" "$SCRIPT_DIR/scripts/registry.py" --registry-dir "$REGISTRY_DIR" register \
|
||||||
--prompt "$PROMPT" --agent "$AGENT" --agent-session "$AGENT_SESSION" --role "$DELEGATE_ROLE" \
|
"${reg_prompt[@]}" --agent "$AGENT" --agent-session "$AGENT_SESSION" --role "$DELEGATE_ROLE" \
|
||||||
--timeout "$TIMEOUT" --idle-timeout "$IDLE_TIMEOUT" \
|
--timeout "$TIMEOUT" --idle-timeout "$IDLE_TIMEOUT" \
|
||||||
--heartbeat-sec "$HEARTBEAT_SEC" --heartbeat-misses "$HEARTBEAT_MISSES" --ack-timeout "$ACK_TIMEOUT" \
|
--heartbeat-sec "$HEARTBEAT_SEC" --heartbeat-misses "$HEARTBEAT_MISSES" --ack-timeout "$ACK_TIMEOUT" \
|
||||||
--transport "$(_is_remote "$AGENT_SESSION" && echo remote || echo herdr)" \
|
--transport "$(_is_remote "$AGENT_SESSION" && echo remote || echo herdr)" \
|
||||||
@@ -392,6 +405,8 @@ EOF
|
|||||||
## 🔎 Task Description
|
## 🔎 Task Description
|
||||||
$current_prompt
|
$current_prompt
|
||||||
EOF
|
EOF
|
||||||
|
# Issue #36: the registry gets the prompt by file, not as one argv string
|
||||||
|
printf '%s' "$current_prompt" > "$job_dir/prompt.md"
|
||||||
echo "provisioned iteration brief: $job_dir/brief.md"
|
echo "provisioned iteration brief: $job_dir/brief.md"
|
||||||
|
|
||||||
attempt=$((attempt + 1))
|
attempt=$((attempt + 1))
|
||||||
@@ -432,7 +447,7 @@ EOF
|
|||||||
"$PY" "$SCRIPT_DIR/scripts/registry.py" --registry-dir "$REGISTRY_DIR" update \
|
"$PY" "$SCRIPT_DIR/scripts/registry.py" --registry-dir "$REGISTRY_DIR" update \
|
||||||
--job "$JOB_ID" \
|
--job "$JOB_ID" \
|
||||||
--agent-session "$current_session" \
|
--agent-session "$current_session" \
|
||||||
--prompt "$current_prompt" \
|
--prompt-file "$job_dir/prompt.md" \
|
||||||
--iteration "$iteration" \
|
--iteration "$iteration" \
|
||||||
--attempt-id "$attempt" \
|
--attempt-id "$attempt" \
|
||||||
--role "$display_role" \
|
--role "$display_role" \
|
||||||
|
|||||||
Reference in new issue
Block a user