Skip to main content
Glama
babayaga370

red-castle

by babayaga370

AI agents may ask to delete things. Only a human decides.

tests license: MIT python dependencies

Red Castle is a guarded deletion pipeline for AI agents. An agent never deletes anything itself: it asks the castle, the object is hidden in quarantine, and a human approves or restores it from the terminal. The castle runs silently in the background and only speaks up, with a desktop notification, when a human is needed.

AI Agent
    │  "I want to delete X"  (cannot delete it themselves)
    ▼
[The Gateway]       receives deletion requests via API or MCP tools
    ▼
[The Judge]         calculates its own risk score and compares it with the agent's
    ▼
[The Quarantine]    the object is hidden but still exists
    ▼
[The Throne Room]   a human approves, restores, or freezes the agent (in the terminal)
    ▼
[The Executioner]   the only role in the world authorized to permanently delete
    ▼
[The Chronicle]     tamper-evident log of every decision

Around the wall:
[The Moat]          guardrails
[The Watchtower]    monitoring and desktop notifications
[The Traps]         canaries

Pure Python standard library. No dependencies, except boto3 if you use the S3 backend.

Quick start

git clone https://github.com/babayaga370/red-castle.git
cd red-castle
pip install .

Or skip the install and use python -m redcastle in place of redcastle below.

redcastle demo

The demo walks through the whole flow in a throw-away castle: quarantine, approval, restore, a sprung trap, a frozen agent and the Chronicle. Nothing on your machine is touched.

To set up a real castle, go to the folder that should hold it and run:

redcastle init

init greets you with the castle and a short guide. It creates redcastle.toml (the configuration), realm/ (the folder agents can see and ask to delete from) and .redcastle/ (database, keys, quarantine and log; keep it private).

redcastle agent add claude
redcastle start
redcastle notify test

That's it: the castle runs silently in the background, and agents reach it at http://127.0.0.1:8750/api/v1. When something needs you, a notification pops up, and you answer it with:

redcastle throne review

Running redcastle without a command shows the welcome screen and the most useful commands. Every command accepts --root <folder> (or REDCASTLE_HOME) when the castle lives elsewhere.

Related MCP server: genesys-mcp

Running silently

Command

redcastle start

Start the castle detached from the terminal, with no window (pythonw on Windows). It keeps running when the terminal closes.

redcastle status

Is it running? How many cases await you? Any alerts in the last day? Is the Chronicle intact?

redcastle stop

Ask the castle to close its gates; forced after 15 seconds if it does not answer.

redcastle serve

Run in the foreground of this terminal (Ctrl+C to stop). --quiet logs only to .redcastle/castle.log.

The castle does not start at login by itself. To make it, schedule pythonw -m redcastle --root <folder> serve --quiet with Windows Task Scheduler (or launchd / systemd).

Notifications

The Watchtower stays quiet unless a human is needed:

  • An object is waiting in quarantine. The notification names the agent and the object, shows the Judge's and the agent's risk scores, and says if the agent underestimated the risk.

  • Alerts (urgent): an agent reached for a canary, an agent was frozen, a canary vanished behind the castle's back, the Chronicle or the quarantine was tampered with, or the Executioner refused to act.

  • Reminders when cases have waited longer than remind_interval_minutes.

Everything that happens within batch_seconds is gathered into one notification, so a busy agent cannot flood your screen. Rejected requests (bad keys, missing objects …) stay quiet.

Windows uses native toast notifications through Windows PowerShell (they show "Windows PowerShell" as the sender), macOS uses osascript and Linux notify-send. Without any of these, notifications go to the log; REDCASTLE_NOTIFY=off forces that, for CI for example.

[notify]
enabled = true
on_quarantine = true          # notify when an object awaits your judgement
min_severity = "warning"      # "warning" or "critical"
batch_seconds = 3
remind_interval_minutes = 60  # 0 = never

Nothing shows up? Check the system's notification settings and Focus Assist / Do Not Disturb.

The Throne Room, in your terminal

redcastle throne review

The review goes through the quarantine one case at a time. For each case it shows the object, the agent, its reason, both risk scores and the Judge's reasoning:

[1/2] Case rc-6c9b6e01fd01  [quarantined]  4m ago
  object:     config/prod.env  (31 bytes)
  agent:      claude
  reason:     Looks unused
  risk:       agent 5, Judge 70 (high)  <-- agent UNDERESTIMATED the risk
       +10  permanent deletion is never risk-free
       +35  path matches sensitive pattern '*.env'
       +20  path matches sensitive pattern '*prod*'
        +5  terse justification
  [a]pprove (delete forever)  [r]estore  [s]kip  [f]reeze claude  [q]uit >

