Most discussions about building with Claude begin with prompt engineering.
Developers learn how to write clearer instructions, use XML tags, add examples, adjust temperature, and structure conversations. These techniques matter, but they are only one part of building a reliable AI application.
The harder question is:
How do you design a system that gives Claude the right context, the right tools, and the right feedback while keeping the application predictable?
That is the difference between a convincing demo and a dependable product.
Claude is a component, not the whole system
Claude can interpret information, generate text, reason through problems, and request tools. But it does not automatically know:
what happened in a previous API request
whether a tool call succeeded
which document is authoritative
whether an answer is supported by evidence
whether a business rule has been satisfied
whether its output is good enough for production
Those responsibilities belong to the application.
A useful architecture looks like this:
User request
↓
Application assembles context
↓
Claude interprets the request
↓
Claude requests a tool when necessary
↓
Application validates and executes the tool
↓
Application observes the result
↓
Claude produces the next response
Claude participates in the loop, but the surrounding system controls the boundaries.
A serious AI application therefore needs to answer five questions:
What information should Claude receive?
What actions should Claude be allowed to request?
How will the application execute those actions safely?
How will the system verify the result?
How will output quality be measured over time?
Context is an engineering decision
When you send information to Claude, you are constructing its working environment.
Poor context design creates predictable problems. Important instructions become buried, unrelated information competes for attention, and users cannot tell which source supports an answer.
Suppose an application needs Claude to analyse customer reviews alongside sales data. Sending everything as one large paragraph makes the roles of the two data sources unclear.
A better structure is:
<customer_reviews>
Review data goes here.
</customer_reviews>
<sales_data>
Sales data goes here.
</sales_data>
<task>
Compare customer sentiment with sales performance.
Identify possible relationships and explain uncertainty.
</task>
The tags are not magic. Their value is that they create explicit boundaries between information with different meanings.
This approach is useful for separating:
instructions
reference documents
user-generated content
structured records
examples
output requirements
Claude should not have to guess whether a paragraph is an instruction, evidence, or an example.
Conversation history is application state
The Claude API does not automatically carry context from one independent request to the next.
If a user asks:
What is pizza?
and then asks:
What toppings are popular?
the application must include the relevant earlier exchange for Claude to understand the reference.
messages = [
{"role": "user", "content": "What is pizza?"},
{
"role": "assistant",
"content": "Pizza is a dish made with a baked dough base and toppings."
},
{"role": "user", "content": "What toppings are popular?"}
]
This creates an architectural responsibility. The application must decide:
how much history to retain
when to summarise older messages
which messages are relevant
what sensitive data should be removed
whether previous tool results should remain available
Conversation history is not merely a chat feature. It is state management.
Tools create a bridge to the real world
Claude’s training data is not a live connection to your application.
Without tools, Claude cannot automatically inspect:
current order status
private company documents
inventory records
internal databases
live customer information
application-specific calculations
Tool use allows Claude to request information or actions from external systems.
A tool may be backed by an ordinary application function:
def get_order_status(order_id):
return {
"order_id": order_id,
"status": "shipped"
}
The application remains responsible for execution:
Describe the tool to Claude.
Receive Claude’s tool-use request.
Validate the arguments.
Check permissions.
Execute the function.
Return the result to Claude.
Continue the conversation.
Claude proposes an action. The application decides whether and how that action is executed.
That separation is essential for security and reliability.
Flexible tools are more useful than narrow tools
A developer may create tools for every anticipated task:
write_python_function
debug_code
generate_customer_report
These tools can work initially, but they limit the system to scenarios the developer has already predicted.
More abstract tools are generally more composable:
read_file
write_file
run_command
search_documents
get_customer
update_record
Claude can combine these capabilities to handle requests that were not explicitly planned.
However, flexible tools still require strict boundaries. Each tool should have:
input validation
permission checks
safe defaults
timeouts
useful error messages
audit logging
clearly defined outputs
The objective is not to give Claude unlimited power. It is to give Claude useful capabilities while keeping execution under application control.
Model Context Protocol reduces integration friction
Model Context Protocol, or MCP, provides a standard communication layer for exposing tools, resources, and prompts to AI applications.
An MCP server can provide:
Tools that perform actions
Resources that expose information
Prompts that provide reusable instructions
An MCP client acts as the bridge between Claude and those servers.
The architecture looks like this:
Claude application
↓
MCP client
↓
MCP server
↓
External service
For example, an MCP server could wrap a document system and expose resources for reading documents and tools for searching or updating them.
This approach moves integration logic closer to the service it represents. The main application does not need to manually recreate every tool definition and execution path.
MCP is also transport agnostic. Clients and servers can communicate using different methods, including standard input/output and HTTP-based transports.
The broader lesson is architectural separation: the client connects Claude to capabilities, while specialised servers own the implementation of those capabilities.
Workflows should come before agents
Agents are useful when the route to a solution is uncertain. They can decide which tools to use and what steps to take next.
But not every task requires that flexibility.
If the steps are known in advance, a workflow is usually easier to test, monitor, and operate.
For example:
Receive document
↓
Extract content
↓
Classify document
↓
Generate summary
↓
Evaluate summary
↓
Return result
A workflow is appropriate when the process is predictable.
An agent is more appropriate when:
the task varies significantly between requests
the available tools must be selected dynamically
the number of steps is unknown
the system must adapt to changing conditions
A useful rule is:
Use workflows for known paths and agents for uncertain paths.
Many systems become unnecessarily expensive and difficult to debug because they use an agent where a few deterministic functions would have been enough.
Chaining and parallelisation solve different problems
If Claude keeps missing requirements in a long prompt, chaining can help.
Instead of asking for everything at once:
Generate, fact-check, format, review, and rewrite this report.
use focused stages:
1. Generate the report.
2. Check it against the requirements.
3. Identify unsupported claims and missing sections.
4. Produce the revised report.
Each stage has a narrower responsibility, making the process easier to test.
Parallelisation is useful when tasks are independent.
Suppose a recommendation must compare metal, plastic, ceramic, and wood. Each material can be evaluated separately using criteria specific to that material. The results can then be combined.
The patterns are different:
Chaining: dependent sequential steps
Parallelisation: independent tasks
Routing: different pipelines for different request types
Evaluator-Optimizer: generate, assess, improve, and repeat
The design should follow the structure of the problem rather than the popularity of the pattern.
Evaluation is part of development
A prompt can sound better while producing worse results.
Prompt engineering should therefore be treated as an experiment, not an opinion.
A basic evaluation loop is:
Create representative test cases
↓
Run the prompt
↓
Collect Claude's responses
↓
Score the responses
↓
Compare with the previous version
↓
Keep or reject the change
A grader may be:
a programmatic check
another AI model
a human reviewer
For structured output, programmatic validation may be enough. For writing quality, a model grader or human review may be more appropriate. Higher-risk use cases may require a combination.
The important principle is:
Never assume a prompt improvement worked because the result sounds better.
Measure it against representative examples.
Inspection completes the agent loop
An agent that can act but cannot inspect the environment is incomplete.
After an action, the system should determine:
whether the action succeeded
what changed
whether the result matches expectations
whether another action is required
The loop is:
Observe
↓
Reason
↓
Act
↓
Inspect
↓
Adapt
This is especially important for coding assistants and systems that interact with desktop environments.
Without inspection, an agent may continue from a false assumption and compound the error.
The production principle
The strongest lesson from building with Claude is that reliability comes from system design.
Prompts matter, but so do:
explicit context boundaries
controlled tool access
MCP-based integrations
deterministic workflows
citations and provenance
prompt caching
evaluation
environment inspection
secure API-key handling
Claude can be remarkably capable. But capability is not the same as reliability.
Reliable AI products are built by designing the surrounding system so Claude has enough context to act intelligently, enough tools to be useful, and enough feedback to recover when its first attempt is not good enough.
That is the real work of building with Claude.







1 comment
Sign in to join the discussion. New to Empirq? Create an account.
I found this article very interesting. I've used Claude, Codex, Antigravity (not fully committed) and a few open-weight models in my development and experiments, and I agree with most of it. In my experience, context can drift in long sessions if I'm not careful, small errors compound and the work slowly moves in the wrong direction. That's also why memory management matters to me: I use Obsidian's vault to keep context and history for the LLM tools I work with, and it helps a lot. This is also the reason I still review majority of the code myself and test piece by piece. I don't fully rely on automation yet, because I need to understand the result to judge its quality, and the decision stays with me. I also like the permission checks and audit logging in the tools section, since we're giving LLMs our important documents and we can't guarantee how they are handled. A lot of good points to learn from, worth reading.