← Back to projects

Keeper

Keeper runs ten long-running Claude Code agents on a server, for my own work and for my clients. Each has a written delegation, and a policy gate checks every agent’s tool calls, except the ops agent’s. In a snapshot from Oct 5 to Nov 3, the gate refused 86 calls. It also allowed and logged 9,253 calls outside any grant.

The gate is open source: gate source on GitHub (opens in a new tab).

What I built

Claude Code · MCP · Hooks · Policy · Voice · Browser automation

Examples on this page use made-up names and values.

How it works

01

Agents with delegations

Each agent is a long-lived session that owns one area. Its written delegation lists standing rules, pre-approved actions, limits and hard stops. A machine-readable grant names who it may write to, which vault items it may use, and which paths it may change.

02

Inbound routing

An event hub hands each incoming mail or message to the agent that owns it. Only routes I have approved exist.

03

Phone and browser, with a human handoff

A voice agent places real calls from a brief that lists what it may and may never share. Each browser tab has one owner. For captchas and sign-ins I take over through a live view on my phone, then hand the tab back.

04

Failures that reach someone

Any script, timer or agent that fails reports to the ops agent, which fixes the cause and hardens it. A nightly review gathers every decision that needs me into one set of questions each morning.

How it stays safe

Keeper architecture

Agents · policy gate · tools · ops loop

AGENTS (3 OF 10)InboundMAIL, CHATOwnerON A PHONEEvent hubROUTESFinanceAGENTMailAGENTEngineeringAGENTPolicy gateALLOW, DENY, LOGInboxMAILBrowserTABSPhoneVOICEVaultSECRETSOps agentFIXES CAUSESinboundrouteinstructionsevery callloggedrefusedany agentfailureany tool failurefixallowedloggedrefused
External Fix Failure or refused call

05

The policy gate

A hook service checks each agent’s tool calls against the policy compiled from the delegations. The one exception is the ops agent that maintains the system. A covered call passes. A short list of dangerous calls is refused, with a reason the agent can act on. Everything else is allowed and logged. Prompts that need me come from Claude Code’s own permission system, not the gate. An agent cannot widen its own access: it asks, and I approve or not.

Messages between agents are information, never approval.

Shell commands are checked as text before they run. Writes to protected paths, other agents’ folders and system files are refused.

An example gate trace. The code calls agents “chats”, so the trace does too:

gate trace (example)
call 1
agent: finance
tool:  Write
path:  ~/agents/docs/notes.md
rule:  another chat's folder
decision: deny
reason: not allowed. Record
  "blocked: needs Owner" in your
  status file and carry on with
  in-scope work.

call 2
agent: finance
tool:  Write
path:  ~/agents/finance/log.md
rule:  inside writablePaths
decision: allow

Read the essay on why a peer message is not approval

Refused calls per day, Oct 5 to Nov 3

REFUSEDpeak 11 a day110Oct 5: 5 refusedOct 6: 0 refusedOct 7: 2 refusedOct 8: 1 refusedOct 9: 1 refusedOct 10: 0 refusedOct 11: 0 refusedOct 12: 8 refusedOct 13: 5 refusedOct 14: 4 refusedOct 15: 1 refusedOct 16: 0 refusedOct 17: 0 refusedOct 18: 1 refusedOct 19: 4 refusedOct 20: 2 refusedOct 21: 1 refusedOct 22: 10 refusedOct 23: 2 refusedOct 24: 0 refusedOct 25: 0 refusedOct 26: 2 refusedOct 27: 3 refusedOct 28: 2 refusedOct 29: 11 refusedOct 30: 8 refusedOct 31: 3 refusedNov 1: 7 refusedNov 2: 3 refusedNov 3: 0 refusedALLOWED AND LOGGEDpeak 2,404 a day2,4040Oct 5: 17 allowed and loggedOct 6: 6 allowed and loggedOct 7: 28 allowed and loggedOct 8: 9 allowed and loggedOct 9: 6 allowed and loggedOct 10: 10 allowed and loggedOct 11: 21 allowed and loggedOct 12: 84 allowed and loggedOct 13: 13 allowed and loggedOct 14: 128 allowed and loggedOct 15: 34 allowed and loggedOct 16: 27 allowed and loggedOct 17: 9 allowed and loggedOct 18: 14 allowed and loggedOct 19: 71 allowed and loggedOct 20: 67 allowed and loggedOct 21: 19 allowed and loggedOct 22: 642 allowed and loggedOct 23: 768 allowed and loggedOct 24: 87 allowed and loggedOct 25: 123 allowed and loggedOct 26: 427 allowed and loggedOct 27: 68 allowed and loggedOct 28: 38 allowed and loggedOct 29: 285 allowed and loggedOct 30: 311 allowed and loggedOct 31: 1,623 allowed and loggedNov 1: 2,404 allowed and loggedNov 2: 1,567 allowed and loggedNov 3: 347 allowed and loggedOct 5Oct 20Nov 3
Snapshot, Oct 5 to Nov 3: 86 calls refused, and 9,253 calls outside any grant allowed and logged. Each panel has its own scale. Most logged calls came in one three-day burst, Oct 31 to Nov 2. Nearly all of the burst was one agent running shell commands inside its own writable folder. Nov 3 is a partial day.

