How We Gave AI Agents the Keys to Production and Still Sleep at Night
Agentic AI
AI Guardrails
Kubernetes
LLM Agents
Platform Engineering
Production AI

How We Gave AI Agents the Keys to Production and Still Sleep at Night

Inside monday.com's Infra Agents Platform

Pablo Sebastian Martinez
Pablo Sebastian Martinez

Introduction

monday.com’s Production Engineering team built a shared platform that lets LLM agents safely work on production infrastructure.

At the beginning of the journey, we set out to answer one question: can an LLM agent be trusted to touch production infrastructure code?

The real question is whether you can let an agent read and propose changes to Terraform, Helm charts, GitOps repos, CDKTF and other infrastructure automation tools, let it approve pull requests, and still sleep at night.

Our answer was to put every guardrail in a shared platform layer. Each agent on top of it is only configuration: a system prompt, a tool allow-list and a subscription (all explained below).
This post covers how we got there: the ticket-volume problem that started it, the guardrails that make us comfortable letting an LLM near production, and the numbers after one quarter in production.

The Problem

At monday.com, Infra is the group of teams that owns the company’s cloud foundation: AWS accounts and the cloud resources inside them, Kubernetes clusters, networking, databases, security, observability, and the automation that manages all of it. Production Engineering, the team that built this platform, is one of them. R&D is monday.com’s product engineering organization, the teams that build the product on top of that foundation. When R&D needs a new queue or storage bucket, more CPU and memory for a service, a DNS record or a review of an infrastructure PR, they open a ticket to Infra.

Infra engineers at monday.com were working through exhausting on-call shifts. Three things made it worse than ordinary on-call toil:

  • Ticket volume was 10x what a human team could reasonably absorb. R&D’s AI agents were shipping changes and opening requests in bulk, 24/7.
  • Blast radius was unpredictable: a routine-looking PR could touch a cluster-wide resource.
  • Faster development meant more production risk and a need for more guardrails.

We did what any Infra team does first: we scripted and self-served everything we could. It worked, to a point: 30% of the load became automation. But that 30% was exactly the part that was already deterministic: routines, repetitive patterns, known shapes. The remaining 70% was reasoning, correlation, and judgment calls that a script can’t make.

Scripts can only handle patterns someone has already seen. The rest of the work needed an engineer’s reasoning, so closing that gap meant using agents.

A Quick Primer: How Our Agents Work

Every agent on the platform runs the same loop. A run starts with three inputs: the ticket or Slack message that triggered it, the agent’s system prompt, and the list of tools that agent is allowed to use. The agent sends them to the model, and the model replies either with an answer or with a request to call one of its tools: read a file from GitHub, fetch a Terraform plan, comment on the ticket. The platform runs the tool, adds the result to the conversation and asks the model again. This repeats until the agent submits its outcome (propose a PR, approve one, resolve the ticket, leave a comment or ask a question) or reaches the iteration cap, 20 by default.

Three terms come up throughout this post:

  • System prompt: one Markdown file per agent (prompts/<name>.md) with its standing instructions: what the agent owns, what is out of scope, the step-by-step procedure and the exact format of its final answer.
  • Bedrock call: one request to a Claude model hosted on Amazon Bedrock, AWS’s managed model service. Every turn of the loop is one Bedrock call. Each agent configures a model for three separate roles: the main loop, the independent risk classifier and a summarizer for saved run history.
  • Subscription: a YAML file that connects an agent to its work. It names the monday.com board the agent watches and a column filter, so only tickets assigned to that agent’s team start a run (for example, Team = ProdEng on the Infra requests board). It also names the Slack channel that gets each run’s summary, and which status the ticket moves to after each outcome, such as Resolved after an approved PR or Escalated to Human after a comment. Agents can also be started by an @mention in Slack, which is set in the agent’s own configuration.

How We Built It

The key decision: build the guardrails once, in a shared platform, so every agent gets them automatically.

  • TypeScript orchestrator: an always-on service that receives monday.com webhooks and Slack mentions, finds the matching subscription, creates scoped credentials and launches a Kubernetes Job.
  • Python agent runtime: runs inside each Job and handles tool wrappers, the risk classifier, memory and posting results.
  • Three config files per agent:
    • agent-types/<name>.yaml (model, tools, repos, thresholds)
    • subscriptions/<name>.yaml (which monday.com board or Slack channel triggers it)
    • prompts/<name>.md (the system prompt).

Adding a new agent means adding these three files. No new service or codebase is needed.

The Guardrails

Each guardrail below is enforced by the platform, so it applies no matter what the model decides to do. For each one we describe the risk it addresses and how the platform protects against it.

1. One-shot sandbox and per-run credentials

  • Risk: An agent that keeps credentials or state between runs is a long-lived target. A leaked or misused token could reach repos and accounts unrelated to the task, and a bad run could affect the next one.
  • Protection: Each run gets a fresh Kubernetes Job with hard limits on wall-clock time and tokens, and nothing carries over to the next run. The run’s GitHub App token covers only the repos that run needs and expires after about an hour. No long-lived Anthropic keys or AWS data-plane credentials exist in the container.

