Architecture¶
What a deployed dispatch agent looks like on AWS, and why it is shaped that way.
One repo, one agent, one Slack app¶
Each generated repository corresponds to one long-running agent and one Slack
application/user. Generate as many as you like — a @swe-pal for code reviews
and PRs, a @reporter that posts scheduled reports, a @docs that keeps
documentation in sync.
The stack¶
The CloudFormation stack (template.yaml in the setup skill) owns the full
infrastructure:
| Resource | Details |
|---|---|
| VPC + subnets | Public subnet, internet gateway; inbound-less security group |
| EBS data volume | Standalone gp3 volume, mounted at /data, DeletionPolicy: Retain |
| Launch template + ASG | Single ARM64 container instance, min=max=desired=1 |
| IAM roles | Task execution, task, container-instance instance profile |
| KMS key | alias/dispatch-ssm — encrypts the SSM secrets |
| Log group | /ecs/dispatch |
| ECS service | EC2 launch type, host networking, DesiredCount gated by parameter |
Key decisions:
- Socket mode, outbound-only. The agent is a Slack socket-mode bot. It makes an outbound WebSocket connection to Slack, so there is no load balancer and no inbound port — the security group is inbound-less. Public IP on the instance ENI provides the outbound path (no NAT gateway).
- EC2 launch type, not Fargate. The persistent state is SQLite, and
SQLite's WAL mode needs a real local block device (it is unsafe on NFS).
EBS gives that, and lets the data volume survive instance replacement: the
volume is standalone with
DeletionPolicy: Retain, and the instance's UserData reattaches it on every boot. - Host networking. The task shares the container instance's ENI. With no inbound ports there is nothing to map.
- ARM64. The instance probes for an ARM64 availability zone at setup and the harness image ships multi-arch (amd64 + arm64).
The persistent volume¶
The EBS volume is surfaced into the container as four host bind-mounts that mirror the harness CLI bind-mounts:
Host path (on /data) |
Container path |
|---|---|
hermes/ |
~/.hermes |
config/ |
~/.config |
mise/ |
~/.local/share/mise |
mise-state/ |
~/.local/state/mise |
Because state lives on EBS, an instance replacement (ASG recycle, instance failure, manual rebuild) does not lose sessions, memories, or skills.
The overlay¶
agent_home/ in the generated repo is the curated state that ships with
the repo: skills, memories, system prompt, personas. It is not baked into the
image.
The manage-dispatch skill's overlay mode pushes this directory onto the
running agent's ~/.hermes over ECS Exec — updating skills, memories, and
prompts without a CloudFormation redeploy or image rebuild. This is the
day-2 workflow: edit agent_home/ in the repo, push the overlay, restart.
config.yaml is excluded from the overlay — it is managed by merge-config
mode, which does a key-level merge into the live config.
The image¶
The agent runs the hardened ghcr.io/boldblackai/harness image
(hermes-<version> tags). The HarnessImageTag stack parameter selects the
tag; bumping it and redeploying rolls the agent to a new image without
rebuilding anything. The
harness releases page lists
the tags.
The secrets path¶
Secrets live as SSM SecureStrings under /dispatch/, encrypted with the
stack's own KMS key (alias/dispatch-ssm). A Hermes secret-source plugin
(aws_ssm, installed during setup) resolves every /dispatch/* parameter into
the gateway's environment at startup. Adding or rotating a key is an SSM write
plus a task restart — no template edit, no redeploy.
See Concepts: secrets for the full inventory.