intentframe-components 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- intentframe_components/__init__.py +11 -0
- intentframe_components/analysis/__init__.py +4 -0
- intentframe_components/analysis/base.py +36 -0
- intentframe_components/analysis/engine.py +419 -0
- intentframe_components/executor/__init__.py +3 -0
- intentframe_components/executor/base.py +5 -0
- intentframe_components/guardian/__init__.py +17 -0
- intentframe_components/guardian/base.py +35 -0
- intentframe_components/guardian/correlation.py +17 -0
- intentframe_components/guardian/deterministic.py +144 -0
- intentframe_components/guardian/engine.py +486 -0
- intentframe_components/onboarding/__init__.py +4 -0
- intentframe_components/onboarding/base.py +73 -0
- intentframe_components/onboarding/engine.py +209 -0
- intentframe_components/onboarding/instructions.py +50 -0
- intentframe_components/prompt/__init__.py +28 -0
- intentframe_components/prompt/boundaries.py +53 -0
- intentframe_components/prompt/data.py +65 -0
- intentframe_components/prompt/hardening.py +117 -0
- intentframe_components/prompt/logging.py +107 -0
- intentframe_components/prompt/normalization.py +41 -0
- intentframe_components/prompt/roles.py +64 -0
- intentframe_components/prompt/runtime_context.py +22 -0
- intentframe_components-0.1.0.dist-info/METADATA +37 -0
- intentframe_components-0.1.0.dist-info/RECORD +27 -0
- intentframe_components-0.1.0.dist-info/WHEEL +4 -0
- intentframe_components-0.1.0.dist-info/licenses/LICENSE +661 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IntentFrame Components — pipeline building blocks.
|
|
3
|
+
|
|
4
|
+
Each sub-package exposes a base class (interface) and an AI-powered
|
|
5
|
+
implementation. The server assembles them into a pipeline.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from intentframe_components.analysis import AnalysisEngine, AIAnalysisEngine
|
|
9
|
+
from intentframe_components.guardian import Guardian, AIGuardian
|
|
10
|
+
from intentframe_components.executor import Executor
|
|
11
|
+
from intentframe_components.onboarding import OnboardingEngine, AIOnboardingEngine
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Layer 3: Analysis Engine ("The Brain")
|
|
3
|
+
|
|
4
|
+
Semantic AI - SECRET, Cloud Only
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
|
|
9
|
+
from intentframe_core.types import (
|
|
10
|
+
AnalysisReport,
|
|
11
|
+
IntentFrame,
|
|
12
|
+
RuntimeContextForLLM,
|
|
13
|
+
)
|
|
14
|
+
from intentframe_bundle_sdk.types import BundleAIContext, BundleContext
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AnalysisEngine(ABC):
|
|
18
|
+
"""
|
|
19
|
+
Layer 3: The Brain - SECRET, Cloud Only (FULLY TRUSTED)
|
|
20
|
+
|
|
21
|
+
Proprietary AI core that provides deep semantic understanding
|
|
22
|
+
of what actions will ACTUALLY do.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
async def analyze(
|
|
27
|
+
self,
|
|
28
|
+
intent: IntentFrame,
|
|
29
|
+
*,
|
|
30
|
+
active_domains: set[str] | None = None,
|
|
31
|
+
runtime_context_for_llm: RuntimeContextForLLM = (),
|
|
32
|
+
bundle_context: BundleContext | None = None,
|
|
33
|
+
bundle_ai_context: BundleAIContext | None = None,
|
|
34
|
+
) -> AnalysisReport:
|
|
35
|
+
"""Analyze what an intent will REALLY do (UNDECIDED path only)."""
|
|
36
|
+
pass
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AI-Powered Analysis Engine
|
|
3
|
+
|
|
4
|
+
Uses OpenAI Agents to semantically understand what an intent will REALLY do.
|
|
5
|
+
|
|
6
|
+
This is the "brain" of the system - it provides UNDERSTANDING, not decisions.
|
|
7
|
+
Guardian uses this understanding to make policy decisions.
|
|
8
|
+
|
|
9
|
+
Deterministic ALLOW/BLOCK is handled by the Bundle SDK (DeterministicGuardian)
|
|
10
|
+
before this engine runs. The AE only executes on UNDECIDED intents.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from enum import IntEnum
|
|
14
|
+
from typing import Annotated
|
|
15
|
+
|
|
16
|
+
from pydantic import BaseModel, Field, StringConstraints
|
|
17
|
+
|
|
18
|
+
from agents import Agent, ModelSettings, Runner
|
|
19
|
+
|
|
20
|
+
from intentframe_core.types import (
|
|
21
|
+
AnalysisReport,
|
|
22
|
+
IntentSignal,
|
|
23
|
+
IntentFrame,
|
|
24
|
+
PromptEvidence,
|
|
25
|
+
RuntimeContextForLLM,
|
|
26
|
+
)
|
|
27
|
+
from intentframe_core.enums import Reversibility, RiskLevel
|
|
28
|
+
from intentframe_bundle_sdk.types import (
|
|
29
|
+
BundleAIContext,
|
|
30
|
+
BundleContext,
|
|
31
|
+
bundle_ai_context_or_empty,
|
|
32
|
+
)
|
|
33
|
+
from intentframe_bundle_sdk.audit_dump import dump_bundle_ai_context
|
|
34
|
+
from intentframe_components.analysis.base import AnalysisEngine
|
|
35
|
+
from intentframe_components.prompt import format_intent_data
|
|
36
|
+
from intentframe_components.prompt.hardening import PromptHardening
|
|
37
|
+
from intentframe_components.prompt.logging import log_output_dump, log_prompt_dump
|
|
38
|
+
from intentframe_components.prompt.roles import ANALYSIS_ENGINE_ROLE
|
|
39
|
+
from intentframe_components.prompt.runtime_context import merge_runtime_context_sections
|
|
40
|
+
from intentframe_prompt_library.library import DEFAULT_AE_SYSTEM_INSTRUCTIONS
|
|
41
|
+
import logging
|
|
42
|
+
|
|
43
|
+
logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ============================================================
|
|
47
|
+
# AE Output Field Limits
|
|
48
|
+
# ============================================================
|
|
49
|
+
# OpenAI structured output enforces these via JSON Schema maxLength /
|
|
50
|
+
# maxItems. The model writes complete text within the budget — no
|
|
51
|
+
# post-hoc truncation needed. These limits are generous: legitimate
|
|
52
|
+
# AE output rarely exceeds half the cap. They exist to structurally
|
|
53
|
+
# bound the surface available for a transitive injection payload.
|
|
54
|
+
|
|
55
|
+
class AEFieldLimit(IntEnum):
|
|
56
|
+
STATED_INTENT = 400
|
|
57
|
+
ACTUAL_BEHAVIOR = 600
|
|
58
|
+
RISK_REASON = 400
|
|
59
|
+
SCOPE_ANALYSIS = 400
|
|
60
|
+
RECOMMENDATION = 600
|
|
61
|
+
HIDDEN_BEHAVIOR_ITEM = 300
|
|
62
|
+
HIDDEN_BEHAVIORS_MAX_ITEMS = 10
|
|
63
|
+
SEMANTIC_DOMAIN_ITEM = 80
|
|
64
|
+
SEMANTIC_DOMAINS_MAX_ITEMS = 15
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# ============================================================
|
|
68
|
+
# Structured Output for AI Analysis
|
|
69
|
+
# ============================================================
|
|
70
|
+
|
|
71
|
+
_BoundedBehavior = Annotated[str, StringConstraints(max_length=AEFieldLimit.HIDDEN_BEHAVIOR_ITEM)]
|
|
72
|
+
_BoundedDomain = Annotated[str, StringConstraints(max_length=AEFieldLimit.SEMANTIC_DOMAIN_ITEM)]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class AIAnalysisOutput(BaseModel):
|
|
76
|
+
"""Structured output from the AI Analysis Agent"""
|
|
77
|
+
|
|
78
|
+
stated_intent: str = Field(
|
|
79
|
+
max_length=AEFieldLimit.STATED_INTENT,
|
|
80
|
+
description="One sentence: what the agent claims to want to do",
|
|
81
|
+
)
|
|
82
|
+
actual_behavior: str = Field(
|
|
83
|
+
max_length=AEFieldLimit.ACTUAL_BEHAVIOR,
|
|
84
|
+
description="What this action will ACTUALLY do in the real world",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
risk_level: str = Field(
|
|
88
|
+
description="Risk level: LOW, MEDIUM, HIGH, or CRITICAL",
|
|
89
|
+
)
|
|
90
|
+
risk_reason: str = Field(
|
|
91
|
+
max_length=AEFieldLimit.RISK_REASON,
|
|
92
|
+
description="Brief explanation of why this risk level was assigned",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
reversibility: str = Field(
|
|
96
|
+
description="How reversible is this action: FULLY_REVERSIBLE, PARTIALLY_REVERSIBLE, TIME_LIMITED, IRREVERSIBLE, or UNKNOWN",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
hidden_behaviors: list[_BoundedBehavior] = Field(
|
|
100
|
+
default_factory=list,
|
|
101
|
+
max_length=AEFieldLimit.HIDDEN_BEHAVIORS_MAX_ITEMS,
|
|
102
|
+
description="Any hidden or non-obvious behaviors this action might cause",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
scope_analysis: str = Field(
|
|
106
|
+
max_length=AEFieldLimit.SCOPE_ANALYSIS,
|
|
107
|
+
description="What resources/data will this action affect?",
|
|
108
|
+
)
|
|
109
|
+
scope_mismatch: bool = Field(
|
|
110
|
+
default=False,
|
|
111
|
+
description="Does the actual scope exceed what was stated/expected?",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
semantic_domains: list[_BoundedDomain] = Field(
|
|
115
|
+
default_factory=list,
|
|
116
|
+
max_length=AEFieldLimit.SEMANTIC_DOMAINS_MAX_ITEMS,
|
|
117
|
+
description="Human-level domains this action falls under: spending, communication, deletion, data_access, scheduling, etc. Empty list if none apply clearly.",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
confidence: float = Field(
|
|
121
|
+
ge=0.0, le=1.0,
|
|
122
|
+
description="Confidence in this analysis (0.0 to 1.0)",
|
|
123
|
+
)
|
|
124
|
+
recommendation: str = Field(
|
|
125
|
+
max_length=AEFieldLimit.RECOMMENDATION,
|
|
126
|
+
description="One-sentence neutral summary of the analysis (no allow/block language)",
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# ============================================================
|
|
131
|
+
# AI Analysis Engine
|
|
132
|
+
# ============================================================
|
|
133
|
+
|
|
134
|
+
class AIAnalysisEngine(AnalysisEngine):
|
|
135
|
+
"""
|
|
136
|
+
AI-powered Analysis Engine using OpenAI Agents.
|
|
137
|
+
|
|
138
|
+
Provides deep semantic understanding of:
|
|
139
|
+
- What actions will ACTUALLY do
|
|
140
|
+
- Hidden or non-obvious behaviors
|
|
141
|
+
- Risk factors
|
|
142
|
+
- Reversibility
|
|
143
|
+
|
|
144
|
+
Does NOT make allow/block decisions - that's Guardian's job.
|
|
145
|
+
|
|
146
|
+
Deterministic fast-paths (safe passive reads, catastrophic commands)
|
|
147
|
+
run upstream in the Bundle SDK before this engine is invoked. This
|
|
148
|
+
class only performs full AI analysis on UNDECIDED intents.
|
|
149
|
+
|
|
150
|
+
User-facing IO (ASK_USER, SHOW_MESSAGE, GET_CONFIRMATION) always
|
|
151
|
+
goes through full AI analysis so that prompt content is inspected
|
|
152
|
+
for phishing / social engineering. Guardian depends on this.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
_hardener = PromptHardening()
|
|
156
|
+
|
|
157
|
+
# ── Model settings note ──────────────────────────────────────
|
|
158
|
+
# The Agents SDK passes ModelSettings fields to the OpenAI Responses
|
|
159
|
+
# API via a _non_null_or_omit() pattern: any field left as None is
|
|
160
|
+
# omitted from the request entirely, falling back to the API's own
|
|
161
|
+
# default (temperature=1.0, top_p=1.0, etc.).
|
|
162
|
+
#
|
|
163
|
+
# For standard completion models (gpt-4o-mini, gpt-4.1, etc.)
|
|
164
|
+
# temperature=0 gives greedy decoding — always picks the highest-
|
|
165
|
+
# probability token. This is the single biggest lever for
|
|
166
|
+
# reproducibility (~95%+ identical outputs on identical inputs).
|
|
167
|
+
# OpenAI recommends not setting both temperature and top_p, and
|
|
168
|
+
# with temperature=0 top_p is irrelevant, so we leave it as None.
|
|
169
|
+
#
|
|
170
|
+
# GPT-5 family models are reasoning models and do NOT accept
|
|
171
|
+
# temperature — use ModelSettings(reasoning=Reasoning(effort=...))
|
|
172
|
+
# instead. See the Guardian engine for that pattern.
|
|
173
|
+
|
|
174
|
+
def __init__(
|
|
175
|
+
self,
|
|
176
|
+
model: str = "gpt-4o-mini",
|
|
177
|
+
verbose: bool = True,
|
|
178
|
+
):
|
|
179
|
+
self.model = model
|
|
180
|
+
self.verbose = verbose
|
|
181
|
+
self._agents: dict[str, Agent] = {}
|
|
182
|
+
self._agent = self._get_agent(DEFAULT_AE_SYSTEM_INSTRUCTIONS)
|
|
183
|
+
|
|
184
|
+
@staticmethod
|
|
185
|
+
def _base_instructions() -> str:
|
|
186
|
+
return DEFAULT_AE_SYSTEM_INSTRUCTIONS
|
|
187
|
+
|
|
188
|
+
def _get_agent(self, base_instructions: str) -> Agent:
|
|
189
|
+
if base_instructions not in self._agents:
|
|
190
|
+
self._agents[base_instructions] = Agent(
|
|
191
|
+
name="Analysis Engine",
|
|
192
|
+
instructions=self._hardener.harden_system_prompt(
|
|
193
|
+
base_instructions=base_instructions,
|
|
194
|
+
role_preamble=ANALYSIS_ENGINE_ROLE,
|
|
195
|
+
),
|
|
196
|
+
model=self.model,
|
|
197
|
+
output_type=AIAnalysisOutput,
|
|
198
|
+
model_settings=ModelSettings(temperature=0),
|
|
199
|
+
)
|
|
200
|
+
return self._agents[base_instructions]
|
|
201
|
+
|
|
202
|
+
async def analyze(
|
|
203
|
+
self,
|
|
204
|
+
intent: IntentFrame,
|
|
205
|
+
*,
|
|
206
|
+
active_domains: set[str] | None = None,
|
|
207
|
+
runtime_context_for_llm: RuntimeContextForLLM = (),
|
|
208
|
+
bundle_context: BundleContext | None = None,
|
|
209
|
+
bundle_ai_context: BundleAIContext | None = None,
|
|
210
|
+
) -> AnalysisReport:
|
|
211
|
+
"""Analyze what an intent will REALLY do via full AI analysis."""
|
|
212
|
+
ai_ctx = bundle_ai_context_or_empty(bundle_ai_context)
|
|
213
|
+
|
|
214
|
+
if self.verbose:
|
|
215
|
+
for hint in ai_ctx.ae_log_hints:
|
|
216
|
+
print(f" │ {hint}")
|
|
217
|
+
|
|
218
|
+
prompt = self._build_analysis_prompt(
|
|
219
|
+
intent,
|
|
220
|
+
ai_ctx,
|
|
221
|
+
active_domains=active_domains,
|
|
222
|
+
runtime_context_for_llm=runtime_context_for_llm,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
system_instructions = self._resolve_system_instructions(ai_ctx)
|
|
226
|
+
prompt_source = self._resolve_prompt_source(ai_ctx)
|
|
227
|
+
prompt_label = self._resolve_prompt_label(ai_ctx)
|
|
228
|
+
agent = self._get_agent(system_instructions)
|
|
229
|
+
|
|
230
|
+
if self.verbose:
|
|
231
|
+
print(
|
|
232
|
+
f" │ AI analyzing: {intent.action} "
|
|
233
|
+
f"(prompt={prompt_source}:{prompt_label})..."
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
log_prompt_dump(
|
|
237
|
+
"analysis",
|
|
238
|
+
prompt,
|
|
239
|
+
prompt_source=prompt_source,
|
|
240
|
+
prompt_label=prompt_label,
|
|
241
|
+
system_prompt=agent.instructions,
|
|
242
|
+
bundle_ai_context=dump_bundle_ai_context(ai_ctx),
|
|
243
|
+
)
|
|
244
|
+
result = await Runner.run(agent, prompt)
|
|
245
|
+
|
|
246
|
+
ai_output = result.final_output
|
|
247
|
+
report = self._convert_to_report(
|
|
248
|
+
intent,
|
|
249
|
+
ai_output,
|
|
250
|
+
intent_signals=list(ai_ctx.ae_intent_signals),
|
|
251
|
+
signal_truncated=ai_ctx.ae_signal_truncated,
|
|
252
|
+
)
|
|
253
|
+
llm_output = ai_output.model_dump(mode="json")
|
|
254
|
+
converted_output = report.model_dump(
|
|
255
|
+
mode="json",
|
|
256
|
+
exclude={"prompt_evidence": True},
|
|
257
|
+
)
|
|
258
|
+
report.prompt_evidence = PromptEvidence(
|
|
259
|
+
prompt_source=prompt_source,
|
|
260
|
+
prompt_label=prompt_label,
|
|
261
|
+
system_prompt=agent.instructions,
|
|
262
|
+
request_prompt=prompt,
|
|
263
|
+
llm_output=llm_output,
|
|
264
|
+
converted_output=converted_output,
|
|
265
|
+
)
|
|
266
|
+
log_output_dump(
|
|
267
|
+
"analysis",
|
|
268
|
+
llm_output=llm_output,
|
|
269
|
+
converted_output=converted_output,
|
|
270
|
+
prompt_source=prompt_source,
|
|
271
|
+
prompt_label=prompt_label,
|
|
272
|
+
)
|
|
273
|
+
return report
|
|
274
|
+
|
|
275
|
+
@staticmethod
|
|
276
|
+
def _resolve_system_instructions(bundle_ai_context: BundleAIContext) -> str:
|
|
277
|
+
if bundle_ai_context.ae_system_instructions:
|
|
278
|
+
return bundle_ai_context.ae_system_instructions
|
|
279
|
+
return DEFAULT_AE_SYSTEM_INSTRUCTIONS
|
|
280
|
+
|
|
281
|
+
@staticmethod
|
|
282
|
+
def _resolve_prompt_source(bundle_ai_context: BundleAIContext) -> str:
|
|
283
|
+
return "bundle" if bundle_ai_context.ae_system_instructions else "fallback_default"
|
|
284
|
+
|
|
285
|
+
@staticmethod
|
|
286
|
+
def _resolve_prompt_label(bundle_ai_context: BundleAIContext) -> str:
|
|
287
|
+
return bundle_ai_context.ae_prompt_label or "fallback_default"
|
|
288
|
+
|
|
289
|
+
def _build_analysis_prompt(
|
|
290
|
+
self,
|
|
291
|
+
intent: IntentFrame,
|
|
292
|
+
bundle_ai_context: BundleAIContext,
|
|
293
|
+
*,
|
|
294
|
+
active_domains: set[str] | None = None,
|
|
295
|
+
runtime_context_for_llm: RuntimeContextForLLM = (),
|
|
296
|
+
) -> str:
|
|
297
|
+
"""Build hardened per-request prompt; bundle supplies external Context text."""
|
|
298
|
+
context_lines = [
|
|
299
|
+
f"Action: {intent.action}",
|
|
300
|
+
f"Agent: {intent.agent_type or intent.agent_id}",
|
|
301
|
+
f"Task: {intent.task_description or 'Not specified'}",
|
|
302
|
+
]
|
|
303
|
+
if bundle_ai_context.ae_external_context:
|
|
304
|
+
context_lines.append(bundle_ai_context.ae_external_context)
|
|
305
|
+
|
|
306
|
+
trusted_sections: dict[str, str] = {
|
|
307
|
+
"Context": "\n".join(context_lines),
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if active_domains:
|
|
311
|
+
domains_str = ", ".join(sorted(active_domains))
|
|
312
|
+
trusted_sections["Active Domains"] = (
|
|
313
|
+
f"The system has rules for these domains: {domains_str}\n"
|
|
314
|
+
"Pay special attention to whether this action falls under any of "
|
|
315
|
+
"these domains. If it does, include the matching domain(s) in your "
|
|
316
|
+
"semantic_domains output. This is a hint — still classify any other "
|
|
317
|
+
"domains you observe."
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
merge_runtime_context_sections(trusted_sections, runtime_context_for_llm)
|
|
321
|
+
|
|
322
|
+
untrusted = {"Target": intent.target, "Reason": intent.reason}
|
|
323
|
+
data_section = format_intent_data(intent.data)
|
|
324
|
+
if data_section:
|
|
325
|
+
untrusted["Data"] = data_section
|
|
326
|
+
|
|
327
|
+
return self._hardener.build_hardened_prompt(
|
|
328
|
+
trusted_sections=trusted_sections,
|
|
329
|
+
untrusted_fields=untrusted,
|
|
330
|
+
closing_instruction="Analyze what this action will REALLY do.",
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
_FIELD_BOUNDS: dict[str, int] = {
|
|
334
|
+
"stated_intent": AEFieldLimit.STATED_INTENT,
|
|
335
|
+
"actual_behavior": AEFieldLimit.ACTUAL_BEHAVIOR,
|
|
336
|
+
"risk_reason": AEFieldLimit.RISK_REASON,
|
|
337
|
+
"scope_analysis": AEFieldLimit.SCOPE_ANALYSIS,
|
|
338
|
+
"recommendation": AEFieldLimit.RECOMMENDATION,
|
|
339
|
+
}
|
|
340
|
+
_LIST_BOUNDS: dict[str, tuple[int, int]] = {
|
|
341
|
+
"hidden_behaviors": (AEFieldLimit.HIDDEN_BEHAVIORS_MAX_ITEMS, AEFieldLimit.HIDDEN_BEHAVIOR_ITEM),
|
|
342
|
+
"semantic_domains": (AEFieldLimit.SEMANTIC_DOMAINS_MAX_ITEMS, AEFieldLimit.SEMANTIC_DOMAIN_ITEM),
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
def _convert_to_report(
|
|
346
|
+
self,
|
|
347
|
+
intent: IntentFrame,
|
|
348
|
+
ai_output: AIAnalysisOutput,
|
|
349
|
+
*,
|
|
350
|
+
intent_signals: list[IntentSignal] | None = None,
|
|
351
|
+
signal_truncated: bool = False,
|
|
352
|
+
) -> AnalysisReport:
|
|
353
|
+
"""Convert AI output to AnalysisReport format.
|
|
354
|
+
|
|
355
|
+
Includes a deterministic backstop: if any field exceeds the
|
|
356
|
+
schema-defined bound (which should never happen when OpenAI
|
|
357
|
+
structured output is enforcing maxLength), the report is flagged
|
|
358
|
+
with ae_output_anomaly so Guardian treats it as elevated risk.
|
|
359
|
+
"""
|
|
360
|
+
anomaly = self._detect_overflow(ai_output)
|
|
361
|
+
|
|
362
|
+
risk_level_map = {
|
|
363
|
+
"LOW": RiskLevel.LOW,
|
|
364
|
+
"MEDIUM": RiskLevel.MEDIUM,
|
|
365
|
+
"HIGH": RiskLevel.HIGH,
|
|
366
|
+
"CRITICAL": RiskLevel.CRITICAL,
|
|
367
|
+
}
|
|
368
|
+
risk_level = risk_level_map.get(ai_output.risk_level.upper(), RiskLevel.MEDIUM)
|
|
369
|
+
|
|
370
|
+
reversibility_map = {
|
|
371
|
+
"FULLY_REVERSIBLE": Reversibility.FULLY_REVERSIBLE,
|
|
372
|
+
"PARTIALLY_REVERSIBLE": Reversibility.PARTIALLY_REVERSIBLE,
|
|
373
|
+
"TIME_LIMITED": Reversibility.TIME_LIMITED,
|
|
374
|
+
"IRREVERSIBLE": Reversibility.IRREVERSIBLE,
|
|
375
|
+
"UNKNOWN": Reversibility.UNKNOWN,
|
|
376
|
+
}
|
|
377
|
+
reversibility = reversibility_map.get(
|
|
378
|
+
ai_output.reversibility.upper(),
|
|
379
|
+
Reversibility.UNKNOWN,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
return AnalysisReport(
|
|
383
|
+
stated_intent=ai_output.stated_intent,
|
|
384
|
+
actual_behaviors=[{
|
|
385
|
+
"action": intent.action,
|
|
386
|
+
"actual_behavior": ai_output.actual_behavior,
|
|
387
|
+
"matches_intent": not ai_output.scope_mismatch,
|
|
388
|
+
}],
|
|
389
|
+
requested_scope=[intent.target],
|
|
390
|
+
actual_scope=[ai_output.scope_analysis],
|
|
391
|
+
scope_mismatch=ai_output.scope_mismatch,
|
|
392
|
+
predicted_outcomes={
|
|
393
|
+
"risk_reason": ai_output.risk_reason,
|
|
394
|
+
},
|
|
395
|
+
hidden_behaviors=ai_output.hidden_behaviors,
|
|
396
|
+
risk_factors={"overall": risk_level},
|
|
397
|
+
reversibility=reversibility,
|
|
398
|
+
semantic_domains=ai_output.semantic_domains,
|
|
399
|
+
confidence=ai_output.confidence,
|
|
400
|
+
recommendation=ai_output.recommendation,
|
|
401
|
+
intent_signals=intent_signals or [],
|
|
402
|
+
ae_output_anomaly=anomaly,
|
|
403
|
+
report_integrity_flags=(
|
|
404
|
+
["intent_signals_truncated"] if signal_truncated else []
|
|
405
|
+
),
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
def _detect_overflow(self, ai_output: AIAnalysisOutput) -> bool:
|
|
409
|
+
"""Return True if any AI output field exceeds its schema bound."""
|
|
410
|
+
for field_name, limit in self._FIELD_BOUNDS.items():
|
|
411
|
+
if len(getattr(ai_output, field_name, "")) > limit:
|
|
412
|
+
return True
|
|
413
|
+
for field_name, (max_items, per_item_limit) in self._LIST_BOUNDS.items():
|
|
414
|
+
items = getattr(ai_output, field_name, [])
|
|
415
|
+
if len(items) > max_items:
|
|
416
|
+
return True
|
|
417
|
+
if any(len(item) > per_item_limit for item in items):
|
|
418
|
+
return True
|
|
419
|
+
return False
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Layer 4: Guardian — policy enforcement and security decisions."""
|
|
2
|
+
|
|
3
|
+
from intentframe_components.guardian.base import Guardian
|
|
4
|
+
from intentframe_components.guardian.deterministic import (
|
|
5
|
+
DeterministicDecision,
|
|
6
|
+
DeterministicGuardian,
|
|
7
|
+
DeterministicResult,
|
|
8
|
+
)
|
|
9
|
+
from intentframe_components.guardian.engine import AIGuardian
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"AIGuardian",
|
|
13
|
+
"DeterministicDecision",
|
|
14
|
+
"DeterministicGuardian",
|
|
15
|
+
"DeterministicResult",
|
|
16
|
+
"Guardian",
|
|
17
|
+
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Layer 4: Guardian ("The Judge")
|
|
3
|
+
|
|
4
|
+
Policy Enforcer - HYBRID (Local + Cloud)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
|
|
9
|
+
from intentframe_core.types import (
|
|
10
|
+
AnalysisReport,
|
|
11
|
+
IntentFrame,
|
|
12
|
+
RuntimeContextForLLM,
|
|
13
|
+
UserContext,
|
|
14
|
+
ValidationResult,
|
|
15
|
+
)
|
|
16
|
+
from intentframe_bundle_sdk.types import BundleAIContext, BundleContext
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Guardian(ABC):
|
|
20
|
+
"""Layer 4: The Judge - policy enforcement on Analysis Report."""
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
async def validate(
|
|
24
|
+
self,
|
|
25
|
+
intent: IntentFrame,
|
|
26
|
+
analysis: AnalysisReport,
|
|
27
|
+
user_context: UserContext,
|
|
28
|
+
*,
|
|
29
|
+
active_domains: set[str] | None = None,
|
|
30
|
+
runtime_context_for_llm: RuntimeContextForLLM = (),
|
|
31
|
+
bundle_context: BundleContext | None = None,
|
|
32
|
+
bundle_ai_context: BundleAIContext | None = None,
|
|
33
|
+
) -> ValidationResult:
|
|
34
|
+
"""Validate intent against user policies using Analysis Report."""
|
|
35
|
+
pass
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Guardian Correlation Service
|
|
3
|
+
|
|
4
|
+
Stateful data service that tracks temporal patterns across intents.
|
|
5
|
+
Guardian queries this for historical context when making decisions.
|
|
6
|
+
|
|
7
|
+
Provides:
|
|
8
|
+
- Velocity tracking (intents per time window)
|
|
9
|
+
- Accumulation tracking (e.g. total spend per day)
|
|
10
|
+
- Sequence tracking (ordered action history per agent)
|
|
11
|
+
- Drift detection (behavioral baseline comparison)
|
|
12
|
+
- Anomaly scoring
|
|
13
|
+
|
|
14
|
+
Does NOT make decisions — Guardian owns that.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
# TODO: Implement correlation service
|