Subsections of AI Agent
Hermes
OpenCode
🚀Installation
Preliminary
1. Kubernetes is installed; if not, check 🔗link2. Helm is installed; if not, check 🔗link3. ArgoCD is installed; if not, check 🔗link1.prepare `opencode-configuration.yaml`
Details
kubectl get namespaces opencode > /dev/null 2>&1 || kubectl create namespace opencode
kubectl -n opencode create secret generic opencode-server-secret \
--from-literal=OPENCODE_SERVER_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 16)1.1.choose different LLM configuration
kubectl -n opencode apply -f - <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: opencode-config
data:
opencode.json: |
{
"provider": {
"openai": {
"options": {
"baseURL": "https://v2.qixuw.com/v1",
"apiKey": "sk-ss"
},
"models": {
"gpt-5.3-codex-spark": {
"name": "GPT-5.3 Codex Spark",
"limit": {
"context": 128000,
"output": 32000
},
"options": {
"store": false
},
"variants": {
"low": {},
"medium": {},
"high": {},
"xhigh": {}
}
}
}
}
},
"agent": {
"build": {
"options": {
"store": false
}
},
"plan": {
"options": {
"store": false
}
}
},
"$schema": "https://opencode.ai/config.json"
}
EOFkubectl -n opencode apply -f - <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: opencode-config
data:
opencode.json: |
{
"provider": {
"openai": {
"options": {
"baseURL": "https://v2.qixuw.com/v1",
"apiKey": "sk-ss"
},
"models": {
"gpt-5.3-codex-spark": {
"name": "GPT-5.3 Codex Spark",
"limit": {
"context": 128000,
"output": 32000
},
"options": {
"store": false
},
"variants": {
"low": {},
"medium": {},
"high": {},
"xhigh": {}
}
}
}
}
},
"mcp": {
"euclid-catalog": {
"type": "remote",
"url": "https://catalog.euclid.mcp.dev.72602.online:32443/sse",
"enabled": true
},
"astro_k3s_mcp": {
"type": "remote",
"url": "http://eva24002-entrance.lab.zverse.space:30082/mcp",
"enabled": true,
"oauth": false,
"timeout": 15000
}
},
"agent": {
"build": {
"options": {
"store": false
}
},
"plan": {
"options": {
"store": false
}
}
},
"$schema": "https://opencode.ai/config.json"
}
EOFkubectl -n opencode apply -f - <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: opencode-config
data:
opencode.json: |
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"minimax": {
"npm": "@ai-sdk/openai-compatible",
"name": "MiniMax M2.5",
"options": {
"baseURL": "http://10.200.92.41:31551/v1",
"apiKey": "sk-sss"
},
"models": {
"minimax-m2.5": {
"name": "MiniMax M2.5",
"id": "MiniMaxAI/MiniMax-M2.5",
"limit": {
"context": 196608,
"output": 8192
}
}
}
}
},
"model": "minimax/minimax-m2.5"
}
EOFkubectl -n opencode apply -f - <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: opencode-config
data:
opencode.json: |
{
"$schema": "https://opencode.ai/config.json",
"model": "opencode/minimax-m2.5-free",
"small_model": "opencode/minimax-m2.5-free"
}
EOFkubectl -n opencode apply -f - <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: opencode-config
data:
opencode.json: |
{
"provider": {
"qiniu": {
"options": {
"baseURL": "https://api.qnaigc.com/v1",
"apiKey": "sk-sss"
},
"models": {
"minimax-m2.5": {
"name": "Minimax-M2.5",
"limit": {
"context": 128000,
"output": 32000
},
"options": {
"store": false
},
"variants": {
"low": {},
"medium": {},
"high": {},
"xhigh": {}
}
}
}
}
},
"agent": {
"build": {
"options": {
"store": false
}
},
"plan": {
"options": {
"store": false
}
}
},
"$schema": "https://opencode.ai/config.json"
}
EOF2.prepare `deploy-opencode.yaml`; change the default model when you apply a different ConfigMap
Details
kubectl -n argocd apply -f - <<'EOF'
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: opencode
namespace: argocd
spec:
project: default
source:
repoURL: oci://ghcr.io/aaronyang0628/opencode
targetRevision: 0.20.0
chart: opencode
helm:
values: |
image:
repository: ghcr.io/nimbleflux/opencode-docker
tag: 1.2.26
pullPolicy: Always
replicaCount: 1
command:
- opencode
args:
- serve
- --port
- "4000"
- --hostname
- "0.0.0.0"
service:
type: ClusterIP
port: 4000
env:
OPENCODE_PORT: "4000"
extraVolumes:
- name: opencode-config
configMap:
name: opencode-config
extraVolumeMounts:
- name: opencode-config
mountPath: /home/opencode/.config/opencode/opencode.json
subPath: opencode.json
readOnly: true
persistence:
enabled: true
storageClass: local-path
config:
enabled: false
data:
enabled: true
size: 1Gi
workspace:
enabled: true
size: 5Gi
playbook:
enabled: true
mountPath: /home/opencode/workspace/playbook
example:
enabled: true
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 2
memory: 4Gi
probes:
liveness:
tcpSocket:
port: 4000
initialDelaySeconds: 15
periodSeconds: 30
readiness:
tcpSocket:
port: 4000
initialDelaySeconds: 10
periodSeconds: 10
startup:
tcpSocket:
port: 4000
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 30
ingress:
enabled: true
className: nginx
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: self-signed-ca-issuer
nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/upstream-keepalive-connections: "50"
nginx.ingress.kubernetes.io/upstream-keepalive-timeout: "60"
hosts:
- host: opencode.dev.72602.online
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- opencode.dev.72602.online
secretName: opencode.dev.72602.online-tls
globalLabels:
app.kubernetes.io/part-of: opencode
environment: production
bridge:
enabled: true
image:
repository: crpi-wixjy6gci86ms14e.cn-hongkong.personal.cr.aliyuncs.com/ay-dev/opencode-bridge
tag: "v20260326r4"
env:
defaultModel: "opencode/minimax-m2.5-free"
openaiStreamChunkSize: "4"
openaiStreamChunkDelayMs: "10"
enableLeadingEchoFilter: "false"
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: self-signed-ca-issuer
nginx.ingress.kubernetes.io/proxy-buffering: "off"
nginx.ingress.kubernetes.io/proxy-request-buffering: "off"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/upstream-keepalive-connections: "50"
nginx.ingress.kubernetes.io/upstream-keepalive-timeout: "60"
hosts:
- host: opencode-bridge.dev.72602.online
paths:
- path: /
pathType: Prefix
tls:
- secretName: opencode-bridge-tls
hosts:
- opencode-bridge.dev.72602.online
destination:
server: https://kubernetes.default.svc
namespace: opencode
syncPolicy:
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
EOF3.sync by argocd
Details
argocd app sync argocd/opencode4.then you can talk with LLM with rest api
Details
curl -k -X POST https://opencode.dev.72602.online:32443/session \
-H "Content-Type: application/json" \
-d '{"model": "opencode/minimax-m2.5-free"}'
## {"id":"ses_30a879abeffe6KRC0Rmg4aPrmK","slug":"brave-eagle","version":"1.2.26","projectID":"global","directory":"/home/opencode/workspace","title":"New session - 2026-03-16T07:07:14.113Z","time":{"created":1773644834113,"updated":1773644834113}}5.reuse the same session
Details
curl -k -X POST https://opencode.dev.72602.online:32443/session/ses_30a879abeffe6KRC0Rmg4aPrmK/message \
-H "Content-Type: application/json" \
-d '{"parts": [{"type": "text", "text": "你好"}]}'
## {"info":{"role":"assistant","time":{"created":1773644844131,"completed":1773644848700},"parentID":"msg_cf5788c12001RS4wX3hwMRe0If","modelID":"minimax-m2.5","providerID":"minimax","mode":"build","agent":"build","path":{"cwd":"/home/opencode/workspace","root":"/"},"cost":0,"tokens":{"total":10628,"input":10567,"output":61,"reasoning":0,"cache":{"read":0,"write":0}},"finish":"stop","id":"msg_cf5788c63001hBrorYejcHc1tO","sessionID":"ses_30a879abeffe6KRC0Rmg4aPrmK"},"parts":[{"type":"step-start","id":"prt_cf57899120016WXp4jT4AHeTiG","sessionID":"ses_30a879abeffe6KRC0Rmg4aPrmK","messageID":"msg_cf5788c63001hBrorYejcHc1tO"},{"type":"text","text":"<think>The user said \"你好\" which means \"Hello\" in Chinese. According to the instructions, I should be concise and direct. I should respond briefly without unnecessary preamble. Since this is a simple greeting, I can just respond with a greeting back.\n</think>\n\n你好!有什么可以帮你的吗?","time":{"start":1773644848689,"end":1773644848689},"id":"prt_cf5789913001dUZnoW9w63ThkC","sessionID":"ses_30a879abeffe6KRC0Rmg4aPrmK","messageID":"msg_cf5788c63001hBrorYejcHc1tO"},{"type":"step-finish","reason":"stop","cost":0,"tokens":{"total":10628,"input":10567,"output":61,"reasoning":0,"cache":{"read":0,"write":0}},"id":"prt_cf5789e35001dLGIXJb5WHizMX","sessionID":"ses_30a879abeffe6KRC0Rmg4aPrmK","messageID":"msg_cf5788c63001hBrorYejcHc1tO"}]}Preliminary
1. Kubernetes is installed; if not, check 🔗link2. Helm is installed; if not, check 🔗link3. ArgoCD is installed; if not, check 🔗link0. (optional) deploy `oauth2-proxy` for GitHub OAuth 2FA
Details
kubectl get namespaces oauth2-proxy > /dev/null 2>&1 || kubectl create namespace oauth2-proxy
kubectl -n oauth2-proxy create secret generic oauth2-proxy-secret \
--from-literal=client-id=<your-github-oauth-client-id> \
--from-literal=client-secret=<your-github-oauth-client-secret> \
--from-literal=cookie-secret=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 32)
kubectl -n argocd apply -f - <<'EOF'
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: oauth2-proxy
namespace: argocd
spec:
project: default
source:
repoURL: oci://ghcr.io/aaronyang0628/oauth2-proxy
targetRevision: 7.7.0
chart: oauth2-proxy
helm:
values: |
config:
clientID: ""
existingSecret: oauth2-proxy-secret
extraEnv:
- name: OAUTH2_PROXY_CLIENT_ID
valueFrom:
secretKeyRef:
name: oauth2-proxy-secret
key: client-id
- name: OAUTH2_PROXY_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: oauth2-proxy-secret
key: client-secret
- name: OAUTH2_PROXY_COOKIE_SECRET
valueFrom:
secretKeyRef:
name: oauth2-proxy-secret
key: cookie-secret
provider: github
upstreams:
- http://opencode:4000
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt
hosts:
- host: ops.agent.72602.online
paths:
- /
tls:
- secretName: ops.agent.72602.online-tls
hosts:
- ops.agent.72602.online
destination:
server: https://kubernetes.default.svc
namespace: oauth2-proxy
syncPolicy:
syncOptions:
- CreateNamespace=true
EOF1.prepare `opencode-configuration.yaml`
Details
kubectl get namespaces opencode > /dev/null 2>&1 || kubectl create namespace opencode
kubectl -n opencode create secret generic opencode-server-secret \
--from-literal=OPENCODE_SERVER_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 16)2.prepare `deploy-opencode.yaml`
Details
kubectl -n argocd apply -f - <<'EOF'
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: opencode
namespace: argocd
spec:
project: default
source:
repoURL: oci://ghcr.io/aaronyang0628/opencode
targetRevision: 0.20.0
chart: opencode
helm:
values: |
image:
repository: ghcr.io/nimbleflux/opencode-docker
tag: 1.2.26
pullPolicy: Always
replicaCount: 1
command:
- opencode
args:
- serve
- --port
- "4000"
- --hostname
- "0.0.0.0"
service:
type: ClusterIP
port: 4000
env:
OPENCODE_PORT: "4000"
extraVolumes:
- name: opencode-config
configMap:
name: opencode-config
extraVolumeMounts:
- name: opencode-config
mountPath: /home/opencode/.config/opencode/opencode.json
subPath: opencode.json
readOnly: true
persistence:
enabled: true
storageClass: local-path
config:
enabled: false
data:
enabled: true
size: 1Gi
workspace:
enabled: true
size: 5Gi
playbook:
enabled: true
mountPath: /home/opencode/workspace/playbook
example:
enabled: true
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 2
memory: 4Gi
probes:
liveness:
tcpSocket:
port: 4000
initialDelaySeconds: 15
periodSeconds: 30
readiness:
tcpSocket:
port: 4000
initialDelaySeconds: 10
periodSeconds: 10
startup:
tcpSocket:
port: 4000
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 30
ingress:
enabled: true
className: nginx
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt
nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/upstream-keepalive-connections: "50"
nginx.ingress.kubernetes.io/upstream-keepalive-timeout: "60"
hosts:
- host: ops.agent.72602.online
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- ops.agent.72602.online
secretName: ops.agent.72602.online-tls
globalLabels:
app.kubernetes.io/part-of: opencode
environment: production
bridge:
enabled: true
image:
repository: crpi-wixjy6gci86ms14e.cn-hongkong.personal.cr.aliyuncs.com/ay-dev/opencode-bridge
tag: "v20260326r4"
env:
defaultModel: "openai/gpt-5.3-codex-spark"
openaiStreamChunkSize: "4"
openaiStreamChunkDelayMs: "10"
enableLeadingEchoFilter: "false"
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt
nginx.ingress.kubernetes.io/proxy-buffering: "off"
nginx.ingress.kubernetes.io/proxy-request-buffering: "off"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/upstream-keepalive-connections: "50"
nginx.ingress.kubernetes.io/upstream-keepalive-timeout: "60"
hosts:
- host: opencode-bridge.72602.online
paths:
- path: /
pathType: Prefix
tls:
- secretName: opencode-bridge-tls
hosts:
- opencode-bridge.72602.online
destination:
server: https://kubernetes.default.svc
namespace: opencode
syncPolicy:
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
EOF3.sync by argocd
Details
argocd app sync argocd/opencode4.then you can talk with LLM with rest api
Details
curl -s "https://opencode-bridge.72602.online/v1/models"
## {"id":"ses_30a879abeffe6KRC0Rmg4aPrmK","slug":"brave-eagle","version":"1.2.26","projectID":"global","directory":"/home/opencode/workspace","title":"New session - 2026-03-16T07:07:14.113Z","time":{"created":1773644834113,"updated":1773644834113}}4.then you can talk with LLM with rest api
Details
curl -k -X POST https://ops.agent.72602.online/session \
-H "Content-Type: application/json" \
-d '{"model": "MiniMaxAI/MiniMax-M2.5"}'
## {"id":"ses_30a879abeffe6KRC0Rmg4aPrmK","slug":"brave-eagle","version":"1.2.26","projectID":"global","directory":"/home/opencode/workspace","title":"New session - 2026-03-16T07:07:14.113Z","time":{"created":1773644834113,"updated":1773644834113}}5.reuse the same session
Details
curl -k -X POST https://ops.agent.72602.online/session/ses_30a879abeffe6KRC0Rmg4aPrmK/message \
-H "Content-Type: application/json" \
-d '{"parts": [{"type": "text", "text": "你好"}]}'
## {"info":{"role":"assistant","time":{"created":1773644844131,"completed":1773644848700},"parentID":"msg_cf5788c12001RS4wX3hwMRe0If","modelID":"minimax-m2.5","providerID":"minimax","mode":"build","agent":"build","path":{"cwd":"/home/opencode/workspace","root":"/"},"cost":0,"tokens":{"total":10628,"input":10567,"output":61,"reasoning":0,"cache":{"read":0,"write":0}},"finish":"stop","id":"msg_cf5788c63001hBrorYejcHc1tO","sessionID":"ses_30a879abeffe6KRC0Rmg4aPrmK"},"parts":[{"type":"step-start","id":"prt_cf57899120016WXp4jT4AHeTiG","sessionID":"ses_30a879abeffe6KRC0Rmg4aPrmK","messageID":"msg_cf5788c63001hBrorYejcHc1tO"},{"type":"text","text":"<think>The user said \"你好\" which means \"Hello\" in Chinese. According to the instructions, I should be concise and direct. I should respond briefly without unnecessary preamble. Since this is a simple greeting, I can just respond with a greeting back.\n</think>\n\n你好!有什么可以帮你的吗?","time":{"start":1773644848689,"end":1773644848689},"id":"prt_cf5789913001dUZnoW9w63ThkC","sessionID":"ses_30a879abeffe6KRC0Rmg4aPrmK","messageID":"msg_cf5788c63001hBrorYejcHc1tO"},{"type":"step-finish","reason":"stop","cost":0,"tokens":{"total":10628,"input":10567,"output":61,"reasoning":0,"cache":{"read":0,"write":0}},"id":"prt_cf5789e35001dLGIXJb5WHizMX","sessionID":"ses_30a879abeffe6KRC0Rmg4aPrmK","messageID":"msg_cf5788c63001hBrorYejcHc1tO"}]}1. following the steps in `https://opencode.ai`
Details
curl -fsSL https://opencode.ai/install | bash6.use bridge to manage session

