How We Built an AI Project Manager on Claude Managed Agents

Orr Yakobi
SWARECO runs an AI project manager called Clara Bennett. She sits in Slack with her own account, files and updates Jira tickets herself, answers delivery questions, and posts a status update every weekday morning. She runs on Claude Managed Agents, and the Rails application we wrote to support her is 536 lines of Ruby.
That number is the point. Managed agents move most of what you would have built onto the platform, and we still overbuilt. We wrote far more than 536 lines first and deleted it. In one commit on 15 September we removed 5,862 lines and added 1,430 across 140 files, and the agent got better. What follows is how to build one, in the order we would do it again.
What Claude Managed Agents gives you, and what you stop building
A managed agent is an agent Anthropic runs for you. You declare what the agent is and what it may touch; Claude runs the agent loop, the sandbox it executes in, and the session state across turns. Clara is one managed agent, defined once and versioned like code.
That is the part worth understanding before you start, because it decides how much code you write. On a self-hosted agent harness you own the loop: you call the Claude API, you read the tool call the model returns, you execute it, you feed the result back, you decide when to stop, and you build the sandbox that makes running a tool call safe. With Claude Managed Agents none of that is yours. You declare an agent, an environment, and a set of tools, and you start sessions against it.
What you write instead is small and boring, which is the correct outcome. Our Rails application receives Slack events and starts sessions. It does not orchestrate anything, it does not run a tool call, and it never sees the agent loop. The runtime is not ours to operate.
The pieces you do declare:
- The agent — its name, its purpose, the Claude model and the effort level. Clara runs on Opus with effort set high. Every change to an agent creates a new version, so a deployment is a version bump.
- The tools — connector tools by name, and sandbox tools such as bash. Deny by default.
- The environment — the sandboxed machine the agent runs in, and what the network may reach from it.
- The vault — one credential per connected server, held by the platform rather than by your application.
- Sessions — one per unit of work, each with its own budget, resources and opening message.
- Deployments — scheduled runs, on a cron expression in a timezone you pick.
The Claude console holds every session transcript, which is how you debug and manage an agent you are not hosting. That console is the whole observability story for a managed agent, so it is worth learning early.
Give the AI agent its own account, not a bot token
For our use case the first real decision was making Clara a user rather than a bot. She has a Slack account, an email address, and her own logins to the systems she touches.
This is not cosmetic. A Slack bot reads what it is invited to through an app token, and our first attempt could not read the private channels where the delivery conversation actually happens. With her own user token she reads what she is a member of, and she posts as herself. Adding her to a channel is the whole permission model: membership is the allowlist.
It also creates the first failure mode. Her own messages arrive back as events, so without a way to recognize them the agent answers itself forever. The application refuses to start a session at all if her user id is missing from the configuration. Not a warning, a refusal.
Write the permission list before you write the prompt
We declared what Clara may do before we wrote a word telling her who she is, and that ordering is worth copying.
Her tool list is deny by default: a tool that is not named does not exist for the agent. Across every connected server she has four write tools, all of them Jira — create an issue, edit one, comment on one, move one. Everything else is read-only, including the docs, the roadmap and the code. In the sandbox she has four tools: bash, read, grep and glob. There is no file write, no web search and no web fetch. Network egress from the sandbox reaches Slack and the connector servers, and nothing else.
This is the same instinct as putting walls and rails around agentic coding, moved from review time to definition time. The part that makes it hold is a test. An 87-line spec fails the build if a fifth write tool appears in the agent definition, if the network opens up, or if a skill leaks another client's material. Permissions written in a config file drift. Permissions with a failing test do not.
Keep the token out of the agent
Clara calls the Slack API with curl, authenticated by a token she never sees. The token is a vault credential, substituted at the sandbox's edge, only for requests to Slack, and only into headers. Inside the session it is a placeholder.
This matters more for an agent than for ordinary software, because an agent's whole job is to read things other people wrote. A token sitting in its context is something every message it reads gets a chance to talk it out of. The same reasoning applies to anything an agent opens automatically, which is how a repository's own config file became a way to run code inside coding agents.
When to use Claude managed agents, and how they differ from Claude Code and the Agent SDK
We use all three daily, and they answer different questions. The difference that decides it is who runs the agent loop and who runs the sandbox.
| Who is at the keyboard | Who runs the loop and sandbox | |
|---|---|---|
| Claude Code | An engineer, interactively | Your machine |
| Claude Agent SDK | Nobody; your code drives it | You, in your own process |
| Claude managed agents | Nobody; events and schedules drive it | Anthropic |
Choose a managed agent when the work has to happen with no human at a terminal — on a schedule, or whenever a message arrives — and when the agent needs its own identity and its own permissions rather than borrowing a developer's. That is the shape of a project manager, and of most real use cases for this. Building agents from scratch on the SDK is the right call when you need the loop itself to be different, and Claude Code is the right call when a person is in the loop anyway.
On cost, we do not publish a per-run figure, because it moves with how much the agent decides to read. What we control is the ceiling, and that is worth setting before the first real session rather than after the first surprising one.
The Rails application, and how little of it there is
The application does three things: take Slack's events, drop what the agent should not see, and start or continue a session. Ten files carry it.
| File | Lines | Job |
|---|---|---|
| Agent definition | 158 | Builds the definition: tools, connector allowlist, network, schedule |
| Session | 65 | Starts a session, or sends the message into the thread's existing one |
| Slack event model | 56 | Stores each event once, with its status and session id |
| Task | 54 | Assembles the message the agent receives |
| Events controller | 43 | Verifies the signature, stores the event, returns 200 |
| Config | 42 | Every setting, all from environment variables |
| Signature | 42 | Slack request signature verification |
| Prompt | 38 | Assembles the system prompt from files on disk |
| Gate | 35 | The six checks that decide whether a session starts |
| Start session job | 25 | Runs the gate, then the session |
Three decisions inside it matter more than the file list.
The gate does no thinking
Every filter in the application is mechanical. Is this a message. Is it the agent's own. Is it a bot. Is this channel on the blacklist. Is it empty. Is the subtype one we handle. Six checks, 35 lines, and not one of them judges whether a message deserves a reply.
That was the second version. The first had a Ruby layer that classified messages, decided which ones mattered and drafted a response for the agent to approve. It was the largest part of the codebase and it was wrong: it made the cheap decisions badly and left the expensive ones to the agent anyway. Judgement is now the agent's, including the judgement to stay silent.
Nothing is awaited
The application starts the session and stops. It does not wait for a result and there is no callback. The agent posts its own answer in Slack, and the Claude console holds the transcript.
One session per thread
A new message starts a new session. A reply inside a thread we already have a session for sends the message into that session instead, so the agent keeps everything it read the first time. State across sessions is the platform's problem, not ours. If a session has terminated or vanished, a new one starts, and that fallback is four lines.
The skills are seven times the size of the prompt
This is the part we would tell someone else to copy first. Clara's system prompt is 128 lines across 8 files. Her skills are 969 lines across 7.
| Skill | Lines | What it holds |
|---|---|---|
| Ticket writing | 351 | The ticket format, what stays unconfirmed, the search to run before filing |
| Slack voice | 253 | The register, with real examples, and what the agent may say in front of a client |
| Slack API | 118 | Every call it needs as curl, plus posting rules, pagination and rate limits |
| Status update | 95 | Where to look, what the post contains, and the limits |
| Writing voice | 71 | Plain words, short sentences, facts before inference |
| Plain-language standard | 46 | The standard the writing voice applies |
| Re-explain | 35 | Say it again more simply when someone says it did not land |
These are Claude Code skills: the same folder-and-front-matter format, which is why two of them moved across from our own Claude Code setup unchanged. The structure matters more than the sizes, because the skills narrow each other rather than repeat each other. The plain-language standard is the base. The writing voice applies it to everything the agent writes. The Slack voice narrows that for Slack. The ticket skill narrows it again for tickets. Each one names, in its own description, which skill it is narrowing.
Two things follow. Nothing is stated twice, so nothing can drift out of agreement with itself. And a skill loads when it is relevant, while a system prompt is paid for on every session. Knowledge that only matters when the agent writes a ticket does not belong in the text it reads before every message.
The prompt keeps what is true every time: who the agent is, what it is for, which channels are which, and what it must never do. Editing one is a configuration change, not a code change. It is the same discipline as keeping a CLAUDE.md short, for the same reason: everything in it is paid for on every session.
Tell the agent where it is, not what happened
The message that opens a session is short. It says a Slack message arrived, which conversation it is, whether it is a reply or a top-level post, when it was posted in the team's timezone, who sent it, and who the approver is. Then it stops.
It does not contain the thread. Clara reads that herself, through the Slack API, using the skill that tells her how. Our first version digested the conversation and handed her a summary, which meant a Ruby object decided what mattered before the model ever saw it. Handing over the location instead of the content was the single change that improved her answers most.
Schedule the recurring work, and cap the spend
The weekday status update is a scheduled deployment on the Claude platform rather than a job in our application: a cron expression in the team's timezone, with its own budget. That is the only part of the workflow we do not trigger ourselves.
Every time the agent runs it carries a per-session spend cap in cents, and a session that reaches its cap pauses rather than continuing. On an agent that decides autonomously how much to read, a cap is not a cost optimization. It is what stops one confusing thread becoming an expensive afternoon.
What we would do differently
Build the deletion into the plan. Our first architecture was a Ruby application that used a model; the working one is an agent with a small Rails application in front of it. Most of the work between them was removal.
The open question is sub-agents. One agent holding every skill is easier to debug, and that is what runs today. Splitting into a coordinating agent and specialists would keep each system prompt sharper, at the cost of a harder thing to debug. We have not resolved it.
Other Articles
We build the engineering. You build the business.
If you are trying to figure out whether SWARECO is the right fit for what you are building, the best way to find out is to talk. Tell us what you have. We will be direct about what we can do and how we would approach it.









