From 3e9ac2b2618625a53dcdbc3e4866d7568d025651 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 9 Apr 2026 20:09:38 +0000 Subject: [PATCH] fix: feat: generate_compose() should support local-model agent containers (#520) --- bin/disinto | 12 +++ lib/generators.sh | 143 ++++++++++++++++++++++++++++++++++ lib/load-project.sh | 39 ++++++++++ projects/disinto.toml.example | 12 +++ 4 files changed, 206 insertions(+) diff --git a/bin/disinto b/bin/disinto index 22554f9..4c663a1 100755 --- a/bin/disinto +++ b/bin/disinto @@ -359,6 +359,18 @@ check_prs = true check_dev_agent = true check_pipeline_stall = false +# Local-model agents (optional) — configure to use llama-server or similar +# for local LLM inference. Each agent gets its own container with isolated +# credentials and configuration. +# +# [agents.llama] +# base_url = "http://10.10.10.1:8081" +# model = "unsloth/Qwen3.5-35B-A3B" +# api_key = "sk-no-key-required" +# roles = ["dev"] +# forge_user = "dev-qwen" +# compact_pct = 60 + # [mirrors] # github = "git@github.com:user/repo.git" # codeberg = "git@codeberg.org:user/repo.git" diff --git a/lib/generators.sh b/lib/generators.sh index 5bb72f7..c9aea55 100644 --- a/lib/generators.sh +++ b/lib/generators.sh @@ -26,6 +26,146 @@ PROJECT_NAME="${PROJECT_NAME:-project}" # PRIMARY_BRANCH defaults to main (env.sh may have set it to 'master') PRIMARY_BRANCH="${PRIMARY_BRANCH:-main}" +# Parse project TOML for local-model agents and emit compose services. +# Writes service definitions to stdout; caller handles insertion into compose file. +_generate_local_model_services() { + local compose_file="$1" + local projects_dir="${FACTORY_ROOT}/projects" + local temp_file + temp_file=$(mktemp) + local has_services=false + local all_vols="" + + # Find all project TOML files and extract [agents.*] sections + for toml in "${projects_dir}"/*.toml; do + [ -f "$toml" ] || continue + + # Parse [agents.*] sections using Python - output YAML-compatible format + while IFS='=' read -r key value; do + case "$key" in + NAME) service_name="$value" ;; + BASE_URL) base_url="$value" ;; + MODEL) model="$value" ;; + ROLES) roles="$value" ;; + API_KEY) api_key="$value" ;; + FORGE_USER) forge_user="$value" ;; + COMPACT_PCT) compact_pct="$value" ;; + ---) + if [ -n "$service_name" ] && [ -n "$base_url" ]; then + cat >> "$temp_file" </dev/null) + done + + if [ "$has_services" = true ]; then + # Insert the services before the volumes section + local temp_compose + temp_compose=$(mktemp) + # Get everything before volumes: + sed -n '1,/^volumes:/p' "$compose_file" | sed '$d' > "$temp_compose" + # Add the services + cat "$temp_file" >> "$temp_compose" + # Add the volumes section and everything after + sed -n '/^volumes:/,$p' "$compose_file" >> "$temp_compose" + + # Add local-model volumes to the volumes section + if [ -n "$all_vols" ]; then + # Find the volumes section and add the new volumes + sed -i "/^volumes:/{n;:a;n;/^[a-z]/!{s/$/\n$all_vols/;b};ba}" "$temp_compose" + fi + + mv "$temp_compose" "$compose_file" + fi + + rm -f "$temp_file" +} + # Generate docker-compose.yml in the factory root. _generate_compose_impl() { local forge_port="${1:-3000}" @@ -265,6 +405,9 @@ COMPOSEEOF sed -i "/image: codeberg\.org\/forgejo\/forgejo:11\.0/a\\ ports:\\n - \"3000:3000\"" "$compose_file" fi + # Append local-model agent services if any are configured + _generate_local_model_services "$compose_file" + echo "Created: ${compose_file}" } diff --git a/lib/load-project.sh b/lib/load-project.sh index e1e16ab..134461c 100755 --- a/lib/load-project.sh +++ b/lib/load-project.sh @@ -125,4 +125,43 @@ if [ -z "${FORGE_OPS_REPO:-}" ] && [ -n "${FORGE_REPO:-}" ]; then export FORGE_OPS_REPO="${FORGE_REPO}-ops" fi +# Parse [agents.*] sections for local-model agents +# Exports AGENT__BASE_URL, AGENT__MODEL, AGENT__API_KEY, +# AGENT__ROLES, AGENT__FORGE_USER, AGENT__COMPACT_PCT +if command -v python3 &>/dev/null; then + _AGENT_VARS=$(python3 -c " +import sys, tomllib + +with open(sys.argv[1], 'rb') as f: + cfg = tomllib.load(f) + +agents = cfg.get('agents', {}) +for name, config in agents.items(): + if not isinstance(config, dict): + continue + # Emit variables in uppercase with the agent name + if 'base_url' in config: + print(f'AGENT_{name.upper()}_BASE_URL={config[\"base_url\"]}') + if 'model' in config: + print(f'AGENT_{name.upper()}_MODEL={config[\"model\"]}') + if 'api_key' in config: + print(f'AGENT_{name.upper()}_API_KEY={config[\"api_key\"]}') + if 'roles' in config: + roles = ' '.join(config['roles']) if isinstance(config['roles'], list) else config['roles'] + print(f'AGENT_{name.upper()}_ROLES={roles}') + if 'forge_user' in config: + print(f'AGENT_{name.upper()}_FORGE_USER={config[\"forge_user\"]}') + if 'compact_pct' in config: + print(f'AGENT_{name.upper()}_COMPACT_PCT={config[\"compact_pct\"]}') +" "$_PROJECT_TOML" 2>/dev/null) || true + + if [ -n "$_AGENT_VARS" ]; then + while IFS='=' read -r _key _val; do + [ -z "$_key" ] && continue + export "$_key=$_val" + done <<< "$_AGENT_VARS" + fi + unset _AGENT_VARS +fi + unset _PROJECT_TOML _PROJECT_VARS _key _val diff --git a/projects/disinto.toml.example b/projects/disinto.toml.example index 61781e5..8721545 100644 --- a/projects/disinto.toml.example +++ b/projects/disinto.toml.example @@ -23,6 +23,18 @@ check_prs = true check_dev_agent = true check_pipeline_stall = false +# Local-model agents (optional) — configure to use llama-server or similar +# for local LLM inference. Each agent gets its own container with isolated +# credentials and configuration. +# +# [agents.llama] +# base_url = "http://10.10.10.1:8081" +# model = "unsloth/Qwen3.5-35B-A3B" +# api_key = "sk-no-key-required" +# roles = ["dev"] +# forge_user = "dev-qwen" +# compact_pct = 60 + # [mirrors] # github = "git@github.com:johba/disinto.git" # codeberg = "git@codeberg.org:johba/disinto.git"