{"slug": "blog-9-i-gave-my-minecraft-mod-a-serverless-brain-and-it-decides-how-many-allies", "title": "Blog #9: I Gave My Minecraft Mod a Serverless Brain — And It Decides How Many Allies to Summon Based on How Screwed You Are", "summary": "A developer has created a Minecraft mod that integrates a serverless AI agent to dynamically determine the number of allies summoned based on the player's combat situation. The mod, called Cloud Swords, uses AWS services like Lambda and DynamoDB to process combat context and return decisions, with a fallback rule-based system. The developer detailed the architecture, including API key authentication and a deployment timer, in a blog post.", "body_md": "*Cloud Swords Mod — Blog 9*\n\nI'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.\n\nBut 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.\n\nThe game never freezes. The AI never breaks balance. And the whole thing runs on a serverless backend that costs essentially nothing.\n\nHere's how I built it.\n\nEvery 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).\n\nWhen you first pick it up, it generates a unique API key (`csk-XXXX`\n\nformat) and shows 100 charges. It glows with enchant shimmer. It's your passport to the cloud.\n\n```\n// On first inventory tick, generate unique key\nif (!stack.getNbt().contains(\"api_key\")) {\n    String key = \"csk-\" + UUID.randomUUID().toString().substring(0, 8);\n    stack.getNbt().putString(\"api_key\", key);\n    stack.getNbt().putInt(\"charges\", 100);\n}\n```\n\nThe 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.\n\nThe **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:\n\nDrop both in, wait ~8 seconds (the \"deployment timer\"), and your weapon gets marked with `cloud_enabled=true`\n\nin its NBT data. The tooltip now shows \"☁ Cloud Enabled\" in cyan.\n\nThis is the CI/CD pipeline of the mod. You're deploying your weapon to the cloud.\n\nOnce cloud-enabled, the Sword of Lambda changes behavior completely. Instead of spawning a hardcoded number of minions, it:\n\nIf you're NOT cloud-enabled, the sword works exactly as before. Backward compatible. No cloud required.\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                     MINECRAFT CLIENT                         │\n│                                                             │\n│  Player swings Sword of Lambda (cloud-enabled)              │\n│       │                                                     │\n│       ▼                                                     │\n│  CloudCombatHandler builds context:                         │\n│  {player, weapon, health, biome, dimension, nearby_mobs}   │\n│       │                                                     │\n│       ▼                                                     │\n│  CloudApiClient.sendEvent(context)  ← async, non-blocking  │\n└───────┬─────────────────────────────────────────────────────┘\n        │ POST /event\n        ▼\n┌─────────────────────────────────────────────────────────────┐\n│                    BACKEND (CDK Stack)                        │\n│                                                             │\n│  API Gateway (rate limited: 100/day, 10/sec)                │\n│       │                                                     │\n│       ▼                                                     │\n│  Lambda: IngestEvent                                        │\n│  - Receives context                                         │\n│  - Calls Strands Agent (or rule-based fallback)             │\n│  - Stores decision in DynamoDB (TTL: 60s)                   │\n│  - Returns {request_id}                                     │\n│                                                             │\n│  DynamoDB: CloudSwordsResults                               │\n│  - Partition key: request_id                                │\n│  - TTL auto-cleanup                                         │\n│                                                             │\n│  Lambda: PollResult                                         │\n│  - GET /result/{request_id}                                 │\n│  - Returns decision when ready                              │\n└───────┬─────────────────────────────────────────────────────┘\n        │ Poll every 2s\n        ▼\n┌─────────────────────────────────────────────────────────────┐\n│  CloudApiClient receives decision                           │\n│       │                                                     │\n│       ▼                                                     │\n│  CloudCombatHandler.applyDecision()                         │\n│  - Summon \"☁ Cloud Invocation\" minions (named, cyan)        │\n│  - Or apply buff (regeneration, resistance, strength)       │\n│  - Or do nothing (\"No action needed\")                       │\n└─────────────────────────────────────────────────────────────┘\n```\n\nThe game can NEVER freeze waiting for a network response. So I used the same pattern you'd use in any real-time application:\n\n`request_id`\n\nimmediately`ServerTickEvents`\n\n), check if the result is ready\n\n```\n// CloudApiClient — non-blocking\npublic void sendEvent(JsonObject context) {\n    httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())\n        .thenAccept(response -> {\n            String requestId = parseRequestId(response.body());\n            startPolling(requestId);\n        });\n}\n\n// Polling on server tick (every 40 ticks = 2 seconds)\nServerTickEvents.END_SERVER_TICK.register(server -> {\n    // Execute queued callbacks on server thread\n    Runnable callback;\n    while ((callback = pendingCallbacks.poll()) != null) {\n        callback.run();\n    }\n});\n```\n\nThe threading fix was critical. Java's `HttpClient`\n\nruns callbacks on its own async thread — but Minecraft entity spawning MUST happen on the server thread. Without the `ConcurrentLinkedQueue`\n\nbridge, buffs wouldn't apply and entities would crash the game.\n\nThe brain is a Strands Agent running `gpt-oss:20b`\n\nvia Ollama Cloud (free tier — no Bedrock cost for development):\n\n``` python\nfrom strands import Agent\nfrom strands.models.ollama import OllamaModel\n\nSYSTEM_PROMPT = \"\"\"You are a combat advisor for a Minecraft RPG mod.\nGiven player context, decide ONE action.\n\nAvailable actions (respond ONLY with valid JSON):\n- {\"action\": \"summon\", \"count\": 1-5, \"duration\": 5-15, \"reason\": \"brief\"}\n- {\"action\": \"buff\", \"effect\": \"regeneration|resistance|strength|speed\", \"duration\": 3-10, \"reason\": \"brief\"}\n- {\"action\": \"none\", \"reason\": \"brief\"}\n\nRules:\n- If health < 8 and many mobs: summon max allies or buff resistance\n- If health > 15 and few mobs: action none or summon 1\n- Scale response to threat level\n- ONLY output the JSON object, nothing else\"\"\"\n\nmodel = OllamaModel(\n    host=\"https://api.ollama.com\",\n    model_id=\"gpt-oss:20b\",\n)\nagent = Agent(model=model, system_prompt=SYSTEM_PROMPT)\n```\n\nThe agent receives context like:\n\n```\n{\n  \"player\": \"Carlos\",\n  \"weapon\": \"sword_of_lambda\",\n  \"health\": 6,\n  \"nearby_mobs\": 8,\n  \"biome\": \"nether_wastes\",\n  \"dimension\": \"minecraft:the_nether\"\n}\n```\n\nAnd responds:\n\n```\n{\"action\": \"summon\", \"count\": 5, \"duration\": 10, \"reason\": \"Critical threat\"}\n```\n\nThe 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.\n\nThe production backend is a single CDK stack:\n\n``` python\nclass CloudSwordsStack(Stack):\n    def __init__(self, scope, id, **kwargs):\n        super().__init__(scope, id, **kwargs)\n\n        # DynamoDB for async results\n        results_table = dynamodb.Table(self, \"DecisionResults\",\n            partition_key={\"name\": \"request_id\", \"type\": \"STRING\"},\n            time_to_live_attribute=\"ttl\",\n            billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST,\n        )\n\n        # Lambda: receive event, decide, store\n        ingest_fn = _lambda.Function(self, \"IngestEvent\",\n            runtime=_lambda.Runtime.PYTHON_3_12,\n            handler=\"handler.lambda_handler\",\n            code=_lambda.Code.from_asset(\"lambda/ingest\"),\n        )\n\n        # Lambda: poll result\n        poll_fn = _lambda.Function(self, \"PollResult\",\n            runtime=_lambda.Runtime.PYTHON_3_12,\n            handler=\"handler.lambda_handler\",\n            code=_lambda.Code.from_asset(\"lambda/poll\"),\n        )\n\n        # API Gateway with usage plan (rate limiting = gameplay mechanic)\n        api = apigw.RestApi(self, \"CloudSwordsApi\")\n        plan = api.add_usage_plan(\"Basic\",\n            throttle={\"rate_limit\": 10, \"burst_limit\": 20},\n            quota={\"limit\": 100, \"period\": apigw.Period.DAY},\n        )\n```\n\nThe 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.\n\nFor development, I run a mock server that integrates the Strands Agent directly:\n\n```\n# mock_server.py — localhost:8777\nagent = None\ntry:\n    from combat_advisor import create_agent, decide\n    agent = create_agent()\n    print(\"✓ Strands Agent loaded\")\nexcept:\n    print(\"⚠ Agent unavailable, using rule-based fallback\")\n\ndef decide_action(body):\n    if agent:\n        return decide(agent, body)\n    # Fallback: simple rules\n    if body[\"health\"] < 8 and body[\"nearby_mobs\"] > 5:\n        return {\"action\": \"summon\", \"count\": 5, \"duration\": 10, \"reason\": \"Critical\"}\n    return {\"action\": \"summon\", \"count\": 1, \"duration\": 5, \"reason\": \"Standard\"}\n```\n\nThe mock server logs every interaction to `interactions.json`\n\n— which becomes the eval dataset for Blog 10.\n\n| Game Mechanic | Cloud Concept |\n|---|---|\n| Tome of API Key | API keys + authentication |\n| 100 charges | Rate limiting / usage plans |\n| Cloud API Requester (8s timer) | CI/CD deployment pipeline |\n`cloud_enabled=true` NBT |\nFeature flags |\n| POST /event → request_id | Async event processing |\n| Poll every 2s | Eventual consistency |\n| AI decides action | Serverless compute (Lambda) |\n| Bounded actions (1-5 minions) | Guardrails / IAM boundaries |\n| CDK stack | Infrastructure as Code |\n| Mock server | Local development environment |\n\n**Threading in Minecraft is brutal** — anything touching entities must be on the server thread. Async HTTP + game logic = careful queue management.\n\n**Rate limiting as gameplay works** — players naturally strategize around limited cloud invocations. It teaches resource management without a lecture.\n\n**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.\n\n**Fallback patterns matter** — if the cloud is down, the sword still works with local logic. Graceful degradation, taught through a video game.\n\nThe 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.\n\n**Code:**\n\n*I'm Carlos Cortez — cloud engineer, gamer, and apparently someone who connects Minecraft to AI agents for fun. This is Breaking the Cloud.*", "url": "https://wpnews.pro/news/blog-9-i-gave-my-minecraft-mod-a-serverless-brain-and-it-decides-how-many-allies", "canonical_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_at": "2026-08-01 16:13:30+00:00", "updated_at": "2026-08-01 16:44:31.622916+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-agents"], "entities": ["Minecraft", "AWS", "Lambda", "DynamoDB", "API Gateway", "Cloud Swords Mod"], "alternates": {"html": "https://wpnews.pro/news/blog-9-i-gave-my-minecraft-mod-a-serverless-brain-and-it-decides-how-many-allies", "markdown": "https://wpnews.pro/news/blog-9-i-gave-my-minecraft-mod-a-serverless-brain-and-it-decides-how-many-allies.md", "text": "https://wpnews.pro/news/blog-9-i-gave-my-minecraft-mod-a-serverless-brain-and-it-decides-how-many-allies.txt", "jsonld": "https://wpnews.pro/news/blog-9-i-gave-my-minecraft-mod-a-serverless-brain-and-it-decides-how-many-allies.jsonld"}}