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.
@@ -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,4 @@
1
+ """Layer 3: Analysis Engine — semantic understanding of agent intents."""
2
+
3
+ from intentframe_components.analysis.base import AnalysisEngine
4
+ from intentframe_components.analysis.engine import AIAnalysisEngine
@@ -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,3 @@
1
+ """Layer 5: Executor — abstract interface for action execution."""
2
+
3
+ from intentframe_components.executor.base import Executor
@@ -0,0 +1,5 @@
1
+ """Re-export :class:`~intentframe_core.executor.Executor` for back-compat."""
2
+
3
+ from intentframe_core.executor import Executor
4
+
5
+ __all__ = ["Executor"]
@@ -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