factory-ai 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +22 -4
  2. package/Dockerfile.worker +1 -1
  3. package/README.md +8 -2
  4. package/ROADMAP.md +0 -1
  5. package/bin/factory +118 -1
  6. package/bootstrap/agent-factory-worker.service +2 -2
  7. package/bootstrap/auto-update.sh +20 -12
  8. package/bootstrap/deploy-runtime.sh +5 -3
  9. package/bootstrap/factory-ai-container-firewall.service +14 -0
  10. package/bootstrap/factory-ai-snapshot.service +1 -0
  11. package/bootstrap/factory-ai-watchdog.service +21 -0
  12. package/bootstrap/factory-ai-watchdog.timer +11 -0
  13. package/bootstrap/setup.sh +20 -9
  14. package/cmd/factory-ui/main.go +455 -0
  15. package/go.mod +39 -0
  16. package/go.sum +82 -0
  17. package/package.json +7 -3
  18. package/scripts/test.js +7 -0
  19. package/src/acp-adapter.js +26 -0
  20. package/src/acp-cli.js +16 -0
  21. package/src/activity.js +96 -0
  22. package/src/agent-executor.js +39 -5
  23. package/src/agent-runner.js +30 -10
  24. package/src/approval-policy.js +12 -0
  25. package/src/azure-harness.js +47 -14
  26. package/src/bedrock-harness.js +45 -14
  27. package/src/config.js +9 -0
  28. package/src/container-runner.js +65 -12
  29. package/src/context-compaction.js +16 -0
  30. package/src/control-plane.js +77 -16
  31. package/src/control-service.js +6 -1
  32. package/src/dashboard.js +26 -6
  33. package/src/extension-cli.js +11 -0
  34. package/src/extension-manifest.js +76 -0
  35. package/src/hooks.js +62 -0
  36. package/src/instructions.js +45 -0
  37. package/src/mcp-tools.js +19 -3
  38. package/src/objective-status.js +16 -0
  39. package/src/operator-logs.js +24 -0
  40. package/src/operator.js +31 -9
  41. package/src/process.js +3 -3
  42. package/src/release-gate.js +1 -0
  43. package/src/release-service.js +4 -1
  44. package/src/repo-map.js +128 -0
  45. package/src/reporter.js +11 -0
  46. package/src/retriever.js +18 -18
  47. package/src/routing.js +11 -1
  48. package/src/scanner-suite.js +4 -3
  49. package/src/snapshot.js +16 -0
  50. package/src/task-entry.js +28 -6
  51. package/src/task-graph.js +0 -5
  52. package/src/telegram-service.js +7 -1
  53. package/src/telegram.js +5 -1
  54. package/src/telemetry.js +173 -0
  55. package/src/tui.js +27 -8
  56. package/src/validation.js +41 -9
  57. package/src/watchdog.js +62 -0
  58. package/src/worker.js +32 -3
  59. package/src/workspace-tools.js +13 -3
  60. package/src/workspace.js +22 -2
  61. package/templates/AGENTS.md +15 -0
package/CHANGELOG.md CHANGED
@@ -4,12 +4,30 @@ All notable changes follow semantic versioning and the Keep a Changelog structur
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.5.1] - 2026-07-13
8
+
9
+ ### Fixed
10
+
11
+ - Exclude unfinished tasks from stale-agent health after their objective reaches a terminal state.
12
+
13
+ ## [1.5.0] - 2026-07-13
14
+
7
15
  ### Added
8
16
 
9
- - Full-screen terminal administration console.
10
- - Telegram remote objective intake with chat allowlisting.
11
- - Azure and Bedrock provider wizard.
12
- - Durable evaluation, analytics, scaling, policy, extension, and recovery roadmap.
17
+ - Cross-platform Bubble Tea/Lip Gloss operator client with direct Azure Blob snapshots, verified release binaries, scrolling, and safe Node action fallback.
18
+ - Automatic Azure and Bedrock context compaction with immutable objective retention and recent tool evidence.
19
+ - Hierarchical `AGENTS.md` discovery, preconfigured project instructions, and bounded `.agent-factory` context.
20
+ - Durable worktree crash recovery, parallel read-only tool batches, live activity timelines, retry/error telemetry, and stale-agent watchdog recovery.
21
+ - Post-setup `factory models show|set|reset` and `factory configure models` workflows with validated routes, atomic runtime rollback, and update-safe persistence.
22
+ - Open-source harness adoption research and executable Go CI gates.
23
+ - Token-budgeted repository maps, safe OpenTelemetry GenAI records, Promptfoo/Inspect/Harbor evaluation contracts, typed lifecycle hooks, durable approval interrupts, optional ACP intake, and signed extension verification.
24
+
25
+ ### Security
26
+
27
+ - Read-only role filesystems and command policy, deterministic pre-push Gitleaks enforcement, typed scanner release blockers, terminal-safe Go rendering, and structured allowlisted operator logs.
28
+ - Repository-scoped MCP memory; legacy unscoped memory is retained as `legacy-unscoped-knowledge-graph.jsonl` instead of being mixed into new repositories.
29
+ - Model credentials now enter agent containers through a consumed stdin envelope rather than environment variables; a persistent Docker firewall blocks cloud metadata access.
30
+ - Release publishing now binds tags to package versions, requires full gates and npm provenance, and attests immutable Go binaries.
13
31
 
14
32
  ## [1.4.0] - 2026-07-13
15
33
 
package/Dockerfile.worker CHANGED
@@ -1,4 +1,4 @@
1
- FROM node:20.20.2-bookworm-slim
1
+ FROM node:22.17.0-bookworm-slim
2
2
 
3
3
  RUN apt-get update \