Approving asks you to type yes. Every decision is recorded under your name (your username, or --by).

Command

redcastle throne list

Cases waiting in quarantine (--status all for everything)

redcastle throne show <id>

One case, the Judge's reasoning and its Chronicle entries

redcastle throne approve <id>

Approve; the Executioner deletes the object permanently

redcastle throne restore <id> [--as KEY]

Decline and put the object back

redcastle throne freeze <agent> [--restore-all]

Freeze an agent (and optionally restore all it asked for)

redcastle throne unfreeze <agent>

Let it submit requests again

redcastle chronicle show --follow

Watch every decision as it happens

redcastle chronicle verify

Verify the hash chain

redcastle watchtower alerts / patrol

Recent alerts / check canaries, quarantine and Chronicle now

redcastle traps plant <key>

Plant a canary

redcastle executioner run

Carry out approved cases that are still pending

The Throne Room exists only in the terminal. There is no web interface and no admin API, so nobody can approve a deletion over the network.

For agents: HTTP API

Every call needs Authorization: Bearer <agent token>.

Method

Path

GET

/api/v1/objects?prefix=

Visible objects (quarantined ones are hidden)

POST

/api/v1/deletion-requests

{"object_key", "reason", "risk_score"} → 202 quarantined, 403 rejected

GET

/api/v1/deletion-requests

Your own requests (?status=)

GET

/api/v1/deletion-requests/{id}

Status of one of your requests

curl -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"object_key":"logs/app-2025.log","reason":"Last year log, already archived","risk_score":10}' http://127.0.0.1:8750/api/v1/deletion-requests

For agents: MCP

redcastle mcp is an MCP server over stdio with the tools list_objects, request_deletion, get_deletion_request and list_my_deletion_requests. No tool can delete, approve or restore anything.

Point it at the running castle with --gateway. The agent's process then holds nothing but its own token and cannot reach the database or the keys. For Claude Code, in .mcp.json:

{
  "mcpServers": {
    "red-castle": {
      "command": "redcastle",
      "args": ["mcp", "--gateway", "http://127.0.0.1:8750"],
      "env": { "REDCASTLE_AGENT_TOKEN": "rc_agent_..." }
    }
  }
}

Without --gateway the castle is opened in-process (needs --root and access to .redcastle/).

How it works

Life of a request

received ──► rejected     (Moat: frozen agent, invalid key, protected object, rate limit …)
         ──► trapped      (a canary: the agent is frozen and only ever sees "rejected")
         ──► failed       (storage error; nothing was deleted)
         ──► quarantined ──► approved ──► executed
                         └─► restored

The roles in the code

Role

File

What it does

Gateway

gateway.py

Authenticates the agent and walks the request through the castle. Has no delete function.

Judge

judge.py

Its own 0–100 risk score with explained factors; flags agents that under- or overestimate.

Quarantine

quarantine.py

Moves the object out of sight and records its checksum.

Throne Room

throne.py

Approve (seals the decision with an HMAC), restore, freeze or unfreeze agents.

Executioner

executioner.py

Holds the only storage handle with can_purge=True; checks seal and checksum before deleting.

Chronicle

chronicle.py

Hash-chained, HMAC-signed log; SQLite triggers refuse UPDATE and DELETE.

Moat

moat.py

Frozen agents, key validation, protected patterns, required reason and score, rate limits.

Watchtower

watchtower.py, notify.py

Alerts, automatic freezes, patrols, reminders, desktop notifications, webhook.

Traps

traps.py

Canaries. Asking to delete one freezes the agent on the spot.

Storage backends: local folder and S3. Background mode: daemon.py.

The Judge

The Judge starts at 10 points and adds or subtracts for:

  • sensitive paths (*.env, *secret*, *backup*, *.db, *prod* …) and low-value ones (*.tmp, *.log, __pycache__ …)

  • size (≥ 1 / 10 / 100 MB), and whether the object changed in the last hour or day

  • the agent's behaviour: bursts of requests, earlier underestimates

  • a terse reason

If the Judge's score is at least discrepancy_threshold (25) above the agent's own, the request is flagged as underestimated. The notification says so, and three of those within an hour freeze the agent. Patterns and weights live in redcastle.toml.