🌐Hugo Docs Site
The Hugo documentation site is deployed via ArgoCD on ops.docs.72602.online (formerly port.72602.online).
Preliminary
1. Kubernetes is installed; if not, check 🔗link2. Helm is installed; if not, check 🔗link3. ArgoCD is installed; if not, check 🔗link1.prepare `deploy-hugo-docs.yaml`
Details
kubectl -n argocd apply -f - <<'EOF'
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: hugo-docs
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/aaronyang0628/ops-docs.git
targetRevision: main
path: .
destination:
server: https://kubernetes.default.svc
namespace: hugo-docs
syncPolicy:
syncOptions:
- CreateNamespace=true
automated:
prune: true
selfHeal: true
EOF2.sync by argocd
Details
argocd app sync argocd/hugo-docs🤖Multi-Agent System
The opencode deployment includes two specialized agents for cluster and documentation maintenance:
| Agent | Role | Target |
|---|---|---|
k3s-maintainer | Cluster operations | k3s cluster health, pod management, node maintenance |
doc-maintainer | Documentation updates | Hugo docs site content, ArgoCD Application updates |
Agent Configuration (applied as ConfigMaps)
Details
kubectl -n opencode apply -f - <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: k3s-maintainer-config
data:
opencode.json: |
{
"provider": {
"openai": {
"options": {
"baseURL": "https://v2.qixuw.com/v1",
"apiKey": "sk-ss"
},
"models": {
"gpt-5.3-codex-spark": {
"name": "GPT-5.3 Codex Spark",
"limit": { "context": 128000, "output": 32000 }
}
}
}
},
"mcp": {
"k8s-mcp": {
"type": "remote",
"url": "http://k8s-mcp.opencode.svc.cluster.local:8080/mcp",
"enabled": true,
"timeout": 15000
}
},
"agent": {
"build": { "options": { "store": false } },
"plan": { "options": { "store": false } }
}
}
EOFDeploy agents via ArgoCD - each agent runs as a separate opencode server Pod with its own ConfigMap
Details
kubectl -n argocd apply -f - <<'EOF'
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: k3s-maintainer
namespace: argocd
spec:
project: default
source:
repoURL: oci://ghcr.io/aaronyang0628/opencode
targetRevision: 0.20.0
chart: opencode
helm:
values: |
image:
repository: ghcr.io/nimbleflux/opencode-docker
tag: 1.2.26
replicaCount: 1
command: ["opencode"]
args: ["serve", "--port", "4001", "--hostname", "0.0.0.0"]
service:
type: ClusterIP
port: 4001
env:
OPENCODE_PORT: "4001"
extraVolumes:
- name: agent-config
configMap:
name: k3s-maintainer-config
extraVolumeMounts:
- name: agent-config
mountPath: /home/opencode/.config/opencode/opencode.json
subPath: opencode.json
persistence:
enabled: true
storageClass: local-path
config:
enabled: false
data:
enabled: true
size: 1Gi
workspace:
enabled: true
size: 5Gi
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 2
memory: 4Gi
destination:
server: https://kubernetes.default.svc
namespace: opencode
syncPolicy:
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
EOF⚡Optimizations
Several optimizations were applied to improve the ArgoCD workflow:
| Optimization | Before | After |
|---|---|---|
| GitHub proxy for ArgoCD | Direct GitHub access | ghfast.top mirror |
| Git repo size | 117 MB | 44 MB |
| ArgoCD sync time | slow (~minutes) | ~7 seconds |
1. GitHub Proxy - configure ArgoCD to use `ghfast.top` for faster OCI/Helm chart pulls
Details
# In argocd-cm ConfigMap:
data:
repositories: |
- url: oci://ghcr.io
proxy: https://ghfast.top2. Repo Size Reduction - cleaned large files and unused assets from the git repository
Details
# Remove git history and prune
git filter-repo --strip-blobs-bigger-than 1M
git gc --aggressive --prune=now3. Fast ArgoCD Sync - achieved by combining ServerSideApply, lean manifests, and cached images
Details
syncPolicy:
syncOptions:
- ServerSideApply=true🛎️FAQ
ClaudeCode
Ops Agent
🚀Installation
Preliminary
1. Kubernetes is installed; if not, check 🔗link2. Helm is installed; if not, check 🔗link3. ArgoCD is installed; if not, check 🔗link1.build and push Ops Agent image
Details
cd /home/aaron/Ops/docs
./manifests/ops-agent/build-and-push.shThe image contains OpenCode, kubectl, Argo CD CLI, Git, SSH, opencode-vibeguard, DCP, and Goal Mode. The repository is not baked into the image; the Pod mounts /home/aaron/Ops/docs at /workspace.
2.prepare runtime Secrets
Details
./manifests/ops-agent/create-secrets.sh
./manifests/ops-agent/create-sub2api-mcp-secret.shcreate-secrets.sh loads the ignored repository .env by default (or the
file named by OPENCODE_ENV_FILE) and requires the five model variables:
OPENAI_API_KEY, GROK_API_KEY, OLLAMA_API_KEY, BACKUP_ENDPOINT, and
BACKUP_API_KEY. It creates or updates model, SSH, Git
credential, Registry, and Basic Auth Secrets without writing their values
into Git. The Sub2API helper accepts the admin API key only through its
silent interactive prompt, validates its admin-<hex64> shape, writes
only application/sub2api-mcp key admin-api-key, and unsets the local
variable before exit. The script refuses any other format.
3.sync by ArgoCD
Details
argocd app get ops-docs --hard-refresh
argocd app sync ops-docs --revision main
argocd app wait ops-docs --sync --health --timeout 300
argocd app get ops-agent --hard-refresh
argocd app sync ops-agent
argocd app wait ops-agent --sync --health --timeout 600
kubectl -n application rollout status deployment/ops-agent --timeout=600sargocd/ops-docs owns manifests/ops-agent-argocd.yaml; the child
argocd/ops-agent Application deploys manifests/ops-agent into namespace
application. Do not apply the Kustomization directly as a second ownership
path. The Deployment mounts the host workspace read-write and stores OpenCode
session data in the opencode-data PVC.
Startup configuration is loaded only when OpenCode starts. After changing
.opencode/opencode.json, agents, skills, or plugins, restart only the managed
workload and wait for readiness:
Details
kubectl -n application rollout restart deployment/ops-agent
kubectl -n application rollout status deployment/ops-agent --timeout=300sThe live strategy is Recreate, so a restart briefly makes the web endpoint
unavailable while the replacement Pod becomes Ready.
4.verify configuration and access
Details
kubectl -n application get pod,svc,ingress \
-l app.kubernetes.io/name=ops-agent
kubectl -n application get certificate ops.agent.72602.space-tls
kubectl -n application exec deployment/ops-agent -c ops-agent -- \
opencode debug agent hugo-doc-maintainer
kubectl -n application exec deployment/ops-agent -c ops-agent -- sh -c \
'curl -fsS -u "$OPENCODE_SERVER_USERNAME:$OPENCODE_SERVER_PASSWORD" \
http://127.0.0.1:4000/global/health'
kubectl -n application exec deployment/ops-agent -c ops-agent -- sh -c \
'curl -fsS -u "$OPENCODE_SERVER_USERNAME:$OPENCODE_SERVER_PASSWORD" \
http://127.0.0.1:4000/mcp | jq -e ". == {\"sub2api-admin\": {\"status\": \"connected\"}}"'
kubectl -n application exec deployment/ops-agent -c ops-agent -- sh -c \
'CFG=$(curl -fsS -u "$OPENCODE_SERVER_USERNAME:$OPENCODE_SERVER_PASSWORD" \
http://127.0.0.1:4000/config); echo "$CFG" | \
jq -e ".mcp[\"sub2api-admin\"].environment[\"SUB2API_BASE_URL\"] == \
\"http://sub2api.application.svc.cluster.local:8080\"" && \
! echo "$CFG" | grep -Eq "SUB2API_ADMIN_API_KEY|admin-api-key|x-api-key"'
kubectl -n application exec deployment/ops-agent -c ops-agent -- \
opencode mcp list
kubectl -n application exec deployment/ops-agent -c ops-agent -- sh -c \
'curl -fsS -u "$OPENCODE_SERVER_USERNAME:$OPENCODE_SERVER_PASSWORD" \
http://127.0.0.1:4000/provider | \
jq "{connected,ollama:(.all[]|select(.id==\"ollama\")|.models|keys)}"'
PASSWORD="$(kubectl -n application get secret opencode-basic-auth \
-o jsonpath='{.data.password}' | base64 -d)"
curl -sS -o /dev/null -w '%{http_code}\n' \
-u "aaron:$PASSWORD" https://ops.agent.72602.space/Expected result: the Pod is Ready, the certificate is True, anonymous
access returns 401, authenticated access returns 200, global/health
reports healthy: true, /mcp returns exactly
{"sub2api-admin":{"status":"connected"}}, the merged OpenCode config
carries SUB2API_BASE_URL=http://sub2api.application.svc.cluster.local:8080
without the admin key, opencode mcp list reports sub2api-admin
connected, and the connected Ollama provider lists gemma4:31b,
and gpt-oss:120b. The live authenticated version read
through 72602-k3s-maintainer using sub2api-admin_read against
/api/v1/admin/system/version returns 0.1.168.
🤖Agents
| Agent | Responsibility | Operational source |
|---|---|---|
72602-k3s-maintainer | Operate the local 72602 cluster | Live cluster, then content/CSP/72602/_index.md |
zjlab-ops-maintainer | Operate ZJLAB through private SSH aliases | Live cluster, then the private inventory |
hugo-doc-maintainer | Maintain Relearn layout and runbooks | Relearn and Installation skills |
All three are subagents and inherit the model selected by the active conversation. Cluster agents verify live state before following a runbook and delegate verified documentation updates to the Hugo agent.
Aliyun operations for 72602-k3s-maintainer are documented by the public .opencode/skills/aliyun-72602-operations/SKILL.md, introduced in commit 96a9354.
🧰MCP & Skills
Local Sub2API admin MCP
The OpenCode config registers a single local MCP server sub2api-admin
under mcp.sub2api-admin:
{
"type": "local",
"command": ["node", ".opencode/mcp/sub2api-admin/server.mjs"],
"cwd": "/workspace",
"environment": {
"SUB2API_BASE_URL": "http://sub2api.application.svc.cluster.local:8080",
"SUB2API_ALLOWED_HOST": "sub2api.application.svc.cluster.local",
"SUB2API_TIMEOUT_MS": "30000",
"SUB2API_MAX_RECHARGE": "10000"
},
"enabled": true,
"timeout": 40000
}The server reads its credential from the environment variable
SUB2API_ADMIN_API_KEY. The Deployment injects that variable from the
dedicated runtime Secret application/sub2api-mcp key admin-api-key:
- name: SUB2API_ADMIN_API_KEY
valueFrom:
secretKeyRef:
name: sub2api-mcp
key: admin-api-keyThe key is provisioned interactively by
manifests/ops-agent/create-sub2api-mcp-secret.sh, which accepts the value
from the operator, validates its admin-<hex64> shape, writes the Secret,
and unsets the local variable. The script refuses any other format. The
value never enters Git or opencode.json.
The server only accepts the configured cluster-local host; every authenticated GET runs through a reviewed route allowlist, sensitive fields are recursively redacted, and the MCP exposes only dedicated mutation tools.
Tool surface
The MCP registers nine tools:
describe: capabilities and safety constraints.health: anonymous check of/healththrough the configured internal cluster-local URL (SUB2API_BASE_URL), not the publictoken.72602.spaceendpoint.read: one allowlisted authenticated GET route.find_user: resolve exactly one user by normalized email.preview_recharge: non-mutating preview of an additive credit.recharge: additive credit with backend idempotency.update_user_limits: per-user RPM and/or concurrency.set_account_schedulable: enable or disable scheduling for an entire upstream account.model_control_options: explain the available scheduling boundaries and rate-limit scopes.
There is no generic authenticated write tool.
Sub2API 72602 operations skill
The skill .opencode/skills/sub2api-72602-operations/SKILL.md is loaded by
72602-k3s-maintainer whenever the task touches live Sub2API state. It
records the boundaries, the recharge workflow, the limits and model
availability constraints, and the verification loop. Recharge is additive
USD-style internal credit only. The MCP accepts one exact normalized email
and an immutable user id; every call must pass one stable
Idempotency-Key (16-128 URL-safe characters) and re-read the user plus
balance history. The MCP reports an exact verified or
accepted_unverified outcome; the skill forbids rewriting an
accepted_unverified response as success.
Limits and model availability are constrained in Sub2API v0.1.168:
- User RPM and concurrency can be set through
sub2api-admin_update_user_limits. - Group RPM exists through the admin UI; this MCP does not expose it.
- Sub2API v0.1.168 has no configurable per-model RPM.
- Temporary model unavailability uses the actual scheduling boundary:
dedicated accounts (
set_account_schedulable), restricted channel model lists, or composite routes.models_list_configonly hides discovery and must never be reported as a disable. Changes affect new requests only.
The skill is documentation; the runtime stays scoped to the maintainer. No account is recharged as part of installation.
Permission scope
The global OpenCode permission set denies every sub2api-admin_* tool.
The maintainer agent permits the read-only tools directly and asks before
calling sub2api-admin_recharge, sub2api-admin_update_user_limits, and
sub2api-admin_set_account_schedulable:
permission:
"sub2api-admin_describe": allow
"sub2api-admin_health": allow
"sub2api-admin_read": allow
"sub2api-admin_find_user": allow
"sub2api-admin_preview_recharge": allow
"sub2api-admin_model_control_options": allow
"sub2api-admin_recharge": ask
"sub2api-admin_update_user_limits": ask
"sub2api-admin_set_account_schedulable": askOther agents inherit the global deny and must request the maintainer for
any sub2api-admin_* mutation. The MCP does not expose credential
rotation, OAuth exchange, backup restore, or application lifecycle
operations.
Secret rotation
Sub2API supports only one Admin API Key. Generating or regenerating the key in the admin UI immediately invalidates the previous key, so the MCP will briefly fail to authenticate until the Ops Agent workload restarts with the new value. Plan an expected maintenance window of one to two restart cycles. Rotate in this exact order without intermediate pauses:
- Generate the new key in the Sub2API admin UI and immediately enter it
into the silent prompt of
manifests/ops-agent/create-sub2api-mcp-secret.sh; the helper runs once, validates the format, writesapplication/sub2api-mcpkeyadmin-api-key, and exits without echoing the value. - Restart the managed workload so the new env var is injected:
kubectl -n application rollout restart deployment/ops-agent kubectl -n application rollout status deployment/ops-agent --timeout=300s - Verify
global/healthreportshealthy: true,/mcpreports exactly{"sub2api-admin":{"status":"connected"}}, and the merged OpenCode config carries the cluster-localSUB2API_BASE_URLwithout the admin key. - Through
72602-k3s-maintainer, runsub2api-admin_readagainst/api/v1/admin/system/version; the live application must respond0.1.168.
Enter the key only into the helper’s silent prompt. Never place it in shell arguments, chat, Git, OpenCode config, logs, or command output. Regenerating the key upstream is itself the invalidation step; no follow-up “disable previous key” call is needed or supported.
Test the MCP server with node --test .opencode/mcp/sub2api-admin/server.test.mjs;
three tests pass and exercise the allowlist, path-traversal rejection, and
recharge idempotency.
🔌Plugins
opencode-vibeguard@0.1.0: redacts configured credential patterns before model requests.@prevalentware/opencode-goal-plugin@0.1.24: persists long-running goals and evidence-gated completion state.
Langfuse is not installed or enabled.
↩️Rollback
Restore the previous reviewed configuration and Deployment through Git, then let the parent and child Applications converge:
git -C /home/aaron/Ops/docs log --oneline -- \
.opencode/opencode.json manifests/ops-agent
git -C /home/aaron/Ops/docs revert <change-commit>
git -C /home/aaron/Ops/docs push origin HEAD:main
argocd app get ops-agent --hard-refresh
argocd app sync ops-agent
argocd app wait ops-agent --sync --health --timeout 600
kubectl -n application rollout restart deployment/ops-agent
kubectl -n application rollout status deployment/ops-agent --timeout=300sDo not patch the Deployment, delete the Ingress/PVC/Secrets, or scale the Git-owned workload as a rollback path.
OpenClaw
🚀Installation
Preliminary
1. Kubernetes is installed; if not, check 🔗link2. Helm is installed; if not, check 🔗link3. ArgoCD is installed; if not, check 🔗link1.prepare `openclaw-env-secret.yaml`
Details
kubectl get namespaces claw > /dev/null 2>&1 || kubectl create namespace claw
kubectl create secret generic openclaw-env-secret -n claw \
--from-literal=ANTHROPIC_API_KEY=REPLACE_WITH_YOUR_API_KEY \
--from-literal=OPENCLAW_GATEWAY_TOKEN=REPLACE_WITH_YOUR_TOKEN_KEY2.prepare `deploy-openclaw.yaml`
Details
kubectl -n argocd apply -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: openclaw
namespace: argocd
spec:
project: default
source:
repoURL: https://serhanekicii.github.io/openclaw-helm
chart: openclaw
targetRevision: 1.4.4
helm:
releaseName: openclaw
values: |
app-template:
openclawVersion: "2026.2.23"
chromiumVersion: "124"
configMode: merge
controllers:
main:
containers:
main:
image:
repository: ghcr.io/openclaw/openclaw
tag: "2026.2.23"
pullPolicy: IfNotPresent
envFrom:
- secretRef:
name: openclaw-env-secret
args:
- "gateway"
- "--bind"
- "lan"
- "--port"
- "18789"
- "--allow-unconfigured"
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 2000m
memory: 2Gi
chromium:
image:
repository: zenika/alpine-chrome
tag: "124"
pullPolicy: IfNotPresent
args:
- "--no-sandbox"
- "--disable-dev-shm-usage"
- "--remote-debugging-address=0.0.0.0"
- "--remote-debugging-port=9222"
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 2000m
memory: 2Gi
persistence:
data:
enabled: true
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 10Gi
globalMounts:
- path: /root/.openclaw
ingress:
main:
enabled: true
className: nginx
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: lets-encrypt
nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/upstream-keepalive-connections: "50"
nginx.ingress.kubernetes.io/upstream-keepalive-timeout: "60"
hosts:
- host: openclaw.dev.72602.online
paths:
- path: /
pathType: Prefix
service:
identifier: main
port: http
tls:
- secretName: openclaw-tls
hosts:
- openclaw.dev.72602.online
configMaps:
config:
data:
openclaw.json: |
{
gateway: {
controlUi: {
allowedOrigins: ["https://openclaw.dev.72602.online"],
dangerouslyAllowHostHeaderOriginFallback: true,
},
},
browser: {
gatewayToken: "${OPENCLAW_GATEWAY_TOKEN}",
},
agents: {
main: {
brain: {
provider: "anthropic",
model: "claude-sonnet-4-20250514",
apiKey: "${ANTHROPIC_API_KEY}",
},
},
},
}
destination:
server: https://kubernetes.default.svc
namespace: claw
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ApplyOutOfSyncOnly=false
EOF3.sync by argocd
Details
argocd app sync argocd/openclawPreliminary
1. Kubernetes is installed; if not, check 🔗link2. Helm is installed; if not, check 🔗link3. ArgoCD is installed; if not, check 🔗link1.prepare `openclaw-env-secret.yaml`
Details
kubectl get namespaces claw > /dev/null 2>&1 || kubectl create namespace claw
kubectl create secret generic openclaw-env-secret -n claw \
--from-literal=ANTHROPIC_API_KEY=REPLACE_WITH_YOUR_API_KEY \
--from-literal=OPENCLAW_GATEWAY_TOKEN=REPLACE_WITH_YOUR_TOKEN_KEY2.prepare `deploy-openclaw.yaml`
Details
kubectl -n argocd apply -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: openclaw
namespace: argocd
spec:
project: default
source:
repoURL: https://serhanekicii.github.io/openclaw-helm
chart: openclaw
targetRevision: 1.4.4
helm:
releaseName: openclaw
values: |
app-template:
openclawVersion: "2026.2.23"
chromiumVersion: "124"
configMode: merge
controllers:
main:
containers:
main:
image:
repository: ghcr.io/openclaw/openclaw
tag: "2026.2.23"
pullPolicy: IfNotPresent
envFrom:
- secretRef:
name: openclaw-env-secret
args:
- "gateway"
- "--bind"
- "lan"
- "--port"
- "18789"
- "--allow-unconfigured"
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 2000m
memory: 2Gi
chromium:
image:
repository: zenika/alpine-chrome
tag: "124"
pullPolicy: IfNotPresent
args:
- "--no-sandbox"
- "--disable-dev-shm-usage"
- "--remote-debugging-address=0.0.0.0"
- "--remote-debugging-port=9222"
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 2000m
memory: 2Gi
persistence:
data:
enabled: true
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 10Gi
globalMounts:
- path: /root/.openclaw
ingress:
main:
enabled: true
className: nginx
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: lets-encrypt
nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/upstream-keepalive-connections: "50"
nginx.ingress.kubernetes.io/upstream-keepalive-timeout: "60"
hosts:
- host: openclaw.72602.online
paths:
- path: /
pathType: Prefix
service:
identifier: main
port: http
tls:
- secretName: openclaw-tls
hosts:
- openclaw.72602.online
configMaps:
config:
data:
openclaw.json: |
{
gateway: {
controlUi: {
allowedOrigins: ["https://openclaw.72602.online"],
dangerouslyAllowHostHeaderOriginFallback: true,
},
},
browser: {
gatewayToken: "${OPENCLAW_GATEWAY_TOKEN}",
},
agents: {
main: {
brain: {
provider: "anthropic",
model: "claude-sonnet-4-20250514",
apiKey: "${ANTHROPIC_API_KEY}",
},
},
},
}
destination:
server: https://kubernetes.default.svc
namespace: claw
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ApplyOutOfSyncOnly=false
EOF3.sync by argocd
Details
argocd app sync argocd/openclaw