# Blog #9: I Gave My Minecraft Mod a Serverless Brain — And It Decides How Many Allies to Summon Based on How Screwed You Are

> Source: <https://dev.to/aws-heroes/-blog-9-i-gave-my-minecraft-mod-a-serverless-brain-and-it-decides-how-many-allies-to-summon-5bid>
> Published: 2026-08-01 16:13:30+00:00

*Cloud Swords Mod — Blog 9*

I've been building a Minecraft mod that teaches cloud computing through gameplay. Swords named after AWS services. Energy systems that work like network infrastructure. Villagers that trade cloud certifications.

But version 0.3.8 is where things got weird. I connected the mod to an actual AI agent running in the cloud. Now when you swing the Sword of Lambda, instead of spawning a fixed number of minions — an AI evaluates your combat situation and decides how many allies you need.

The game never freezes. The AI never breaks balance. And the whole thing runs on a serverless backend that costs essentially nothing.

Here's how I built it.

Every cloud connection starts with authentication. In the mod, that's the **Tome of API Key** — a rare artifact found in dungeon chests (8% drop rate in bastions, end cities, nether fortresses).

When you first pick it up, it generates a unique API key (`csk-XXXX`

format) and shows 100 charges. It glows with enchant shimmer. It's your passport to the cloud.

```
// On first inventory tick, generate unique key
if (!stack.getNbt().contains("api_key")) {
    String key = "csk-" + UUID.randomUUID().toString().substring(0, 8);
    stack.getNbt().putString("api_key", key);
    stack.getNbt().putInt("charges", 100);
}
```

The tooltip shows your key preview, remaining charges, and connection status. When charges hit zero, the cloud refuses your invocations — just like hitting a rate limit on a real API.

The **Cloud API Requester** is the bridge between Minecraft and AWS. It's a crafted block (Iron + Circuit Board + Redstone + Ender Pearl + Data Core) with two slots:

Drop both in, wait ~8 seconds (the "deployment timer"), and your weapon gets marked with `cloud_enabled=true`

in its NBT data. The tooltip now shows "☁ Cloud Enabled" in cyan.

This is the CI/CD pipeline of the mod. You're deploying your weapon to the cloud.

Once cloud-enabled, the Sword of Lambda changes behavior completely. Instead of spawning a hardcoded number of minions, it:

If you're NOT cloud-enabled, the sword works exactly as before. Backward compatible. No cloud required.

```
┌─────────────────────────────────────────────────────────────┐
│                     MINECRAFT CLIENT                         │
│                                                             │
│  Player swings Sword of Lambda (cloud-enabled)              │
│       │                                                     │
│       ▼                                                     │
│  CloudCombatHandler builds context:                         │
│  {player, weapon, health, biome, dimension, nearby_mobs}   │
│       │                                                     │
│       ▼                                                     │
│  CloudApiClient.sendEvent(context)  ← async, non-blocking  │
└───────┬─────────────────────────────────────────────────────┘
        │ POST /event
        ▼
┌─────────────────────────────────────────────────────────────┐
│                    BACKEND (CDK Stack)                        │
│                                                             │
│  API Gateway (rate limited: 100/day, 10/sec)                │
│       │                                                     │
│       ▼                                                     │
│  Lambda: IngestEvent                                        │
│  - Receives context                                         │
│  - Calls Strands Agent (or rule-based fallback)             │
│  - Stores decision in DynamoDB (TTL: 60s)                   │
│  - Returns {request_id}                                     │
│                                                             │
│  DynamoDB: CloudSwordsResults                               │
│  - Partition key: request_id                                │
│  - TTL auto-cleanup                                         │
│                                                             │
│  Lambda: PollResult                                         │
│  - GET /result/{request_id}                                 │
│  - Returns decision when ready                              │
└───────┬─────────────────────────────────────────────────────┘
        │ Poll every 2s
        ▼
┌─────────────────────────────────────────────────────────────┐
│  CloudApiClient receives decision                           │
│       │                                                     │
│       ▼                                                     │
│  CloudCombatHandler.applyDecision()                         │
│  - Summon "☁ Cloud Invocation" minions (named, cyan)        │
│  - Or apply buff (regeneration, resistance, strength)       │
│  - Or do nothing ("No action needed")                       │
└─────────────────────────────────────────────────────────────┘
```

The game can NEVER freeze waiting for a network response. So I used the same pattern you'd use in any real-time application:

`request_id`

immediately`ServerTickEvents`

), check if the result is ready

```
// CloudApiClient — non-blocking
public void sendEvent(JsonObject context) {
    httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
        .thenAccept(response -> {
            String requestId = parseRequestId(response.body());
            startPolling(requestId);
        });
}

// Polling on server tick (every 40 ticks = 2 seconds)
ServerTickEvents.END_SERVER_TICK.register(server -> {
    // Execute queued callbacks on server thread
    Runnable callback;
    while ((callback = pendingCallbacks.poll()) != null) {
        callback.run();
    }
});
```

The threading fix was critical. Java's `HttpClient`

runs callbacks on its own async thread — but Minecraft entity spawning MUST happen on the server thread. Without the `ConcurrentLinkedQueue`

