Skip to content

This is the multi-page printable view of this section. .

Return to the regular view of this page.

Operations

Last updated:

Run, configure, and maintain a self-hosted instance.

This chapter is for people installing and maintaining Server. Start with Standalone; read split deployment when management and execution need separate services.

1 - Standalone and Docker

Last updated:

Run a Standalone Server with its bundled Web UI.

Standalone provides management pages, conversations, and scheduled jobs in one Server. It suits a local trial or a single host. Defaults use SQLite for storage and run tasks in the Server process, without a separate database service.

Use the Docker image, or build a Portable Server for your operating system and processor. Both include Web. Start with local access, then configure directory mounts and remote access as needed.

Local Docker trial

docker run -d --name agw \
  --restart unless-stopped \
  -p 127.0.0.1:30816:8080 \
  -v agw-data:/data \
  ghcr.io/zxyao145/agw:latest

Open http://localhost:30816/setup and initialize. Through a Docker port mapping, the container does not see a loopback source address, so Setup asks for the one-time Setup Code; find “Agw remote setup code” in the startup log with docker logs agw. The image includes static Web assets, so the browser connects directly to Server. latest is convenient for evaluation; use a fixed release tag for a maintained deployment.

To access host projects, add an explicit bind mount and configure its container-side path as Project Workspace. Do not confuse a host path with the path visible inside the container.

Portable Server

Portable Server is not attached to Releases. Build it from the repository root, for example:

PUBLISH_MODE=portable APP_VERSION=0.1.0 RIDS=linux-x64 ./publish.sh

The output lands in artifacts/publish/portable/agw-server-<version>-<RID>/, along with a matching archive. Start it from that directory:

./agw-server serve

On Windows, use agw-server.exe serve. The default listener is http://127.0.0.1:30816; override it with ASPNETCORE_URLS when needed. Without ASPNETCORE_URLS, if port 30816 is busy, Server picks a random free local port and records the actual address in <AgwDataDir>/runtime/server.json. Verify local Setup, Web, and a conversation before configuring remote access.

Storage and networking

Docker uses /data for data. Logs default independently to logs under the working directory; persistent file logs need a separate mount for the configured log path. Project workspaces are also independent of the data volume.

Remote hosting requires correct AllowedHosts, trusted proxies, HTTPS, and WebSocket forwarding. The repository Compose example includes domain and proxy settings; replace them with actual environment values. See Backup and upgrades for the complete persistence set.

Implementation and references

2 - Split Control/Data Plane deployment

Last updated:

Share PostgreSQL, keys, and workspaces, and route requests by Host role.

Split deployment runs management and scheduling in Control Plane and task execution in Data Plane. Use it when execution environments need separate maintenance or additional nodes. For a trial on one host, Standalone is simpler to configure.

This page assumes familiarity with containers, databases, and reverse proxies. Prepare shared PostgreSQL, Data Protection keys for decrypting credentials, and workspaces accessible to every execution node. The entry proxy must support WebSocket for conversation events.

Roles

HostResponsibility
Control PlaneSetup, Web, management APIs, Job scheduling
Data PlaneSignalR Execution, A2A, durable execution workers
StandaloneBoth roles combined for a single-server setup

Split deployment uses Distributed execution. In this mode, turns that run an External Agent (Claude Code, Codex, or Pi) directly in Chat fail with “Distributed execution currently supports System Agents only.” To use those external agents directly, choose Standalone with InProcess execution.

Split deployments require PostgreSQL for the database and locks, plus Distributed execution on both roles. SQLite or in-memory locks cannot replace cross-node coordination.

Database__Provider: postgres
Database__ConnectionString: "${AGW_DATABASE_CONNECTION_STRING}"
Execution__Provider: Distributed
DistributedLock__Provider: postgres
DistributedLock__ConnectionString: ""

This is an environment configuration fragment for both roles. An empty lock connection string reuses the database connection. Supply the real database connection string through Secrets.