4
4
  && apt-get install -y --no-install-recommends git ripgrep ca-certificates \
package/README.md CHANGED
@@ -73,7 +73,13 @@ factory ui
73
73
  | `factory issue OWNER/REPO NUMBER` | Turn a GitHub issue into a tracked objective |
74
74
  | `factory telegram configure` | Configure allowlisted Telegram remote intake |
75
75
  | `factory dashboard` | Objectives, agents, models, queue, DLQ, and Azure cost |
76
- | `factory init PATH` | Create durable repo-local project context |
76
+ | `factory init PATH` | Create `AGENTS.md` and durable repo-local project context without overwriting existing files |
77
+ | `factory models show` | Show effective role-to-model routes |
78
+ | `factory models set ROLE PROVIDER/MODEL` | Validate and atomically switch a role to a new model |
79
+ | `factory models reset ROLE` | Restore a role to the versioned default |
80
+ | `factory configure models` | Interactively change model routing after setup |
81
+ | `factory acp REQUEST.json` | Submit a strictly validated optional ACP objective |
82
+ | `factory extension verify MANIFEST ARTIFACT PUBLIC_KEY` | Verify a signed extension manifest and immutable artifact digest |
77
83
  | `factory doctor` | Services, storage, memory, and host health |
78
84
  | `factory queue` | Queue and dead-letter depth |
79
85
  | `factory logs` | Consolidated service logs |
@@ -157,7 +163,7 @@ Every repository gets two memory layers:
157
163
 
158
164
  Built-in progressive skills include `/goal`, `/loop`, project context, systematic debugging, TDD, verification, security review, dependency security, browser verification, release discipline, and token efficiency. Context7 and Playwright MCPs are pinned and role-scoped.
159
165
 
160
- Use `factory init PATH` to create `.agent-factory/` project, architecture, commands, decisions, risks, and handoff files without overwriting existing context.
166
+ Use `factory init PATH` to create a safe root `AGENTS.md` plus `.agent-factory/` project, architecture, commands, decisions, risks, and handoff files without overwriting existing context. The runtime discovers repository `AGENTS.md` instructions for planners and workers. Active Azure and Bedrock conversations compact automatically after their configured token threshold while preserving bounded recent tool evidence.
161
167
 
162
168
  ## Credentials
163
169
 
package/ROADMAP.md CHANGED
@@ -4,7 +4,6 @@
4
4
 
5
5
  - Evaluation Lab with model promotion and rollback evidence.
6
6
  - Enhanced TUI timeline, diffs, files, checks, and safe takeover leases.
7
- - Telegram remote objective status and completion notifications.
8
7
  - Multi-repository objective schema and release groups.
9
8
 
10
9
  ## Platform
