Giving every marketer
their own AI agent
I gave my cofounder an AI agent and he replaced half his marketing stack in an afternoon. Then we had to figure out how to make it work for everyone.
For the past year, Graphed has been a data analytics agent. We solved some genuinely hard problems: grounding AI in your actual data, understanding your ontology, learning your business definitions so “show me revenue” means the right thing for your company. But we kept running into the same feedback: the insights were nice, but customers wanted an agent that could act on them.
My cofounder Cody, who you probably already know for the marketing shitposts in your X timeline, was already doing all of this in Claude Code. Google Ads, cold email, SEO, content ops. He'd built agent workflows for all of it. The problem was that those workflows lived on his laptop. We couldn't share them with customers. We couldn't give a marketing team Cody's playbooks as a running agent connected to their own data.
Then he found Hermes, an open-source AI agent, and realized it could be the runtime for exactly that, connected to the live business data we'd already solved access to.
We stood up an MVP in three hours. Hermes connected to our data warehouse, browsed the web, ran terminal commands, and managed files. Cody pointed it at his marketing stack and validated that it actually worked. An agent that could manage Google Ads, write and send cold emails, run SEO audits, all grounded in real business data. And unlike Claude Code, we could package it up and give it to anyone.
The prototype was exciting. It was also a single Python process with a SQLite database and a local filesystem. One agent, one machine.
We wanted to give every Graphed customer their own Hermes agent. A dedicated instance running 24/7 with Cody's marketing playbooks, connected to their data. That meant going from one agent to hundreds. Here are the four problems we had to solve.
The agent shouldn't know your secrets
A marketing agent that manages your Google Ads needs your API keys. One that sends cold email needs SMTP credentials. But handing real secrets to an LLM-controlled process is a non-starter. Prompt injection, hallucinated tool calls, or a simple bug could leak them.
We needed to give Hermes the ability to use credentials without ever giving it the credentials themselves.
How secret injection works
When the agent needs a credential, it asks the user. A secure UI form collects the value. The agent only receives an opaque ID like sct_abc123.
Hermes places sct_abc123 in request headers or body. It has no idea what the actual value is. It just knows the ID refers to the API key it needs.
An egress proxy sidecar intercepts the request, replaces sct_abc123 with the real value, and forwards it upstream. The real credential never enters Hermes's memory.
This same pattern applies to every credential the agent uses, including the platform-level keys that power its LLM and web browsing capabilities. No real secret value ever enters the agent's process.
Each agent needs its own filesystem
Cody's agent generates reports, downloads CSVs, writes scripts. It needs a real filesystem. And when he wants to scale a workflow (say, enriching 100k leads) he needs multiple agent instances working in parallel, all reading and writing to the same files.
The solution: S3 Files with per-agent access points
We use AWS S3 Files, a relatively new technology that makes S3 buckets accessible as POSIX file systems. Each agent gets its own access point rooted at an agent-specific path. When the container mounts this volume, the filesystem physically cannot see other agents' directories. Isolation is enforced by AWS, not application code.
This is the key part: because S3 Files provides a real shared filesystem backed by S3, Cody can spin up ten instances of the same agent and they all read and write to the same directory concurrently. No data migration, no syncing step. When you scale an agent horizontally or rotate its pod, the new instance picks up exactly where the others left off.
The volume is provisioned lazily on first use and survives pod restarts, image rotations, and even instance teardown. Cleanup only happens on explicit account deletion.
Controlling what the agent can reach
A marketing agent that's useful needs to hit a lot of APIs: ad platforms, email services, analytics tools. But an AI agent with web browsing and terminal access can make arbitrary HTTP requests. Without guardrails, it could send data anywhere. We built an egress firewall that operates at three layers:
Kernel-level redirection
An init container installs iptables rules that transparently redirect all outbound traffic through the proxy sidecar. Hermes makes normal HTTPS calls and they're intercepted before leaving the pod.
Domain allowlisting
The proxy checks each domain against a per-agent allowlist stored in Postgres. Unapproved domains are blocked, and the agent is told to call request_network_access, a tool that pauses execution until the user approves or denies the request in the UI.
Kubernetes NetworkPolicy
As a defense-in-depth measure, the pod's NetworkPolicy restricts egress to DNS, our control plane, and public internet, excluding all private IP ranges. A compromised pod can't reach internal services or cloud metadata endpoints.
This is also where secrets get injected. The same egress proxy that enforces domain allowlists is responsible for replacing opaque secret IDs with real values on the wire (described in problem 1). A single layer handles both “where can the agent go?” and “what credentials should it use when it gets there?”
Critically, all of this happens at the network level. The agent can use any HTTP client it wants (curl, requests, httpx, fetch) and the proxy handles enforcement transparently. We don't patch standard libraries or wrap SDK calls. It just works.
When the proxy blocks a request, it records the failure. The Hermes gateway injects those details into the tool result so the agent understands why a request failed and can guide the user through approval.
Replacing SQLite with Postgres
The original Hermes stored sessions and messages in a local SQLite database. That works for one agent, but it doesn't scale: state is trapped on a single pod, there's no way to query across agents, and backups require snapshotting each pod's volume separately.
We moved session and message state into our shared Postgres database. Sessions, messages, secrets, and network rules all live alongside our core tables (accounts, conversations, dashboards) in one place.
Monkey-patching the state provider
Hermes is open source and we want to stay close to upstream. Rather than forking the storage layer, we monkey-patch the state provider in graphed_state.py, swapping the default SQLite backend for our Postgres API while keeping the rest of Hermes untouched. This means we can pull upstream changes without merge conflicts in the storage code.
To make sure upstream updates don't break our monkey-patched provider, we run end-to-end tests in CI against every new Hermes release. If an upstream change alters the state interface, we catch it before it ships.
What this unlocks
Together, these four changes let us go from Cody's three-hour prototype to a platform where every customer gets their own always-on marketing agent:
Zero-knowledge credentials
The agent uses secrets it can't read. No credential can be exfiltrated even by a fully compromised agent process.
Hard agent isolation
Storage is isolated at the filesystem level. Network access requires explicit user approval. Pod-level policies prevent lateral movement.
Seamless updates
We ship new Hermes images and instances rotate automatically. Data, secrets, and network rules persist across updates.
Shared infrastructure
Sessions, messages, and admin tools use the same Postgres database as the rest of Graphed. No special backup or monitoring needed.
We just shipped this, and there's more to do. Our goal is full feature parity with upstream Hermes, every tool, every workflow, but running at scale. If you're using Hermes or building on top of it, we'd love to hear what you're working on.