Around the wall

  • Moat: see [moat] in redcastle.toml. The castle's own files (.redcastle/, redcastle.toml) are never reachable, even if the realm is the castle's own folder.

  • Watchtower: freezes an agent on a sprung trap, after 5 Moat rejections within 10 minutes, or after 3 underestimates within an hour. The running castle patrols every minute: the Chronicle must verify, every canary must be in place and unchanged, and every quarantined object must match its checksum. Each new problem raises an urgent notification. An optional webhook_url receives alerts as JSON.

  • Traps: redcastle traps plant "backups/db-dump-prod.sql" writes a decoy with fake credentials, or turns an existing object into a canary.

S3

[storage]
backend = "s3"
bucket = "my-bucket"
prefix = "data/"                      # what agents can see
quarantine_prefix = ".redcastle-quarantine/"
profile = "redcastle"                 # the castle's AWS profile
executioner_profile = "redcastle-executioner"

Quarantine copies the object to quarantine_prefix and removes the original. With bucket versioning enabled, that removal is only a delete marker, so nothing is lost. Suggested IAM roles:

  • Agent: no S3 permissions at all, or read-only with an explicit Deny on s3:DeleteObject*.

  • Castle (profile): s3:ListBucket, s3:GetObject, s3:PutObject, s3:DeleteObject, and an explicit Deny on s3:DeleteObjectVersion.

  • Executioner (executioner_profile): s3:DeleteObject, s3:DeleteObjectVersion, s3:ListBucketVersions, s3:GetObject.

The only irreversible call in the whole system is then made with the Executioner's keys.

Security model and limits

  • The Chronicle is tamper-evident, not tamper-proof. Someone holding both the database and secrets.json can rewrite history. Publish the head from redcastle chronicle verify (sequence number and hash) somewhere else now and then to detect truncation too.

  • Run the castle as a different OS user than the agent. An agent with file access to .redcastle/ could run throne approve itself. With the local backend, an agent with a shell can also delete files in realm/ directly; the Traps and the patrols detect and report that, but cannot prevent it. Real isolation needs separate users or containers, or S3 with IAM.

  • Agent tokens are stored only as SHA-256 hashes. Text written by agents (such as object keys) is XML-escaped before it reaches a notification.

  • The HTTP server listens on 127.0.0.1 by default and has no TLS. Put a TLS-terminating reverse proxy in front of it before exposing it to a network.

Development

python -m unittest discover -s tests -t .

CI runs the tests on Windows and Linux with Python 3.11–3.14. macOS is supported (notifications use osascript) but not tested in CI.

License

MIT

Available Tools

4 tools
get_deletion_requestGet deletion requestA
Read-only

Show the current status of one of your deletion requests.

NameRequiredDescriptionDefault
request_idYes

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and openWorldHint=false, so safety and scope are covered. The description adds only the ownership scoping ('your'), and says nothing about behavior when a request is still pending, completed, or the ID is unknown.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single short sentence with no filler, and the verb plus resource are front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple read tool with no output schema, the agent still doesn't know what 'status' contains (states, fields, terminal vs in-progress values) or how to interpret a pending request, which is exactly what the tool exists to reveal.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

One required parameter with 0% schema description coverage, so the description must carry the burden. 'One of your deletion requests' hints at the ID's origin and ownership, but no format, source tool, or error behavior is given for request_id.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb ('Show') and resource ('the current status of one of your deletion requests'), which an agent can distinguish from the sibling tools that create or list requests. It never names those siblings explicitly, so differentiation relies on inference.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase 'one of your deletion requests' implies a request_id obtained from request_deletion or list_my_deletion_requests, but the description gives no explicit when-to-use, no deadline/retention context, and no mention of alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_my_deletion_requestsList my deletion requestsC
Read-only

List your deletion requests, newest first.

NameRequiredDescriptionDefault
statusNo

TDQS

C2.9/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and openWorldHint=false, so the safety profile is covered. The description adds one genuinely new behavioral fact — results are ordered newest first — but says nothing about pagination, result caps, or what happens when the status filter matches nothing.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One short front-loaded sentence containing no filler, and the sort order is stated up front. It is efficient but arguably undersized for a tool with an optional filter parameter.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema and no per-parameter documentation, the description is the only place the status filter could be explained, and it is silent. Annotations cover the read-only nature, but an agent still lacks enough to know how filtering and pagination behave.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The single 'status' parameter has 0% schema description coverage; only the enum values are documented. The description never mentions that filtering by status is possible, so the agent must reverse-engineer the parameter's purpose from its name and enum alone.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('List') and resource ('your deletion requests') plus an ordering guarantee ('newest first'), and the possessive 'your' implicitly scopes results to the caller. It does not explicitly contrast with siblings like list_objects or get_deletion_request, so an agent must infer the boundary from the plural/singular split.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no statement of when to use this tool versus list_objects (for live objects) or get_deletion_request (for a single request), and no prerequisites or exclusions. Usage is only implied by the name and the word 'deletion'.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_objectsList objectsB
Read-only

