lifecycle/evals_improvement.py and register a store. Harnest runs capture in the background after the response.
This is separate from Simulations and live evaluation, where “live” means ADK’s real-time inference mode, not production traffic.
Select invocations
lifecycle/evals_improvement.py
CaptureEvent argument and returns True to capture the invocation. It can be synchronous or asynchronous. Only one capture rule is allowed.
sample_rate is optional and must be between 0 and 1. It additionally captures that share of traffic rejected by the rule. The example keeps failures plus a 5% sample of other invocations. Records identify the selection reason as rule or sample.
Both @lifecycle.evals.capture and @lifecycle.evals.redact must live in lifecycle/evals_improvement.py; Harnest refuses them in other files. Deleting this file turns capture off without changing the rest of the application.
What the rule sees
tool_error is a best guess: it detects ADK results with an error field and LangGraph tool messages with status="error". Use explicit checks in your rule when your tools have a different failure contract.
user_id, session_id, and metadata are never stored in capture records.
Register a store
The rule’sstore="captures" names a @lifecycle.storage.custom("captures") provider:
lifecycle/storage.py
LocalCaptureStore(directory, retention_days=30) writes one JSON Lines file per UTC day. It deletes old days on startup and when a new day begins. Use it locally or on a single instance with a persistent volume.
For another backend, return a value implementing harnest.eval_capture.CaptureStore:
Choose what content to keep
A redactor takes exactly one
CaptureRecord and must return a CaptureRecord. Only one redactor is allowed. If it returns anything else, Harnest drops the capture. The "redacted" marker means your policy ran; it does not certify that all personal data was removed.
Example with Presidio
Presidio’s analyzer detects supported personal-data entities, and its anonymizer replaces detected spans. Installpresidio-analyzer and presidio-anonymizer in your agent environment and provision the NLP model required by your analyzer configuration before serving.
Add this to the same lifecycle/evals_improvement.py file as the rule. This example applies Presidio to input and output text and discards tool arguments and results, whose schemas may contain sensitive values beyond free text.
lifecycle/evals_improvement.py
Stored record format
CaptureRecord serializes with schemaVersion: 1 and these fields:
When
content is "omitted", inputText and outputText are empty strings, and each tool’s arguments and result are null. Tool names and failure flags remain available.
Configure sampling and disable capture
Harnest selects the sample rate in this order:- The authored
sample_rateon@lifecycle.evals.capture. HARNEST_EVAL_CAPTURE_SAMPLE_RATE.0, which disables sampling of rejected traffic.
sample_rate if operators should control it through the environment. A zero rate still captures invocations accepted by the rule.
Set HARNEST_EVAL_CAPTURE=off to disable capture without changing code. false, 0, no, and disabled also switch it off. Both environment variables can be set under spec.environment in config.yaml:
config.yaml
Failure handling and validation
Capture runs in the background after the response. Errors in the rule, redactor, or store are logged aseval.capture.failed and never reach users. Successful captures log eval.capture.stored. Neither event contains content.
Compilation rejects:
- Either decorator outside
lifecycle/evals_improvement.py. - A rule or redactor with the wrong number of arguments.
- Duplicate capture rules or redactors.
- A missing named store or a provider value that does not implement
CaptureStore.
Coverage and limitations
Capture covers Harnest’s own routes, including/responses, streaming, and AG-UI. It does not cover ADK’s native /run or /run_sse routes. Playground evaluation runs are never captured.
There is no harnest evals pull command yet. Use your store’s list method to read captured records.