fix: fix: disinto hire-an-agent + compose generator defects blocking multi-llama-dev parallel operation (#834)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/secret-scan Pipeline was successful
ci/woodpecker/pr/smoke-init Pipeline was successful

Hiring a second llama-backed dev agent (e.g. `dev-qwen2`) alongside
`dev-qwen` tripped four defects that prevented safe parallel operation.

Gap 1 — hire-agent keyed per-agent token as FORGE_<ROLE>_TOKEN, so two
dev-role agents overwrote each other's token in .env. Re-key by agent
name via `tr 'a-z-' 'A-Z_'`: FORGE_TOKEN_<AGENT_UPPER>.

Gap 2 — hire-agent generated a random FORGE_PASS but never wrote it to
.env. The container's git credential helper needs both token and pass
to push over HTTPS (#361). Persist FORGE_PASS_<AGENT_UPPER> with the
same update-in-place idempotency as the token.

Gap 3 — _generate_local_model_services hardcoded FORGE_TOKEN_LLAMA for
every local-model service, forcing all hired llama agents to share one
Forgejo identity. Derive USER_UPPER from the TOML's `forge_user` field
and emit \${FORGE_TOKEN_<USER_UPPER>:-} per service.

Gap 4 — every local-model service mounted the shared `project-repos`
volume, so concurrent llama devs collided on /_factory worktree and
state/.dev-active. Switch to per-agent `project-repos-<service_name>`
and emit the matching top-level volume. Also escape embedded newlines
in `$all_vols` before the sed insertion so multi-agent volume lists
don't unterminate the substitute command.

.env.example documents the new FORGE_TOKEN_<AGENT> / FORGE_PASS_<AGENT>
naming convention (and preserves the legacy FORGE_TOKEN_LLAMA path used
by the ENABLE_LLAMA_AGENT=1 singleton build).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude 2026-04-16 08:55:45 +00:00
parent 311e1926bb
commit 43dc86d84c
3 changed files with 63 additions and 16 deletions

View file

@ -167,10 +167,14 @@ disinto_hire_an_agent() {
echo ""
echo "Step 1.5: Generating Forge token for '${agent_name}'..."
# Convert role to uppercase token variable name (e.g., architect -> FORGE_ARCHITECT_TOKEN)
local role_upper
role_upper=$(echo "$role" | tr '[:lower:]' '[:upper:]')
local token_var="FORGE_${role_upper}_TOKEN"
# Key per-agent credentials by *agent name*, not role (#834 Gap 1).
# Two agents with the same role (e.g. two `dev` agents) must not collide on
# FORGE_<ROLE>_TOKEN — the compose generator looks up FORGE_TOKEN_<USER_UPPER>
# where USER_UPPER = tr 'a-z-' 'A-Z_' of the agent's forge_user.
local agent_upper
agent_upper=$(echo "$agent_name" | tr 'a-z-' 'A-Z_')
local token_var="FORGE_TOKEN_${agent_upper}"
local pass_var="FORGE_PASS_${agent_upper}"
# Generate token using the user's password (basic auth)
local agent_token=""
@ -194,7 +198,7 @@ disinto_hire_an_agent() {
if [ -z "$agent_token" ]; then
echo " Warning: failed to create API token for '${agent_name}'" >&2
else
# Store token in .env under the role-specific variable name
# Store token in .env under the per-agent variable name
if grep -q "^${token_var}=" "$env_file" 2>/dev/null; then
# Use sed with alternative delimiter and proper escaping for special chars in token
local escaped_token
@ -208,6 +212,23 @@ disinto_hire_an_agent() {
export "${token_var}=${agent_token}"
fi
# Persist FORGE_PASS_<AGENT_UPPER> to .env (#834 Gap 2).
# The container's git credential helper (docker/agents/entrypoint.sh) needs
# both FORGE_TOKEN_* and FORGE_PASS_* to pass HTTPS auth for git push
# (Forgejo 11.x rejects API tokens for git push, #361).
if [ -n "${user_pass:-}" ]; then
local escaped_pass
escaped_pass=$(printf '%s\n' "$user_pass" | sed 's/[&/\]/\\&/g')
if grep -q "^${pass_var}=" "$env_file" 2>/dev/null; then
sed -i "s|^${pass_var}=.*|${pass_var}=${escaped_pass}|" "$env_file"
echo " ${agent_name} password updated (${pass_var})"
else
printf '%s=%s\n' "$pass_var" "$user_pass" >> "$env_file"
echo " ${agent_name} password saved (${pass_var})"
fi
export "${pass_var}=${user_pass}"
fi
# Step 2: Create .profile repo on Forgejo
echo ""
echo "Step 2: Creating '${agent_name}/.profile' repo (if not exists)..."