Harness engineering / practical series
ADK agent, runner and harness: who owns what?
By Sandeep Belgavi · 18 October 2026
Place the framework lifecycle inside application-owned identity, budgets and delivery checks.
Begin with an observable task
Suppose an assistant answers a question using two approved policy records. The agent describes the task and selects or generates a response. A runner drives interaction through a framework lifecycle. Session and artifact services preserve the state needed between turns. The application still identifies the caller, supplies authorized records, enforces resources and decides what may be delivered. Using an agent framework does not transfer those responsibilities to the language model.
Translate at an explicit adapter boundary
Use an adapter that accepts a validated internal definition and produces a framework agent. Its inputs include instructions, a resolved model and resolved tools. Reject unsupported properties rather than saving them and pretending they executed. A Java LlmAgent instruction editor cannot silently configure a Python custom BaseAgent. Show the runtime language and agent type beside the source preview, and include both in the execution record.
Reserve capacity before calling the runner
Before model work begins, record the allowed capacity in durable application state. Retain reservations after ambiguous timeouts until the accounting policy resolves them. Session history is not a financial ledger. Supply bounded context to each specialist rather than copying an unlimited transcript into every invocation. Context reduction needs evaluation too: removing a relevant requirement can make a short prompt faster while making the task outcome incorrect.
Know which worker actually executes
Current Harness Studio wraps evidence selection in a Python custom agent when ADK is selected. Its default extractive worker does not invoke an LLM. The optional Ollama worker performs a model request in local review mode. Neither path becomes a Java multi-agent deployment merely because ADK appears in the dropdown. The Java workshop is a separate source-generation experience. Clear execution labels help distinguish a framework lifecycle exercise, exported code and actual inference.
Test behavior without provider spending
The exercise below supplies a worker and an outer budget and validation wrapper. It shows the ownership boundary without an SDK dependency or provider request. A framework adapter test should inject a controlled response and verify the same invariants: budget denial prevents invocation, invalid output is withheld and revoked sessions prevent delivery. Follow that with an installed-SDK integration test. Keep mocked provider behavior distinct from measured live behavior in the acceptance report.
Avoid a misleading exception path
A runner can emit intermediate events and then fail before producing a final result. Do not treat the latest event text as a successful answer. Persist a terminal state, classify cancellation separately from a provider failure and decide whether a retry is permitted by the original budget. A framework callback should not become a second delivery path that bypasses the outer validator. Test failure after an intermediate event to expose that shortcut.
Run the exercise
Save the code as 06-adk-agent-and-runner.py and run python 06-adk-agent-and-runner.py with Python 3.11 or newer. This standard-library exercise uses synthetic data. It does not invoke a model or require an account, network connection or API key.
# Author: Sandeep Belgavi. Synthetic exercise; no network or provider calls.
calls=[]
def worker():
calls.append("invoked")
return {"citations":["policy-1"]}
def run(budget):
if budget<1:return "BUDGET_DENIED"
return "DELIVER" if worker()=={"citations":["policy-1"]} else "WITHHOLD"
assert run(0)=="BUDGET_DENIED" and not calls
assert run(1)=="DELIVER" and len(calls)==1
print("budget before worker; validation before delivery")
Expected output:
budget before worker; validation before deliveryChange one assumption
Change the worker citation to an unknown record and confirm delivery is withheld. Then make the worker raise a timeout. Describe where a durable reservation must remain and how a subsequent bounded retry would be recorded. The exercise intentionally does not implement storage, provider billing or an SDK runner.
Implementation scope and further reading
This is an original AICassindra teaching design. The exercise demonstrates the stated invariant; it is not a complete production implementation. The local Harness Studio extension supports saved read-only agent teams, reviewed count-based routing and bounded sequential execution. Arbitrary imported graphs, effectful tools and automatic self-promotion remain unsupported.
All harness articles · Interactive boundary labs · Our public lab code
Primary reference: ADK agent configuration. The adapter boundary and exercise here are our own design.