package/bin/factory CHANGED
@@ -23,6 +23,9 @@ usage() {
23
23
  'Factory AI operator console' \
24
24
  'Usage: factory <command>' \
25
25
  ' setup' \
26
+ ' configure models' \
27
+ ' models show | set ROLE PROVIDER/MODEL | reset ROLE' \
28
+ ' acp REQUEST.json | extension verify MANIFEST ARTIFACT PUBLIC_KEY' \
26
29
  ' github status | connect ORG | transfer OWNER/REPO ORG' \
27
30
  ' telegram status | configure | start | stop' \
28
31
  ' submit <owner/repo> <objective>' \
@@ -36,7 +39,97 @@ usage() {
36
39
 
37
40
  command=${1:-ui}
38
41
  shift || true
42
+
43
+ apply_runtime_model() {
44
+ local variable=$1 value=${2:-} mode=${3:-set} script settings
45
+ script="set -euo pipefail; exec 9>/run/lock/factory-ai-config.lock; flock -x 9; first=/etc/agent-factory.env; second=/etc/agent-factory-control.env; backup_first=\$(mktemp); backup_second=\$(mktemp); cp --preserve=mode,ownership \"\$first\" \"\$backup_first\"; cp --preserve=mode,ownership \"\$second\" \"\$backup_second\"; rollback() { install -m 0600 -o root -g root \"\$backup_first\" \"\$first\"; install -m 0600 -o root -g root \"\$backup_second\" \"\$second\"; systemctl restart agent-factory-control agent-factory-worker agent-factory-release || true; rm -f \"\$backup_first\" \"\$backup_second\"; }; trap rollback ERR TERM INT; for file in \"\$first\" \"\$second\"; do tmp=\$(mktemp); grep -v '^${variable}=' \"\$file\" > \"\$tmp\" || true; if [[ '$mode' == set ]]; then printf '${variable}=%s\\n' '$value' >> \"\$tmp\"; fi; install -m 0600 -o root -g root \"\$tmp\" \"\$file\"; rm -f \"\$tmp\"; done; systemctl restart agent-factory-control agent-factory-worker agent-factory-release; systemctl is-active --quiet agent-factory-control agent-factory-worker agent-factory-release; trap - ERR TERM INT; rm -f \"\$backup_first\" \"\$backup_second\""
46
+ printf -v script 'bash -c %q' "$script"
47
+ settings=$(jq -n --arg command "$script" '{commandToExecute:$command}')
48
+ az vm extension set --resource-group "$RESOURCE_GROUP" --vm-name "$VM" --name FactoryAIConfigure \
49
+ --publisher Microsoft.Azure.Extensions --version 2.1 --force-update --protected-settings "$settings" --output none
50
+ }
51
+
52
+ update_local_model() {
53
+ local variable=$1 value=${2:-} mode=${3:-set} temporary
54
+ mkdir -p "$(dirname "$CONFIG_FILE")"
55
+ temporary=$(mktemp "${CONFIG_FILE}.XXXXXX")
56
+ if [[ -f $CONFIG_FILE ]]; then grep -v "^${variable}=" "$CONFIG_FILE" > "$temporary" || true
57
+ elif [[ -f $LEGACY_CONFIG_FILE ]]; then grep -v "^${variable}=" "$LEGACY_CONFIG_FILE" > "$temporary" || true; fi
58
+ if [[ $mode == set ]]; then printf '%s=%q\n' "$variable" "$value" >> "$temporary"; fi
59
+ chmod 0600 "$temporary"
60
+ mv "$temporary" "$CONFIG_FILE"
61
+ }
62
+
63
+ configure_model_transaction() {
64
+ local variable=$1 value=${2:-} mode=${3:-set}
65
+ MODEL_CONFIG_BACKUP=$(mktemp)
66
+ MODEL_CONFIG_EXISTED=false
67
+ if [[ -f $CONFIG_FILE ]]; then cp "$CONFIG_FILE" "$MODEL_CONFIG_BACKUP"; MODEL_CONFIG_EXISTED=true; fi
68
+ rollback_local_model() {
69
+ if [[ $MODEL_CONFIG_EXISTED == true ]]; then mv "$MODEL_CONFIG_BACKUP" "$CONFIG_FILE"; else rm -f "$CONFIG_FILE" "$MODEL_CONFIG_BACKUP"; fi
70
+ }
71
+ trap rollback_local_model EXIT INT TERM
72
+ update_local_model "$variable" "$value" "$mode"
73
+ apply_runtime_model "$variable" "$value" "$mode"
74
+ rm -f "$MODEL_CONFIG_BACKUP"
75
+ trap - EXIT INT TERM
76
+ }
77
+
39
78
  case "$command" in
79
+ acp)
80
+ request=${1:?ACP request JSON file required}
81
+ SERVICE_BUS_NAMESPACE="$NAMESPACE" CONTROL_QUEUE=control-events FACTORY_ACP_ENABLED=true exec node "$FACTORY_ROOT/src/acp-cli.js" "$request"
82
+ ;;
83
+ extension)
84
+ [[ ${1:-} == verify ]] || { printf 'Usage: factory extension verify MANIFEST ARTIFACT PUBLIC_KEY\n' >&2; exit 2; }
85
+ exec node "$FACTORY_ROOT/src/extension-cli.js" "${2:?manifest required}" "${3:?artifact required}" "${4:?public key required}"
86
+ ;;
87
+ configure)
88
+ area=${1:-models}
89
+ [[ $area == models ]] || { printf 'Supported configuration area: models\n' >&2; exit 2; }
90
+ "$0" models show
91
+ read -r -p 'Role to change (or Enter to leave unchanged): ' role
92
+ [[ -n $role ]] || exit 0
93
+ read -r -p 'New provider/model route (or "default"): ' route
94
+ if [[ $route == default ]]; then exec "$0" models reset "$role"; fi
95
+ exec "$0" models set "$role" "$route"
96
+ ;;
97
+ models)
98
+ action=${1:-show}
99
+ role=${2:-}
100
+ route=${3:-}
101
+ case "$action" in
102
+ show)
103
+ for current in scout planner builder tester debugger reviewer security release; do
104
+ upper=$(printf '%s' "$current" | tr '[:lower:]' '[:upper:]')
105
+ variable="FACTORY_MODEL_${upper}"
106
+ case "$current" in
107
+ scout) default=azureai-textved/factory-gpt-5-4-nano ;;
108
+ tester) default=azureai-responses/gpt-5.4 ;;
109
+ builder) default=azureai-textved/gpt-5.5 ;;
110
+ *) default=azureai-textved/gpt-5.6-sol ;;
111
+ esac
112
+ printf '%-10s %s%s\n' "$current" "${!variable:-$default}" "$([[ -n ${!variable:-} ]] && printf ' (override)' || true)"
113
+ done
114
+ ;;
115
+ set)
116
+ [[ $role =~ ^(scout|planner|builder|tester|debugger|reviewer|security|release)$ ]] || { printf 'Invalid model role\n' >&2; exit 2; }
117
+ node --input-type=module -e "import { validateModelRoute } from 'file://$FACTORY_ROOT/src/routing.js'; validateModelRoute(process.argv[1])" "$route"
118
+ upper=$(printf '%s' "$role" | tr '[:lower:]' '[:upper:]')
119
+ variable="FACTORY_MODEL_${upper}"
120
+ configure_model_transaction "$variable" "$route" set
121
+ printf '%s now routes to %s. Runtime services are healthy.\n' "$role" "$route"
122
+ ;;
123
+ reset)
124
+ [[ $role =~ ^(scout|planner|builder|tester|debugger|reviewer|security|release)$ ]] || { printf 'Invalid model role\n' >&2; exit 2; }
125
+ upper=$(printf '%s' "$role" | tr '[:lower:]' '[:upper:]')
126
+ variable="FACTORY_MODEL_${upper}"
127
+ configure_model_transaction "$variable" "" reset
128
+ printf '%s restored to the versioned default. Runtime services are healthy.\n' "$role"
129
+ ;;
130
+ *) printf 'Usage: factory models show | set ROLE PROVIDER/MODEL | reset ROLE\n' >&2; exit 2 ;;
131
+ esac
132
+ ;;
40
133
  telegram)
41
134
  action=${1:-status}
42
135
  case "$action" in
@@ -80,6 +173,29 @@ case "$command" in
80
173
  "$0" setup
81
174
  exec "$0" ui
82
175
  fi
