From 1e4754675dc8029b4bcc764eaec3327210583339 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 10 Apr 2026 05:47:34 +0000 Subject: [PATCH] fix: feat: hire-an-agent should support local models (--local-model flag) (#521) Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/disinto | 5 +- lib/hire-agent.sh | 188 +++++++++++++++++++++++++++------------------- 2 files changed, 113 insertions(+), 80 deletions(-) diff --git a/bin/disinto b/bin/disinto index 4c663a1..45875b4 100755 --- a/bin/disinto +++ b/bin/disinto @@ -51,7 +51,7 @@ Usage: disinto ci-logs [--step ] Read CI logs from Woodpecker SQLite disinto release Create vault PR for release (e.g., v1.2.0) - disinto hire-an-agent [--formula ] + disinto hire-an-agent [--formula ] [--local-model ] [--model ] Hire a new agent (create user + .profile repo) Init options: @@ -64,6 +64,9 @@ Init options: Hire an agent options: --formula Path to role formula TOML (default: formulas/.toml) + --local-model Base URL for local model server (e.g., http://10.10.10.1:8081) + --model Model name for local model (e.g., unsloth/Qwen3.5-35B-A3B) + --poll-interval Poll interval in seconds (default: 60) CI logs options: --step Filter logs to a specific step (e.g., smoke-init) diff --git a/lib/hire-agent.sh b/lib/hire-agent.sh index b15b2b7..d6e6fe8 100644 --- a/lib/hire-agent.sh +++ b/lib/hire-agent.sh @@ -13,7 +13,7 @@ # # Usage: # source "${FACTORY_ROOT}/lib/hire-agent.sh" -# disinto_hire_an_agent [--formula ] [--local-model ] [--poll-interval ] +# disinto_hire_an_agent [--formula ] [--local-model ] [--model ] [--poll-interval ] # ============================================================================= set -euo pipefail @@ -22,11 +22,12 @@ disinto_hire_an_agent() { local role="${2:-}" local formula_path="" local local_model="" + local model_name="" local poll_interval="" if [ -z "$agent_name" ] || [ -z "$role" ]; then echo "Error: agent-name and role required" >&2 - echo "Usage: disinto hire-an-agent [--formula ] [--local-model ] [--poll-interval ]" >&2 + echo "Usage: disinto hire-an-agent [--formula ] [--local-model ] [--model ] [--poll-interval ]" >&2 exit 1 fi shift 2 @@ -42,6 +43,10 @@ disinto_hire_an_agent() { local_model="$2" shift 2 ;; + --model) + model_name="$2" + shift 2 + ;; --poll-interval) poll_interval="$2" shift 2 @@ -71,7 +76,8 @@ disinto_hire_an_agent() { echo "Formula: ${formula_path}" if [ -n "$local_model" ]; then echo "Local model: ${local_model}" - echo "Poll interval: ${poll_interval:-300}s" + echo "Model name: ${model_name:-local-model}" + echo "Poll interval: ${poll_interval:-60}s" fi # Ensure FORGE_TOKEN is set @@ -367,11 +373,6 @@ EOF echo "" echo "Step 6: Configuring local model agent..." - local override_file="${FACTORY_ROOT}/docker-compose.override.yml" - local override_dir - override_dir=$(dirname "$override_file") - mkdir -p "$override_dir" - # Validate model endpoint is reachable echo " Validating model endpoint: ${local_model}" if ! curl -sf --max-time 10 "${local_model}/health" >/dev/null 2>&1; then @@ -384,83 +385,112 @@ EOF echo " Model endpoint is reachable" fi - # Generate service name from agent name (lowercase) - local service_name="agents-${agent_name}" - service_name=$(echo "$service_name" | tr '[:upper:]' '[:lower:]') - - # Set default poll interval - local interval="${poll_interval:-300}" - - # Generate the override compose file - # Bash expands ${service_name}, ${local_model}, ${interval}, ${PROJECT_NAME} at generation time - # \$HOME, \$FORGE_TOKEN become ${HOME}, ${FORGE_TOKEN} in the file for docker-compose runtime expansion - cat > "$override_file" <&2 + echo " Run 'disinto init' first to create a project config" >&2 + exit 1 + fi + + echo " Project TOML: ${toml_file}" + + # Derive a safe section name from the agent name (lowercase, alphanumeric+hyphens) + local section_name + section_name=$(echo "$agent_name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g') + + # Default model name if not provided + local model="${model_name:-local-model}" + + # Write [agents.] section to the project TOML + echo " Writing [agents.${section_name}] to ${toml_file}..." + python3 -c ' +import sys, re, pathlib + +toml_path = sys.argv[1] +section_name = sys.argv[2] +base_url = sys.argv[3] +model = sys.argv[4] +agent_name = sys.argv[5] +role = sys.argv[6] +compact_pct = sys.argv[7] + +p = pathlib.Path(toml_path) +text = p.read_text() + +# Build the new section +new_section = f""" +[agents.{section_name}] +base_url = "{base_url}" +model = "{model}" +api_key = "sk-no-key-required" +roles = ["{role}"] +forge_user = "{agent_name}" +compact_pct = {compact_pct} +""" + +# Check if section already exists and replace it +pattern = rf"\[agents\.{re.escape(section_name)}\][^\[]*" +if re.search(pattern, text): + text = re.sub(pattern, new_section.strip() + "\n", text) +else: + # Remove commented-out example [agents.llama] block if present + text = re.sub( + r"\n# Local-model agents \(optional\).*?(?=\n# \[mirrors\]|\n\[mirrors\]|\Z)", + "", + text, + flags=re.DOTALL, + ) + # Append before [mirrors] if it exists, otherwise at end + mirrors_match = re.search(r"\n(# )?\[mirrors\]", text) + if mirrors_match: + text = text[:mirrors_match.start()] + "\n" + new_section + text[mirrors_match.start():] + else: + text = text.rstrip() + "\n" + new_section + +p.write_text(text) +' "$toml_file" "$section_name" "$local_model" "$model" "$agent_name" "$role" "${poll_interval:-60}" + + echo " Agent config written to TOML" + + # Regenerate docker-compose.yml to include the new agent container + local compose_file="${FACTORY_ROOT}/docker-compose.yml" + if [ -f "$compose_file" ]; then + echo " Regenerating docker-compose.yml..." + rm -f "$compose_file" + # generate_compose is defined in the calling script (bin/disinto) via generators.sh + # Use _generate_compose_impl directly since generators.sh is already sourced + local forge_port="3000" + if [ -n "${FORGE_URL:-}" ]; then + forge_port=$(printf '%s' "$FORGE_URL" | sed -E 's|.*:([0-9]+)/?$|\1|') + forge_port="${forge_port:-3000}" + fi + _generate_compose_impl "$forge_port" + echo " Compose regenerated with agents-${section_name} service" + fi + + local service_name="agents-${section_name}" + echo "" echo " Service name: ${service_name}" - echo " Poll interval: ${interval}s" echo " Model endpoint: ${local_model}" + echo " Model: ${model}" echo "" echo " To start the agent, run:" - echo " docker compose --profile local-model up -d ${service_name}" + echo " docker compose --profile ${service_name} up -d ${service_name}" fi echo ""