List the objects you can see. Objects waiting in quarantine are hidden.

NameRequiredDescriptionDefault
prefixNoOnly keys starting with this, e.g. 'reports/'.

TDQS

B3.3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish readOnlyHint=true and openWorldHint=false. The description adds useful behavioral context about visibility scoping—quarantined objects are hidden—but says nothing about pagination, ordering, permissions, or how hidden objects relate to the sibling deletion-request tools.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with the core action and followed by the key visibility constraint. There is no wasted wording.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple read-only listing tool this is close to adequate, but with no output schema the description does not indicate the return shape, pagination behavior, or whether an empty result is possible—details an agent might need to use the results correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the single prefix parameter is documented in the schema with an example. The description adds no parameter semantics beyond that, which is acceptable given the schema already carries the burden.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb+resource: 'List the objects you can see.' It is clearly a listing tool and is distinct from the deletion-request siblings, though it does not explicitly contrast with them.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no explicit guidance on when to use this tool versus alternatives, nor any prerequisites or exclusions. The implicit usage is obvious for a list tool, but the description provides no real routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

request_deletionRequest deletionA

Ask Red Castle to delete one object. Nothing is deleted right away: the object is hidden in quarantine until a human approves (permanent deletion) or restores it. Use get_deletion_request to follow up.

NameRequiredDescriptionDefault
reasonYesWhy it should be deleted. The human reads this.
object_keyYesKey of the object, exactly as list_objects shows it.
risk_scoreYesYour honest estimate of how risky deleting it is (0 = harmless, 100 = disastrous).

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Adds substantial behavior beyond the annotations: the deletion is not immediate, the object is hidden in quarantine, and a human either approves permanent deletion or restores it. This resolves the potentially confusing combination of destructiveHint=false with a deletion tool and tells the agent the real-world lifecycle effect.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences, each earning its place: what it does, the quarantine/approval consequence, and how to follow up. The key behavioral fact (nothing is deleted right away) is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation tool with annotated safety hints and no output schema, the description covers the lifecycle and the follow-up tool. It stops short of saying what identifier or object reference the agent should use with get_deletion_request, a minor remaining gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so reason, object_key, and risk_score are already well documented in the schema, including that the human reads the reason and the 0-100 risk scale. The description only implies 'one object' and adds no further parameter guidance, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Ask Red Castle to delete one object') with explicit scope of exactly one object. It also differentiates the follow-up path by naming get_deletion_request, so an agent can distinguish it from siblings without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Clearly establishes the context of use (requesting deletion of an object) and points to get_deletion_request for follow-up. It does not state explicit exclusions such as when to use list_my_deletion_requests or when not to request deletion, so it falls short of a full when/when-not treatment.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv0.1.0
    • First observedget_deletion_request
    • First observedlist_my_deletion_requests
    • First observedlist_objects
    • First observedrequest_deletion

TDQS

A3.7/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: requesting deletion, listing visible objects, checking one request, and listing all of the user's requests. The single-request vs list-request distinction is explicitly clarified by the descriptions.

Naming Consistency5/5

All tool names use snake_case with a predictable verb-object pattern: request_deletion, list_objects, get_deletion_request, list_my_deletion_requests. The slight inclusion of 'my' is readable and consistent with the possessive scope.

Tool Count5/5

Four tools is well-scoped for a deletion-request workflow. Each tool earns its place by covering requesting, browsing targets, checking status, and listing requests.

Completeness4/5

The core deletion-request lifecycle is covered: create a request, inspect status, list requests, and view visible objects. A cancellation or withdrawal operation for pending requests is a minor gap, but human approval/restore is intentionally outside the agent surface.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI agents to access registered resources based on a fixed subject identity, enforcing default-deny policies and auditing for controlled context sharing.
    Apache 2.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to build and manage Genesys Cloud resources such as queues, skills, users, wrap-up codes, Architect flows, and outbound campaign cadences, while deliberately preventing deletes and leaving campaigns off until humans start them.
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Enables MCP-capable AI clients to read a local AI ticket triage queue and human decision log through four read-only tools for checking queue status, listing pending review items, viewing recent decisions, and searching decisions. It does not allow approving, rejecting, or sending anything.
    4
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Enables AI agents to inspect and manage video renders, templates, generation models, hosted assets, and ingest sources while using isolated private account profiles. It requires explicit approval for mutation operations and supports both MCP and CLI access.
    23
    30 npm
    AGPL 3.0