AFP engineering series · Chapter 10 of 25

Design output validation around irreversible streaming, tool results, generated links, and authoritative action status.

A late block cannot retract an earlier disclosure

A streaming assistant sends text as it is generated. If a later guardrail discovers a secret in the completed answer, earlier chunks may already be visible to the user or stored by a client. Replacing the final message does not erase those bytes. The same issue applies to tool-call arguments or intermediate events exposed by a debugging interface.

Start by listing every release channel: user-visible text, event streams, tool results, citations, remote images, attachments, and logs. Decide which channels are part of the product and which are internal. Internal channels still require access control, but they should not be accidentally forwarded to an ordinary client.

Choose a release policy deliberately

PolicyBenefitCost or limitation
Buffer full candidateCheck complete output before releaseHigher time to first visible content
Bounded chunk bufferLower delay with some contextCannot assess arbitrary future context
Structured result onlySmall validated output surfaceLess expressive interaction
Immediate streamLow latencyLater checks cannot retract content

Use different policies for different tasks. Public brainstorming may tolerate a different release strategy from a support response containing account details. High-impact action status should be structured and backend-derived regardless of the prose streaming policy. A model should not be able to stream “payment completed” before the processor confirms it.

If you use chunk screening, define the overlap window and what happens at boundaries. A sensitive string can be split across chunks, and a semantic violation can depend on several sentences. Chunk-level inspection is a latency tradeoff, not equivalent to reviewing the complete candidate.

Validate structure and destination

A JSON schema can constrain field types and allowed keys, but it cannot prove that a factual claim is true or an action is authorized. Validate business semantics after parsing. Use explicit enums for status, server-resolved identifiers for resources, and bounded lists for citations. Reject unexpected fields rather than silently forwarding them to another service.

For HTML and Markdown, encode text and sanitize allowed markup at the renderer. Disable or constrain remote resource loading. A generated image URL can transmit information through a network request even when the visible answer looks harmless. Apply destination and data-classification rules to links and attachments just as you would to a tool invocation.

Keep release state separate from generation state

Illustrative python; adapt the persistence and identity adapters to your service.

class ReleaseBuffer:
    def __init__(self):
        self.parts = []
        self.released = False

    def add(self, text):
        if self.released:
            raise RuntimeError("candidate already released")
        self.parts.append(text)

    def finish(self, approved):
        candidate = "".join(self.parts)
        self.parts.clear()
        self.released = True
        return candidate if approved else "Response withheld for review."

This in-memory example illustrates full buffering only. Production implementations need size limits, cancellation handling, secure temporary storage if used, and a timeout policy. The approval input must come from a trusted screening decision, not from the candidate itself. A buffer without a maximum size can become a resource-exhaustion problem.

Record whether any content was released before a failure. That field is essential during incident response. A blocked final response with zero released bytes is a different event from a response blocked after several paragraphs were already delivered.

Integrate at the actual emission boundary

ADK provides model and event hooks, but the application must verify which events its chosen runtime sends to clients. Test text deltas, completed messages, function-call arguments, tool responses, and errors. A guardrail attached to a completed-model response should not be assumed to cover every transport event.

Build a fake model that emits a known synthetic marker across several chunks. Capture the client stream and assert that the marker never appears under the buffered policy. Repeat with a tool result and an exception message. This end-to-end test detects leaks that a unit test of the redaction function cannot see.

Handle redaction without creating misleading answers

Removing a sensitive span can change meaning. A redacted account identifier may be acceptable, while removing a negation or qualification can turn a cautious answer into a false claim. Prefer structured minimization before generation where possible, and check the final answer for coherence after redaction.

Keep raw and sanitized values separate in memory and storage. Do not redact the display while leaving the original in a downloadable transcript or telemetry event. If the user is entitled to a value, reveal it through an authenticated interface designed for that purpose rather than asking the model to decide whether to include it in a general answer.

Worked release test: a sensitive marker spans chunks

A fake model emits the harmless marker SYNTHETIC-PRIVATE-42 in three chunks. A naive filter checks each chunk independently for the complete marker and passes all three. The browser receives the combined marker even though no individual chunk matched. This fixture demonstrates a boundary problem without using a real secret.

Under a full-buffer policy, the application accumulates the bounded candidate, screens the complete value, and releases either the approved answer or a safe withheld response. The test captures the actual client stream and asserts that no part designated as unreleasable is emitted. It also verifies that cancellation clears the buffer and does not expose it through an error response.

For a chunk-buffer policy, define the overlap and supported detection contract. A fixed overlap may catch bounded string patterns, but it cannot guarantee detection of arbitrary semantic relationships across a long answer. State that limitation explicitly. Use this mode only where its residual risk fits the product, rather than describing it as equivalent to full-output review.

Add a tool event containing the marker and a model error whose message includes it. If the transport forwards those events directly, the final-answer guardrail is irrelevant to those channels. Filter or suppress internal events before they reach the client, and keep debugging interfaces separately authenticated. Test downloadable transcripts too, since they may use a different serialization path.

Finally, include a legitimate answer quoting the marker for a security lesson in a controlled environment. The application's policy may allow that context. A simplistic string detector would block it, revealing a false-positive tradeoff. The fixture is useful for testing release mechanics, not for claiming that a keyword match is a complete privacy detector.

The release record should say which channels are buffered, which are structured, which are internal, and which remain streaming with limited inspection. This makes the guarantee understandable: the system controls when bytes leave, while the detector's accuracy remains a separately evaluated property.

Measure safety and user experience together

Track time to first approved content, complete-response latency, blocked candidates, detector failures, and released-before-block incidents. Evaluate legitimate cases containing security terminology or quoted sensitive formats so the guardrail does not simply suppress useful work. Compare results by language and task.

The release contract should state what is guaranteed: for example, no candidate text reaches the client before a complete-output check, and no action status is shown without a service record. It should also state limits, such as detector false negatives. Output guardrails reduce disclosure risk when placed before release; they do not reverse actions or bytes already delivered.

Sources and implementation scope

Primary references checked October 15, 2026. Framework details follow the linked documentation; the Northstar cases, policy contracts, and test designs are original illustrative designs. Python snippets labeled illustrative are not complete production integrations. ADK package metadata checked for this series reported 2.12.0; pin and test the version used by your application.

Protect the action boundary with verified evidence, and test both containment and legitimate use.