You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -30,7 +30,9 @@ and Continuum adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
30
30
- Lifecycle `on_end` hook now fires for agents reached via handoff — previously only the entry agent's hook ran, and a raising hook no longer masks a successful handoff as a failure.
31
31
- Output scanners now run in streaming mode, closing a PII-leak path where the streamed final response bypassed redaction applied in non-streaming runs.
32
32
-`return_to_parent=True` handoffs are now bounded by a loop guard (`HandoffLoopError`) instead of recursing unbounded between parent and child.
33
-
-`RunContext.data_labels` are now enforced as additional policy subjects by the tool-access engine (deny policies on `data:*` resources take effect).
33
+
-`RunContext.data_labels` are now enforced **end-to-end** as additional policy subjects across six sinks — model routing (`llm:<model>`), tool calls (`tool:<name>`), long-term memory (`memory:<scope>`), telemetry, session persistence, and the decision trace — not just tool access. A deny `AccessPolicy` on the label subject takes effect at each, in both streaming and non-streaming runs.
34
+
- HITL `ApprovalStep` now authorizes decisions: a decision is honored only when `decided_by` is in `approvers`, gating both approvals and rejections; unauthorized decisions are recorded (`unauthorized_attempts`) and ignored, leaving the step pending. An empty `approvers` list preserves the prior open behavior, and `escalated` decisions are exempt (they re-target rather than resolve).
35
+
-`@observe` now records async/sync **generator** spans correctly — the span stays open across iteration, so streaming spans (e.g. `llm_chat_stream`) capture duration and exceptions, including the `WARNING` level a data-label model-routing deny raises mid-stream (previously the span closed before the body ran).
34
36
- Removed docs references to a non-existent `PIIPolicy` API; clarified that PII redaction is opt-in via `pre_store_filter` (memory) and output scanners (runs).
| ToolAccessDeniedError | Policy denial surfaced to the LLM with a configurable message |
110
-
| Data sensitivity labels | Taint labels (e.g. `pii`, `phi`) on `RunContext.data_labels` propagate through handoffs and are matched as extra policy subjects by `PolicyStore`. They gate access only when you configure a policy — add a **deny**`AccessPolicy`(e.g. deny `tool:send_email` for subject`pii`); with no policy store configured they have no effect. Tool access only — not wired into the memory path; to keep sensitive content out of memory use `pre_store_filter` (content redaction), not labels. |
110
+
| Data sensitivity labels | Taint labels (e.g. `pii`, `phi`) on `RunContext.data_labels` propagate forward through a run and are matched as extra policy subjects by `PolicyStore`. They gate access only when you configure a policy — add a **deny**`AccessPolicy`for the label as subject; with no policy store configured they have no effect. Enforced **end-to-end** across six sinks: model routing (`llm:<model>`), tool calls (`tool:<name>`), long-term memory (`memory:<scope>`), telemetry, session persistence, and the decision trace. Labels come only from declared producers (`tool_data_labels`, memory `scope_data_labels`, run-level `data_labels`, or `ctx.taint()`) — the SDK ships no PII detector. |
111
111
| Input sanitization | Injection detection at the system boundary |
112
112
| Secret utilities | Utilities for safe handling of secrets and credentials |
<p>Make sensitivity labels such as <code>pii</code>, <code>confidential</code>, or <code>restricted</code> change what a run is <em>allowed to do</em>. A run carries a set of labels (<code>ctx.data_labels</code>); labels are added by <strong>producers</strong> and then <strong>gate</strong> six runtime sinks. Taint is monotonic (only added, propagates forward) and is enforced identically in streaming and non-streaming.</p>
1708
+
<div class="code-wrapper"><button class="copy-btn" onclick="copyCode(this)">copy</button><pre><span class="cm"># producer (taint) ─▶ ctx.data_labels = {"pii"} ─▶ gate at each sink (deny?)</span></pre></div>
1709
+
1710
+
<p><strong>Independent of <a onclick="scrollToAnchor('run-safety')" style="cursor:pointer;text-decoration:underline">Input / Output Scanning</a>.</strong> Scanners <em>sanitize text</em>; data labels <em>gate behavior</em>. Neither feeds the other — a scanner never taints a run.</p>
1711
+
1712
+
<h3>Defaults — opt-in & inert</h3>
1713
+
<p>With nothing configured the feature does nothing (zero behavior change, fully backward-compatible): no policy store on the agent, no producers, every run unlabeled. Policy evaluation is <strong>open-default-allow</strong> with <strong>deny-overrides</strong>.</p>
<p><strong>1 · Define policies.</strong> Subjects are matched against <code>[agent.name, *sorted(labels)]</code>, so a rule whose subject is a label fires for <em>any</em> run carrying that label. Resources support fnmatch globs.</p>
denial_message=<span class="str">"Confidential data may not leave via this tool."</span>,
1741
+
))</pre></div>
1742
+
1743
+
<p><strong>2 · Attach the store to the agent.</strong> The runner publishes it as the ambient policy for the whole run, so every sink is gated — you never thread it through call sites.</p>
<p><strong>3 · Declare at least one producer</strong> — otherwise nothing is ever labeled and the policies never fire.</p>
1750
+
<div class="code-wrapper"><button class="copy-btn" onclick="copyCode(this)">copy</button><pre><span class="cm"># (a) Tool provenance — a tool's result taints the run</span>
<p>Write policy <code>resources=[…]</code> using these strings. Each sink checks the policy with subjects <code>[agent.name, *sorted(labels)]</code>.</p>
1764
+
<table>
1765
+
<tr><th>Sink</th><th>Resource string</th><th>What a deny does</th></tr>
1766
+
<tr><td>Model routing</td><td><code>llm:<model></code></td><td>raises <code>ModelAccessDeniedError</code> (catch → reroute to an allowed model)</td></tr>
1767
+
<tr><td>Tool call</td><td><code>tool:<name></code> (globs)</td><td>tool result becomes <code>POLICY DENIED: …</code> (tool not run; model sees it)</td></tr>
1768
+
<tr><td>Long-term memory</td><td><code>memory:<scope></code> or <code>memory:*</code></td><td>write blocked (<code>MemoryAccessDeniedError</code> on explicit add; auto-store skipped)</td></tr>
1769
+
<tr><td>Telemetry</td><td><code>telemetry</code></td><td>payload replaced with <code>{"_redacted": "…"}</code> before egress</td></tr>
1770
+
<tr><td>Short-term session</td><td><code>session</code></td><td>assistant answer stored as a placeholder, not verbatim</td></tr>
1771
+
<tr><td>Decision trace</td><td><code>telemetry</code> (reused)</td><td>trace content redacted before persistence; audit skeleton kept</td></tr>
<strong>"Model routing"</strong> is Continuum's gate; the gateway (<a onclick="scrollToAnchor('sec-smart')" style="cursor:pointer;text-decoration:underline">smart layer</a>) is a separate project (AURA) that does its own model routing. When using the gateway, rely on AURA for model routing instead of Continuum's "Model routing" gate.
1798
+
</div>
1799
+
1800
+
<h3>Example</h3>
1801
+
<p>A complete, runnable example lives at <code>playground/data-label-clinic/</code>.</p>
1802
+
1803
+
1704
1804
<a class="anchor" id="run-testing"></a>
1705
1805
<h2>Testing Guide</h2>
1706
1806
<p>Continuum favors integration tests over mocks. The <code>[dev]</code> extra ships <code>fakeredis</code>, <code>respx</code>, and <code>pytest-asyncio</code>.</p>
<strong>HITL approver authorization (<code>decided_by ∈ approvers</code>).</strong> When <code>approvers</code> is non-empty, a decision is honored only if its <code>decided_by</code> is in the list — gating both approvals <em>and</em> rejections. Unauthorized decisions are recorded and ignored; the workflow stays paused until a listed approver resolves it (or it auto-expires at <code>timeout</code>). An empty <code>approvers</code> list means anyone may decide; <code>escalated</code> always routes through. A full workflow with this approval gate lives at <code>playground/temporal-leadflow/</code>.
0 commit comments