A dependency-free Go client for the OGuardAI API with context-first methods, fail-closed guarded calls, and streaming
The sdks/go module provides a Go client for the OGuardAI API. It has no third-party dependencies, every network method takes a context.Context, and it enforces the runtime's trust boundary on the client side.
GuardedCall wraps any string -> string step: it transforms the input, runs your function on the tokenized text, and rehydrates the output. It fails closed, so if the transform returns no session state the function is never run and no unrestored output is returned.
detected, err := client.Detect(ctx, oguardai.DetectRequest{ Input: "Email test@example.com, SSN 123-45-6789",})for _, e := range detected.Entities { fmt.Printf("%s: %s (%.2f)\n", e.Type, e.Value, e.Confidence)}// IncludeValues is a pointer, so a false is transmitted verbatim to keep raw// values out of a value-free caller (a plain bool would be dropped and default to true).no := false_, _ = client.Detect(ctx, oguardai.DetectRequest{Input: "SSN 123-45-6789", IncludeValues: &no})health, _ := client.Health(ctx) // health.Status == oguardai.HealthHealthycaps, _ := client.Capabilities(ctx) // caps.EntityTypes, caps.Languages, caps.Detectors
SpanInfo.Start and SpanInfo.End are UTF-8 byte offsets into the input, which is what Go string
slicing already uses, so input[e.Span.Start:e.Span.End] is correct. Ranging over the string by
rune index is not.
OutputGuard re-scans finished LLM output for newly generated PII and returns the safe text plus whether the guard rewrote anything. It is stateless, so no session is minted. Policy and OutputChannel are optional and select the guard rules.
guarded, err := client.OutputGuard(ctx, oguardai.OutputGuardRequest{ Output: llmReply, Policy: "strict-pii", OutputChannel: oguardai.ChannelCustomerEmail,})if err != nil { // a block posture returns 422, a scanner outage 503; handle before using guarded return err}if guarded.OutputGuardTriggered { // the model emitted new PII; guarded.SafeOutput carries the rewritten text}
When the policy blocks rather than rewrites, the call returns an *APIError that IsOutputBlocked matches.
Streaming runs the full pipeline then streams the safe (transform) or restored (rehydrate) text. The terminal event carries the session state needed to rehydrate a streamed transform. The *Stream iterator spawns no goroutine and is bounded by the context, so cancelling ctx or calling Close stops it cleanly.
stream, err := client.TransformStream(ctx, "Contact Erika Mustermann", oguardai.TransformStreamOptions{Policy: "default"})if err != nil { log.Fatal(err)}defer stream.Close()var sessionState stringfor stream.Next() { ev := stream.Event() switch ev.Kind { case oguardai.EventText: fmt.Print(ev.Text) case oguardai.EventComplete: sessionState = ev.SessionState }}if err := stream.Err(); err != nil { // a terminal server error surfaces here log.Fatal(err)}
A complete walkthrough lives in the repository at examples/go-quickstart. The scenario is a fictional German mail-order retailer, Musterfirma GmbH, whose service desk runs an AI copilot that drafts formal German replies to customer emails. The copilot never sends raw customer PII to the LLM, yet the one reply it drafts still reaches the customer with real contact details, reaches the internal ticket with those details masked, and reaches the audit log with nothing sensitive at all. One inbound email drives the whole pipeline: a compliance pre-scan with Detect, tokenization with GuardedChat, the copilot draft against tokens only, restore for the customer with RehydrateReply, and the same draft restored again for the ticket and the audit log with Rehydrate.
Needs NER. This example detects person and company names, so it requires the NER detector sidecar, not the runtime alone. Start the full stack, which brings up the runtime plus the NER detector, from the repository root:
# The bundled stack requires these four secrets, and its client key ships least-privilege (channel# user_output only), so widen it to the channels this example restores on.export GUARDAI_SESSION_SECRET=$(openssl rand -base64 32)export GUARDAI_API_KEY_1=$(openssl rand -hex 32)export GUARDAI_DETECTOR_API_KEY=$(openssl rand -hex 32)export GUARDAI_REDIS_PASSWORD=$(openssl rand -hex 32)export GUARDAI_API_KEY_1_ALLOWED_OUTPUT_CHANNELS=user_output,customer_email,internal_summary,log_safedocker compose -f deploy/docker/docker-compose.yml up -d
german-support declares person detection as required, so a runtime without the NER detector sidecar rejects the transform (fail closed, HTTP 503) rather than let an un-tokenized name reach the model. Structured PII (email, IBAN, and so on) is still caught by the built-in regex, but this walkthrough needs the full stack with NER to complete.
The example module uses a local replace directive pointing at ../../sdks/go, so it builds against the in-repo SDK without a published release. In your own project you depend on the SDK directly instead:
go get github.com/oronts/OGuardAI/sdks/go
Run it from examples/go-quickstart:
export GUARDAI_URL="http://localhost:3000"export GUARDAI_API_KEY="$GUARDAI_API_KEY_1" # the client key from the stack startup above (this stack runs api_key auth)export OPENAI_API_KEY="sk-..." # optional: draft with a real model instead of the stubgo run .
Without OPENAI_API_KEY the copilot falls back to a deterministic stub so the example runs offline; the OGuardAI protection path is identical either way.
examples/go-quickstart/inbound-email.txt is the inbound email that drives the pipeline. Edit it to see the pipeline react to different PII.
Betreff: Ausstehende Erstattung fuer nicht gelieferte BestellungSehr geehrtes Support-Team,mein Name ist Frau Erika Mustermann. Meine Kundennummer KNR-48213765 betrifftdie Bestellung ORD-2026-55841, die laut Sendungsverfolgung zugestellt sein soll,bei mir aber nie ankam.Ich bitte Sie, den Kaufpreis auf mein Konto DE97 9900 0000 0123 4567 89 zuerstatten. Zur Identitaetspruefung haenge ich meinen Reisepass A123456789 an.Erreichbar bin ich telefonisch unter +49 151 23456789, per E-Mail untererika.mustermann@beispiel.de. Meine Lieferadresse lautet Lindenstrasse 14, 24103 Kiel.Mit freundlichen GruessenErika Mustermann
The complete program, verbatim from examples/go-quickstart/main.go:
// Command go-quickstart is a company-grade walkthrough of the OGuardAI Go SDK for// a German customer-support desk.//// Scenario: "Musterfirma GmbH", a fictional German mail-order retailer, runs// an AI support copilot that drafts formal German replies to customer emails. The// LLM must never see raw PII. OGuardAI tokenizes the inbound email, the copilot// drafts against tokens only, and each downstream reader gets the reply restored to// exactly what its channel is allowed to see: full contact details for the customer,// masked values for the internal ticket, and nothing sensitive in the audit log.//// Pipeline://// Detect compliance pre-scan: inventory the PII with no raw values// GuardedChat tokenize the whole conversation in one call (fails closed)// draft the copilot writes the reply on tokenized text only// RehydrateReply restore for the customer (full, gender-aware "Frau/Herr")// Rehydrate restore the SAME reply for the ticket and the audit log// fail-closed hallucinated tokens and a missing session never leak a value//// The german-support policy drives all of this: gender/formality-aware name// restoration, IBAN masked, passport redacted outright (level 1, never a token),// and channel rules that only ever tighten what a reader sees.//// Prerequisites://// # Runtime with the NER detector active (person/company), e.g. the full stack:// GUARDAI_SESSION_SECRET=$(openssl rand -base64 32) \// docker compose -f ../../deploy/docker/docker-compose.yml up -d//// export GUARDAI_URL="http://localhost:3000"// export GUARDAI_API_KEY="<key>" # only if the server runs with auth// export OPENAI_API_KEY="sk-..." # optional: draft with a real model// go run .//// Without OPENAI_API_KEY the copilot falls back to a deterministic stub so the// example runs offline; the OGuardAI protection path is identical either way.package mainimport ( "bytes" "context" "encoding/json" "errors" "fmt" "log" "net/http" "os" "regexp" "strings" "time" oguardai "github.com/oronts/OGuardAI/sdks/go")const ( policyName = "german-support" language = "de" brand = "Musterfirma GmbH")func main() { client, err := oguardai.NewClient(oguardai.Config{ BaseURL: env("GUARDAI_URL", "http://localhost:3000"), APIKey: os.Getenv("GUARDAI_API_KEY"), Timeout: 30 * time.Second, }) if err != nil { log.Fatalf("client: %v", err) } ctx := context.Background() nerActive := preflight(ctx, client) inbound, err := os.ReadFile("inbound-email.txt") if err != nil { log.Fatalf("read fixture: %v", err) } email := string(inbound) // Stage A: compliance pre-scan. Detect reports what PII is present WITHOUT the // raw values, so this inventory is safe to keep even in a compliance log. It is // also the "what a naive prompt would have leaked" baseline. complianceScan(ctx, client, email) // Stage B: tokenize the conversation. GuardedChat transforms every message in one // call and hands back the session state needed to restore the reply. It fails // closed: no session means the reply could never be restored, so it errors here // rather than letting the copilot run against unprotected text. safeMessages, sessionState := guardedTokenize(ctx, client, email) // Every message content crosses to the model. Structured PII appearing raw is a // builtin-detection failure, fatal on every lane; a raw person name is fatal when // the runtime advertises NER and a printed note when it does not. One record of the // noted names drives both the exit code and the closing line. var nameLeaks []string var userSafe string for _, m := range safeMessages { content, _ := m["content"].(string) if m["role"] == "user" { userSafe = content } nameLeaks = append(nameLeaks, boundaryScan("the model", content, nerActive)...) } // Stage C: the copilot drafts the reply. It only ever sees tokenized text. reply, err := draftReply(ctx, safeMessages) if err != nil { log.Fatalf("draft reply: %v", err) } fmt.Println("\n--- Copilot draft (as the model returned it: tokens only) ---") fmt.Println(reply) nameLeaks = append(nameLeaks, boundaryScan("the copilot draft", reply, nerActive)...) // Stage D: restore for the customer. customer_email is not tightened by the // policy's channel rules, so the customer sees full contact details and a // gender-aware salutation ("Frau Erika Mustermann"). customerReply, err := client.RehydrateReply(ctx, reply, sessionState, oguardai.ChannelCustomerEmail) if err != nil { log.Fatalf("rehydrate for customer: %v", explain(err)) } fmt.Println("\n--- Reply sent to the customer (channel: customer_email, full restore) ---") fmt.Println(customerReply) // exit 0 is not proof: a reply that reads "[DELETED]" (a revoked value) or still // carries a {{token}} is corrupted output the customer must never receive. The // surname is required only when a person token actually reached the reply; with NER // off no person token exists to restore. assertRestored(customerReply, "Mustermann", firstToken(userSafe, "person") != "") // Stage E: the SAME reply and the SAME session, restored for two other readers. // One session state, many safe views: the channel decides what each reader sees. multiChannelRestore(ctx, client, reply, sessionState) // Stage E2: the six restore modes on one token, so the difference is concrete. restoreModeMatrix(ctx, client, sessionState) // Stage F: the fail-closed guarantees that make this safe to ship. failClosed(ctx, client, reply, sessionState) fmt.Println() if names := uniqueStrings(nameLeaks); len(names) == 0 { fmt.Println("Done. Raw PII never left the trusted zone; the model only ever saw tokens.") } else { fmt.Printf("Done. Structured PII stayed inside the runtime; the name %s reached the model because this runtime runs without NER. Start the full stack to tokenize names too.\n", strings.Join(names, ", ")) }}// preflight confirms the runtime is reachable and reports whether it advertises NER,// which is the one signal that decides how a raw person name at the boundary is judged.// A runtime without NER still tokenizes structured PII (email, IBAN, ...) but misses// names, so those cross raw and are noted rather than treated as a leak.func preflight(ctx context.Context, client *oguardai.Client) bool { caps, err := client.Capabilities(ctx) if err != nil { log.Fatalf("capabilities: %v", explain(err)) } fmt.Printf("Runtime up. NER active: %v. Policy in use: %s (language %s).\n", caps.NerActive, policyName, language) if !caps.NerActive { fmt.Println("WARNING: NER is off; person and company names will not be detected. Start the full stack (see the README).") } return caps.NerActive}// complianceScan inventories the PII in the inbound email without exposing values.// IncludeValues is an explicit false so the runtime returns types and spans only.func complianceScan(ctx context.Context, client *oguardai.Client, email string) { no := false resp, err := client.Detect(ctx, oguardai.DetectRequest{ Input: email, Language: language, IncludeValues: &no, }) if err != nil { log.Fatalf("detect: %v", explain(err)) } counts := map[string]int{} for _, e := range resp.Entities { counts[string(e.Type)]++ } fmt.Printf("\n--- Stage A: compliance pre-scan (%d entities, no raw values) ---\n", len(resp.Entities)) for t, n := range counts { fmt.Printf(" %-12s x%d\n", t, n) } fmt.Println(" Every one of these would otherwise be sitting in the prompt you send to the model.")}// guardedTokenize tokenizes the system+user conversation. The system prompt is// developer-controlled; the user message is the untrusted inbound email. Both are// tokenized, so even our own brand name in the system prompt round-trips as a token.func guardedTokenize(ctx context.Context, client *oguardai.Client, email string) ([]oguardai.ChatMessage, string) { messages := []oguardai.ChatMessage{ {"role": "system", "content": "Du bist ein hoeflicher Kundendienst-Assistent der " + brand + ". Antworte formell auf Deutsch (Sie-Form). Verwende ausschliesslich die vorhandenen " + "Platzhalter-Token und erfinde keine Werte."}, {"role": "user", "content": email}, } safeMessages, sessionState, err := client.GuardedChat(ctx, messages, oguardai.GuardedChatOptions{ Policy: policyName, Language: language, }) if err != nil { log.Fatalf("guarded chat: %v", explain(err)) } fmt.Println("\n--- Stage B: tokenized conversation the model will see ---") for _, m := range safeMessages { fmt.Printf(" [%v] %v\n", m["role"], m["content"]) } // The passport is a level-1 identifier: the policy redacts it in place, so it is // [REDACTED:passport] above rather than a token. Redacted values have no entry in // the session and can never be restored, by anyone, on any channel. fmt.Println(" Note: the passport is redacted, not tokenized; it is gone for good.") return safeMessages, sessionState}// multiChannelRestore restores the copilot's reply for the internal ticket note and// the audit log from the same session. The policy's channel rules only tighten, so// each reader sees strictly less than the customer, never more.func multiChannelRestore(ctx context.Context, client *oguardai.Client, reply, sessionState string) { internal, err := client.Rehydrate(ctx, oguardai.RehydrateRequest{ Output: reply, SessionState: sessionState, OutputChannel: oguardai.ChannelInternalSummary, }) if err != nil { log.Fatalf("rehydrate internal: %v", explain(err)) } fmt.Println("\n--- Stage E: internal ticket note (channel: internal_summary) ---") fmt.Println(internal.RestoredText) audit, err := client.Rehydrate(ctx, oguardai.RehydrateRequest{ Output: reply, SessionState: sessionState, OutputChannel: oguardai.ChannelLogSafe, }) if err != nil { log.Fatalf("rehydrate audit: %v", explain(err)) } fmt.Println("\n--- Stage E: audit log line (channel: log_safe, everything masked) ---") fmt.Println(audit.RestoredText)}// restoreModeMatrix shows the six restore modes applied to a single name token, on a// channel the policy does not tighten, so the mode is what you see. This is how a// deployment tunes exactly how much of a value each surface may reveal.func restoreModeMatrix(ctx context.Context, client *oguardai.Client, sessionState string) { // A fresh, standalone transform gives a clean single-token line to restore. tf, err := client.Transform(ctx, oguardai.TransformRequest{ Input: "Herr Max Mustermann", Policy: policyName, Language: language, }) if err != nil { log.Fatalf("transform for mode matrix: %v", explain(err)) } token := firstToken(tf.SafeText, "person") line := "Guten Tag " + token + ", Ihre Anfrage ist eingegangen." fmt.Println("\n--- Stage E2: the same name token under each restore mode ---") for _, mode := range []oguardai.RestoreMode{ oguardai.RestoreFull, oguardai.RestoreFormatted, oguardai.RestorePartial, oguardai.RestoreMasked, oguardai.RestoreAbstract, oguardai.RestoreNone, } { rh, err := client.Rehydrate(ctx, oguardai.RehydrateRequest{ Output: line, SessionState: tf.SessionState, OutputChannel: oguardai.ChannelCustomerEmail, RestoreMode: mode, }) if err != nil { log.Fatalf("rehydrate mode %s: %v", mode, explain(err)) } fmt.Printf(" %-10s -> %s\n", mode, rh.RestoredText) }}// failClosed demonstrates the two ways OGuardAI refuses to invent data.func failClosed(ctx context.Context, client *oguardai.Client, reply, sessionState string) { fmt.Println("\n--- Stage F: fail-closed guarantees ---") // 1. A model that hallucinates a token id gets nothing back for it. The unknown // token is reported as unresolved, never guessed, and the request still succeeds // for the tokens that are real. hallucinated := reply + " Interne Referenz: {{person:p_999:deadbeef0000}}." rh, err := client.Rehydrate(ctx, oguardai.RehydrateRequest{ Output: hallucinated, SessionState: sessionState, OutputChannel: oguardai.ChannelInternalSummary, }) if err != nil { log.Fatalf("rehydrate hallucinated: %v", explain(err)) } if len(rh.TokensUnresolved) > 0 { fmt.Printf(" Hallucinated token left unresolved (not fabricated): %v\n", rh.TokensUnresolved) } else { log.Fatalf("fail-closed check failed: a hallucinated token was resolved instead of left unresolved: %q", rh.RestoredText) } // 2. A rehydrate with no session state cannot restore anything, so the SDK rejects // it locally before any network call. errors.As lets you branch on the reason. _, err = client.RehydrateReply(ctx, reply, "", oguardai.ChannelCustomerEmail) var apiErr *oguardai.APIError if errors.As(err, &apiErr) { fmt.Printf(" Missing-session rehydrate refused locally: %s (%s)\n", apiErr.Message, apiErr.Code) } else { log.Fatalf("fail-closed check failed: a rehydrate with no session state was not refused (err=%v)", err) }}// draftReply is the copilot's model call. It reads OPENAI_API_KEY exactly as a real// integration would; when the key is set it drafts with a real model, otherwise it// falls back to a deterministic stub. Either way the model sees only tokenized text.func draftReply(ctx context.Context, safeMessages []oguardai.ChatMessage) (string, error) { if key := os.Getenv("OPENAI_API_KEY"); key != "" { return callOpenAI(ctx, key, safeMessages) } fmt.Println("\n(OPENAI_API_KEY not set: using a deterministic stub reply. Set it to draft with a real model.)") return stubReply(safeMessages), nil}// stubReply composes a plausible formal German reply from the tokens in the user// message, so the example is fully runnable without any provider. A real model would// use the gender/formality metadata to choose "geehrte/geehrter" and the salutation.func stubReply(safeMessages []oguardai.ChatMessage) string { var userSafe, systemSafe string for _, m := range safeMessages { switch m["role"] { case "user": userSafe, _ = m["content"].(string) case "system": systemSafe, _ = m["content"].(string) } } person := firstToken(userSafe, "person") order := firstToken(userSafe, "order") iban := firstToken(userSafe, "iban") email := firstToken(userSafe, "email") // Our own brand is tokenized too, so the signature has to echo the token from the // tokenized system prompt. Writing it out raw is what a careless model does, and the // output guard rejects the whole rehydrate for it. signature := "Ihr Serviceteam" if company := firstToken(systemSafe, "company"); company != "" { signature = "Ihr Serviceteam der " + company } return fmt.Sprintf( "Sehr geehrte %s, vielen Dank fuer Ihre Nachricht zur Bestellung %s. "+ "Wir haben eine Erstattung auf das hinterlegte Konto %s veranlasst; die Gutschrift "+ "erfolgt in den naechsten Werktagen. Eine schriftliche Bestaetigung senden wir an %s. "+ "Mit freundlichen Gruessen, %s.", person, order, iban, email, signature)}// callOpenAI sends the tokenized messages to the OpenAI chat completions API. No key// is hardcoded; it is passed in from the environment. The request body carries only// tokenized content because that is what GuardedChat returned.func callOpenAI(ctx context.Context, apiKey string, safeMessages []oguardai.ChatMessage) (string, error) { body, err := json.Marshal(map[string]any{ "model": env("OPENAI_MODEL", "gpt-4o-mini"), "messages": safeMessages, }) if err != nil { return "", err } req, err := http.NewRequestWithContext(ctx, "POST", "https://api.openai.com/v1/chat/completions", bytes.NewReader(body)) if err != nil { return "", err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+apiKey) resp, err := http.DefaultClient.Do(req) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return "", fmt.Errorf("openai: status %d", resp.StatusCode) } var parsed struct { Choices []struct { Message struct { Content string `json:"content"` } `json:"message"` } `json:"choices"` } if err := json.NewDecoder(resp.Body).Decode(&parsed); err != nil { return "", err } if len(parsed.Choices) == 0 { return "", errors.New("openai: no choices returned") } return parsed.Choices[0].Message.Content, nil}// explain turns an SDK error into an operator-friendly message using the typed// predicates, so the caller reacts to the reason rather than a status code.func explain(err error) string { switch { case oguardai.IsPolicyDenied(err): return "a policy blocked this content: " + err.Error() case oguardai.IsAuth(err): return "authentication failed; check GUARDAI_API_KEY: " + err.Error() case oguardai.IsSessionExpired(err): return "the session expired; re-run the transform: " + err.Error() case oguardai.IsRateLimited(err): return "rate limited; back off and retry: " + err.Error() case oguardai.IsValidation(err): return "the request was rejected as invalid: " + err.Error() default: return err.Error() }}var tokenRe = regexp.MustCompile(`\{\{([a-z_]+):[^}]+\}\}`)// firstToken returns the first {{type:...}} token of the given entity type in text,// or an empty string. Person tokens are identity-stable, so the salutation and the// signature share one token; taking the first is enough.func firstToken(text, entityType string) string { for _, m := range tokenRe.FindAllStringSubmatch(text, -1) { if m[1] == entityType { return m[0] } } return ""}// structuredSentinels are the builtin-detectable PII values in inbound-email.txt. Any// of them appearing raw in a string bound for the model or a downstream reader is a// builtin-detection failure and fails the run on every lane.var structuredSentinels = []string{ "KNR-48213765", "ORD-2026-55841", "DE97 9900 0000 0123 4567 89", "A123456789", "+49 151 23456789", "erika.mustermann@beispiel.de",}// nameSentinels are the person names in inbound-email.txt. A name only tokenizes when// NER is active, so a raw name is fatal when the runtime advertises NER and a printed// note otherwise; the note always prints, so a name leak is never silent.var nameSentinels = []string{ "Erika Mustermann",}// boundaryScan checks one string about to cross to the model or a downstream reader.// Structured PII is fatal on every lane. A person name is fatal when nerActive and a// printed note otherwise; it returns the names it noted so the caller decides the exit// code and the closing line from one record.func boundaryScan(where, text string, nerActive bool) []string { for _, s := range structuredSentinels { if strings.Contains(text, s) { log.Fatalf("boundary leak: structured PII %q reached %s in cleartext: %s", s, where, text) } } var noted []string for _, n := range nameSentinels { if !strings.Contains(text, n) { continue } if nerActive { log.Fatalf("boundary leak: the name %q reached %s while the runtime advertises NER: %s", n, where, text) } fmt.Printf("NOTE: the name %q reached %s because this runtime runs without NER; structured PII stayed tokenized.\n", n, where) noted = append(noted, n) } return noted}func uniqueStrings(in []string) []string { seen := make(map[string]bool, len(in)) out := make([]string, 0, len(in)) for _, s := range in { if !seen[s] { seen[s] = true out = append(out, s) } } return out}// assertRestored fails the run when a customer-facing restore is corrupted rather// than letting it pass on exit code alone. The expected surname must survive when a// person token reached the reply; no unresolved {{token}} or revoked [DELETED] marker// may ever reach a customer channel. A gender-honorific prefix a detector may prepend// ("[PERSON]Frau ...") is tolerated; only the surname is required.func assertRestored(text, wantName string, requireName bool) { switch { case requireName && !strings.Contains(text, wantName): log.Fatalf("customer reply is missing the restored name %q: %s", wantName, text) case strings.Contains(text, "{{"): log.Fatalf("customer reply still carries an unresolved token: %s", text) case strings.Contains(text, "[DELETED]"): log.Fatalf("customer reply carries [DELETED] (a revoked value reached a customer channel): %s", text) }}func env(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback}
Runtime up. NER active: true. Policy in use: german-support (language de).--- Stage A: compliance pre-scan (10 entities, no raw values) --- person x2 customer_id x1 order x1 passport x1 phone x1 email x1 address x2 iban x1--- Stage B: tokenized conversation the model will see --- [system] Du bist ein hoeflicher Kundendienst-Assistent der {{company:c_001:...}}. ... [user] ... mein Name ist Frau {{person:p_001:...}}. Meine Kundennummer {{customer_id:cid_001:...}} ... auf mein Konto {{iban:ib_001:...}} ... meinen Reisepass [REDACTED:passport] an. ... Note: the passport is redacted, not tokenized; it is gone for good.--- Reply sent to the customer (channel: customer_email, full restore) ---Sehr geehrte Frau Erika Mustermann, ... zur Bestellung ORD-2026-55841. Wir haben eineErstattung auf das hinterlegte Konto D*** **** **** **** **** *0 veranlasst; ... Bestaetigungsenden wir an erika.mustermann@beispiel.de. ...--- Stage E: internal ticket note (channel: internal_summary) ---Sehr geehrte Frau Erika Mustermann, ... Konto D*** **** **** **** **** *0 ... ane*****************************e. ...--- Stage E: audit log line (channel: log_safe, everything masked) ---Sehr geehrte [REDACTED], ... zur Bestellung [REDACTED]. ... Konto [REDACTED] ... an [REDACTED]. ...--- Stage E2: the same name token under each restore mode --- full -> Guten Tag Herr Max Mustermann, ... formatted -> Guten Tag Herr Max Mustermann, ... partial -> Guten Tag H. Mustermann, ... masked -> Guten Tag H*** *** *********n, ... abstract -> Guten Tag (Kunde), ... none -> Guten Tag [REDACTED], ...--- Stage F: fail-closed guarantees --- Hallucinated token left unresolved (not fabricated): [{{person:p_999:deadbeef0000}}] Missing-session rehydrate refused locally: session_state is required for rehydration (GUARDAI_SESSION_MISSING)Done. Raw PII never left the trusted zone; the model only ever saw tokens.
The single reply text is one draft the copilot wrote once. The customer, the ticket, and the audit log each read it through a different channel, so each sees exactly what it is allowed to.
This release covers Transform, Rehydrate, RehydrateFile (the JSON /v1/rehydrate/file route, same body as Rehydrate), Detect, OutputGuard, Health, Capabilities, EvaluatePolicy, BatchTransform and BatchDetect, revocation (Revoke, BulkRevoke, RevocationCount), session lifecycle (SessionStatus, DeleteSession), the GuardedCall, GuardedChat, and RehydrateReply helpers, a stateful Session, and streaming. The RAG pipeline, the multipart transform file and image upload routes, and the admin endpoints (diagnostics, metrics, policy validate and reload) are served by the HTTP API and the TypeScript and Python SDKs; they are not yet wrapped in the Go client.