I spent an afternoon last week chasing a bug where an AI endpoint returned one user's email address to another user. The model was innocent; my code had a module-level list that accumulated every request's context. It wasn't a model-memory problem, it was an application-memory problem, and it made me rethink how we treat context.
We keep talking about AI models having memory, but the real issue is that we give them memory by accident. Every global variable, every cached conversation, every 'smart' retry that tacks on previous messages turns a stateless inference call into a hidden state machine. And a hidden state machine is the last thing you want in production.
Here's where free tiers come in. A free model with a small context window won't let you stuff the entire conversation history into the prompt. A free server gives you just enough compute to build a context-assembly layer without overprovisioning. Both forces push you toward a stateless design: pass the minimum context the model needs, every time, and let your application own the state.
Disclosure: This article was prepared as part of MonkeyCode's product outreach. I've been using MonkeyCode's free model access and free server option for this exact workflow, and it fits the pattern beautifully.
MonkeyCode is an open-source project that offers a free tier with a generous token allowance (currently 10M in my plan) and a free server target for your own services. I won't quote latency or uptime numbers because they change; what matters is the architectural discipline it lets you practice.
Here's the pattern I use. The client sends a request and a user ID. The server—running on a free MonkeyCode server—loads only the facts relevant to that user, constructs a one-shot prompt, and calls the model. No session state, no hidden history.
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import db, llm # your storage and MonkeyCode client
app = FastAPI()
class AskRequest(BaseModel):
user_id: str
question: str
class AskResponse(BaseModel):
answer: str
@app.post("/ask", response_model=AskResponse)
async def ask(req: AskRequest):
# Load only the facts needed for this question
context = db.get_relevant_facts(req.user_id, req.question)
if not context:
raise HTTPException(status_code=404, detail="No context for this user")
# One-shot, stateless call
answer = llm.call(
"You answer with facts only. Reply using the given context.",
f"Context: {context}\n\nQuestion: {req.question}"
)
return AskResponse(answer=answer)
Because the model call is one-shot, you can test it in isolation. Two requests with the same context produce the same result (modulo temperature). And because the server owns the context, you can change models, add guardrails, or version your prompts without touching the client.
The free tier forces this discipline. A 10M-token allowance is generous, but it's still finite; you won't waste it on giant prompts. A free server is also finite; you'll write code that runs within its limits. These constraints are features, not bugs.
To verify you're actually stateless, write a test that replays the same request twice and asserts the same answer (with temperature set to 0). Then delete the 'context' and see if the model still returns something—it shouldn't, if your system is honest. Here's a minimal pytest suite that captured my regression:
def test_stateless_ask():
c1 = client.post("/ask", json={"user_id": "42", "question": "What's my plan?"})
c2 = client.post("/ask", json={"user_id": "42", "question": "What's my plan?"})
assert c1.json() == c2.json()
def test_missing_context_rejected():
r = client.post("/ask", json={"user_id": "nobody", "question": "hi"})
assert r.status_code == 404
The second test might surprise you. A model that 'remembers' would return something even with no context. My stateless service refuses. That's the point.
But this pattern isn't for every AI feature. If you're building a chatbot that genuinely needs multi-turn memory, you'll need to store that state somewhere—a database, a vector store, or a cache. The trick is to make that state explicit and retrievable, not implicit in the model call. Also, free tiers have rate limits and no SLA; don't build a critical path on them without a fallback. You've been warned.
Here's my short checklist for adopting this pattern: make sure every model call is one-shot with all needed context in the prompt, the server explicitly loads that context, and a test proves the endpoint rejects missing context. That's the contract.
MonkeyCode's free tier is a great way to try this without spinning up your own GPU. Check the repo, and let me know which layer of your AI stack is the most stateful—I'm curious.
Top comments (0)