176
+ version=$(node -p "require('$FACTORY_ROOT/package.json').version")
177
+ os=$(uname -s | tr '[:upper:]' '[:lower:]')
178
+ arch=$(uname -m)
179
+ [[ $arch == x86_64 ]] && arch=amd64
180
+ [[ $arch == aarch64 ]] && arch=arm64
181
+ cache="$HOME/.cache/factory-ai/$version"
182
+ binary="$cache/factory-ui-$os-$arch"
183
+ if [[ ! -x $binary ]]; then
184
+ mkdir -p "$cache"
185
+ base="https://github.com/itsvedantkumar/factory-ai/releases/download/v$version"
186
+ if curl -fsSL "$base/factory-ui-$os-$arch" -o "$binary.tmp" && curl -fsSL "$base/checksums.txt" -o "$cache/checksums.txt"; then
187
+ expected=""
188
+ while read -r checksum asset; do [[ $asset == "factory-ui-$os-$arch" ]] && expected=$checksum; done < "$cache/checksums.txt"
189
+ actual=""
190
+ if command -v shasum >/dev/null; then actual=$(shasum -a 256 "$binary.tmp"); actual=${actual%% *};
191
+ elif command -v sha256sum >/dev/null; then actual=$(sha256sum "$binary.tmp"); actual=${actual%% *}; fi
192
+ if [[ -n $expected && $actual == "$expected" ]] && gh attestation verify "$binary.tmp" --repo itsvedantkumar/factory-ai >/dev/null 2>&1; then mv "$binary.tmp" "$binary"; chmod 0755 "$binary"; else rm -f "$binary.tmp"; fi
193
+ fi
194
+ fi
195
+ if [[ -x $binary ]]; then
196
+ FACTORY_STORAGE_ACCOUNT="$STORAGE" FACTORY_NAME="$FACTORY_NAME" FACTORY_PURPOSE="$FACTORY_PURPOSE" exec "$binary"
197
+ fi
198
+ printf 'Charm client unavailable for this release; using Node fallback.\n' >&2
83
199
  FACTORY_RESOURCE_GROUP="$RESOURCE_GROUP" FACTORY_VM="$VM" FACTORY_SERVICE_BUS="$NAMESPACE" FACTORY_KEY_VAULT="$VAULT" FACTORY_STORAGE_ACCOUNT="$STORAGE" FACTORY_NAME="$FACTORY_NAME" FACTORY_PURPOSE="$FACTORY_PURPOSE" node "$FACTORY_ROOT/src/tui.js"
84
200
  ;;
85
201
  setup)
@@ -164,7 +280,7 @@ case "$command" in
164
280
  printf 'Azure foundation and credential vault ready. Config: %s\n' "$CONFIG_FILE"
165
281
  if [[ $deploy_now == true ]]; then
166
282
  source_ref=$(gh api repos/itsvedantkumar/factory-ai/commits/main --jq .sha)
167
- parameters=(KEY_VAULT_NAME "$vault" SERVICE_BUS_NAMESPACE "$namespace" SERVICE_BUS_QUEUE code-tasks FACTORY_STORAGE_ACCOUNT "$storage" FACTORY_NAME "$factory_name" FACTORY_PURPOSE "$factory_purpose" SOURCE_REPOSITORY itsvedantkumar/factory-ai SOURCE_REF "$source_ref")
283
+ parameters=(KEY_VAULT_NAME "$vault" SERVICE_BUS_NAMESPACE "$namespace" FACTORY_STORAGE_ACCOUNT "$storage" FACTORY_NAME "$factory_name" FACTORY_PURPOSE "$factory_purpose" SOURCE_REPOSITORY itsvedantkumar/factory-ai SOURCE_REF "$source_ref")
168
284
  if [[ $provider == bedrock ]]; then
169
285
  parameters+=(AWS_REGION "$aws_region")
170
286
  for role in SCOUT PLANNER BUILDER TESTER DEBUGGER REVIEWER SECURITY RELEASE; do parameters+=("FACTORY_MODEL_$role" "bedrock/$bedrock_builder"); done
@@ -243,6 +359,7 @@ case "$command" in
243
359
  [[ -d $target ]] || { printf 'Project directory does not exist: %s\n' "$target" >&2; exit 2; }
244
360
  context="$target/.agent-factory"
245
361
  mkdir -p "$context"
362
+ [[ -e $target/AGENTS.md ]] || cp "$FACTORY_ROOT/templates/AGENTS.md" "$target/AGENTS.md"
246
363
  for template in "$FACTORY_ROOT/templates/project/"*.md; do
247
364
  destination="$context/$(basename "$template")"
248
365
  [[ -e $destination ]] || cp "$template" "$destination"
@@ -1,8 +1,8 @@
1
1
  [Unit]
2
2
  Description=Factory AI durable worker
3
- After=network-online.target docker.service factory-ai-qdrant.service factory-ai-ollama.service
3
+ After=network-online.target docker.service factory-ai-container-firewall.service factory-ai-qdrant.service factory-ai-ollama.service
4
4
  Wants=network-online.target factory-ai-qdrant.service factory-ai-ollama.service
5
- Requires=docker.service
5
+ Requires=docker.service factory-ai-container-firewall.service
6
6
  StartLimitIntervalSec=300
7
7
  StartLimitBurst=10
8
8
 
@@ -18,6 +18,12 @@ set -a
18
18
  source /etc/agent-factory.env
19
19
  set +a
20
20
  github_token=$(az keyvault secret show --vault-name "$KEY_VAULT_NAME" --name github-token --query value --output tsv)