Startup and routing

  1. Configure the database, both Hosts, shared keys, and directories using the cluster Compose reference.
  2. Start Control Plane first, initialize it, and confirm readiness: GET /api/health/ready returns 503 until the Host is initialized and can reach its database, then 200; GET /api/health/live only shows that the process is running. Neither requires sign-in.
  3. Start Data Plane, then add replicas as needed.
  4. Route /api/hubs/exec, /a2a/*, and /.well-known/agents.json to Data Plane; route other application paths to Control Plane.

Preserve Host, authentication headers/Cookies, and WebSocket Upgrade. Exclude execution Hub query strings from proxy access logs. Control Plane does not serve A2A.

Reading the examples

For Docker Compose, start with the cluster Compose file linked below and follow the startup order and routes above. For Kubernetes, the next section uses kind, which runs a local Kubernetes cluster in containers. Pods run applications, Services provide access addresses, and PVs/PVCs declare and request storage.

Both approaches need a shared client entry point. The Nginx section shows which requests go to each role. Verify the services and database first, then routing, to distinguish service failures from proxy failures.

Kubernetes YAML examples

The repository’s deploy/k8s directory provides a local, single-node kind example. It uses separate Control Plane and Data Plane Deployments, an external PostgreSQL database, and NodePorts that can connect to the Nginx configuration below. These files do not create PostgreSQL or an Ingress Controller.

FilePurpose
kind-agw-cluster.yamlCreate the local kind cluster with port and directory mappings
agw-data-pv-pvc.yamlProvide a data volume shared by Pods on the same node
agw-control-plane-deployment.yamlOne Control Plane replica and a NodePort Service
agw-data-plane-deployment.yamlTwo Data Plane replicas and a NodePort Service

Cluster entry points and shared directory

kind-agw-cluster.yaml maps both NodePorts to the host loopback address and mounts /opt/agw into the kind node:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    extraPortMappings:
      - containerPort: 30816
        hostPort: 30816
        listenAddress: "127.0.0.1"
        protocol: TCP
      - containerPort: 30820
        hostPort: 30820
        listenAddress: "127.0.0.1"
        protocol: TCP
    extraMounts:
      # Required by agw-data-pv: expose the host directory inside the kind node.
      - hostPath: /opt/agw
        containerPath: /opt/agw

Prepare /opt/agw/agw-data on the container runtime host before creating the cluster. With a Docker/Podman VM, configure file sharing so the directory is available inside the VM. The control-plane node role here belongs to Kubernetes; it is distinct from AGW’s Control Plane service.

The storage path is:

Host /opt/agw/agw-data
  → kind node /opt/agw/agw-data
  → PV agw-data-pv → PVC agw-data
  → /data in Control/Data Plane Pods

The corresponding PV/PVC follows. Retain preserves reclaimed volume data but does not replace backups. The PVC requests 1Gi, while the PV declares 5Gi capacity.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: agw-data-pv
  labels:
    app: agw
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  hostPath:
    # Node path backed by kind-agw-cluster.yaml extraMounts; single-node use only.
    path: /opt/agw/agw-data
    type: DirectoryOrCreate
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: agw-data
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: manual
  resources:
    requests:
      storage: 1Gi
  volumeName: agw-data-pv

ReadWriteOnce permits multiple Pods on the same node to mount the volume, covering both roles and Data Plane replicas in this example. hostPath is not shared storage across nodes. For multiple nodes, use shared storage supported by your cluster and keep keys, credentials, and Project workspace paths consistent across execution nodes. Add mounts for workspaces located outside /data.

Data Plane Deployment and Service

This is the repository’s complete Data Plane example. It runs two replicas on container port 8080, reads the agw-database Secret, and exposes Service port 30820. The Control Plane file uses the same volume and database settings, with one replica and NodePort 30816. It also reads password from agw-admin as Setup__AdminPassword.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: agw-data-plane
  labels:
    app: agw-data-plane
spec:
  replicas: 2
  selector:
    matchLabels:
      app: agw-data-plane
  template:
    metadata:
      labels:
        app: agw-data-plane
    spec:
      containers:
        - name: agw-data-plane
          image: localhost/agw-data-plane:local
          imagePullPolicy: Never
          env:
            - name: AgwLogDir
              value: /data/logs
            - name: AgwDataDir
              value: /data
            - name: ASPNETCORE_ENVIRONMENT
              value: Production
            - name: ASPNETCORE_URLS
              value: http://0.0.0.0:8080
            - name: DistributedLock__Provider
              value: postgres
            - name: Execution__Provider
              value: Distributed
            - name: Database__Provider
              value: postgres
            - name: Database__ConnectionString
              valueFrom:
                secretKeyRef:
                  name: agw-database
                  key: connection-string
          ports:
            - name: http
              containerPort: 8080
              protocol: TCP
          securityContext:
            # The local hostPath is owned by the host user with mode 0700; preserve the
            # current local Podman setup's root access so the server can write /data.
            runAsUser: 0
            runAsGroup: 0
            runAsNonRoot: false
          volumeMounts:
            - name: agw-data
              mountPath: /data
      volumes:
        - name: agw-data
          persistentVolumeClaim:
            claimName: agw-data
---
apiVersion: v1
kind: Service
metadata:
  name: agw-data-plane
  labels:
    app: agw-data-plane
spec:
  type: NodePort
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800
  selector:
    app: agw-data-plane
  ports:
    - name: http
      protocol: TCP
      port: 30820
      targetPort: http
      nodePort: 30820

Before using it, check:

  • Images: localhost/agw-…:local with imagePullPolicy: Never requires images to be built and loaded into kind first. For registry images, use reachable image addresses and versions, with an appropriate pull policy and credentials.
  • Database: both roles must use the same PostgreSQL database reachable from the Pods. localhost in a connection string refers to the Pod itself, usually not the host database.
  • Permissions: the local example runs as root to accommodate its directory permissions. Set an appropriate UID/GID for your storage in other environments instead of copying this local setting unchanged.
  • Connection affinity: the Service uses ClientIP affinity to help SignalR requests reach the same Pod. With Nginx outside the cluster, multiple clients may appear as one proxy IP, so this does not guarantee even load distribution.

Deployment order

Prepare the local images, data directory, and files containing the two Secret values, then run from the repository root. Secret files should contain only the relevant values; keep real credentials out of Git. These commands use the default namespace of the current kubectl context. If you choose another namespace, keep Deployments, Services, PVCs, and Secrets together.

# Run from the repository root, after preparing /opt/agw/agw-data.
kind create cluster --name agw --config deploy/k8s/kind-agw-cluster.yaml
kubectl apply -f deploy/k8s/agw-data-pv-pvc.yaml

# These images must already exist in the local container runtime.
kind load docker-image --name agw \
  localhost/agw-control-plane:local localhost/agw-data-plane:local

# Replace these paths with files containing the actual secret values.
kubectl create secret generic agw-database \
  --from-file=connection-string=/secure/agw-database-connection-string
kubectl create secret generic agw-admin \
  --from-file=password=/secure/agw-admin-password

kubectl apply -f deploy/k8s/agw-control-plane-deployment.yaml
kubectl rollout status deployment/agw-control-plane
kubectl logs deployment/agw-control-plane --tail=100

# Continue only after Control Plane initialization has completed.
kubectl apply -f deploy/k8s/agw-data-plane-deployment.yaml
kubectl rollout status deployment/agw-data-plane
kubectl get pods,services,pvc

rollout status confirms the Deployment rollout, not application initialization. In this example, Control Plane’s Setup__AdminPassword triggers first-run initialization. Check logs and the sign-in page before starting Data Plane. Existing database authentication settings are not overwritten by this initial password.

With the kind mappings above, the upstreams in the Nginx example below can use 127.0.0.1:30816 and 127.0.0.1:30820. Nginx inside the cluster can instead use agw-control-plane:30816 and agw-data-plane:30820 in the same namespace. After checking that the PVC is Bound and Pods are running, verify login, execution connections, and which nodes receive requests.

Do not run kubectl apply -f deploy/k8s/: the kind Cluster file is input to kind, not a Kubernetes API resource. Changing kind port or mount mappings requires recreating the cluster; back up data first. See the local kind deployment guide for the complete source instructions.

Nginx configuration example

In this example, Nginx provides one entry point, Control Plane listens on 30816, and Data Plane on 30820, matching the kind example above; the repository’s deploy/nginx.split.conf.example and cluster Compose example use 30817 for Data Plane. Replace these example ports with the actual Host listeners. For separate hosts or containers, replace 127.0.0.1 with addresses reachable from Nginx.

Web hosted by Control Plane

Save this as a site configuration included from the http {} block in nginx.conf. map, log_format, and upstream must not be nested inside server {}. Log paths are relative to the Nginx prefix; create the directories or use writable absolute paths.

# Included inside nginx.conf's http {} block.
map $http_upgrade $agw_connection_upgrade {
    default upgrade;
    ''      close;
}

# Do not log query strings, which may contain an execution token.
log_format agw_route '$remote_addr [$time_local] '
                     '"$request_method $uri $server_protocol" $status '
                     'upstream=$upstream_addr upstream_status=$upstream_status';

upstream agw_control_plane {
    server 127.0.0.1:30816;
}

upstream agw_data_plane {
    ip_hash;
    server 127.0.0.1:30820;
    # server 127.0.0.1:30821;
}

server {
    listen 80;
    server_name agw.example.com;

    client_max_body_size 100m;
    access_log logs/agw_access.log agw_route;
    error_log  logs/agw_error.log warn;

    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $agw_connection_upgrade;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;

    # Includes /api/hubs/exec/negotiate and the WebSocket endpoint.
    location ^~ /api/hubs/exec {
        proxy_buffering off;
        proxy_pass http://agw_data_plane;
    }

    location ^~ /a2a/ {
        proxy_buffering off;
        proxy_pass http://agw_data_plane;
    }

    location = /.well-known/agents.json {
        proxy_pass http://agw_data_plane;
    }

    # Setup, management APIs, OpenAPI, and the hosted Web client.
    location / {
        proxy_pass http://agw_control_plane;
    }
}

The example uses HTTP for local verification. For external access, configure listen 443 ssl;, ssl_certificate, and ssl_certificate_key in this server, using your domain and valid certificate, and redirect HTTP to HTTPS.

RequestDestinationPurpose
/api/hubs/exec and child pathsData PlaneSignalR negotiation and execution connections
/a2a/*Data PlaneA2A requests and streaming responses
/.well-known/agents.jsonData PlaneAgent discovery
Other pathsControl PlaneSetup, management APIs, OpenAPI, Web pages, and static assets

The proxy_pass directives have no URI suffix, preserving the original path and query parameters. Authentication headers and Cookies are forwarded by default. Upgrade headers and HTTP/1.1 support WebSockets. Disabling response buffering on execution and A2A routes lets streaming output reach clients promptly. The 3600s values are proxy read/write timeouts, not a guarantee of uninterrupted tasks of any duration.

With multiple Data Plane instances, ip_hash aims to keep requests from one IP on the same instance so SignalR negotiation and subsequent connections reach the same node. It does not replace shared database, execution-state, and recovery configuration. If another proxy sits in front of Nginx, configure trusted proxies and client-IP handling for your network rather than trusting arbitrary X-Forwarded-For headers.

The access log uses $uri without query parameters. Its upstream field helps identify the receiving node. client_max_body_size controls only Nginx’s request limit; it does not increase AGW’s attachment limits.

A separate Web service

If Web runs separately on 3001, retain the Data Plane routes and common proxy settings above. Add the agw_web upstream and the management routes below, replacing the original location /. Port 3001 is the repository’s Web development port; use your Web service’s actual port in deployment.

# Add inside http {}, alongside the other upstream blocks.
upstream agw_web {
    server 127.0.0.1:3001;
}

# Add these locations inside the existing server {}.
location /api/ {
    proxy_pass http://agw_control_plane;
}
location /openapi/ {
    proxy_pass http://agw_control_plane;
}
location = /setup {
    proxy_pass http://agw_control_plane;
}
location /setup/ {
    proxy_pass http://agw_control_plane;
}

# Replace the existing location /; do not add a second one.
location / {
    proxy_pass http://agw_web;
}

Both /setup and /setup/ reach Control Plane, as do ordinary /api/ requests. The longer /api/hubs/exec match still reaches Data Plane. Configure the separate Web service’s own backend address to point to Control Plane. Clients should use the shared Nginx entry point.

Check and load the configuration

After saving your deployment configuration, validate it before reloading:

nginx -t
nginx -s reload

Run the reload in your own deployment environment. Verify login and page assets. In browser network tools, check for a successful WebSocket upgrade (101) on the execution connection and confirm its upstream is Data Plane in the access log. Management APIs should reach Control Plane. For login failures, check Cookies, forwarded scheme, and application proxy-trust settings. For execution connection failures, check Upgrade headers, routing, and the Data Plane port.

Verify

Test login, a Chat run, and a Job in order, inspecting the actual execution node. All Hosts read initialization and authentication from the same database. Recovery also requires consistent directories, keys, and runtime credentials; running containers alone do not prove readiness.

Implementation and references

3 - Configuration and authentication

Last updated:

Understand configuration precedence, deployment defaults, and API Key behavior.

This page covers deployment settings consumed by AGW Server and logging settings in the Host template. Configure models, Agents, Projects, and integration accounts through the management UI as described in their guides. Choose your deployment mode first, then apply changes and restart the relevant Hosts.

Start with the settings relevant to your task

For an initial local setup, keep defaults and complete initialization. Before relying on the service, locate its database and data directory and follow the backup guide.

For remote access, check listening addresses, allowed origins, proxy trust, and authentication first. For split deployment, begin with the required shared configuration. Polling and batch settings are tuning references; they do not all need changes during initial setup.

Configuration and precedence

General precedence, from low to high: built-in defaults → appsettings.json → environment-specific JSON → Development Secrets → environment variables → command line. Files reside in the Server executable directory, and overrides apply per key. Serilog has a separate configuration reader described below.

Colons denote hierarchy: Database:Provider becomes nested JSON, Database__Provider in the environment, or --Database:Provider postgres on the command line. Use true and false for booleans and the listed names for enums.

“Template” means the repository Host’s appsettings.json. “Omitted” means the key is absent from all configuration sources. Differences are marked explicitly.

Choose settings for your deployment

Deployment topology determines which Server programs you start. Execution mode determines how tasks run. Choose both explicitly.

GroupWhen to use it
Common settingsEvery deployment: endpoints, directories, database, authentication, and logs
Standalone deploymentOne Server manages, schedules, and executes; defaults to SQLite and InProcess
Split Control/Data Plane deploymentSeparate management and execution; requires PostgreSQL and Distributed
Distributed execution tuningAny deployment using Distributed, including Standalone when explicitly enabled

Standalone deployment

For local use or a single Server, start with this default combination. These keys can usually be left unset:

Full keyDefaultMeaning
Database:ProvidersqliteUse a local database file
Database:ConnectionStringData Source=agw.dbSQLite file location; relative paths start from <AgwDataDir>/database/, so the default file is <AgwDataDir>/database/agw.db
Execution:ProviderInProcessRun tasks directly in the current Server
DistributedLock:ProviderUnsetAutomatically use an in-process lock with SQLite
DistributedLock:ConnectionStringEmptyIn-process locks need no database connection

Standalone can also use PostgreSQL while retaining InProcess execution. If you choose Distributed, satisfy the PostgreSQL database and lock requirements below and configure distributed execution accordingly. See Standalone and Docker.

Split Control/Data Plane deployment

Both planes must use the same application database and the combination below. Initialize Control Plane before starting Data Plane.

Full keyRequired settingWhere to configure
Database:ProviderpostgresBoth planes
Database:ConnectionStringThe same PostgreSQL databaseBoth planes
Execution:ProviderDistributedBoth planes
DistributedLock:Providerpostgres, or omit to follow the databaseBoth planes
DistributedLock:ConnectionStringEmpty to reuse the database connection, or the same lock serviceConsistent across both planes

Apply common settings according to each Server’s responsibilities:

  • Control Plane: configure initial setup, management URLs, and public integration OAuth URLs.
  • Data Plane: prepare Agent CLIs, Shell, workspaces, and files. Worker concurrency and polling settings affect execution here.
  • Both planes: check listening URLs, client origins, proxies, logs, and monitoring. Nodes decrypting shared data need matching encryption keys. All execution nodes must be able to access the captured task directories.

Execution mode

All fields below use the prefix Execution:.

SettingDefaultPurpose and accepted values
ProviderInProcessInProcess: execute in the current process. Distributed: coordinate durable execution through PostgreSQL, with workers claiming work. Distributed requires PostgreSQL for both the database and locks.
TurnBroadcastRetentionSeconds300Seconds this Server keeps a finished turn’s replay buffer in memory. Clients that reconnect, or retry the same accepted turn, within this window receive the full replay.

In InProcess mode, turns exist only inside the current Server process. When the Server restarts, running turns left by the previous process end as Interrupted and do not resume; use Distributed for recovery across restarts.

The executable determines the Host role: Standalone combines both planes, Control Plane manages and schedules, and Data Plane executes. Both split Hosts require PostgreSQL, Distributed execution, and PostgreSQL locks. This setting does not change one Host executable into another role.

Distributed locks

All fields below use the prefix DistributedLock:.

SettingDefaultPurpose and accepted values
ProviderUnspecified; follows the databaseinmemory: coordinate within one process, for single-node use. postgres: coordinate multiple nodes through PostgreSQL. When omitted or null, SQLite selects inmemory and PostgreSQL selects postgres.
ConnectionStringEmptyConnection string for PostgreSQL locks. An empty value reuses Database:ConnectionString. In-memory locks do not use a connection string.

Distributed execution tuning

These settings apply to Distributed execution, including split deployments and Standalone with Distributed enabled. Start with the defaults and adjust individual values only to address an observed performance issue.

Distributed workers

All fields below use the prefix Execution:Distributed:.

SettingDefaultPurpose and accepted values
WorkerPollingMilliseconds250Polling interval for pending work in milliseconds. Lower values reduce claiming latency but increase database queries.
MaxConcurrentExecutions4Maximum concurrent executions per execution Server, not a cluster-wide total.
LeaseSeconds30Execution lease duration in seconds. The Server that claims a run holds the lease; if it expires without renewal, another Server may take over the run. Long runs keep renewing, so they are not taken over merely for exceeding 30 seconds.
LeaseRenewSeconds10How often the lease holder renews, in seconds; must be shorter than LeaseSeconds.

All of these fields must be positive integers, and LeaseSeconds must exceed LeaseRenewSeconds, or the Server fails at startup. They are used for worker coordination in Distributed mode.

Execution events and replay

All fields below use the prefix Execution:Distributed:EventStream:.

SettingDefaultPurpose and accepted values
ProviderPostgresEvents always commit to PostgreSQL first. Postgres: read them from PostgreSQL only, without Redis. Redis: also project committed events to a Redis Stream; reads prefer Redis and fill any missing part from PostgreSQL, including expired entries or times when Redis is unavailable. Task records and locks always need PostgreSQL.
ReadPollingMilliseconds250Delay between reads when no new events exist, in milliseconds; must be positive.
ReadBatchSize100Maximum events per read; must be positive.
WriteIntervalMilliseconds250Batch-write delay measured from the first pending event, in milliseconds. 0 writes immediately; negative values are invalid.
WriteBatchSize100Event-count threshold for a write batch; must be positive.
Redis:ConnectionStringEmptyRequired when Redis is selected. Related Servers must use the same Redis service, for example redis:6379,password=....
Redis:StreamTtlMinutes1440Redis Stream retention in minutes, defaulting to 24 hours; must be positive when Redis is selected. Expired entries are read from PostgreSQL instead.

Common settings

These settings apply to either topology. In split deployments, configure them according to each Server’s role.

Server endpoints and directories

SettingDefaultPurpose and accepted values
ASPNETCORE_URLS / --urlsLocal default port 30816; container runtime supplies its bindingListening URLs. Use --urls http://127.0.0.1:30816 for local access or bind another address as required. Separate multiple URLs with semicolons.
ASPNETCORE_ENVIRONMENTProductionSelects environment-specific JSON, such as appsettings.Production.json. Common names are Development, Staging, and Production; custom names are allowed.
AgwDataDir~/agwAGW data root for runtime data, Skills, encryption keys, and related files. Supports ~; other relative paths resolve from the process working directory.
AgwLogDir./logsSeparate log directory; moving the data root does not move it. Supports ~, with other relative paths relative to the working directory.
AllowedHosts*HTTP Host filtering. * allows any hostname; use semicolon-separated hostnames such as agw.example.com;localhost to restrict it. This is not the client-origin list.

Database

All fields below use the prefix Database:.

SettingDefaultPurpose and accepted values
Providersqlitesqlite: a local SQLite file for standalone use. postgres: a PostgreSQL service supporting split and distributed deployment. These are the two supported values.
ConnectionStringData Source=agw.dbConnection string for the selected database. SQLite uses Data Source=..., with relative paths starting from <AgwDataDir>/database/; PostgreSQL uses Host=...;Port=5432;Database=...;Username=...;Password=... with a nonempty Host. Change it together with Provider.

Setup, origins, and reverse proxies

SettingDefaultPurpose and accepted values
Setup:AdminPasswordUnsetInitial administrator password, 8–256 characters. Inject through the environment or Secrets for unattended setup; it does not overwrite existing authentication. Initialize on Control Plane in a split deployment.
Auth:AllowedOriginsagw://app, http://localhost:3000, http://127.0.0.1:3000Allowed client Origin array for CORS and origin checks. Match the actual client scheme and port. The array is empty if omitted.
ReverseProxy:TrustedProxiesTemplate contains 127.0.0.1, 172.16.0.0/12, 10.0.0.0/8Trusted proxies. The current implementation adds only individual IP addresses; CIDR entries are not applied. Configure actual proxy IPs. It processes forwarded For, Host, and Proto headers with a forward limit of 1.

Configure arrays with numeric indices, such as Auth__AllowedOrigins__0=agw://app. Overrides merge by index; overriding index 0 does not remove template entries 1 and 2. Check the complete resulting list.

Integration OAuth URLs

All fields below use the prefix Integrations:OAuth:.

SettingDefaultPurpose and accepted values
PublicBaseUrlhttp://localhost:30816Public Server base URL used to construct the OAuth callback at api/integrations/oauth/callback. For remote deployments, use the public URL reachable by the browser.
WebBaseUrlhttp://localhost:3001Web base URL used after OAuth completes. Set the actual Web URL when using bundled Web or a reverse proxy.

Both accept absolute HTTP(S) base URLs without user information, query strings, or fragments. Omitted or blank values fall back to the current request base URL.

Conversation history writes

All fields below use the prefix ConversationHistory:.

SettingDefaultPurpose and accepted values
ModeIntervalImmediate: write pending data immediately. Interval: buffer and flush periodically. TurnEnd: primarily flush when a turn ends. Reaching the buffer limit also triggers a flush.
FlushIntervalSecondsTemplate: 10; omitted: 5Flush interval in seconds for Interval mode; must be positive and within the supported timer range.
MaxBufferedBytes16777216 (16 MiB)Buffer limit in bytes; must be positive. Reaching it triggers an early flush.

These settings control persistence timing, not whether live output is visible. Buffering reduces writes, but abnormal termination can lose unflushed data. Choose Immediate when prompt persistence matters.

Shell tool

All fields below use the prefix Agents:Shell:.

SettingDefaultPurpose and accepted values
Backendlocallocal: run commands in the project workspace on the execution host. docker: use the Docker Shell executor, mounting the primary workspace at /workspace and additional directories at /project-directories/{id}. It requires Docker; networking is currently disabled and timeout is 30 seconds.

This selects only the AGW Shell tool backend. It does not change Server deployment mode or install external Agent CLIs.

OpenTelemetry

All fields below use the prefix OpenTelemetry:.

SettingDefaultPurpose and accepted values
ServiceNameTemplate: AgwTelemetry service name. Split Hosts replace the template value Agw with Agw.ControlPlane or Agw.DataPlane; when omitted, the fallback is Agw.{HostProfile}.
ServiceVersion1.0.0Service-version label in telemetry.
OtlpEndpointTemplate: emptyOTLP receiver URL. Empty or omitted values disable OpenTelemetry tracing, metrics, and log export.

Logging configuration

SettingDefaultPurpose and accepted values
Logging:LogLevel:DefaultInformationDefault Microsoft logging level; override a category with Logging:LogLevel:{category}.
Logging:LogLevel:Microsoft.AspNetCoreWarningASP.NET Core category level.
Logging:LogLevel:Microsoft.EntityFrameworkCoreWarningEF Core category level.
Serilog:UsingConsole, File, Async sinksAssemblies providing Serilog configuration extensions.
Serilog:MinimumLevel:DefaultInformation; Development: DebugDefault minimum level for the current Serilog pipeline.
Serilog:MinimumLevel:Override:Microsoft.AspNetCoreWarningOverride the ASP.NET Core category.
Serilog:MinimumLevel:Override:Microsoft.EntityFrameworkCoreWarningOverride the EF Core category.
Serilog:MinimumLevel:Override:SystemWarningOverride System; additional categories use the same structure.
Serilog:WriteTo:0:NameAsyncName of the template’s asynchronous sink wrapper.
Serilog:WriteTo:0:Args:configure:0:NameConsoleConsole sink inside the asynchronous wrapper.
Serilog:WriteTo:0:Args:configure:0:Args:outputTemplateSee belowConsole format containing timestamp, level, source, TraceId, SpanId, thread, message, and exception.
Serilog:EnrichFromLogContext, WithMachineName, WithThreadId, WithOpenTelemetryTraceId, WithOpenTelemetrySpanIdAdd context, machine, thread, and tracing identifiers.

The Host reads Serilog separately from appsettings.json and appsettings.{ASPNETCORE_ENVIRONMENT}.json. Do not assume Serilog__... environment variables override this pipeline. Edit the relevant JSON and restart to change its output or levels. Logging and Serilog are separate level configurations; the main output currently uses Serilog.

All Microsoft logging levels are Trace (finest tracing), Debug (diagnostics), Information (normal activity), Warning (potential trouble), Error (failed operations), Critical (severe failures), and None (disabled). Serilog supports Verbose, Debug, Information, Warning, Error, and Fatal. Verbose is its finest tracing level and Fatal denotes severe failures; its minimum levels do not include None.

WriteTo Name, Using, and Enrich values are plugin names, not fixed enums. The table lists the current template. The Host additionally writes AgwLogDir/application-{profile}-.log, rolling hourly, retaining 30 files, and flushing every second. These values are fixed in code rather than configurable keys.

[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{SourceContext}] [TraceId:{TraceId}] [SpanId:{SpanId}] [ThreadId:{ThreadId}] {Message:lj}{NewLine}{Exception}

Authentication and API Keys

Remote Web signs in with the administrator password and receives a Cookie. Desktop, Mobile, and automation use API Keys, sent as Bearer credentials:

Authorization: Bearer agw_<your-token>

A request made directly on the Server host is authenticated automatically as administrator 1001, without a password or API Key, when all of these hold: it comes from a loopback address, carries no forwarding headers, targets localhost or a loopback IP as the host name, and carries no authentication header or sign-in Cookie. Requests through a reverse proxy or from another host do not qualify.

API Key plaintext is returned only on creation. Store and supply it through the environment or Secrets, and revoke unused keys. Authentication uses the key creator’s stable ID. Each Server caches a successfully validated API Key for 30 seconds. Revoking a key clears the cache of the Server that handles the revocation at once; in a split deployment without a shared distributed cache, other replicas stop accepting the key within 30 seconds. Multiple login accounts come from the third-party sign-in configuration below; there are currently no configuration keys for roles, API Key scopes, or JWT.

Administrator password hashes, initialization state, and session versions are stored in the database’s global auth group in setting; API Key hashes are in api_token. Management features maintain these values; they are not appsettings entries. Password changes update the session version. Hosts refresh every second and discard cached credentials if refresh fails. To recover a forgotten password, stop Server and run agw-server auth reset-password; split deployments use agw-control-plane auth reset-password. The new password needs 12–256 characters, and resetting it invalidates all existing Web sessions.

Third-party sign-in

Third-party sign-in lets people reach Web and Desktop with an organization account. The first sign-in with an account creates an isolated local user numbered from 10000; the administrator remains 1001. With no provider configured, the administrator password and API Keys continue to work. See Third-party account sign-in for the resulting behavior.

Register AGW at the provider as a web application (confidential client). The callback URL is built from the provider ID. Desktop users go through the same URL: the provider returns the browser to Server, not to the desktop application:

https://agw.example.com/api/auth/oidc/callback/company

The following keys are all prefixed with Auth:Oidc:, where {id} is the ID you choose for a provider.

SettingDefaultPurpose and values
PublicBaseUrlEmptyThe browser-visible Server origin the provider returns to. api/auth/oidc/callback/{id} is appended to it. Use a full origin without a path: HTTPS in production, loopback HTTP allowed in development. Required once any provider is enabled.
WebBaseUrlEmptyWhere the browser lands after sign-in. Leave empty when Web shares the Server origin; set it during source development, where Web runs on 3001 and the backend on 30816.
Providers:{id}:EnabledfalseWhether this provider is available.
Providers:{id}:TypeOidcOidc discovers endpoints from Authority and requests openid profile email. OAuth2 uses explicit endpoints and claim names.
Providers:{id}:DisplayNameProvider IDThe name shown on the sign-in button.
Providers:{id}:ClientIdEmptyClient ID issued when registering AGW at the provider. Required.
Providers:{id}:ClientSecretEmptyMatching client secret. Required; inject it through the environment or Secrets.
Providers:{id}:AuthorityEmptyRequired for Oidc, such as https://sso.example.com/realms/company.
Providers:{id}:AuthorizationEndpointEmptyRequired for OAuth2: where the user authorizes AGW.
Providers:{id}:TokenEndpointEmptyRequired for OAuth2: where Server exchanges the authorization code.
Providers:{id}:IssuerEmptyRequired for OAuth2: identifies the account source and, with the account ID, determines the user.
Providers:{id}:IdentitySourceUserInfoOAuth2 only. UserInfo reads the account from the user information endpoint. AccessToken reads it from a signed JWT access token.
Providers:{id}:UserInfoEndpointEmptyRequired when IdentitySource is UserInfo.
Providers:{id}:AccessTokenIssuerEmptyRequired when IdentitySource is AccessToken: validates who issued the token.
Providers:{id}:AccessTokenAudienceEmptyRequired when IdentitySource is AccessToken: validates the intended recipient.
Providers:{id}:AccessTokenJwksUriEmptyRequired when IdentitySource is AccessToken: where signing keys are published.
Providers:{id}:ClientAuthMethodPostHow OAuth2 client credentials are sent: Post in the request body, Basic in the header.
Providers:{id}:UsePkcefalseEnables S256 for OAuth2. Oidc always uses it.
Providers:{id}:ScopesEmptyOAuth2 scopes, configured by index, such as Scopes__0=read:user.
Providers:{id}:SubjectClaimsubOAuth2 only: field holding the account ID; GitHub uses id. Oidc always uses sub.
Providers:{id}:DisplayNameClaimnameOAuth2 only: field holding the display name; GitHub uses login. Oidc always uses name.
Providers:{id}:EmailClaimemailOAuth2 only: field holding the email address; Oidc always uses email. A missing display name or email does not block sign-in.

Provider IDs use lowercase letters, digits, and hyphens, up to 64 characters, such as company or entra-id. Each ID owns its callback URL; keep it stable after registration.

Example configuration without secrets:

{
  "Auth": {
    "Oidc": {
      "PublicBaseUrl": "https://agw.example.com",
      "Providers": {
        "company": {
          "Enabled": true,
          "Type": "Oidc",
          "DisplayName": "Company account",
          "Authority": "https://sso.example.com/realms/company",
          "ClientId": "agw"
        }
      }
    }
  }
}

Inject the matching secret as Auth__Oidc__Providers__company__ClientSecret. Keep it out of appsettings, frontend environment files, and screenshots. Services that offer OAuth2 only, such as GitHub, use explicit endpoints:

{
  "Auth": {
    "Oidc": {
      "Providers": {
        "github": {
          "Enabled": true,
          "Type": "OAuth2",
          "DisplayName": "GitHub",
          "AuthorizationEndpoint": "https://github.com/login/oauth/authorize",
          "TokenEndpoint": "https://github.com/login/oauth/access_token",
          "UserInfoEndpoint": "https://api.github.com/user",
          "Issuer": "https://github.com/login/oauth",
          "IdentitySource": "UserInfo",
          "UsePkce": true,
          "ClientAuthMethod": "Post",
          "Scopes": ["read:user"],
          "SubjectClaim": "id",
          "DisplayNameClaim": "login",
          "EmailClaim": "email",
          "ClientId": "github-client-id"
        }
      }
    }
  }
}

Authority values for common platforms:

PlatformAuthority
Googlehttps://accounts.google.com
Microsoft Entra IDhttps://login.microsoftonline.com/<tenant-id>/v2.0
Keycloakhttps://sso.example.com/realms/<realm>
Authentikhttps://sso.example.com/application/o/<application-slug>/

Restart the relevant Server after changing these values. In a split deployment, route sign-in requests to Control Plane; Data Plane runs executions with the resulting local credentials. All replicas share one database and Data Protection keys, so a Desktop one-time code can be exchanged on a different replica. Disabling a provider blocks new sign-ins and pending Desktop exchanges; Cookies and API Keys already issued are revoked separately.

Example and verification

This example illustrates configuration syntax. Supply real connection values through the environment or Secrets:

export Database__Provider=postgres
export Database__ConnectionString='Host=db;Port=5432;Database=agw;Username=agw;Password=REPLACE_ME'
export Execution__Provider=Distributed
export DistributedLock__Provider=postgres
agw-server --urls http://127.0.0.1:30816

For split deployment, supply matching database and execution settings to each Host, initialize Control Plane first, then start Data Plane. The example’s agw-server is Standalone; split deployment uses the corresponding Host executables.

After restarting, inspect startup logs, the listening URL, database connectivity, and client sign-in. Validate execution tuning with a small task while observing latency and load. For startup errors, check enum names, numeric ranges, connection strings, and Distributed dependencies. Do not expose passwords or API Keys when sharing logs.

Implementation and references

4 - Data, backups, and upgrades

Last updated:

Back up databases and keys, and manage project workspaces separately.

A complete backup includes AGW configuration and records, encryption keys, and project files. Copying only the installation directory or code repository can leave out data needed to restore the service.

Locate the actual database, data directory, and Project workspaces first. Use the defaults below as a guide and confirm them against the running configuration.

What to preserve

AgwDataDir defaults to ~/agw; Docker uses /data. Paths expand ~; other relative paths resolve from the process working directory. The default SQLite database file is <AgwDataDir>/database/agw.db, or /data/database/agw.db in Docker.

Preserve together:

  • The database, including setting, api_token, conversations, and execution records.
  • keys/ and skills/ under the data directory.
  • Deployment configuration and secret references, with actual secrets backed up in secure storage.
  • Business files in each primary and additional Project directory, using a separate file-backup strategy.

Logs are independent of the data directory. Logs and temporary files are not required for authentication recovery. Losing Data Protection keys can make protected credentials unreadable.

Make a backup inventory

Record the database location, resolved AgwDataDir, all Project directories, and current application version. Include the hidden .agw/memory/ directory for workspace-based Memory; database-based Memory is included in the database backup. The form’s default primary directory ~/.agw/<project folder name> and the Server default ~/.agw/projects/{projectId:N} both live under the home directory of the account running Server, outside AgwDataDir. In Docker they are also outside the /data volume, so mount and back them up separately.

Wait for tasks to finish or interrupt them before backing up, so they do not keep changing files. For a simple SQLite deployment, stop Server before copying the database and related files. Use PostgreSQL’s backup tools for PostgreSQL. Data and workspaces may be in different locations; check each rather than assuming everything is under /data.

Upgrade sequence

  1. Read the target Release notes and record the current image or package version.
  2. Stop every old Standalone, Control Plane, and Data Plane process, then take a consistent backup: copy a simple SQLite installation after stopping, or use PostgreSQL’s database backup mechanism.
  3. Apply the new version’s migrations for your database (the SQLite or the PostgreSQL set, never both). An initialized Server does not run migrations during a normal start; only first-run Setup does. Source deployments can use the provider-specific commands in the Development Guide.
  4. Update Server and clients while retaining data, Data Protection keys, and directory mounts, then start the new version. Before accepting requests and starting workers, the new Host checks and upgrades in-flight execution records; if a record cannot be decrypted or validated, startup fails until the data is corrected.
  5. Verify initialization state, login, Project files, and a small task. For split deployments, also verify workers and a Job.

Pre-1.0 upgrades may include schema changes. Rollback requires a mutually compatible application version, database backup, and key backup.

Verify recovery

Test restoration in an isolated environment first, including credential decryption and file visibility. Data or log root changes require restarting and moving existing files yourself; AGW does not relocate them automatically.

Implementation and references

5 - Logs and troubleshooting

Last updated:

Diagnose connection, authentication, model, file, and execution failures.

First locate the failing step: opening the page, signing in, calling a model, or using files and tools. Reproduce the problem with a small task so that each check narrows the cause.

Record the Server, Project, conversation, and time, then inspect the matching logs. In a split deployment, check Control Plane for management problems and Data Plane for execution problems.

Diagnostic order

SymptomCheck first
UI unavailableListener, port, container mapping, proxy target
Setup failsDatabase connection, writable directories, remote Setup Code
Login or API Key failsActual Server, revoked API Key, database auth state
Third-party sign-in failsAuth:Oidc:PublicBaseUrl, the callback URL registered at the provider, client credentials, Server-to-provider network
No agent responseModel Provider, model ID, credentials, pending approval/input
CLI cannot startExecution-node executable, account, environment
Files missingSelected root, Server path, mounts, permissions
Job does not runEnablement, future timestamp, UTC Cron, valid target, logs
Wrong state after disconnectConversation selection, WebSocket proxy, background execution; in InProcess mode, running conversations show Interrupted after a Server restart and do not continue

Example: the page opens but the agent does not reply

  1. Check whether Chat is waiting for approval or information, and respond if needed.
  2. Send a plain text question through the same model connection. If it fails, check the API endpoint, model ID, and credentials.
  3. If text works but a tool task fails, check tool bindings, directories, and host permissions.
  4. In split deployments, a working management page with a failed chat connection suggests checking that /api/hubs/exec routes to Data Plane and supports WebSocket.
  5. Find the specific error in Server logs at the recorded time. Repeat the same small task after the fix.

This sequence separates model, tool, and connection failures without changing several settings at once.

Third-party sign-in failures

A failed sign-in returns the browser to the sign-in page with error=oidc-<category> in the URL; Desktop shows a message in the Server profile. Pair that category with the Agw.Auth.Oidc log, which records the provider, client, failing stage, failure category, and TraceId.

CategoryUsual cause
provider-unavailable, provider-timeoutServer cannot reach the provider: network, outbound proxy, or firewall
provider-rejectedThe provider was reached, but its OAuth2 user information endpoint returned an error status: check UserInfoEndpoint, Scopes, and token permissions
protocol-rejectedThe OIDC provider returned a protocol error: client ID, secret, or registered callback URL does not match
invalid-state, invalid-nonceCallback validation failed: the browser origin differs from PublicBaseUrl, or validation Cookies were blocked
invalid-tokenToken validation failed: Authority, issuer, audience, or signing-key URL does not match
protocol-validation-failedAny other protocol failure, including a rejected OAuth2 token exchange: check the client ID, secret, and callback URL first
provisioning-failed, grant-creation-failed, session-creation-failedLocal completion failed: check the database connection and applied migrations first
authorization-deniedThe user cancelled authorization at the provider

Do not work around a failure by disabling issuer, audience, signature, state, nonce, or PKCE validation. A reverse proxy must preserve the original scheme and host; otherwise the callback arrives on a different origin. When reporting a problem, exclude authentication query strings and complete provider responses.

Logs and telemetry

AgwLogDir defaults to ./logs and does not follow AgwDataDir. Inspect the relevant role’s logs in split deployments. Configure OpenTelemetry:OtlpEndpoint for centralized telemetry. Blank or missing values disable OpenTelemetry tracing, metrics, and log export.

History uses Interval batch writes. The Host template sets ConversationHistory:FlushIntervalSeconds to 10 seconds; omitted configuration falls back to five seconds. Live output and committed history can differ temporarily.

Web development proxy

Web development runs on 3001, with backend default 30816. Proxy target precedence is BACKEND_API_BASE_URL, then NEXT_PUBLIC_API_BASE_URL, then the local default. Static export does not use the Next.js proxy; Server or ingress must supply same-origin routing.

After a fix, repeat the small failing task and verify both UI state and logs. When reporting an issue, include version, deployment method, reproduction steps, and sanitized errors, without real API Keys or complete OAuth responses.

Implementation and references