Version: 3.10 (Fleet-Ready Edition) Date: 2026-03-04 Primary Host: g2s.cybertribe.com (128GB RAM)
| Change | Detail |
|---|---|
| Matrix key generation | Replaced broken ssh-keygen / Docker generate-keys with Python-based generator that creates Continuwuity-native MATRIX PRIVATE KEY format |
| Variable safety | All variables pre-declared to prevent set -u unbound variable crashes |
| Database syntax patch | Auto-applies connection_string: fix after continuwuity.yaml generation |
| Gold standard template | continuwuity.yaml.template replaced with working agent0-2 config (includes relay_api, room_server blocks) |
| Federation-ready compose | docker-compose.yml.template now includes --https-bind-address :8448 command flag and TLS cert volume mounts |
| Self-signed TLS certs | Script auto-generates placeholder certs so Continuwuity starts cleanly; replace with step-ca certs for real federation |
| Matrix credentials | Auto-generates random password, stores in .env, prints registration command with pre-filled password |
| Step-CA compatibility | Documented --not-after=8760h (equals syntax required for Smallstep CLI v0.29.0), --bundle flag not supported |
The v3.8 script handles everything — directory creation, config generation, API key injection, and Matrix identity key generation — in a single command.
# 1. Export keys (auto-injected into .env)
export API_KEY_OPENROUTER="sk-or-v1-..."
# 2. Generate the full instance scaffold
cd /opt/agent-zero/multi-instance-deploy
./create-instance.sh 3 # Standard agent0-3
./create-instance.sh --profile hacker 4 # Hacker-flavored agent0-4
# 3. Start the stack
cd /opt/agent-zero/agent0-3
docker compose up -d
# 4. Verify Continuwuity is running clean
docker compose logs -f continuwuity
# Expected: jetstream + MSC2946 warnings only, NO fatal errorsNote: Steps 3-5 from v3.4 (manual key generation) are no longer needed.
The script auto-generates a valid Matrix identity key AND self-signed TLS certs.
Continuwuity starts with federation listener on port 8448 immediately.
Replace self-signed certs with step-ca certs (Section 6) for real federation.
| CLI Value | Profile Name | Description |
|---|---|---|
agent0 |
Standard | Balanced assistant (Default) |
hacker |
Security | Cybersecurity & Pentesting specialist |
developer |
Coding | Software Engineering & Architecture specialist |
researcher |
Analysis | Data Analysis & Reporting specialist |
Run ./create-instance.sh --help at any time to see valid flavors, descriptions, port mappings, and IP patterns.
The script automatically:
- Creates directory structure —
usr/,mhs/,mhs/data/ - Generates docker-compose.yml — from template with correct IPs, MACs, ports
- Injects API Keys — exported in your current shell
- Sets the Profile — via
A0_SET_agent_profilein.env - Configures Models — defaults to
gemini-2.0-flash-001(Fast & Cheap) - Generates continuwuity.yaml — from gold standard template with
connection_string:safety patch - Generates Matrix identity key — native Continuwuity
MATRIX PRIVATE KEYformat via Python - Generates self-signed TLS certs — placeholder certs so Continuwuity starts with port 8448 listener
- Federation-ready compose —
--https-bind-address :8448command flag and TLS cert volume mounts baked in
Continuwuity requires a proprietary PEM format — NOT OpenSSH or standard PKCS8:
-----BEGIN MATRIX PRIVATE KEY-----
Key-ID: ed25519:<random6chars>
<base64_encoded_32_byte_seed>
-----END MATRIX PRIVATE KEY-----
Important:
ssh-keygen -t ed25519producesOPENSSH PRIVATE KEY→ REJECTED by Continuwuity (keyBlock is nil)- Continuwuity's own
generate-keystool has a chicken-and-egg problem (needs config which needs key) - The v3.8 script uses a Python generator that creates the correct format directly
- Each instance MUST have a unique key — never copy keys between agents
If you ever need to regenerate a key outside the script:
python3 -c "
import base64, os, string, random
seed = os.urandom(32)
chars = string.ascii_letters + string.digits
key_id = ''.join(random.choices(chars, k=6))
key_b64 = base64.b64encode(seed).decode()
lines = []
lines.append('-----BEGIN MATRIX PRIVATE KEY-----')
lines.append('Key-ID: ed25519:' + key_id)
lines.append('')
lines.append(key_b64)
lines.append('-----END MATRIX PRIVATE KEY-----')
with open('/opt/agent-zero/agent0-N/mhs/matrix_key.pem', 'w') as f:
f.write(chr(10).join(lines) + chr(10))
print('Key generated: ed25519:' + key_id)
"
chmod 600 /opt/agent-zero/agent0-N/mhs/matrix_key.pemAfter create-instance.sh and docker compose up -d, federation requires TLS certificates and gateway registration.
The create-instance.sh script auto-generates a random password and prints the exact registration command. After docker compose up -d, run it:
docker exec -it agent0-N-mhs /usr/bin/create-account \
-config /etc/continuwuity/continuwuity.yaml \
-username agent0-N -password <auto-generated-password> -adminThe password is stored in /opt/agent-zero/agent0-N/.env as MATRIX_PASSWORD.
The returned AccessToken should also be saved to .env as MATRIX_ACCESS_TOKEN for the MCP server.
The create-instance.sh script generates self-signed placeholder certificates so Continuwuity can start immediately with the federation listener on port 8448. However, real federation with Synapse requires trusted certificates from your step-ca infrastructure.
To upgrade from self-signed to step-ca certs:
- Generate: Request a certificate for
agent0-N-mhs.cybertribe.com(see Section 6) - Deploy: Overwrite
server.crtandserver.keyinside themhs/directory - Permissions:
chmod 600 server.key - Restart:
docker compose restart continuwuity - Verify:
curl -k https://172.23.89.N:8448/_matrix/key/v2/server
Your central Synapse hub (v-site.net) must trust the new node:
- Whitelist: Add the new FQDN to the
federation_domain_whitelistin Synapse config - DNS/HostAlias: Ensure Synapse knows that
agent0-N-mhs.cybertribe.comresolves to your g2s VPN IP
The matrix-mcp-server is a Node.js application that gives the agent Matrix
capabilities (send messages, list rooms, manage invites, etc.) via the Model
Context Protocol. It is automatically deployed by create-instance.sh v3.11+.
- Copies the
matrix-mcp-servertemplate fromtemplates/matrix-mcp-server/into the instance'susr/workdir/matrix-mcp-server/ - Generates a configured
.envfrom.env.templatewith instance-specific homeserver URL, user ID, and domain - Sets
MATRIX_ACCESS_TOKEN=PENDING_REGISTRATION(updated after Step 5.1)
After registering the Matrix account (Step 5.1), update the token:
# Replace PENDING_REGISTRATION with the actual token from create-account output
sed -i 's/PENDING_REGISTRATION/<actual_access_token>/' \
/opt/agent-zero/agent0-N/usr/workdir/matrix-mcp-server/.envdocker exec -it agent0-N bash
cd /a0/usr/workdir/matrix-mcp-server
npm install
node dist/http-server.js &
# Verify it is listening
curl -s http://localhost:3000/mcp | head -c 200- Open the Agent Zero dashboard at
http://agent0-N.cybertribe.com/ - Click Settings (gear icon) in the left sidebar
- Click the MCP/A2A tab
- In the JSON editor, replace the empty
{}with:
{
"mcpServers": {
"matrix": {
"description": "Matrix homeserver bridge for agent-to-agent and human-agent communication",
"url": "http://localhost:3000/mcp",
"type": "streamable-http"
}
}
}Note: No headers or credentials are needed in this JSON. The MCP server reads all Matrix credentials from its own
.envfile (configured in Step 1).
- Click Save
- The agent should now show ~20 Matrix tools in its tool list
| JSON Field | Value | Notes |
|---|---|---|
type |
"streamable-http" |
Required — must be streamable-http, NOT http or sse |
url |
http://localhost:3000/mcp |
MCP server runs inside the agent container |
description |
Free text | Optional description shown in the UI |
Ask the agent in the chat: "What is your Matrix identity?"
The agent should respond with @agent0-N:agent0-N-mhs.cybertribe.com.
The matrix-bot is a Python application that listens on Matrix rooms for incoming
messages and routes them through the Agent Zero API for intelligent responses.
This enables humans and other agents to communicate with the agent via any Matrix
client (Element, FluffyChat, etc.). It is automatically deployed by
create-instance.sh v3.11+.
- The bot connects to the agent's Continuwuity homeserver using
matrix-nio - When invited to a room, it auto-joins
- When a message arrives, it forwards it to the Agent Zero
/api_messageendpoint - The agent's response is posted back into the Matrix room
- Each room gets its own Agent Zero conversation context (stored in
room_contexts.json)
- Copies the
matrix-bottemplate fromtemplates/matrix-bot/into the instance'susr/workdir/matrix-bot/ - Generates a configured
.envfrom.env.templatewith instance-specific homeserver URL, user ID, display name, and API key - Sets
MATRIX_ACCESS_TOKEN=PENDING_REGISTRATION(updated after Step 5.1)
After registering the Matrix account (Step 5.1), update the token:
sed -i 's/PENDING_REGISTRATION/<actual_access_token>/' \
/opt/agent-zero/agent0-N/usr/workdir/matrix-bot/.envdocker exec -it agent0-N bash
cd /a0/usr/workdir/matrix-bot
/opt/venv-a0/bin/pip install -r requirements.txt
/opt/venv-a0/bin/python3 matrix_bot.py &
# Check the log
tail -f bot.log- Open Element (or any Matrix client) connected to
matrix.v-site.net - Start a new direct message with
@agent0-N:agent0-N-mhs.cybertribe.com - Send a message — the bot should auto-join and respond via Agent Zero
| Variable | Value | Purpose |
|---|---|---|
MATRIX_HOMESERVER_URL |
http://agent0-N-mhs:8008 |
Continuwuity client API (Docker DNS) |
MATRIX_USER_ID |
@agent0-N:agent0-N-mhs.cybertribe.com |
Bot's Matrix identity |
MATRIX_ACCESS_TOKEN |
Token string | From create-account (Step 5.1) |
MATRIX_DEVICE_ID |
AgentZeroBot |
Device identifier for sync |
A0_API_URL |
http://localhost:80/api_message |
Agent Zero API endpoint |
A0_API_KEY |
Auto-generated | API key for Agent Zero authentication |
BOT_DISPLAY_NAME |
Agent0-N |
Display name in Matrix rooms |
TRIGGER_PREFIX |
(empty) | Optional prefix to filter messages |
SYNC_TIMEOUT_MS |
30000 |
Matrix sync polling interval |
From Element, invite @agent0-N:agent0-N-mhs.cybertribe.com to a room and send
a message. You should see the bot join and respond within a few seconds.
Agent Zero can send emails via Gmail SMTP. This requires a Google account with 2-Step Verification and an App Password.
- A Gmail account with 2-Step Verification enabled
- A 16-character App Password (not the regular account password)
- Generate at: myaccount.google.com/apppasswords
- Select: Mail > Other > name it "Agent0-N"
Regular Gmail passwords will NOT work. SMTP login requires an App Password. The error
535 5.7.8 Username and Password not acceptedalways means wrong credentials or missing App Password.
Append the following to the matrix-bot .env file at
/opt/agent-zero/agent0-N/usr/workdir/matrix-bot/.env:
# Sync timeout in milliseconds
SYNC_TIMEOUT_MS=30000
# Gmail SMTP configuration
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=<gmail-address>@gmail.com
SMTP_PASS=<16-char-app-password-no-spaces>
SMTP_FROM=<gmail-address>@gmail.com
FORCE_TLS=true| Variable | Value | Notes |
|---|---|---|
| SMTP_HOST | smtp.gmail.com | Gmail SMTP server |
| SMTP_PORT | 587 | STARTTLS port |
| SMTP_USER | your-agent@gmail.com | Gmail address |
| SMTP_PASS | 16-char App Password | No spaces - remove spaces from the displayed format |
| SMTP_FROM | your-agent@gmail.com | Usually same as SMTP_USER |
| FORCE_TLS | true | Enforce TLS encryption |
| SYNC_TIMEOUT_MS | 30000 | Bot sync timeout (optional) |
After updating .env, restart the matrix-bot:
docker exec agent0-N bash -c 'pkill -f matrix_bot.py; sleep 2; cd /a0/usr/workdir/matrix-bot && nohup /opt/venv-a0/bin/python3 matrix_bot.py > bot.log 2>&1 &'Test SMTP from inside the agent container:
docker exec agent0-N /opt/venv-a0/bin/python3 << 'TEST'
import smtplib
from email.mime.text import MIMEText
server = smtplib.SMTP("smtp.gmail.com", 587, timeout=10)
server.starttls()
server.login("<gmail-address>@gmail.com", "<app-password-no-spaces>")
msg = MIMEText("SMTP test from Agent0-N")
msg["Subject"] = "Agent0-N SMTP Test"
msg["From"] = "<gmail-address>@gmail.com"
msg["To"] = "<your-email>@gmail.com"
server.sendmail(msg["From"], msg["To"], msg.as_string())
server.quit()
print("SUCCESS: Email sent!")
TESTNote: Use
/opt/venv-a0/bin/python3- barepython3resolves to the system Python which lacks required packages.
Since the CA lives on Tarnover, you must run these commands there to generate the TLS layer for your nodes on g2s.
Important: Smallstep CLI v0.29.0 quirks:
- The
--not-afterflag requires equals syntax:--not-after=8760h(space-separated causes "too many positional arguments" error)- The
--bundleflag is NOT supported in this version despite being documented- Use the two-step approach below to bundle the CA chain
# Run on Tarnover (note the = syntax for --not-after)
step ca certificate "agent0-N-mhs.cybertribe.com" server.crt server.key --not-after=8760hYou will be prompted for the provisioner password.
Synapse requires the full chain (leaf + intermediate + root). Append your ca-bundle.pem:
cat ca-bundle.pem >> server.crtVerify the chain contains multiple certs:
openssl crl2pkcs7 -nocrl -certfile server.crt | openssl pkcs7 -print_certs -nooutscp server.crt server.key g2s:/opt/agent-zero/agent0-N/mhs/chmod 600 /opt/agent-zero/agent0-N/mhs/server.key
cd /opt/agent-zero/agent0-N && docker compose down && docker compose up -ddocker exec agent0-N-mhs wget -qO- --no-check-certificate https://localhost:8448/_matrix/key/v2/serverThe Synapse pod requires hostAliases to resolve agent homeserver hostnames over the VPN tunnel.
Check current hostAliases:
kubectl get deployment -n matrix matrix-synapse -o jsonpath='{.spec.template.spec.hostAliases}' | python3 -m json.toolPatch to add missing agents (can pre-populate slots 3-5 in one shot):
kubectl patch deployment -n matrix matrix-synapse --type=json -p '[
{"op": "add", "path": "/spec/template/spec/hostAliases/-", "value": {"ip": "172.23.89.3", "hostnames": ["agent0-3-mhs.cybertribe.com"]}},
{"op": "add", "path": "/spec/template/spec/hostAliases/-", "value": {"ip": "172.23.89.4", "hostnames": ["agent0-4-mhs.cybertribe.com"]}},
{"op": "add", "path": "/spec/template/spec/hostAliases/-", "value": {"ip": "172.23.89.5", "hostnames": ["agent0-5-mhs.cybertribe.com"]}}]'Note: This triggers a rolling pod restart. Wait for the new pod before testing.
Test from the openvpn sidecar (the matrix container does NOT have wget):
kubectl exec -it -n matrix <synapse-pod> -c openvpn -- wget -qO- --no-check-certificate https://agent0-N-mhs.cybertribe.com:8448/_matrix/key/v2/serverAlso verify the federation whitelist includes the agent domain:
kubectl exec -it -n matrix <synapse-pod> -c matrix -- cat /data/homeserver.yaml | grep -A20 federation_domain_whitelistBefore deploying any containers on g2s, the host networking must be configured to allow 'macvlan' traffic to reach the physical network through eno1.
Instead of manual ip link commands, use the agent-bridge.service to survive reboots:
cat << 'EOF' | sudo tee /etc/systemd/system/agent-bridge.service
[Unit]
Description=Agent-Matrix Host Bridge (mac0)
After=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/bash -c "\
ip link set eno1 promisc on && \
ip link add mac0 link eno1 type macvlan mode bridge || true && \
ip addr add 172.23.88.254/32 dev mac0 || true && \
ip link set mac0 up && \
ip route add 172.23.88.0/24 dev mac0 || true && \
ip route add 172.23.89.0/24 dev mac0 || true"
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable --now agent-bridge.serviceThe DD-WRT router must be aware of the new MAC addresses to correctly assign IPs and provide internal DNS resolution.
For each new instance N, add two entries in the Static Leases table:
| Device Name | MAC Address | IP Address | Hostname |
|---|---|---|---|
| agent0-N | 02:42:AC:17:58:0N | 172.23.88.N | agent0-N.cybertribe.com |
| agent0-N-mhs | 02:42:AC:17:59:0N | 172.23.89.N | agent0-N-mhs.cybertribe.com |
To ensure full-mesh resolution, add these local DNS entries to the DNSMasq box if they aren't auto-populated by the leases:
address=/agent0-N.cybertribe.com/172.23.88.N
address=/agent0-N-mhs.cybertribe.com/172.23.89.N
Utility scripts for fleet-wide operations. All scripts live in:
/opt/agent-zero/multi-instance-deploy/templates/scripts/
Reports which LLM models each agent is currently using. Reads actual runtime configuration from /a0/usr/plugins/_model_config/config.json inside each container.
As of v2.0, you can also change models fleet-wide with validation, dry-run, and audit trail support.
Usage:
cd /opt/agent-zero/multi-instance-deploy/templates/scripts
# Standard table view
./fleet-models.sh
# With provider details and context window sizes
./fleet-models.sh --verbose
# Diagnostic checks (Change Detection, Config Sync)
./fleet-models.sh --diagnose
# JSON output (for scripting)
./fleet-models.sh --json
# Specific agents only
./fleet-models.sh --instances 2,3
# Fleet-wide model change (preview first)
./fleet-models.sh --instances 1-5 --set-chat-model moonshotai/kimi-k2.6 --dry-run
# Apply with confirmation
./fleet-models.sh --instances 1-5 --set-chat-model moonshotai/kimi-k2.6
# Skip confirmation
./fleet-models.sh --instances 1-5 --set-utility-model openai/gpt-5.4-nano --yesFlags:
| Flag | Description |
|---|---|
--verbose |
Show providers, context window sizes, and full model names |
--diagnose |
Run Change Detection and MCP Config Sync Check |
--json |
Machine-readable JSON output |
--instances N,N,... or N-M |
Comma-separated list or range (default: all) |
--set-chat-model ID |
Set main/chat model fleet-wide (requires --instances) |
--set-utility-model ID |
Set utility model fleet-wide (requires --instances) |
--set-embedding-model ID |
Set embedding model fleet-wide (requires --instances) |
--dry-run |
Preview changes without applying |
--yes |
Skip confirmation prompt |
Sample Output:
Agent Profile Main Model Utility Model Embedding
agent0-1 agent0 xiaomi/mimo-v2-pro openai/gpt-5.4-nano sentence-transform
agent0-2 agent0 perplexity/sonar google/gemini-3-flash- sentence-transform
agent0-4 hacker minimax/minimax-m2.7 google/gemini-3-flash- sentence-transform
Note: This script reads
/a0/usr/plugins/_model_config/config.jsoninside each container — the actual runtime configuration, not.envtemplate defaults. Audit Trail: Every model change is logged to/opt/agent-zero/logs/fleet-models-audit.log.
Zero-touch updater that triggers Agent Zero's built-in self-update mechanism across the fleet. As of v1.4+, supports --json for structured output and explicitly detects MCP server variant (Rust r2:X.Y.Z, TypeScript ts, Python py).
Usage:
# Human-readable fleet status table
./update-fleet.sh --status
# JSON output (for monitoring / dashboards)
./update-fleet.sh --status --json
# Update fleet to specific version
./update-fleet.sh --version v1.9
# Update fleet with JSON report
./update-fleet.sh --version v1.9 --json
# Clean up stale zombie matrix-bot processes
./update-fleet.sh --cleanup
# Cleanup with JSON report
./update-fleet.sh --cleanup --jsonJSON output fields (per instance):
{
"agent": "agent0-2",
"container": "up",
"version": "v1.9",
"mcp_alive": true,
"mcp_variant": "rust",
"mcp_version": "0.1.1",
"watchdog_alive": true
}Verifies Matrix MCP tokens are consistent between .env and settings.json.
python3 check-token-sync.py| Instance | Host | Agent IP | MHS IP | Profile | Federation | Status |
|---|---|---|---|---|---|---|
| agent0-1 | tarnover | 172.23.88.1 | 172.23.89.1 | standard | TBD | Legacy |
| agent0-2 | g2s | 172.23.88.2 | 172.23.89.2 | standard | ✅ Verified | Sovereign |
| agent0-3 | g2s | 172.23.88.3 | 172.23.89.3 | standard | ✅ Verified | Sovereign |
| agent0-4 | g2s | 172.23.88.4 | 172.23.89.4 | hacker | Pre-wired | Available |
| agent0-5 | g2s | 172.23.88.5 | 172.23.89.5 | standard | Pre-wired | Available |
| Symptom | Cause | Fix |
|---|---|---|
keyBlock is nil |
Wrong key format (OpenSSH instead of MATRIX PRIVATE KEY) | Regenerate with Python generator (Section 4.2) |
unbound variable |
Script variable used before declaration | Ensure all vars initialized at top of script |
connection: unsupported |
Old connection: key in continuwuity.yaml |
Run sed -i 's/connection:/connection_string:/g' on the file |
Missing relay_api / room_server |
Outdated continuwuity.yaml.template | Replace template with gold standard from agent0-2 |
| Port 8448 unreachable | No TLS listener configured | Add --https-bind-address :8448 to docker-compose command + mount certs |
| Container can't read key | Permission mismatch | chmod 600 + chown 1000:1000 on matrix_key.pem |
Matrix bots can have custom display names ("aliases") that show up in Element rooms next to their messages.
Set in your .env file:
BOT_DISPLAY_NAME=My Awesome Bot # Shows as "My Awesome Bot" in all rooms
AGENT_IDENTITY=Agent Zero # Used in system promptsThe bot automatically sets this name on startup.
Use the set_display_name.py utility to change names without restarting:
# Set global display name (all rooms)
python3 set_display_name.py "New Name"
# Set per-room display name
python3 set_display_name.py "Room Helper" --room '!roomid:server.com'
# Reset to default
python3 set_display_name.py --reset
# Show configuration
python3 set_display_name.py --list| Method | Scope | API Call |
|---|---|---|
| Global | All rooms | client.set_displayname() |
| Per-room | Specific room | client.room_put_state(m.room.member) |