Most AI agent demos start with the model.
We ended up spending far more time thinking about what gets put around the model.
I run 23 holiday lets and have been building AI agents into the day-to-day operation. Guest messaging sounds like one of the easier problems:
- Guest sends a message.
- Give the message to an LLM.
- Send back the answer.
That works brilliantly right up until the guest asks:
Can I park a second car?
Now the answer depends on the property, the booking, the parking arrangement, whether they're currently checked in, whether anything has changed since the listing was written and potentially something a member of the team said twenty minutes ago.
The model is suddenly the easy bit.
While building Zugrow, one of the lessons that kept coming back was this:
An agent can have a very good model and still make a bad decision because you gave it the wrong state.
So we stopped treating context as a giant blob of text and started treating it like application data.
1. Don't give the model everything you know
My instinct initially was simple.
More context = better answer.
So if a guest messaged about a booking, why not give the agent:
- the property description
- all amenities
- house rules
- the entire conversation
- booking details
- previous guest questions
- internal notes
- host instructions
It feels sensible.
It also produces a mess.
Important information gets buried amongst things that have nothing to do with the current question.
Instead, the agent should get the smallest useful view of reality.
Something closer to:
type GuestContext = {
property: {
name: string;
checkInTime: string;
checkOutTime: string;
parking: ParkingPolicy;
};
booking: {
arrivalDate: string;
departureDate: string;
guestCount: number;
status: BookingStatus;
};
conversation: {
recentMessages: Message[];
};
};
If somebody asks about parking, the agent doesn't need the Wi-Fi password, the boiler instructions and six months of pricing history.
Give it what it needs to answer the question in front of it.
That sounds obvious.
In an agent system, it is surprisingly easy to forget.
2. Separate facts from instructions
This made a bigger difference than I expected.
Consider:
Parking is available behind the building.
Guests should normally use Bay 14.
Sometimes another space may be available.
Do not guarantee a second space.
There are two completely different things happening here.
The first three sentences describe the world.
The last sentence describes what the agent is allowed to do.
Mixing those together makes the prompt harder to reason about.
We now think of them separately:
const facts = {
parkingType: "allocated",
primaryBay: "14",
additionalSpacePossible: true
};
const policy = {
mayGuaranteeAdditionalSpace: false
};
The distinction matters because facts can change.
Policy usually changes much less often.
It also means the application can enforce some rules without relying on the model remembering them.
3. Database state beats listing text
Listings are written for humans.
Agents need structured state.
Suppose the listing says:
Parking is available for guests.
Perfectly reasonable marketing copy.
But the agent needs to know:
{
parkingAvailable: true,
guaranteedSpaces: 1,
extraSpacesRequireApproval: true
}
Those two things communicate roughly the same information to a human.
They are very different inputs for software.
The more agents we added, the more I found myself converting vague property information into explicit state.
Instead of:
Early check-in may sometimes be available.
Store:
{
standardCheckIn: "15:00",
earlyCheckInAllowed: true,
earliestPossibleTime: "13:00",
requiresTeamApproval: true
}
The agent can now reason from something much closer to reality.
And more importantly, our application can stop it making promises it shouldn't make.
4. Freshness matters as much as accuracy
There is another problem.
A fact can be correct and still be wrong.
Yesterday:
wifi.status = "working";
Today the router has died.
The database technically contains a fact.
It is just stale.
So useful agent context needs some idea of freshness:
type ContextValue<T> = {
value: T;
updatedAt: Date;
source: "host" | "system" | "channel" | "agent";
};
That opens up much better behaviour.
The application can say:
if (hoursSince(wifi.updatedAt) > 72) {
requireVerification();
}
Or the agent can respond cautiously rather than stating something as certain.
This became an important mental model for me:
Agent context is not knowledge. It is a snapshot.
Snapshots age.
5. Recheck state before doing anything
This matters even more once agents can act.
Imagine this sequence:
10:00:00 Guest asks for early check-in
10:00:02 Agent reads availability
10:00:08 Cleaner changes schedule
10:00:11 Agent confirms early check-in
The model made the right decision using the information it had.
The system still made the wrong decision.
That is a normal software concurrency problem wearing an AI hat.
The fix is boring:
const suggestion = await agent.decide(context);
const latestState = await bookings.getCurrent(bookingId);
if (!stillValid(suggestion, latestState)) {
return requireHumanReview();
}
return execute(suggestion);
We use the model to decide what it would like to do.
The application checks whether it is still allowed to do it.
That second check matters far more than making the prompt another 500 words longer.
6. Give the agent events, not endless history
Conversation history causes the same problem.
It is tempting to keep throwing every previous message into the context window.
But imagine a guest has sent 70 messages during a two-week stay.
Most of that conversation is irrelevant when they ask:
What time is checkout tomorrow?
Instead of treating history as one enormous transcript, you can reduce it into state and recent events.
Something like:
const context = {
booking: currentBooking,
property: relevantPropertyFacts,
recentEvents: [
{
type: "guest_message",
text: "What time is checkout tomorrow?"
},
{
type: "late_checkout_request",
status: "not_requested"
}
]
};
The model gets much less information.
But the information it does get matters more.
That is usually the trade I want.
7. Log what the agent actually saw
This is the part I would build earlier if I started again.
When an agent gives a strange answer, knowing the output is not enough.
You need to know:
What did it believe was true at the time?
So every decision should have a trace.
interface AgentTrace {
agent: string;
contextVersion: string;
input: unknown;
decision: unknown;
model: string;
timestamp: Date;
}
Then when somebody asks:
Why did the agent tell this guest they had two parking spaces?
you don't have to guess.
You can inspect the exact state supplied to the model.
A surprising number of apparent "AI mistakes" turn out to be ordinary software mistakes upstream.
Wrong property.
Old data.
Missing field.
Incorrect booking state.
The model simply gave a perfectly reasonable answer to the reality we accidentally handed it.
The pattern we ended up with
Our agent flow increasingly looks like this:
Guest message
↓
Intent / task
↓
Context builder
↓
Relevant current state
↓
AI decision
↓
Deterministic validation
↓
State recheck
↓
Human approval or action
↓
Audit log
The LLM sits in the middle.
It isn't the application.
That distinction seems obvious written down, but a lot of agent prototypes blur it.
They build:
data → enormous prompt → model → action
Then try to improve reliability by making the enormous prompt even larger.
Eventually you are asking a probabilistic model to compensate for missing application architecture.
That doesn't scale particularly well.
The short version
If I were building an agent system from scratch now:
- Give the model the minimum context required for the current task.
- Store important facts as structured data rather than prose.
- Keep facts and agent permissions separate.
- Track when important context was last updated.
- Recheck state immediately before an agent takes an action.
- Prefer recent events and current state over enormous conversation histories.
- Record exactly what context the model saw when it made a decision.
The strange thing about building AI agents is that the longer I work on them, the less time I spend thinking about the model.
The model is important.
But most of the reliability comes from fairly ordinary software engineering around it.
And honestly, I think that is good news.
I built Zugrow, an AI-first property management platform, and use the same systems across the holiday lets I operate. I'm particularly interested in how other people are handling context construction, stale state and pre-action validation in agent systems.
Top comments (2)
"Agent context is not knowledge. It is a snapshot. Snapshots age." — that is the whole cache-coherence problem wearing an LLM hat, and your 10:00:02 / 10:00:08 timeline is the clearest way I have seen it written down. The model reasoned correctly about a world that had already stopped existing four seconds earlier. Re-deciding is not the fix either; validating the decision against current state before executing is, and it is cheap enough that there is no reason to gate it behind confidence.
Where I would push on the trace:
contextVersionis only useful if it lets you reconstruct exactly what the agent believed at that instant. A schema version tells you which fields existed, not what they held, so when someone disputes an answer you are left reasoning about inputs you cannot recover. The version that earned its keep for me was a hash of the assembled context payload plus the read timestamps of the facts inside it — that turns "why did it say that" into a lookup instead of an archaeology dig. How are you pinning that today across the 23 properties, given the facts come from several systems that move independently?The freshness metadata is the part most agent systems miss. I have found it useful to pair
updatedAtwith an explicit validity window and a provenance field, then make the application block or recheck once that window expires. That keeps "the model noticed the timestamp" from becoming another soft promise. Your point that context is a snapshot, not knowledge, is a very good design rule.