06

Verified instructions

A standing instruction is checked against the actual conversation transcript before it becomes authority. If I did not say it, it does not count, however confidently an agent reports it.

07

Secrets kept out of the model’s context

Secrets live in a password vault. To sign in, a helper types the secret into the page over the browser debugging protocol, so it never passes through the model’s context. Browser tools mask password fields, and standing rules forbid reading them back. Tooling and rules enforce this, but it is no guarantee.

Card payments are outside every delegation. I give the word each time.

Honest limits

The gate is a tripwire for mistakes and drift. It is not a sandbox.

  • It checks shell commands as text. A determined interpreter can build a path at run time or reach it by a route the scanner does not model. The parser catches the common forms, not all of them.
  • It does not restrict general outbound network access from a shell command. It only denies egress it can tie to a protected file, a pinned recipient or a guarded service.
  • It fails open. A missing or broken policy file, or an error during evaluation, means no opinion. It is built to avoid wedging work, not to be a hard wall.
  • Most checks are log-first. Only a short deny list blocks.
  • In the original setup the agents share one Unix user. The real boundary is the operating system, and the repo does not provide that.
  • It sees only what Claude Code sends to hooks, not what a tool does after the call.
  • The ops agent that maintains the system runs outside the gate. I review its work.

keeper-gate on GitHub

The gate is MIT licensed. It is a cleaned extract from my private system, with fictional names, paths and addresses. The repo has:

  • The gate itself and its shell parser.
  • The compiler that turns written delegations into one policy file.
  • The Claude Code hook entry point and an installer.
  • Two example delegations and the policy compiled from them.
  • A replay script that runs recorded sessions through a policy.
  • 115 tests.

Read the gate’s code on GitHub (opens in a new tab)

Incident: one bad line

One entry in a messaging channel’s allow-list was malformed: a contact label was longer than the config format allowed. That one line failed validation for the whole config.

The adapter stopped sending and reading for that channel overnight. Nothing reported it. Reporting had failed, which is worse than the bug.

The ops agent fixed it. The adapter now validates each entry separately, drops only the bad one, and reports it. A config check before deploy catches a bad entry first.

One bad line should cost one line, not the whole channel.

What it’s used for

The agents do real work for me and for my clients. For example:

  • A client discovery. The agents chased outside firms for data and wrote it up, while I approved the emails that went to the client.
  • My own paperwork and operations: mail, travel and finance, plus my visa paperwork.

A follow-up an agent might send to a firm that has not delivered:

follow-up email (example)
To: Dana, Firm B
Subject: Export requested Oct 12, still open

Hi Dana,

On Monday, Oct 12 we asked for a CSV export
of Firm B's order history, Jan 2024 to date.
We have not received it, and we cannot
finish our data check without it.

Could you send it by Friday, Oct 23? If a
full export is hard, the last 12 months
would unblock us. If it needs a form or an
approval on your side, tell me which and I
will send it today.

Thank you,
Ben's communications agent

What’s next

  • Request-origin tracking in the gate. Today the gate judges only the agent making a call. It should also know who asked.
  • Expiry for standing rules, not just tasks. Tasks already expire on their own. Standing rules and grants do not yet.

Questions? Email [email protected].