Humans as API Wrappers: A Systems Architecture View of AI-Augmented Work

For years, APIs wrapped human labor. Amazon’s Mechanical Turk was the canonical example — a REST API where the compute was literally human cognition. You’d POST a task, a worker somewhere would process it, and you’d GET a response. The implementation detail was a person, but the interface was pure HTTP.
POST /tasks
{
"type": "image_classification",
"payload": "base64_encoded_image",
"callback_url": "https://your-service.com/results"
}The human was abstracted away. An implementation detail behind a clean interface.
Now we’ve inverted the stack.
Today, knowledge workers have become wrapper classes around foundation models. We instantiate a model, inject context through prompts, call its methods, and post-process the outputs. We’re not the underlying implementation anymore. We’re the abstraction layer on top of it.
This isn’t a metaphor. It’s a literal description of what happens when you work with AI.
The Wrapper Pattern in Software
In software engineering, the wrapper pattern (sometimes called adapter or facade) solves a specific problem: you have a powerful but general-purpose interface, and you need to make it usable for a specific context.
class PaymentGatewayWrapper:
def __init__(self, api_key: str, environment: str):
self.client = StripeClient(api_key)
self.environment = environment
self.retry_policy = ExponentialBackoff(max_retries=3)
def charge(self, amount: Money, customer: Customer) -> Result:
# Context injection
validated_amount = self._apply_business_rules(amount)
# Error handling
try:
response = self.retry_policy.execute(
lambda: self.client.charges.create(
amount=validated_amount.cents,
currency=validated_amount.currency,
customer=customer.stripe_id
)
)
except StripeError as e:
return self._handle_error(e)
# Output transformation
return self._to_domain_result(response)The wrapper adds: initialization logic, context injection, retry policies, error handling, input validation, output transformation. The underlying API is powerful but raw. The wrapper makes it usable.
This is exactly what humans now do with LLMs.
Human Wrapper Architecture
When you use Claude or GPT to do knowledge work, you’re implementing a wrapper with the same structural components.
Constructor: Context Initialization
Before you write a single prompt, you’ve already initialized state. Your mental model of the problem. Your understanding of the stakeholder’s actual needs (often different from their stated requirements). Your knowledge of constraints the model doesn’t have access to.
class HumanWrapper:
def __init__(self):
self.domain_expertise = load_from_experience(years=15)
self.organizational_context = {
"actual_priorities": [...], # Not the mission statement
"political_constraints": [...],
"unwritten_rules": [...]
}
self.taste_model = train_on_exposure(
good_examples=thousands,
bad_examples=thousands
)This initialization is invisible but essential. It’s why a senior and a junior can use the same model and get wildly different results. The wrapper’s constructor matters.
Input Layer: Prompt Engineering as Interface Design
Prompt engineering is interface design. You’re defining the method signature for an API call.
A naive implementation passes the raw request through:
def generate_report(self, request: str) -> str:
return self.llm.complete(request) # Thin wrapper, low value-addA sophisticated implementation transforms the input:
def generate_report(self, request: ReportRequest) -> Report:
# Decompose ambiguous request into structured input
parsed = self._parse_intent(request)
# Inject context the model doesn't have
context = self._build_context(
domain=parsed.domain,
audience=parsed.audience,
constraints=self.organizational_context
)
# Construct prompt with appropriate framing
prompt = self._build_prompt(
task=parsed.task,
context=context,
output_format=self._select_format(parsed.audience),
examples=self._retrieve_relevant_examples(parsed.domain)
)
return self.llm.complete(prompt)The thickness of your input layer — how much transformation and context injection you do — directly correlates with output quality.
Output Validation: The Type Checker You Can’t Automate
LLM outputs are untyped at a semantic level. The model returns a string that looks like what you asked for, but there’s no compiler checking whether it’s actually correct.
You’re the type checker.
def _validate_output(self, output: str, request: ReportRequest) -> ValidationResult:
checks = [
self._check_factual_accuracy(output), # Hallucination detection
self._check_tonal_alignment(output, request.audience),
self._check_completeness(output, request.requirements),
self._check_organizational_fit(output), # Will this survive review?
self._check_implicit_constraints(output), # Things nobody wrote down
]
return ValidationResult(
passed=all(c.passed for c in checks),
issues=[c.issue for c in checks if not c.passed]
)Some of these checks are automatable. Factual accuracy can be partially verified against retrieval sources. Completeness can be checked against explicit requirements.
But check_organizational_fit and check_implicit_constraints? Those require the accumulated state from your constructor. They're the kind of validation that only works if you've loaded the right context at initialization.
Error Handling: Failure Modes and Recovery
LLMs fail in ways that traditional software doesn’t. They don’t throw exceptions; they return confidently wrong outputs.
class LLMFailureMode(Enum):
HALLUCINATION = "factually incorrect, high confidence"
DRIFT = "technically accurate, misses the point"
SYCOPHANCY = "tells you what you want to hear"
REFUSAL = "declines for unclear reasons"
SHALLOW = "correct but surface-level"
VERBOSE = "buries the answer in noise"Your job as a wrapper is to detect these failure modes and handle them:
def _handle_failure(self, output: str, failure_mode: LLMFailureMode) -> str:
match failure_mode:
case LLMFailureMode.HALLUCINATION:
return self._retry_with_retrieval_augmentation()
case LLMFailureMode.DRIFT:
return self._retry_with_explicit_constraints()
case LLMFailureMode.SHALLOW:
return self._retry_with_depth_prompting()
case _:
return self._manual_intervention()This error handling is often implicit and instantaneous. You read the output, recognize the failure mode, and adjust. But it’s real work, and it’s work the model can’t do for itself.
Thin Wrappers vs. Thick Wrappers
In software architecture, thin wrappers add minimal logic — they’re mostly pass-through with some convenience methods. Thick wrappers add substantial transformation, validation, and business logic.
The same distinction applies to human wrappers.
Thin wrapper:
class ThinHumanWrapper:
def do_task(self, request: str) -> str:
output = self.llm.complete(request)
typos_fixed = self._fix_obvious_typos(output)
return typos_fixedThick wrapper:
class ThickHumanWrapper:
def do_task(self, request: str) -> Result:
# Deep request analysis
intent = self._analyze_intent(request)
stakeholders = self._identify_stakeholders(intent)
constraints = self._gather_constraints(intent, stakeholders)
# Multi-stage generation
outline = self._generate_outline(intent, constraints)
validated_outline = self._validate_with_expertise(outline)
draft = self._generate_draft(validated_outline)
# Sophisticated validation
for check in self._get_validation_suite(intent):
draft = self._apply_check(draft, check)
# Accountability layer
return Result(
output=draft,
confidence=self._assess_confidence(draft),
owner=self # I stake my reputation on this
)The value you provide is the delta between what a thin wrapper would produce and what your thick wrapper produces.
As models improve, this delta for thin wrappers approaches zero. The tasks that required light prompting and basic editing become fully automatable. The wrapper gets optimized out of the stack.
Thick wrappers survive because they encode logic that can’t be easily replicated: domain expertise loaded from years of experience, taste models trained on exposure to thousands of examples, organizational context that exists nowhere in the training data.
The Dependency Inversion
There’s an architectural principle called dependency inversion: high-level modules shouldn’t depend on low-level modules; both should depend on abstractions.
The interesting question for human wrappers is: which layer is “high-level”?
In one framing, you’re the high-level module. You define what needs to happen; the LLM is a low-level implementation detail. You could swap out Claude for GPT for Gemini; your logic stays the same.
class KnowledgeWorker:
def __init__(self, llm: LLMInterface): # Dependency injection
self.llm = llm
# Your logic is model-agnosticIn another framing, you’re the adapter layer. The business need is the high-level module; you’re middleware that translates between business requirements and AI capabilities. You could be swapped out for a better adapter — or for a direct integration, if the model gets good enough.
Which framing is correct determines whether you’re building a defensible position or a temporary bridge.
Building a Defensible Wrapper
If you accept this model, the strategic question is: how do you build a wrapper that remains valuable as the underlying API improves?
Maximize constructor complexity. The more state you need to initialize — the more domain expertise, tacit knowledge, and contextual understanding required — the harder it is to replicate your wrapper.
Deepen your validation layer. Develop the ability to catch failure modes that others miss. This requires exposure to both good and bad outputs, pattern recognition that comes from volume, and domain knowledge that lets you spot subtle errors.
Own the accountability layer. In the wrapper above, there’s an owner=self field. Someone has to stake their reputation on the output. That's a function that can't be delegated to the model.
Build integration depth. The more seamlessly you can operate across the full cycle — from ambiguous request to validated output — the more valuable you are. Fluency with the tool compounds.
Conclusion
The software industry spent decades building abstractions to hide human labor behind clean APIs. Now we’re the abstraction layer, wrapping AI capabilities and exposing them to the problems that actually need solving.
This is the job now. Not doing the work, but orchestrating its execution: context injection, prompt engineering, output validation, error handling, accountability.
The code analogy isn’t cute; it’s precise. You have a constructor, methods, validation logic, and error handling. You instantiate context, process inputs, and transform outputs.
The only question is whether you’re building a thick wrapper or a thin one.
Thin wrappers get deprecated.
If this resonated, I write about systems thinking, technology, and the architecture of work. Follow for more.
About the Author
Brian Curry is a Kansas City-based Data Scientist, AI researcher, futurist, theorist, and founder of Vector1 Research, where he explores the frontier of Artificial Intelligence, Causal Inference, Agentic Systems, and Economic Modeling.
His work focuses on intelligent systems, future state technologies and systems, and human and AI integrations that reveal the causal structures driving performance across marketing, content, and economics. He is the creator of PyCausalSim (causal discovery through simulation), Papilon (complex system optimization), Daedalus (causal and economic analysis), and Memory-Node Encapsulation (data structures for artificial episodic memory).
Contact: brian@vector1.ai