21
+ tag_commit=$(GH_TOKEN="$github_token" gh api "repos/itsvedantkumar/factory-ai/commits/v$version" --jq .sha)
22
+ [[ $tag_commit == "$commit" ]] || { printf 'npm gitHead does not match the immutable release tag.\n' >&2; exit 1; }
23
+ release_ok=$(GH_TOKEN="$github_token" gh release view "v$version" --repo itsvedantkumar/factory-ai --json isDraft,isPrerelease --jq '(.isDraft == false) and (.isPrerelease == false)')
24
+ [[ $release_ok == true ]] || { printf 'Matching stable GitHub release is unavailable.\n' >&2; exit 1; }
25
+ provenance=$(npm view "factory-ai@$version" dist.attestations.provenance.url 2>/dev/null || true)
26
+ [[ $provenance == https://search.sigstore.dev/* ]] || { printf 'npm release lacks verifiable provenance metadata.\n' >&2; exit 1; }
21
27
  checks=$(GH_TOKEN="$github_token" gh api "repos/itsvedantkumar/factory-ai/commits/$commit/check-runs" --jq '[.check_runs[] | select(.name == "verify") | .conclusion] | any(. == "success")')
22
28
  [[ $checks == true ]] || { printf 'Candidate commit lacks successful CI verification.\n' >&2; exit 1; }
23
29
 
@@ -41,20 +47,22 @@ docker run --rm --read-only --volume "$temporary/source:/workspace:ro" \
41
47
  detect --source /workspace --redact --no-banner --exit-code 1
42
48
 
43
49
  printf 'Deploying Factory AI %s (%s)\n' "$version" "$commit"
44
- if ! bash "$temporary/source/bootstrap/deploy-runtime.sh" \
45
- KEY_VAULT_NAME "$KEY_VAULT_NAME" \
46
- SERVICE_BUS_NAMESPACE "$SERVICE_BUS_NAMESPACE" \
47
- SERVICE_BUS_QUEUE code-tasks \
48
- SOURCE_REPOSITORY itsvedantkumar/factory-ai \
49
- SOURCE_REF "$commit"; then
50
+ deployment_parameters=(
51
+ KEY_VAULT_NAME "$KEY_VAULT_NAME"
52
+ SERVICE_BUS_NAMESPACE "$SERVICE_BUS_NAMESPACE"
53
+ SOURCE_REPOSITORY itsvedantkumar/factory-ai
54
+ SOURCE_REF "$commit"
55
+ )
56
+ for variable in FACTORY_STORAGE_ACCOUNT FACTORY_NAME FACTORY_PURPOSE AWS_REGION FACTORY_MODEL_SCOUT FACTORY_MODEL_PLANNER FACTORY_MODEL_BUILDER FACTORY_MODEL_TESTER FACTORY_MODEL_DEBUGGER FACTORY_MODEL_REVIEWER FACTORY_MODEL_SECURITY FACTORY_MODEL_RELEASE FACTORY_COMPACT_AFTER_INPUT_TOKENS FACTORY_COMPACT_MAX_CHARACTERS FACTORY_WATCHDOG_STALE_SECONDS FACTORY_HOOKS_JSON; do
57
+ [[ -n ${!variable:-} ]] && deployment_parameters+=("$variable" "${!variable}")
58
+ done
59
+ if ! bash "$temporary/source/bootstrap/deploy-runtime.sh" "${deployment_parameters[@]}"; then
50
60
  printf 'Candidate deployment failed. Restoring previous runtime.\n' >&2
51
61
  if [[ $previous_commit =~ ^[0-9a-f]{40}$ ]]; then
52
- bash "$temporary/rollback-deploy.sh" \
53
- KEY_VAULT_NAME "$KEY_VAULT_NAME" \
54
- SERVICE_BUS_NAMESPACE "$SERVICE_BUS_NAMESPACE" \
55
- SERVICE_BUS_QUEUE code-tasks \
56
- SOURCE_REPOSITORY itsvedantkumar/factory-ai \
57
- SOURCE_REF "$previous_commit"
62
+ for ((index=0; index<${#deployment_parameters[@]}; index+=2)); do
63
+ [[ ${deployment_parameters[index]} == SOURCE_REF ]] && deployment_parameters[index+1]=$previous_commit
64
+ done
65
+ bash "$temporary/rollback-deploy.sh" "${deployment_parameters[@]}"
58
66
  fi
59
67
  exit 1
60
68
  fi
@@ -20,7 +20,7 @@ while (($# > 0)); do
20
20
  shift
21
21
  fi
22
22
  case "$name" in
23
- KEY_VAULT_NAME|SERVICE_BUS_NAMESPACE|SERVICE_BUS_QUEUE|SOURCE_REPOSITORY|SOURCE_REF|FACTORY_STORAGE_ACCOUNT|FACTORY_NAME|FACTORY_PURPOSE|AWS_REGION|FACTORY_MODEL_SCOUT|FACTORY_MODEL_PLANNER|FACTORY_MODEL_BUILDER|FACTORY_MODEL_TESTER|FACTORY_MODEL_DEBUGGER|FACTORY_MODEL_REVIEWER|FACTORY_MODEL_SECURITY|FACTORY_MODEL_RELEASE) printf -v "$name" '%s' "$value" ;;
23
+ KEY_VAULT_NAME|SERVICE_BUS_NAMESPACE|SOURCE_REPOSITORY|SOURCE_REF|FACTORY_STORAGE_ACCOUNT|FACTORY_NAME|FACTORY_PURPOSE|AWS_REGION|FACTORY_MODEL_SCOUT|FACTORY_MODEL_PLANNER|FACTORY_MODEL_BUILDER|FACTORY_MODEL_TESTER|FACTORY_MODEL_DEBUGGER|FACTORY_MODEL_REVIEWER|FACTORY_MODEL_SECURITY|FACTORY_MODEL_RELEASE|FACTORY_COMPACT_AFTER_INPUT_TOKENS|FACTORY_COMPACT_MAX_CHARACTERS|FACTORY_WATCHDOG_STALE_SECONDS|FACTORY_HOOKS_JSON) printf -v "$name" '%s' "$value" ;;
24
24
  *) echo "Unknown deployment parameter: $name" >&2; exit 1 ;;
25
25
  esac
26
26
  done
@@ -29,7 +29,6 @@ done
29
29
  : "${SERVICE_BUS_NAMESPACE:?SERVICE_BUS_NAMESPACE is required}"
30
30
  : "${SOURCE_REPOSITORY:?SOURCE_REPOSITORY as OWNER/REPOSITORY is required}"
31
31
  : "${SOURCE_REF:?SOURCE_REF must be a full commit SHA}"
32
- SERVICE_BUS_QUEUE=${SERVICE_BUS_QUEUE:-code-tasks}
33
32
  [[ $SOURCE_REPOSITORY =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || { echo "Invalid SOURCE_REPOSITORY" >&2; exit 1; }
34
33
  [[ $SOURCE_REF =~ ^[0-9a-f]{40}$ ]] || { echo "SOURCE_REF must be a full lowercase commit SHA" >&2; exit 1; }
35
34
 
@@ -60,7 +59,6 @@ rsync -a --delete --exclude .git "$temporary/source/" /opt/agent-factory/app/
60
59
 
61
60
  KEY_VAULT_NAME="$KEY_VAULT_NAME" \
62
61
  SERVICE_BUS_NAMESPACE="$SERVICE_BUS_NAMESPACE" \
63
- SERVICE_BUS_QUEUE="$SERVICE_BUS_QUEUE" \
64
62
  FACTORY_WORKER_IMAGE="agent-factory-worker:$SOURCE_REF" \
65
63
  FACTORY_STORAGE_ACCOUNT="${FACTORY_STORAGE_ACCOUNT:-}" \
66
64
  FACTORY_NAME="${FACTORY_NAME:-Factory AI}" \
@@ -74,6 +72,10 @@ FACTORY_MODEL_DEBUGGER="${FACTORY_MODEL_DEBUGGER:-}" \
74
72
  FACTORY_MODEL_REVIEWER="${FACTORY_MODEL_REVIEWER:-}" \
75
73
  FACTORY_MODEL_SECURITY="${FACTORY_MODEL_SECURITY:-}" \
76
74
  FACTORY_MODEL_RELEASE="${FACTORY_MODEL_RELEASE:-}" \
75
+ FACTORY_COMPACT_AFTER_INPUT_TOKENS="${FACTORY_COMPACT_AFTER_INPUT_TOKENS:-80000}" \
76
+ FACTORY_COMPACT_MAX_CHARACTERS="${FACTORY_COMPACT_MAX_CHARACTERS:-24000}" \
77
+ FACTORY_WATCHDOG_STALE_SECONDS="${FACTORY_WATCHDOG_STALE_SECONDS:-900}" \
78
+ FACTORY_HOOKS_JSON="${FACTORY_HOOKS_JSON:-[]}" \
77
79
  bash /opt/agent-factory/app/bootstrap/setup.sh
78
80
  install -d -o factory -g factory -m 0750 /opt/agent-factory/state
79
81
  version=$(node -p 'require("/opt/agent-factory/app/package.json").version')
@@ -0,0 +1,14 @@
1
+ [Unit]
2
+ Description=Block cloud metadata access from Factory AI containers
3
+ After=docker.service
4
+ Before=agent-factory-worker.service
5
+ Requires=docker.service
6
+
7
+ [Service]
8
+ Type=oneshot
9
+ ExecStart=/bin/bash -c '/usr/sbin/iptables -C DOCKER-USER -d 169.254.169.254/32 -j REJECT 2>/dev/null || /usr/sbin/iptables -I DOCKER-USER -d 169.254.169.254/32 -j REJECT'
10
+ RemainAfterExit=yes
11
+ NoNewPrivileges=true
12
+
13
+ [Install]
14
+ WantedBy=multi-user.target
@@ -7,6 +7,7 @@ Wants=network-online.target
7
7
  Type=oneshot
8
8
  User=factory
9
9
  Group=factory
10
+ SupplementaryGroups=systemd-journal
10
11
  WorkingDirectory=/opt/agent-factory/app
11
12
  EnvironmentFile=/etc/agent-factory-control.env
12
13
  ExecStart=/usr/bin/node /opt/agent-factory/app/src/snapshot.js
@@ -0,0 +1,21 @@
1
+ [Unit]
2
+ Description=Recover stale Factory AI agent containers
3
+ After=network-online.target docker.service agent-factory-control.service
4
+ Wants=network-online.target
5
+ Requires=docker.service
6
+
7
+ [Service]
8
+ Type=oneshot
9
+ User=factory
10
+ Group=factory
11
+ SupplementaryGroups=docker
12
+ WorkingDirectory=/opt/agent-factory/app
13
+ EnvironmentFile=/etc/agent-factory.env
14
+ ExecStart=/usr/bin/node /opt/agent-factory/app/src/watchdog.js
15
+ NoNewPrivileges=true
16
+ PrivateTmp=true
17
+ ProtectSystem=strict
18
+ ProtectHome=true
19
+ ReadWritePaths=/opt/agent-factory/state /opt/agent-factory/workspaces
20
+ UMask=0027
21
+ SyslogIdentifier=factory-ai-watchdog
@@ -0,0 +1,11 @@
1
+ [Unit]
2
+ Description=Check Factory AI agent heartbeats every minute
3
+
4
+ [Timer]
5
+ OnBootSec=5m
6
+ OnUnitActiveSec=1m
7
+ RandomizedDelaySec=5
8
+ Persistent=true
9
+
10
+ [Install]
11
+ WantedBy=timers.target
@@ -10,7 +10,6 @@ fi
10
10
  : "${KEY_VAULT_NAME:?Set KEY_VAULT_NAME to the existing vault name}"
11
11
  : "${SERVICE_BUS_NAMESPACE:?Set SERVICE_BUS_NAMESPACE to the existing namespace name}"
12
12
  : "${FACTORY_WORKER_IMAGE:?Set FACTORY_WORKER_IMAGE to an immutable image tag}"
13
- SERVICE_BUS_QUEUE=${SERVICE_BUS_QUEUE:-code-tasks}
14
13
  APP_DIR=${APP_DIR:-/opt/agent-factory/app}
15
14
  FACTORY_USER=${FACTORY_USER:-factory}
16
15
 
@@ -18,11 +17,11 @@ chmod 0755 /opt/agent-factory "$APP_DIR"
18
17
 
19
18
  export DEBIAN_FRONTEND=noninteractive
20
19
  apt-get update
21
- apt-get install -y ca-certificates curl gpg git jq
20
+ apt-get install -y ca-certificates curl gpg git jq iptables
22
21
 
23
22
  install -d -m 0755 /etc/apt/keyrings
24
23
  curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg
25
- printf '%s\n' 'deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main' > /etc/apt/sources.list.d/nodesource.list
24
+ printf '%s\n' 'deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main' > /etc/apt/sources.list.d/nodesource.list
26
25
  curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg -o /etc/apt/keyrings/githubcli-archive-keyring.gpg
27
26
  chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg
28
27
  printf '%s\n' "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list
@@ -30,7 +29,7 @@ curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor --y
30
29
  printf '%s\n' "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/microsoft.gpg] https://packages.microsoft.com/repos/azure-cli/ $(. /etc/os-release && echo "$VERSION_CODENAME") main" > /etc/apt/sources.list.d/azure-cli.list
31
30
  apt-get update
32
31
  apt-get install -y azure-cli gh nodejs
33
- test "$(node --version | cut -d. -f1)" = "v20"
32
+ test "$(node --version | cut -d. -f1)" = "v22"
34
33
 
35
34
  id "$FACTORY_USER" >/dev/null 2>&1 || useradd --system --create-home --shell /usr/sbin/nologin "$FACTORY_USER"
36
35
  test -f "$APP_DIR/package-lock.json"
@@ -46,6 +45,7 @@ for scanner_image in \
46
45
  done
47
46
  docker pull ollama/ollama@sha256:3d8a05e3432d50ea57594fabe971e46cc8fe963a0f9f8c40400bd56cd5388e47
48
47
  docker pull qdrant/qdrant@sha256:31407c0e8e32eb771b71718f1a4772e2ad47a07557917b21ac96792f40eb8007
48
+ iptables -C DOCKER-USER -d 169.254.169.254/32 -j REJECT 2>/dev/null || iptables -I DOCKER-USER -d 169.254.169.254/32 -j REJECT
49
49
 
50
50
  STATE_DEVICE=/dev/disk/azure/scsi1/lun0
51
51
  if [[ -b $STATE_DEVICE ]]; then
@@ -68,6 +68,9 @@ for secret_name in "${GITHUB_TOKEN_SECRET:-github-token}"; do
68
68
  done
69
69
 
70
70
  install -d -o "$FACTORY_USER" -g "$FACTORY_USER" -m 0750 /opt/agent-factory/state /opt/agent-factory/state/home /opt/agent-factory/state/memory /opt/agent-factory/state/telegram /opt/agent-factory/state/retrieval /opt/agent-factory/workspaces /opt/agent-factory/logs
71
+ if [[ -f /opt/agent-factory/state/memory/knowledge-graph.jsonl && ! -e /opt/agent-factory/state/memory/legacy-unscoped-knowledge-graph.jsonl ]]; then
72
+ mv /opt/agent-factory/state/memory/knowledge-graph.jsonl /opt/agent-factory/state/memory/legacy-unscoped-knowledge-graph.jsonl
73
+ fi
71
74
  install -d -o root -g root -m 0750 /opt/agent-factory/state/qdrant /opt/agent-factory/state/qdrant-snapshots /opt/agent-factory/state/ollama
72
75
  install -m 0600 -o root -g root /dev/null /etc/agent-factory.env
73
76
  {
@@ -88,10 +91,11 @@ install -m 0600 -o root -g root /dev/null /etc/agent-factory.env
88
91
  printf 'FACTORY_PURPOSE=%s\n' "${FACTORY_PURPOSE:-Ship secure reviewed software continuously}"
89
92
  printf 'MAX_CONCURRENCY=3\n'
90
93
  printf 'TASK_TIMEOUT_MS=1800000\n'
94
+ printf 'FACTORY_COMPACT_AFTER_INPUT_TOKENS=%s\n' "${FACTORY_COMPACT_AFTER_INPUT_TOKENS:-80000}"
95
+ printf 'FACTORY_COMPACT_MAX_CHARACTERS=%s\n' "${FACTORY_COMPACT_MAX_CHARACTERS:-24000}"
96
+ printf 'FACTORY_WATCHDOG_STALE_SECONDS=%s\n' "${FACTORY_WATCHDOG_STALE_SECONDS:-900}"
97
+ printf 'FACTORY_HOOKS_JSON=%s\n' "${FACTORY_HOOKS_JSON:-[]}"
91
98
  printf 'MAX_DELIVERY_COUNT=8\n'
92
- printf 'FACTORY_VERSION=%s\n' "$factory_version"
93
- printf 'FACTORY_NAME=%s\n' "${FACTORY_NAME:-Factory AI}"
94
- printf 'FACTORY_PURPOSE=%s\n' "${FACTORY_PURPOSE:-Ship secure reviewed software continuously}"
95
99
  [[ -n ${AWS_REGION:-} ]] && printf 'AWS_REGION=%s\nAWS_DEFAULT_REGION=%s\n' "$AWS_REGION" "$AWS_REGION"
96
100
  for variable in FACTORY_MODEL_SCOUT FACTORY_MODEL_PLANNER FACTORY_MODEL_BUILDER FACTORY_MODEL_TESTER FACTORY_MODEL_DEBUGGER FACTORY_MODEL_REVIEWER FACTORY_MODEL_SECURITY FACTORY_MODEL_RELEASE; do
97
101
  [[ -n ${!variable:-} ]] && printf '%s=%s\n' "$variable" "${!variable}"
@@ -111,6 +115,8 @@ install -m 0600 -o root -g root /dev/null /etc/agent-factory-control.env
111
115
  printf 'FACTORY_STATE_DIR=/opt/agent-factory/state\n'
112
116
  printf 'FACTORY_REGISTRY=%s/config/capabilities.json\n' "$APP_DIR"
113
117
  printf 'MAX_DELIVERY_COUNT=8\n'
118
+ printf 'FACTORY_COMPACT_AFTER_INPUT_TOKENS=%s\n' "${FACTORY_COMPACT_AFTER_INPUT_TOKENS:-80000}"
119
+ printf 'FACTORY_COMPACT_MAX_CHARACTERS=%s\n' "${FACTORY_COMPACT_MAX_CHARACTERS:-24000}"
114
120
  [[ -n ${AWS_REGION:-} ]] && printf 'AWS_REGION=%s\nAWS_DEFAULT_REGION=%s\n' "$AWS_REGION" "$AWS_REGION"
115
121
  for variable in FACTORY_MODEL_SCOUT FACTORY_MODEL_PLANNER FACTORY_MODEL_BUILDER FACTORY_MODEL_TESTER FACTORY_MODEL_DEBUGGER FACTORY_MODEL_REVIEWER FACTORY_MODEL_SECURITY FACTORY_MODEL_RELEASE; do
116
122
  [[ -n ${!variable:-} ]] && printf '%s=%s\n' "$variable" "${!variable}"
@@ -123,6 +129,7 @@ chmod 0750 /opt/agent-factory/state/qdrant /opt/agent-factory/state/qdrant-snaps
123
129
  chown -R root:root "$APP_DIR"
124
130
  chmod -R go-w "$APP_DIR"
125
131
  install -m 0644 "$APP_DIR/bootstrap/agent-factory-worker.service" /etc/systemd/system/agent-factory-worker.service
132
+ install -m 0644 "$APP_DIR/bootstrap/factory-ai-container-firewall.service" /etc/systemd/system/factory-ai-container-firewall.service
126
133
  install -m 0644 "$APP_DIR/bootstrap/agent-factory-control.service" /etc/systemd/system/agent-factory-control.service
127
134
  install -m 0644 "$APP_DIR/bootstrap/agent-factory-release.service" /etc/systemd/system/agent-factory-release.service
128
135
  install -m 0644 "$APP_DIR/bootstrap/agent-factory-reporter.service" /etc/systemd/system/agent-factory-reporter.service
@@ -132,17 +139,21 @@ chmod 0755 "$APP_DIR/bootstrap/auto-update.sh"
132
139
  install -m 0644 "$APP_DIR/bootstrap/factory-ai-update.service" /etc/systemd/system/factory-ai-update.service
133
140
  install -m 0644 "$APP_DIR/bootstrap/factory-ai-update.timer" /etc/systemd/system/factory-ai-update.timer
134
141
  install -m 0644 "$APP_DIR/bootstrap/factory-ai-snapshot.service" /etc/systemd/system/factory-ai-snapshot.service
135
- install -m 0644 "$APP_DIR/bootstrap/factory-ai-snapshot.timer" /etc/systemd/system/factory-ai-snapshot.timer
142
+ install -m 0644 "$APP_DIR/bootstrap/factory-ai-snapshot.timer" /etc/systemd/system/factory-ai-snapshot.timer
143
+ install -m 0644 "$APP_DIR/bootstrap/factory-ai-watchdog.service" /etc/systemd/system/factory-ai-watchdog.service
144
+ install -m 0644 "$APP_DIR/bootstrap/factory-ai-watchdog.timer" /etc/systemd/system/factory-ai-watchdog.timer
136
145
  install -m 0644 "$APP_DIR/bootstrap/factory-ai-qdrant.service" /etc/systemd/system/factory-ai-qdrant.service
137
146
  install -m 0644 "$APP_DIR/bootstrap/factory-ai-ollama.service" /etc/systemd/system/factory-ai-ollama.service
138
147
  systemctl daemon-reload
148
+ systemctl enable --now factory-ai-container-firewall.service
139
149
  systemctl enable --now agent-factory-worker.service
140
150
  systemctl enable --now agent-factory-control.service
141
151
  systemctl enable --now agent-factory-release.service
142
152
  systemctl enable --now agent-factory-reporter.timer
143
153
  systemctl enable --now agent-factory-telegram.service
144
154
  systemctl enable --now factory-ai-update.timer
145
- systemctl enable --now factory-ai-snapshot.timer
155
+ systemctl enable --now factory-ai-snapshot.timer
156
+ systemctl enable --now factory-ai-watchdog.timer
146
157
  systemctl enable --now factory-ai-qdrant.service factory-ai-ollama.service
147
158
  for _ in $(seq 1 60); do curl -fsS http://127.0.0.1:11434/api/tags >/dev/null && break; sleep 2; done
148
159
  docker exec factory-ai-ollama ollama show embeddinggemma >/dev/null 2>&1 || docker exec factory-ai-ollama ollama pull embeddinggemma