AI Agent

May 12, 2026

Subsections of AI Agent

Hermes

Mar 7, 2024

OpenCode

🚀Installation

Install By

Preliminary

1. Kubernetes is installed; if not, check 🔗link


2. Helm is installed; if not, check 🔗link


3. ArgoCD is installed; if not, check 🔗link


1.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"
    }
EOF
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": {}
              }
            }
          }
        }
      },
      "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"
    }
EOF
kubectl -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"
    }
EOF
kubectl -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"
    }
EOF
kubectl -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"
    }
EOF

2.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
EOF

3.sync by argocd

Details
argocd app sync argocd/opencode

4.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 🔗link


2. Helm is installed; if not, check 🔗link


3. ArgoCD is installed; if not, check 🔗link


0. (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
EOF

1.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
EOF

3.sync by argocd

Details
argocd app sync argocd/opencode

4.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 | bash

6.use bridge to manage session

bridge

🌐Hugo Docs Site

The Hugo documentation site is deployed via ArgoCD on ops.docs.72602.online (formerly port.72602.online).

Deployment Environment

Preliminary

1. Kubernetes is installed; if not, check 🔗link


2. Helm is installed; if not, check 🔗link


3. ArgoCD is installed; if not, check 🔗link


1.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
EOF

2.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:

AgentRoleTarget
k3s-maintainerCluster operationsk3s cluster health, pod management, node maintenance
doc-maintainerDocumentation updatesHugo 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 } }
      }
    }
EOF

Deploy 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:

OptimizationBeforeAfter
GitHub proxy for ArgoCDDirect GitHub accessghfast.top mirror
Git repo size117 MB44 MB
ArgoCD sync timeslow (~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.top

2. 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=now

3. Fast ArgoCD Sync - achieved by combining ServerSideApply, lean manifests, and cached images

Details
syncPolicy:
  syncOptions:
    - ServerSideApply=true

🛎️FAQ

Q1: OpenCode Pod runs but API returns 401/403

Symptom

  • curl to /session or bridge endpoint returns unauthorized.

Check

kubectl -n opencode get secret opencode-server-secret -o yaml
kubectl -n opencode get configmap opencode-config -o yaml
kubectl -n opencode logs deploy/opencode --tail=100

Fix

  • Confirm OPENCODE_SERVER_PASSWORD secret exists and is mounted correctly.
  • Confirm client request carries expected auth configuration.
  • Re-apply ConfigMap/Application and sync again.

Expected

  • Session creation and message requests return successful JSON responses.
Q2: Model list is empty or model invocation fails

Symptom

  • /v1/models returns empty list, or chat request returns provider/model error.

Check

kubectl -n opencode get configmap opencode-config -o jsonpath='{.data.opencode\.json}'
kubectl -n opencode logs deploy/opencode --tail=200

Fix

  • Verify provider baseURL, apiKey, and model ID in opencode.json.
  • Ensure selected default model exists in configured provider models.
  • Re-apply config and restart workload if needed.

Expected

  • /v1/models shows expected model entries and session message calls succeed.
Mar 7, 2024

ClaudeCode

Mar 7, 2024

Ops Agent

🚀Installation

Environment
Install By

Preliminary

1. Kubernetes is installed; if not, check 🔗link


2. Helm is installed; if not, check 🔗link


3. ArgoCD is installed; if not, check 🔗link


1.build and push Ops Agent image

Details
cd /home/aaron/Ops/docs
./manifests/ops-agent/build-and-push.sh

The 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.sh

create-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=600s

argocd/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=300s

The 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

AgentResponsibilityOperational source
72602-k3s-maintainerOperate the local 72602 clusterLive cluster, then content/CSP/72602/_index.md
zjlab-ops-maintainerOperate ZJLAB through private SSH aliasesLive cluster, then the private inventory
hugo-doc-maintainerMaintain Relearn layout and runbooksRelearn 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-key

The 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 /health through the configured internal cluster-local URL (SUB2API_BASE_URL), not the public token.72602.space endpoint.
  • 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_config only 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": ask

Other 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:

  1. 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, writes application/sub2api-mcp key admin-api-key, and exits without echoing the value.
  2. 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
  3. Verify global/health reports healthy: true, /mcp reports exactly {"sub2api-admin":{"status":"connected"}}, and the merged OpenCode config carries the cluster-local SUB2API_BASE_URL without the admin key.
  4. Through 72602-k3s-maintainer, run sub2api-admin_read against /api/v1/admin/system/version; the live application must respond 0.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=300s

Do not patch the Deployment, delete the Ingress/PVC/Secrets, or scale the Git-owned workload as a rollback path.

Mar 7, 2024

OpenClaw

🚀Installation

Install By

Preliminary

1. Kubernetes is installed; if not, check 🔗link


2. Helm is installed; if not, check 🔗link


3. ArgoCD is installed; if not, check 🔗link


1.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_KEY

2.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
EOF

3.sync by argocd

Details
argocd app sync argocd/openclaw

Preliminary

1. Kubernetes is installed; if not, check 🔗link


2. Helm is installed; if not, check 🔗link


3. ArgoCD is installed; if not, check 🔗link


1.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_KEY

2.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
EOF

3.sync by argocd

Details
argocd app sync argocd/openclaw
Using AY Helm Mirror

for more information, you can check 🔗https://github.com/AaronYang0628/helm-chart-mirror

helm repo add ay-helm-mirror https://aaronyang0628.github.io/helm-chart-mirror/charts
  helm repo update
  helm install ay-helm-mirror/chart-name --generate-name --version a.b.c
Using AY ACR Image Mirror
Using DaoCloud Mirror

🛎️FAQ

Q1: Show me almost endless possibilities

You can add standard markdown syntax:

  • multiple paragraphs
  • bullet point lists
  • emphasized, bold and even bold emphasized text
  • links
  • etc.
...and even source code

the possibilities are endless (almost - including other shortcodes may or may not work)

Q2: Show me almost endless possibilities

You can add standard markdown syntax:

  • multiple paragraphs
  • bullet point lists
  • emphasized, bold and even bold emphasized text
  • links
  • etc.
...and even source code

the possibilities are endless (almost - including other shortcodes may or may not work)