factory-ai 1.0.0

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 (84) hide show
  1. package/AGENTS.md +3 -0
  2. package/ARCHITECTURE.md +9 -0
  3. package/CHANGELOG.md +21 -0
  4. package/CODE_OF_CONDUCT.md +7 -0
  5. package/CONTRIBUTING.md +17 -0
  6. package/Dockerfile.worker +19 -0
  7. package/GOVERNANCE.md +21 -0
  8. package/HANDOFF.md +23 -0
  9. package/LICENSE +21 -0
  10. package/README.md +244 -0
  11. package/ROADMAP.md +23 -0
  12. package/RUNBOOK.md +42 -0
  13. package/SECURITY.md +7 -0
  14. package/SUPPORT.md +7 -0
  15. package/bin/factory +271 -0
  16. package/bootstrap/agent-factory-control.service +32 -0
  17. package/bootstrap/agent-factory-release.service +25 -0
  18. package/bootstrap/agent-factory-reporter.service +17 -0
  19. package/bootstrap/agent-factory-reporter.timer +10 -0
  20. package/bootstrap/agent-factory-telegram.service +31 -0
  21. package/bootstrap/agent-factory-worker.service +39 -0
  22. package/bootstrap/cloud-init.yaml +78 -0
  23. package/bootstrap/deploy-runtime.sh +74 -0
  24. package/bootstrap/setup.sh +124 -0
  25. package/capabilities/autonomous-loop/SKILL.md +10 -0
  26. package/capabilities/code-search.md +3 -0
  27. package/capabilities/dependency-security.md +3 -0
  28. package/capabilities/frontend-verification.md +3 -0
  29. package/capabilities/goal-management/SKILL.md +10 -0
  30. package/capabilities/project-context/SKILL.md +10 -0
  31. package/capabilities/release-checklist.md +3 -0
  32. package/capabilities/secure-review.md +3 -0
  33. package/capabilities/systematic-debugging.md +3 -0
  34. package/capabilities/test-discipline.md +3 -0
  35. package/capabilities/test-driven-development.md +3 -0
  36. package/capabilities/token-efficiency.md +3 -0
  37. package/capabilities/verification-before-completion.md +3 -0
  38. package/config/agent-instructions.md +8 -0
  39. package/config/capabilities.json +107 -0
  40. package/infra/main.bicep +310 -0
  41. package/package.json +78 -0
  42. package/src/agent-executor.js +52 -0
  43. package/src/agent-runner.js +110 -0
  44. package/src/azure-harness.js +118 -0
  45. package/src/bedrock-harness.js +52 -0
  46. package/src/bus.js +20 -0
  47. package/src/capabilities.js +43 -0
  48. package/src/ceo.js +67 -0
  49. package/src/config.js +65 -0
  50. package/src/container-runner.js +77 -0
  51. package/src/control-plane.js +142 -0
  52. package/src/control-service.js +55 -0
  53. package/src/dashboard.js +180 -0
  54. package/src/log.js +19 -0
  55. package/src/mcp-tools.js +53 -0
  56. package/src/operator.js +57 -0
  57. package/src/process.js +48 -0
  58. package/src/project-memory.js +22 -0
  59. package/src/registry.js +6 -0
  60. package/src/release-bot.js +36 -0
  61. package/src/release-gate.js +11 -0
  62. package/src/release-service.js +41 -0
  63. package/src/release.js +102 -0
  64. package/src/reporter.js +49 -0
  65. package/src/routing.js +34 -0
  66. package/src/scanner-suite.js +58 -0
  67. package/src/secrets.js +22 -0
  68. package/src/setup-menu.js +26 -0
  69. package/src/state.js +49 -0
  70. package/src/task-entry.js +19 -0
  71. package/src/task-graph.js +67 -0
  72. package/src/telegram-service.js +99 -0
  73. package/src/telegram.js +31 -0
  74. package/src/tui.js +130 -0
  75. package/src/validation.js +74 -0
  76. package/src/worker.js +77 -0
  77. package/src/workspace-tools.js +121 -0
  78. package/src/workspace.js +78 -0
  79. package/templates/project/architecture.md +3 -0
  80. package/templates/project/commands.md +3 -0
  81. package/templates/project/decisions.md +3 -0
  82. package/templates/project/handoff.md +3 -0
  83. package/templates/project/project.md +3 -0
  84. package/templates/project/risks.md +3 -0