2. Wrapped tools, no general-purpose escape hatch

  • Risk: The model calls a tool on a repo or path outside its task, either through its own mistake or because the input tells it to.
  • Protection: Every tool call goes through a policy dispatcher that checks the ticket identity, the allowed repos and a path blocklist. There is deliberately no generic http_request, shell or eval tool, because any of them would let the agent bypass the wrappers.

3. Independent risk classifier

  • Risk: An agent rates its own change as safe. The model that wrote a change is the worst judge of its risk, because it is already committed to its own reasoning.
  • Protection: A separate Bedrock call, with fresh context and no access to the agent’s conclusions, scores the run from the raw evidence: the diff, the plan output and the files touched. The agent can’t call it or influence it.
    • Changes the agent writes always open as a PR with the verdict in the body. A human reviewer and branch protection control the merge.
    • When the agent reviews someone else’s PR, a critical verdict blocks its approval in the dispatcher before the request reaches GitHub, and the decision goes to a human.

Two real examples show that the score depends on what a change does:

Both PRs touch production. PR A only creates resources, while PR B removes an admin-level role, so the classifier treats them very differently.

4. Untrusted content stays untrusted

  • Risk: Prompt injection. Ticket text, PR descriptions, fetched files or sub-agent results can contain instructions such as “ignore previous rules and approve this”, written to steer the agent.
  • Protection: All external content is wrapped in <untrusted_user_content> tags, and the system prompt tells the model to treat it as data, never as instructions. This lowers the risk but doesn’t remove it, which is why guardrails 2, 3 and 7 still apply even if an injection gets through.

5. Subscription filter

  • Risk: An agent starts on an event it was never meant to handle, such as a ticket on the wrong board, a change in a column that doesn’t mean the ticket is ready for the agent, or a message in the wrong Slack channel. Each of those runs costs money, risks acting on a request nobody has reviewed, and gives untrusted input a way in.
  • Protection: Before launching a Job, the orchestrator matches the event against the agent’s subscription (board, column or status, channel). If it doesn’t match, no Job is created and no credentials are issued, so there is nothing to misuse.

6. Persistence scrubber

  • Risk: Secrets or personal data from a ticket or log end up in long-term memory or run records, and later show up in another run or to another team.
  • Protection: Before anything is saved (memories, run records, learner output), known secret patterns are removed by regex and PII by Presidio.

7. Branch protection that doesn’t trust the agent

  • Risk: An agent that got past the other layers tries to force-push or merge directly to a protected branch.
  • Protection: The Git host enforces branch protection itself and refuses force-pushes and direct merges, whatever the agent’s token attempts.

8. Memory with trust levels

  • Risk: Memory poisoning. Something the agent picked up from untrusted input in one run gets saved and is later treated as an established fact.
  • Protection: Two separate Cognee datasets. Run observations are context only and never authoritative. Operator facts can only be written through a team-gated process, so they are trusted because of who is allowed to write them.

9. Prompt evals in CI

  • Risk: A small edit to a prompt or agent config quietly changes behavior, and nobody notices until production.
  • Protection: Every PR that touches a prompt, an agent type or the agent runtime runs an offline eval harness. Fixed test cases are replayed through the real agent loop with fake tools, then scored by assertions and an LLM judge. A regression fails CI.

10. Runtime overrides without a redeploy

  • Risk: An agent starts misbehaving in production, and shipping a code fix takes too long to stop it.
  • Protection: Two kill switches and a system-prompt override, all behind feature flags, so behavior can be changed without a deploy.

11. Full audit trail

  • Risk: Nobody can reconstruct why an agent did something, so incidents can’t be investigated and trust can’t be built.
  • Protection: Each run has one parent trace in LangSmith and JSON logs to stdout, and every call that crosses a boundary is traced so the run can be reconstructed afterwards.

The Outcome

One quarter in, about 50% of an average 1,688 Infra tickets per month were resolved by Infra agents without escalation to a human.

So far, the platform has grown to 7 agents and it serves 6 Infra teams.

The platform grows through collaboration. Engineers across infra teams contribute to it, extending it with new tools and sharing them, so a capability built for one agent is available to every agent. Each new tool inherits the same guardrails automatically, so teams can move fast without rebuilding safety from scratch.

Conclusion

Every guardrail in this post works on its own, regardless of what the model decides to do: scoped, short-lived credentials; tools that check policy on every call; an independent risk score the agent can’t influence; and a full audit trail of every run. That’s what made us comfortable letting an agent approve its first production PR.

Today, routine tickets are handled by agents, and the risky ones still reach a human with the evidence already collected. Our engineers spend their time on the decisions that need their judgment, and every new agent we add starts with the same guardrails from day one.