bridge, buffs wouldn't apply and entities would crash the game.

The brain is a Strands Agent running `gpt-oss:20b`

via Ollama Cloud (free tier — no Bedrock cost for development):

``` python
from strands import Agent
from strands.models.ollama import OllamaModel

SYSTEM_PROMPT = """You are a combat advisor for a Minecraft RPG mod.
Given player context, decide ONE action.

Available actions (respond ONLY with valid JSON):
- {"action": "summon", "count": 1-5, "duration": 5-15, "reason": "brief"}
- {"action": "buff", "effect": "regeneration|resistance|strength|speed", "duration": 3-10, "reason": "brief"}
- {"action": "none", "reason": "brief"}

Rules:
- If health < 8 and many mobs: summon max allies or buff resistance
- If health > 15 and few mobs: action none or summon 1
- Scale response to threat level
- ONLY output the JSON object, nothing else"""

model = OllamaModel(
    host="https://api.ollama.com",
    model_id="gpt-oss:20b",
)
agent = Agent(model=model, system_prompt=SYSTEM_PROMPT)
```

The agent receives context like:

```
{
  "player": "Carlos",
  "weapon": "sword_of_lambda",
  "health": 6,
  "nearby_mobs": 8,
  "biome": "nether_wastes",
  "dimension": "minecraft:the_nether"
}
```

And responds:

```
{"action": "summon", "count": 5, "duration": 10, "reason": "Critical threat"}
```

The system prompt enforces bounded actions — the AI can't break game balance because it can only choose from a fixed set of actions with capped values.

The production backend is a single CDK stack:

``` python
class CloudSwordsStack(Stack):
    def __init__(self, scope, id, **kwargs):
        super().__init__(scope, id, **kwargs)

        # DynamoDB for async results
        results_table = dynamodb.Table(self, "DecisionResults",
            partition_key={"name": "request_id", "type": "STRING"},
            time_to_live_attribute="ttl",
            billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST,
        )

        # Lambda: receive event, decide, store
        ingest_fn = _lambda.Function(self, "IngestEvent",
            runtime=_lambda.Runtime.PYTHON_3_12,
            handler="handler.lambda_handler",
            code=_lambda.Code.from_asset("lambda/ingest"),
        )

        # Lambda: poll result
        poll_fn = _lambda.Function(self, "PollResult",
            runtime=_lambda.Runtime.PYTHON_3_12,
            handler="handler.lambda_handler",
            code=_lambda.Code.from_asset("lambda/poll"),
        )

        # API Gateway with usage plan (rate limiting = gameplay mechanic)
        api = apigw.RestApi(self, "CloudSwordsApi")
        plan = api.add_usage_plan("Basic",
            throttle={"rate_limit": 10, "burst_limit": 20},
            quota={"limit": 100, "period": apigw.Period.DAY},
        )
```

The usage plan isn't just infrastructure — it's a gameplay mechanic. 100 requests per day means you can't spam the cloud. You have to be strategic about when you invoke it. Just like real API budgets.

For development, I run a mock server that integrates the Strands Agent directly:

```
# mock_server.py — localhost:8777
agent = None
try:
    from combat_advisor import create_agent, decide
    agent = create_agent()
    print("✓ Strands Agent loaded")
except:
    print("⚠ Agent unavailable, using rule-based fallback")

def decide_action(body):
    if agent:
        return decide(agent, body)
    # Fallback: simple rules
    if body["health"] < 8 and body["nearby_mobs"] > 5:
        return {"action": "summon", "count": 5, "duration": 10, "reason": "Critical"}
    return {"action": "summon", "count": 1, "duration": 5, "reason": "Standard"}
```

The mock server logs every interaction to `interactions.json`

— which becomes the eval dataset for Blog 10.

| Game Mechanic | Cloud Concept |
|---|---|
| Tome of API Key | API keys + authentication |
| 100 charges | Rate limiting / usage plans |
| Cloud API Requester (8s timer) | CI/CD deployment pipeline |
`cloud_enabled=true` NBT |
Feature flags |
| POST /event → request_id | Async event processing |
| Poll every 2s | Eventual consistency |
| AI decides action | Serverless compute (Lambda) |
| Bounded actions (1-5 minions) | Guardrails / IAM boundaries |
| CDK stack | Infrastructure as Code |
| Mock server | Local development environment |

**Threading in Minecraft is brutal** — anything touching entities must be on the server thread. Async HTTP + game logic = careful queue management.

**Rate limiting as gameplay works** — players naturally strategize around limited cloud invocations. It teaches resource management without a lecture.

**AI guardrails through system prompts** — bounding the action space (1-5 minions, specific buffs) means the AI literally cannot break the game. The worst it can do is summon 5 minions when 3 would suffice.

**Fallback patterns matter** — if the cloud is down, the sword still works with local logic. Graceful degradation, taught through a video game.

The AI makes decisions. But are they good decisions? In Blog 10, I build an evaluation framework that judges the agent's combat advice — using another AI as the judge. Meta? Absolutely. Useful? Surprisingly yes.

**Code:**

*I'm Carlos Cortez — cloud engineer, gamer, and apparently someone who connects Minecraft to AI agents for fun. This is Breaking the Cloud.*