package/bin/factory ADDED
@@ -0,0 +1,271 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ CONFIG_FILE=${FACTORY_CONFIG_FILE:-$HOME/.config/factory-ai/config}
5
+ LEGACY_CONFIG_FILE=$HOME/.config/agent-factory/config
6
+ if [[ -f $CONFIG_FILE ]]; then source "$CONFIG_FILE"; elif [[ -f $LEGACY_CONFIG_FILE ]]; then source "$LEGACY_CONFIG_FILE"; fi
7
+ RESOURCE_GROUP=${FACTORY_RESOURCE_GROUP:-factory-ai-rg}
8
+ VM=${FACTORY_VM:-agent-factory-vm}
9
+ NAMESPACE=${FACTORY_SERVICE_BUS:-}
10
+ VAULT=${FACTORY_KEY_VAULT:-}
11
+
12
+ remote() {
13
+ az vm run-command invoke --resource-group "$RESOURCE_GROUP" --name "$VM" \
14
+ --command-id RunShellScript --scripts "$1" --query 'value[0].message' --output tsv
15
+ }
16
+
17
+ usage() {
18
+ printf '%s\n' \
19
+ 'Factory AI operator console' \
20
+ 'Usage: factory <command>' \
21
+ ' setup' \
22
+ ' github status | connect ORG | transfer OWNER/REPO ORG' \
23
+ ' telegram status | configure | start | stop' \
24
+ ' submit <owner/repo> <objective>' \
25
+ ' issue <owner/repo> <number>' \
26
+ ' ui' \
27
+ ' init <local-project-path>' \
28
+ ' secret list | set NAME | copy NAME | delete NAME' \
29
+ ' dashboard | status | queue | report | logs | doctor' \
30
+ ' pause | resume'
31
+ }
32
+
33
+ command=${1:-help}
34
+ shift || true
35
+ case "$command" in
36
+ telegram)
37
+ action=${1:-status}
38
+ case "$action" in
39
+ status) remote 'systemctl status agent-factory-telegram.service --no-pager -n 20' ;;
40
+ start) remote 'systemctl restart agent-factory-telegram.service; systemctl is-active agent-factory-telegram.service' ;;
41
+ stop) remote 'systemctl stop agent-factory-telegram.service' ;;
42
+ configure)
43
+ read -r -s -p 'Telegram bot token from @BotFather: ' telegram_token; printf '\n'
44
+ read -r -p 'Allowed Telegram chat IDs (comma-separated): ' telegram_chat_ids
45
+ [[ $telegram_chat_ids =~ ^-?[0-9]+(,-?[0-9]+)*$ ]] || { printf 'Invalid Telegram chat ID allowlist\n' >&2; exit 2; }
46
+ ip=$(curl -fsS https://api.ipify.org)
47
+ cleanup_telegram_vault() { az keyvault network-rule remove --name "$VAULT" --ip-address "$ip/32" --output none >/dev/null 2>&1 || true; }
48
+ trap cleanup_telegram_vault EXIT
49
+ az keyvault network-rule add --name "$VAULT" --ip-address "$ip/32" --output none
50
+ sleep 5
51
+ az keyvault secret set --vault-name "$VAULT" --name telegram-bot-token --value "$telegram_token" --output none
52
+ az keyvault secret set --vault-name "$VAULT" --name telegram-allowed-chat-ids --value "$telegram_chat_ids" --output none
53
+ unset telegram_token
54
+ remote 'systemctl restart agent-factory-telegram.service; systemctl is-active agent-factory-telegram.service'
55
+ ;;
56
+ *) printf 'Unknown telegram action: %s\n' "$action" >&2; exit 2 ;;
57
+ esac
58
+ ;;
59
+ issue)
60
+ repo=${1:?repository owner/name required}
61
+ number=${2:?issue number required}
62
+ [[ $repo =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ && $number =~ ^[0-9]+$ ]] || { printf 'Invalid repository or issue number\n' >&2; exit 2; }
63
+ issue=$(gh issue view "$number" --repo "$repo" --json title,body,url)
64
+ title=$(jq -r .title <<<"$issue")
65
+ body=$(jq -r .body <<<"$issue")
66
+ url=$(jq -r .url <<<"$issue")
67
+ objective="/goal Resolve GitHub issue #$number: $title"
68
+ objective+=$'\n\n'
69
+ objective+="$body"
70
+ objective+=$'\n\nSource: '
71
+ objective+="$url"
72
+ exec "$0" submit "$repo" "$objective"
73
+ ;;
74
+ ui)
75
+ FACTORY_RESOURCE_GROUP="$RESOURCE_GROUP" FACTORY_VM="$VM" FACTORY_SERVICE_BUS="$NAMESPACE" FACTORY_KEY_VAULT="$VAULT" node "$(dirname "$0")/../src/tui.js"
76
+ ;;
77
+ setup)
78
+ for dependency in az gh jq curl ssh-keygen node; do command -v "$dependency" >/dev/null || { printf 'Missing dependency: %s\n' "$dependency" >&2; exit 1; }; done
79
+ choices=$(mktemp)
80
+ trap 'rm -f "$choices"' EXIT
81
+ node "$(dirname "$0")/../src/setup-menu.js" "$choices"
82
+ provider=$(jq -r .provider "$choices")
83
+ location=$(jq -r .location "$choices")
84
+ enterprise_org=$(jq -r .githubOrg "$choices")
85
+ aws_region=$(jq -r .awsRegion "$choices")
86
+ bedrock_builder=$(jq -r .bedrockBuilderModel "$choices")
87
+ deploy_now=$(jq -r .deployNow "$choices")
88
+ telegram=$(jq -r .telegram "$choices")
89
+ az account show --query '{subscription:name,state:state}' --output table
90
+ gh auth status
91
+ az group show --name "$RESOURCE_GROUP" --output none 2>/dev/null || az group create --name "$RESOURCE_GROUP" --location "$location" --output none
92
+ key=${FACTORY_SSH_KEY:-$HOME/.ssh/agent-factory-azure}
93
+ [[ -f $key ]] || ssh-keygen -t ed25519 -f "$key" -N "" -C agent-factory-azure
94
+ operator=$(az ad signed-in-user show --query id --output tsv)
95
+ deployment=$(az deployment group create --name agent-factory-setup --resource-group "$RESOURCE_GROUP" --template-file "$(dirname "$0")/../infra/main.bicep" --parameters adminSshKey="$(< "$key.pub")" operatorObjectId="$operator" --output json)
96
+ vault=$(jq -r '.properties.outputs.keyVaultName.value' <<<"$deployment")
97
+ namespace=$(jq -r '.properties.outputs.serviceBusNamespace.value' <<<"$deployment")
98
+ VAULT=$vault
99
+ NAMESPACE=$namespace
100
+ mkdir -p "$(dirname "$CONFIG_FILE")"
101
+ {
102
+ printf 'FACTORY_RESOURCE_GROUP=%q\n' "$RESOURCE_GROUP"
103
+ printf 'FACTORY_VM=%q\n' agent-factory-vm
104
+ printf 'FACTORY_SERVICE_BUS=%q\n' "$namespace"
105
+ printf 'FACTORY_KEY_VAULT=%q\n' "$vault"
106
+ } > "$CONFIG_FILE"
107
+ chmod 0600 "$CONFIG_FILE"
108
+ ip=$(curl -fsS https://api.ipify.org)
109
+ cleanup_setup_vault() { az keyvault network-rule remove --name "$vault" --ip-address "$ip/32" --output none >/dev/null 2>&1 || true; rm -f "$choices"; }
110
+ trap cleanup_setup_vault EXIT
111
+ az keyvault network-rule add --name "$vault" --ip-address "$ip/32" --output none
112
+ sleep 10
113
+ github_token=$(gh auth token)
114
+ az keyvault secret set --vault-name "$vault" --name github-token --value "$github_token" --output none
115
+ unset github_token
116
+ if [[ -n $enterprise_org ]]; then
117
+ gh api "orgs/$enterprise_org" --silent
118
+ az keyvault secret set --vault-name "$vault" --name github-enterprise-org --value "$enterprise_org" --output none
119
+ fi
120
+ if [[ $provider == azure || $provider == both ]]; then
121
+ read -r -p 'Azure OpenAI-compatible base URL: ' azure_base
122
+ read -r -s -p 'Azure API key: ' azure_key; printf '\n'
123
+ read -r -p 'Separate GPT-5.4 base URL (Enter to reuse): ' azure_small_base
124
+ [[ -n $azure_small_base ]] || azure_small_base=$azure_base
125
+ read -r -s -p 'Separate GPT-5.4 key (Enter to reuse): ' azure_small_key; printf '\n'
126
+ [[ -n $azure_small_key ]] || azure_small_key=$azure_key
127
+ az keyvault secret set --vault-name "$vault" --name azure-primary-base-url --value "$azure_base" --output none
128
+ az keyvault secret set --vault-name "$vault" --name azure-primary-api-key --value "$azure_key" --output none
129
+ az keyvault secret set --vault-name "$vault" --name azure-small-base-url --value "$azure_small_base" --output none
130
+ az keyvault secret set --vault-name "$vault" --name azure-small-api-key --value "$azure_small_key" --output none
131
+ unset azure_key azure_small_key
132
+ fi
133
+ if [[ $provider == bedrock || $provider == both ]]; then
134
+ read -r -p 'AWS access key ID: ' aws_access_key
135
+ read -r -s -p 'AWS secret access key: ' aws_secret_key; printf '\n'
136
+ read -r -s -p 'AWS session token (optional): ' aws_session_token; printf '\n'
137
+ az keyvault secret set --vault-name "$vault" --name aws-access-key-id --value "$aws_access_key" --output none
138
+ az keyvault secret set --vault-name "$vault" --name aws-secret-access-key --value "$aws_secret_key" --output none
139
+ [[ -n $aws_session_token ]] && az keyvault secret set --vault-name "$vault" --name aws-session-token --value "$aws_session_token" --output none
140
+ unset aws_access_key aws_secret_key aws_session_token
141
+ fi
142
+ if [[ $telegram == true ]]; then
143
+ read -r -s -p 'Telegram bot token from @BotFather: ' telegram_token; printf '\n'
144
+ read -r -p 'Allowed Telegram chat IDs (comma-separated): ' telegram_chat_ids
145
+ [[ $telegram_chat_ids =~ ^-?[0-9]+(,-?[0-9]+)*$ ]] || { printf 'Invalid Telegram chat ID allowlist\n' >&2; exit 2; }
146
+ az keyvault secret set --vault-name "$vault" --name telegram-bot-token --value "$telegram_token" --output none
147
+ az keyvault secret set --vault-name "$vault" --name telegram-allowed-chat-ids --value "$telegram_chat_ids" --output none
148
+ unset telegram_token
149
+ fi
150
+ printf 'Azure foundation and credential vault ready. Config: %s\n' "$CONFIG_FILE"
151
+ if [[ $deploy_now == true ]]; then
152
+ source_ref=$(gh api repos/itsvedantkumar/factory-ai/commits/main --jq .sha)
153
+ parameters=(KEY_VAULT_NAME "$vault" SERVICE_BUS_NAMESPACE "$namespace" SERVICE_BUS_QUEUE code-tasks SOURCE_REPOSITORY itsvedantkumar/factory-ai SOURCE_REF "$source_ref")
154
+ if [[ $provider == bedrock ]]; then
155
+ parameters+=(AWS_REGION "$aws_region")
156
+ for role in SCOUT PLANNER BUILDER TESTER DEBUGGER REVIEWER SECURITY RELEASE; do parameters+=("FACTORY_MODEL_$role" "bedrock/$bedrock_builder"); done
157
+ elif [[ $provider == both ]]; then
158
+ parameters+=(AWS_REGION "$aws_region" FACTORY_MODEL_BUILDER "bedrock/$bedrock_builder")
159
+ fi
160
+ az vm run-command invoke --resource-group "$RESOURCE_GROUP" --name agent-factory-vm --command-id RunShellScript \
161
+ --scripts @"$(dirname "$0")/../bootstrap/deploy-runtime.sh" --parameters "${parameters[@]}" --output none
162
+ printf 'Runtime deployed. Run: factory doctor && factory dashboard\n'
163
+ fi
164
+ ;;
165
+ github)
166
+ action=${1:-status}
167
+ case "$action" in
168
+ status)
169
+ gh auth status
170
+ printf 'Organizations:\n'
171
+ gh api user/orgs --jq '.[].login'
172
+ ;;
173
+ connect)
174
+ org=${2:?Enterprise organization required}
175
+ gh api "orgs/$org" --jq '{login,plan:.plan.name}'
176
+ token=$(gh auth token)
177
+ ip=$(curl -fsS https://api.ipify.org)
178
+ cleanup_github_vault() { az keyvault network-rule remove --name "$VAULT" --ip-address "$ip/32" --output none >/dev/null 2>&1 || true; }
179
+ trap cleanup_github_vault EXIT
180
+ az keyvault network-rule add --name "$VAULT" --ip-address "$ip/32" --output none
181
+ sleep 5
182
+ az keyvault secret set --vault-name "$VAULT" --name github-token --value "$token" --output none
183
+ az keyvault secret set --vault-name "$VAULT" --name github-enterprise-org --value "$org" --output none
184
+ unset token
185
+ printf 'Connected organization %s. Restart release/worker services after credential changes.\n' "$org"
186
+ ;;
187
+ transfer)
188
+ repo=${2:?OWNER/REPO required}
189
+ org=${3:?Enterprise organization required}
190
+ gh api --method POST "repos/$repo/transfer" -f new_owner="$org" --silent
191
+ printf 'Transfer requested: %s -> %s. Update submit paths after GitHub completes it.\n' "$repo" "$org"
192
+ ;;
193
+ *) printf 'Unknown github action: %s\n' "$action" >&2; exit 2 ;;
194
+ esac
195
+ ;;
196
+ secret)
197
+ action=${1:-list}
198
+ name=${2:-}
199
+ ip=$(curl -fsS https://api.ipify.org)
200
+ cleanup_vault_rule() { az keyvault network-rule remove --name "$VAULT" --ip-address "$ip/32" --output none >/dev/null 2>&1 || true; }
201
+ trap cleanup_vault_rule EXIT
202
+ az keyvault network-rule add --name "$VAULT" --ip-address "$ip/32" --output none
203
+ sleep 5
204
+ case "$action" in
205
+ list) az keyvault secret list --vault-name "$VAULT" --query '[].{name:name,updated:attributes.updated}' --output table ;;
206
+ set)
207
+ [[ $name =~ ^[A-Za-z0-9-]+$ ]] || { printf 'Valid secret name required\n' >&2; exit 2; }
208
+ read -r -s -p "Value for $name: " value
209
+ printf '\n'
210
+ az keyvault secret set --vault-name "$VAULT" --name "$name" --value "$value" --output none
211
+ unset value
212
+ printf 'Stored %s in Azure Key Vault.\n' "$name"
213
+ ;;
214
+ copy)
215
+ [[ $name =~ ^[A-Za-z0-9-]+$ ]] || { printf 'Valid secret name required\n' >&2; exit 2; }
216
+ az keyvault secret show --vault-name "$VAULT" --name "$name" --query value --output tsv | pbcopy
217
+ printf 'Copied %s to clipboard without printing it.\n' "$name"
218
+ ;;
219
+ delete)
220
+ [[ $name =~ ^[A-Za-z0-9-]+$ ]] || { printf 'Valid secret name required\n' >&2; exit 2; }
221
+ az keyvault secret delete --vault-name "$VAULT" --name "$name" --output none
222
+ printf 'Soft-deleted %s.\n' "$name"
223
+ ;;
224
+ *) printf 'Unknown secret action: %s\n' "$action" >&2; exit 2 ;;
225
+ esac
226
+ ;;
227
+ init)
228
+ target=${1:?local project path required}
229
+ [[ -d $target ]] || { printf 'Project directory does not exist: %s\n' "$target" >&2; exit 2; }
230
+ context="$target/.agent-factory"
231
+ mkdir -p "$context"
232
+ for template in "$(dirname "$0")/../templates/project/"*.md; do
233
+ destination="$context/$(basename "$template")"
234
+ [[ -e $destination ]] || cp "$template" "$destination"
235
+ done
236
+ printf 'Initialized %s\nShared runtime memory remains on Azure; repo context lives in .agent-factory/.\n' "$context"
237
+ ;;
238
+ submit)
239
+ repo=${1:?repository owner/name required}
240
+ shift
241
+ objective=${*:?objective required}
242
+ [[ $repo =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || { printf 'Invalid repository\n' >&2; exit 2; }
243
+ repo64=$(printf '%s' "$repo" | base64)
244
+ objective64=$(printf '%s' "$objective" | base64)
245
+ remote "repo=\$(printf '%s' '$repo64' | base64 -d); objective=\$(printf '%s' '$objective64' | base64 -d); sudo -u factory env \$(xargs < /etc/agent-factory-control.env) node /opt/agent-factory/app/src/ceo.js --repo \"https://github.com/\$repo.git\" --base main \"\$objective\""
246
+ ;;
247
+ dashboard|status)
248
+ remote 'sudo -u factory env $(xargs < /etc/agent-factory-control.env) node /opt/agent-factory/app/src/dashboard.js'
249
+ ;;
250
+ queue)
251
+ az servicebus queue list --resource-group "$RESOURCE_GROUP" --namespace-name "$NAMESPACE" \
252
+ --query '[].{queue:name,active:countDetails.activeMessageCount,deadLetter:countDetails.deadLetterMessageCount}' --output table
253
+ ;;
254
+ report)
255
+ remote 'node -e '\''const fs=require("fs"),p="/opt/agent-factory/state/reports";const f=fs.readdirSync(p).filter(x=>x.endsWith(".md")).sort().at(-1);process.stdout.write(fs.readFileSync(`${p}/${f}`,"utf8"))'\'''
256
+ ;;
257
+ logs)
258
+ remote 'journalctl -u agent-factory-control -u agent-factory-worker -u agent-factory-release --since "1 hour ago" --no-pager -n 250'
259
+ ;;
260
+ doctor)
261
+ remote 'systemctl is-active agent-factory-control agent-factory-worker agent-factory-release; systemctl is-enabled agent-factory-reporter.timer; docker ps --format "{{.Names}} {{.Status}}"; findmnt /opt/agent-factory/state; df -h /opt/agent-factory/state; free -h'
262
+ ;;
263
+ pause)
264
+ remote 'systemctl stop agent-factory-worker agent-factory-release'
265
+ ;;
266
+ resume)
267
+ remote 'systemctl start agent-factory-worker agent-factory-release; systemctl is-active agent-factory-worker agent-factory-release'
268
+ ;;
269
+ help|-h|--help) usage ;;
270
+ *) usage >&2; exit 2 ;;
271
+ esac
@@ -0,0 +1,32 @@
1
+ [Unit]
2
+ Description=Factory AI capability-free control plane
3
+ After=network-online.target
4
+ Wants=network-online.target
5
+ StartLimitIntervalSec=300
6
+ StartLimitBurst=10
7
+
8
+ [Service]
9
+ Type=simple
10
+ User=factory
11
+ Group=factory
12
+ WorkingDirectory=/opt/agent-factory/app
13
+ EnvironmentFile=/etc/agent-factory-control.env
14
+ ExecStart=/usr/bin/node /opt/agent-factory/app/src/control-service.js
15
+ Restart=on-failure
16
+ RestartSec=10
17
+ TimeoutStopSec=45
18
+ NoNewPrivileges=true
19
+ PrivateTmp=true
20
+ ProtectSystem=strict
21
+ ProtectHome=true
22
+ ProtectKernelTunables=true
23
+ ProtectKernelModules=true
24
+ ProtectKernelLogs=true
25
+ ProtectControlGroups=true
26
+ LockPersonality=true
27
+ RestrictSUIDSGID=true
28
+ ReadWritePaths=/opt/agent-factory/state
29
+ UMask=0027
30
+
31
+ [Install]
32
+ WantedBy=multi-user.target
@@ -0,0 +1,25 @@
1
+ [Unit]
2
+ Description=Factory AI trusted release bot
3
+ After=network-online.target
4
+ Wants=network-online.target
5
+
6
+ [Service]
7
+ Type=simple
8
+ User=factory
9
+ Group=factory
10
+ WorkingDirectory=/opt/agent-factory/app
11
+ EnvironmentFile=/etc/agent-factory.env
12
+ Environment=HOME=/opt/agent-factory/state/home
13
+ ExecStart=/usr/bin/node /opt/agent-factory/app/src/release-service.js
14
+ Restart=on-failure
15
+ RestartSec=10
16
+ TimeoutStopSec=45
17
+ NoNewPrivileges=true
18
+ PrivateTmp=true
19
+ ProtectSystem=strict
20
+ ProtectHome=true
21
+ ReadWritePaths=/opt/agent-factory/state /opt/agent-factory/logs
22
+ UMask=0027
23
+
24
+ [Install]
25
+ WantedBy=multi-user.target
@@ -0,0 +1,17 @@
1
+ [Unit]
2
+ Description=Factory AI hourly progress report
3
+ After=agent-factory-worker.service
4
+
5
+ [Service]
6
+ Type=oneshot
7
+ User=factory
8
+ Group=factory
9
+ WorkingDirectory=/opt/agent-factory/app
10
+ EnvironmentFile=/etc/agent-factory.env
11
+ ExecStart=/usr/bin/node /opt/agent-factory/app/src/reporter.js
12
+ NoNewPrivileges=true
13
+ PrivateTmp=true
14
+ ProtectSystem=strict
15
+ ProtectHome=true
16
+ ReadWritePaths=/opt/agent-factory/state
17
+ UMask=0027
@@ -0,0 +1,10 @@
1
+ [Unit]
2
+ Description=Generate Factory AI progress report hourly
3
+
4
+ [Timer]
5
+ OnCalendar=hourly
6
+ Persistent=true
7
+ RandomizedDelaySec=60
8
+
9
+ [Install]
10
+ WantedBy=timers.target
@@ -0,0 +1,31 @@
1
+ [Unit]
2
+ Description=Factory AI Telegram remote objective intake
3
+ After=network-online.target agent-factory-control.service
4
+ Wants=network-online.target
5
+
6
+ [Service]
7
+ Type=simple
8
+ User=factory
9
+ Group=factory
10
+ WorkingDirectory=/opt/agent-factory/app
11
+ EnvironmentFile=/etc/agent-factory.env
12
+ Environment=HOME=/opt/agent-factory/state/home
13
+ ExecStart=/usr/bin/node /opt/agent-factory/app/src/telegram-service.js
14
+ Restart=on-failure
15
+ RestartSec=10
16
+ TimeoutStopSec=40
17
+ NoNewPrivileges=true
18
+ PrivateTmp=true
19
+ ProtectSystem=strict
20
+ ProtectHome=true
21
+ ProtectKernelTunables=true
22
+ ProtectKernelModules=true
23
+ ProtectKernelLogs=true
24
+ ProtectControlGroups=true
25
+ LockPersonality=true
26
+ RestrictSUIDSGID=true
27
+ ReadWritePaths=/opt/agent-factory/state/telegram
28
+ UMask=0027
29
+
30
+ [Install]
31
+ WantedBy=multi-user.target
@@ -0,0 +1,39 @@
1
+ [Unit]
2
+ Description=Factory AI durable worker
3
+ After=network-online.target docker.service
4
+ Wants=network-online.target
5
+ Requires=docker.service
6
+ StartLimitIntervalSec=300
7
+ StartLimitBurst=10
8
+
9
+ [Service]
10
+ Type=simple
11
+ User=factory
12
+ Group=factory
13
+ WorkingDirectory=/opt/agent-factory/app
14
+ EnvironmentFile=/etc/agent-factory.env
15
+ Environment=HOME=/opt/agent-factory/state/home
16
+ Environment=XDG_CACHE_HOME=/opt/agent-factory/state/home/.cache
17
+ Environment=XDG_CONFIG_HOME=/opt/agent-factory/state/home/.config
18
+ ExecStart=/usr/bin/node /opt/agent-factory/app/src/worker.js
19
+ Restart=on-failure
20
+ RestartSec=10
21
+ TimeoutStopSec=45
22
+ KillSignal=SIGTERM
23
+ NoNewPrivileges=true
24
+ PrivateTmp=true
25
+ ProtectSystem=strict
26
+ ProtectHome=true
27
+ ProtectKernelTunables=true
28
+ ProtectKernelModules=true
29
+ ProtectKernelLogs=true
30
+ ProtectControlGroups=true
31
+ LockPersonality=true
32
+ RestrictSUIDSGID=true
33
+ SystemCallArchitectures=native
34
+ SupplementaryGroups=docker
35
+ ReadWritePaths=/opt/agent-factory/state /opt/agent-factory/workspaces /opt/agent-factory/logs
36
+ UMask=0027
37
+
38
+ [Install]
39
+ WantedBy=multi-user.target
@@ -0,0 +1,78 @@
1
+ #cloud-config
2
+ package_update: true
3
+ package_upgrade: true
4
+ packages:
5
+ - ca-certificates
6
+ - curl
7
+ - docker-compose-v2
8
+ - docker.io
9
+ - fail2ban
10
+ - git
11
+ - jq
12
+ - postgresql-client
13
+ - ripgrep
14
+ - sqlite3
15
+ - unattended-upgrades
16
+
17
+ write_files:
18
+ - path: /etc/docker/daemon.json
19
+ permissions: '0644'
20
+ content: |
21
+ {
22
+ "log-driver": "json-file",
23
+ "log-opts": {
24
+ "max-size": "20m",
25
+ "max-file": "5"
26
+ },
27
+ "live-restore": true
28
+ }
29
+ - path: /etc/sysctl.d/99-agent-factory.conf
30
+ permissions: '0644'
31
+ content: |
32
+ fs.inotify.max_user_instances=1024
33
+ fs.inotify.max_user_watches=1048576
34
+ fs.file-max=2097152
35
+ - path: /etc/systemd/system/agent-factory-health.service
36
+ permissions: '0644'
37
+ content: |
38
+ [Unit]
39
+ Description=Agent factory host health check
40
+ After=network-online.target docker.service
41
+ Wants=network-online.target
42
+
43
+ [Service]
44
+ Type=oneshot
45
+ ExecStart=/usr/local/bin/agent-factory-health
46
+ - path: /etc/systemd/system/agent-factory-health.timer
47
+ permissions: '0644'
48
+ content: |
49
+ [Unit]
50
+ Description=Run agent factory host health check
51
+
52
+ [Timer]
53
+ OnBootSec=5min
54
+ OnUnitActiveSec=5min
55
+ Persistent=true
56
+
57
+ [Install]
58
+ WantedBy=timers.target
59
+ - path: /usr/local/bin/agent-factory-health
60
+ permissions: '0755'
61
+ content: |
62
+ #!/usr/bin/env bash
63
+ set -euo pipefail
64
+ test "$(systemctl is-active docker)" = active
65
+ test "$(df --output=pcent / | tail -1 | tr -dc '0-9')" -lt 90
66
+ test "$(awk '/MemAvailable/ {print $2}' /proc/meminfo)" -gt 1048576
67
+
68
+ runcmd:
69
+ - [mkdir, -p, /opt/agent-factory/workspaces, /opt/agent-factory/state, /opt/agent-factory/logs]
70
+ - [chmod, '0750', /opt/agent-factory]
71
+ - [systemctl, enable, --now, docker]
72
+ - [systemctl, enable, --now, fail2ban]
73
+ - [systemctl, enable, --now, unattended-upgrades]
74
+ - [systemctl, daemon-reload]
75
+ - [systemctl, enable, --now, agent-factory-health.timer]
76
+ - [sysctl, --system]
77
+
78
+ final_message: Agent factory base host ready after $UPTIME seconds
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ set +x
4
+
5
+ if [[ ${EUID} -ne 0 ]]; then
6
+ echo "deploy-runtime.sh must run as root" >&2
7
+ exit 1
8
+ fi
9
+
10
+ while (($# > 0)); do
11
+ if [[ $1 == *=* ]]; then
12
+ name=${1%%=*}
13
+ value=${1#*=}
14
+ shift
15
+ else
16
+ name=$1
17
+ shift
18
+ (($# > 0)) || { echo "Missing value for $name" >&2; exit 1; }
19
+ value=$1
20
+ shift
21
+ fi
22
+ case "$name" in
23
+ KEY_VAULT_NAME|SERVICE_BUS_NAMESPACE|SERVICE_BUS_QUEUE|SOURCE_REPOSITORY|SOURCE_REF|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" ;;
24
+ *) echo "Unknown deployment parameter: $name" >&2; exit 1 ;;
25
+ esac
26
+ done
27
+
28
+ : "${KEY_VAULT_NAME:?KEY_VAULT_NAME is required}"
29
+ : "${SERVICE_BUS_NAMESPACE:?SERVICE_BUS_NAMESPACE is required}"
30
+ : "${SOURCE_REPOSITORY:?SOURCE_REPOSITORY as OWNER/REPOSITORY is required}"
31
+ : "${SOURCE_REF:?SOURCE_REF must be a full commit SHA}"
32
+ SERVICE_BUS_QUEUE=${SERVICE_BUS_QUEUE:-code-tasks}
33
+ [[ $SOURCE_REPOSITORY =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || { echo "Invalid SOURCE_REPOSITORY" >&2; exit 1; }
34
+ [[ $SOURCE_REF =~ ^[0-9a-f]{40}$ ]] || { echo "SOURCE_REF must be a full lowercase commit SHA" >&2; exit 1; }
35
+
36
+ export DEBIAN_FRONTEND=noninteractive
37
+ apt-get update
38
+ apt-get install -y ca-certificates curl git gpg rsync
39
+ install -d -m 0755 /etc/apt/keyrings
40
+ curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg -o /etc/apt/keyrings/githubcli-archive-keyring.gpg
41
+ chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg
42
+ 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
43
+ curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor --yes -o /etc/apt/keyrings/microsoft.gpg
44
+ 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 && printf '%s' "$VERSION_CODENAME") main" > /etc/apt/sources.list.d/azure-cli.list
45
+ apt-get update
46
+ apt-get install -y azure-cli gh
47
+
48
+ az login --identity --allow-no-subscriptions --output none
49
+ github_token=$(az keyvault secret show --vault-name "$KEY_VAULT_NAME" --name "${GITHUB_TOKEN_SECRET:-github-token}" --query value --output tsv)
50
+ [[ -n $github_token && $github_token != *$'\n'* && $github_token != *$'\r'* ]] || { echo "GitHub secret is empty or invalid" >&2; exit 1; }
51
+
52
+ temporary=$(mktemp -d /opt/agent-factory-source.XXXXXX)
53
+ trap 'rm -rf "$temporary"' EXIT
54
+ GH_TOKEN="$github_token" gh repo clone "$SOURCE_REPOSITORY" "$temporary/source" -- --no-checkout
55
+ unset github_token
56
+ git -C "$temporary/source" checkout --detach "$SOURCE_REF"
57
+ test "$(git -C "$temporary/source" rev-parse HEAD)" = "$SOURCE_REF"
58
+ install -d -m 0755 /opt/agent-factory/app
59
+ rsync -a --delete --exclude .git "$temporary/source/" /opt/agent-factory/app/
60
+
61
+ KEY_VAULT_NAME="$KEY_VAULT_NAME" \
62
+ SERVICE_BUS_NAMESPACE="$SERVICE_BUS_NAMESPACE" \
63
+ SERVICE_BUS_QUEUE="$SERVICE_BUS_QUEUE" \
64
+ FACTORY_WORKER_IMAGE="agent-factory-worker:$SOURCE_REF" \
65
+ AWS_REGION="${AWS_REGION:-}" \
66
+ FACTORY_MODEL_SCOUT="${FACTORY_MODEL_SCOUT:-}" \
67
+ FACTORY_MODEL_PLANNER="${FACTORY_MODEL_PLANNER:-}" \
68
+ FACTORY_MODEL_BUILDER="${FACTORY_MODEL_BUILDER:-}" \
69
+ FACTORY_MODEL_TESTER="${FACTORY_MODEL_TESTER:-}" \
70
+ FACTORY_MODEL_DEBUGGER="${FACTORY_MODEL_DEBUGGER:-}" \
71
+ FACTORY_MODEL_REVIEWER="${FACTORY_MODEL_REVIEWER:-}" \
72
+ FACTORY_MODEL_SECURITY="${FACTORY_MODEL_SECURITY:-}" \
73
+ FACTORY_MODEL_RELEASE="${FACTORY_MODEL_RELEASE:-}" \
74
+ bash /opt/agent-factory/app/bootstrap/setup.sh