HumboldtJoker commited on
Commit
3a04c53
·
verified ·
1 Parent(s): 7d1d593

Clean up nested duplicate: v2/v2/kintsugi_vendor/kintsugi/skills/base.py

Browse files
v2/v2/kintsugi_vendor/kintsugi/skills/base.py DELETED
@@ -1,620 +0,0 @@
1
- """
2
- Base skill chip abstractions for Kintsugi CMA.
3
-
4
- This module provides the foundational classes and types for all 22 skill chips
5
- in the Kintsugi CMA system. Skill chips are modular, domain-specific handlers
6
- that process user intents and execute actions within ethical guardrails.
7
-
8
- Key concepts:
9
- - SkillDomain: The functional area a chip operates in (fundraising, operations, etc.)
10
- - EFEWeights: Ethical Framing Engine weights for domain-specific prioritization
11
- - SkillContext: Runtime context including organization, user, and BDI state
12
- - BaseSkillChip: Abstract base class all skill chips inherit from
13
- """
14
-
15
- from abc import ABC, abstractmethod
16
- from dataclasses import dataclass, field
17
- from datetime import datetime, timezone
18
- from enum import Enum
19
- from typing import Any, Callable, Coroutine, Optional
20
-
21
-
22
- class SkillDomain(str, Enum):
23
- """Domains that skill chips operate in.
24
-
25
- Deployments extend this with their own domains via register_domain().
26
- Core domains cover common use cases; specialized domains (intimate,
27
- investigation, security) are registered by their respective scaffolds.
28
- """
29
- # Core domains (always available)
30
- GENERAL = "general"
31
- OPERATIONS = "operations"
32
- COMMUNICATIONS = "communications"
33
- SHELL = "shell"
34
-
35
- # Prosocial / nonprofit domains
36
- FUNDRAISING = "fundraising"
37
- PROGRAMS = "programs"
38
- FINANCE = "finance"
39
- GOVERNANCE = "governance"
40
- COMMUNITY = "community"
41
- MUTUAL_AID = "mutual_aid"
42
- ADVOCACY = "advocacy"
43
- MEMBER_SERVICES = "member_services"
44
-
45
- # Companion domains (Ayni)
46
- INTIMATE = "intimate"
47
- CONSENT = "consent"
48
- MEMORY = "memory"
49
- DEVICE = "device"
50
- PRESENCE = "presence"
51
-
52
- # Investigation domains (Scout, Emet)
53
- INVESTIGATION = "investigation"
54
- RESEARCH = "research"
55
- SECURITY = "security"
56
-
57
-
58
- @dataclass
59
- class EFEWeights:
60
- """Ethical Framing Engine weights for domain-specific prioritization.
61
-
62
- The EFE uses these weights to evaluate actions and decisions within
63
- ethical guardrails. Each skill chip can customize weights based on
64
- its domain's priorities.
65
-
66
- Each weight must be between 0.0 and 1.0. Weights should sum to ~1.0
67
- for proper normalization in scoring algorithms.
68
-
69
- Attributes:
70
- mission_alignment: How well an action aligns with org mission
71
- stakeholder_benefit: Benefit to members, community, beneficiaries
72
- resource_efficiency: Efficient use of limited nonprofit resources
73
- transparency: Openness and accountability in operations
74
- equity: Fair and equitable treatment across populations
75
-
76
- Example:
77
- # Fundraising chip might prioritize mission and stakeholder benefit
78
- weights = EFEWeights(
79
- mission_alignment=0.30,
80
- stakeholder_benefit=0.30,
81
- resource_efficiency=0.15,
82
- transparency=0.15,
83
- equity=0.10,
84
- )
85
- """
86
- mission_alignment: float = 0.25
87
- stakeholder_benefit: float = 0.25
88
- resource_efficiency: float = 0.20
89
- transparency: float = 0.15
90
- equity: float = 0.15
91
-
92
- def __post_init__(self) -> None:
93
- """Validate that all weights are in valid range."""
94
- weight_names = [
95
- 'mission_alignment',
96
- 'stakeholder_benefit',
97
- 'resource_efficiency',
98
- 'transparency',
99
- 'equity',
100
- ]
101
- for name in weight_names:
102
- val = getattr(self, name)
103
- if not 0.0 <= val <= 1.0:
104
- raise ValueError(f"{name} must be between 0.0 and 1.0, got {val}")
105
-
106
- def to_dict(self) -> dict[str, float]:
107
- """Convert weights to dictionary for serialization.
108
-
109
- Returns:
110
- Dictionary mapping weight names to their float values.
111
- """
112
- return {
113
- 'mission_alignment': self.mission_alignment,
114
- 'stakeholder_benefit': self.stakeholder_benefit,
115
- 'resource_efficiency': self.resource_efficiency,
116
- 'transparency': self.transparency,
117
- 'equity': self.equity,
118
- }
119
-
120
- def total(self) -> float:
121
- """Calculate total of all weights.
122
-
123
- Returns:
124
- Sum of all weight values. Should be ~1.0 for normalized weights.
125
- """
126
- return (
127
- self.mission_alignment +
128
- self.stakeholder_benefit +
129
- self.resource_efficiency +
130
- self.transparency +
131
- self.equity
132
- )
133
-
134
-
135
- @dataclass
136
- class SkillContext:
137
- """Context passed to skill chip handlers.
138
-
139
- Contains all runtime information a skill chip needs to execute,
140
- including organization identity, user identity, platform details,
141
- and the current BDI (Belief-Desire-Intention) state from the
142
- cognitive architecture.
143
-
144
- Attributes:
145
- org_id: Unique identifier for the organization
146
- user_id: Unique identifier for the requesting user
147
- session_id: Optional session identifier for conversation tracking
148
- platform: Source platform (slack, discord, webchat, etc.)
149
- channel_id: Platform-specific channel identifier
150
- thread_id: Platform-specific thread identifier
151
- metadata: Additional context data
152
- timestamp: When the request was created (UTC)
153
- beliefs: Current beliefs from BDI state
154
- desires: Current desires/goals from BDI state
155
- intentions: Current intentions/plans from BDI state
156
-
157
- Example:
158
- context = SkillContext(
159
- org_id="org_12345",
160
- user_id="user_67890",
161
- platform="slack",
162
- channel_id="C0123456789",
163
- beliefs=[{"type": "budget_status", "value": "healthy"}],
164
- )
165
- """
166
- org_id: str
167
- user_id: str
168
- session_id: str | None = None
169
- platform: str | None = None # slack, discord, webchat
170
- channel_id: str | None = None
171
- thread_id: str | None = None
172
- metadata: dict[str, Any] = field(default_factory=dict)
173
- timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
174
-
175
- # BDI context (populated by orchestrator)
176
- beliefs: list[dict[str, Any]] = field(default_factory=list)
177
- desires: list[dict[str, Any]] = field(default_factory=list)
178
- intentions: list[dict[str, Any]] = field(default_factory=list)
179
-
180
-
181
- @dataclass
182
- class SkillRequest:
183
- """Request to a skill chip.
184
-
185
- Encapsulates what the user wants to do, extracted entities,
186
- and any additional parameters needed for execution.
187
-
188
- Attributes:
189
- intent: Classified intent (e.g., "grant_search", "donor_lookup")
190
- entities: Extracted entities from user input (names, dates, amounts, etc.)
191
- raw_input: Original unprocessed user input
192
- parameters: Additional parameters for the skill handler
193
-
194
- Example:
195
- request = SkillRequest(
196
- intent="grant_search",
197
- entities={"amount_min": 10000, "focus_area": "education"},
198
- raw_input="Find grants over $10k for education programs",
199
- )
200
- """
201
- intent: str # What the user wants to do
202
- entities: dict[str, Any] = field(default_factory=dict) # Extracted entities
203
- raw_input: str = "" # Original user input
204
- parameters: dict[str, Any] = field(default_factory=dict) # Additional params
205
-
206
-
207
- @dataclass
208
- class SkillResponse:
209
- """Response from a skill chip.
210
-
211
- Contains the result of skill execution including the content to
212
- display, structured data, and metadata about the response.
213
-
214
- Attributes:
215
- content: Human-readable response content
216
- success: Whether the skill executed successfully
217
- data: Structured response data for programmatic use
218
- suggestions: Follow-up actions or questions to suggest
219
- requires_consensus: Whether this action needs approval before execution
220
- consensus_action: Which specific action needs approval
221
- attachments: File attachments or rich media
222
- metadata: Additional response metadata
223
-
224
- Example:
225
- response = SkillResponse(
226
- content="Found 5 matching grants for your search.",
227
- success=True,
228
- data={"grants": [...], "total": 5},
229
- suggestions=["Would you like me to draft an application?"],
230
- )
231
- """
232
- content: str
233
- success: bool = True
234
- data: dict[str, Any] = field(default_factory=dict) # Structured response data
235
- suggestions: list[str] = field(default_factory=list) # Follow-up suggestions
236
- requires_consensus: bool = False # Needs approval before execution
237
- consensus_action: str | None = None # Which action needs approval
238
- attachments: list[dict[str, Any]] = field(default_factory=list)
239
- metadata: dict[str, Any] = field(default_factory=dict)
240
-
241
-
242
- class SkillCapability(str, Enum):
243
- """Standard capabilities that chips can declare.
244
-
245
- Capabilities are used for security, auditing, and feature gating.
246
- Each chip declares which capabilities it uses, enabling:
247
- - Permission checking before execution
248
- - Audit logging of sensitive operations
249
- - Feature flags and gradual rollouts
250
-
251
- Attributes:
252
- READ_DATA: Read organizational data from storage
253
- WRITE_DATA: Write/modify organizational data
254
- EXTERNAL_API: Make external API calls
255
- SEND_NOTIFICATIONS: Send emails, SMS, or push notifications
256
- FINANCIAL_OPERATIONS: Process financial transactions
257
- PII_ACCESS: Access personally identifiable information
258
- SCHEDULE_TASKS: Schedule future automated tasks
259
- GENERATE_REPORTS: Generate reports and documents
260
- """
261
- READ_DATA = "read_data"
262
- WRITE_DATA = "write_data"
263
- EXTERNAL_API = "external_api"
264
- SEND_NOTIFICATIONS = "send_notifications"
265
- FINANCIAL_OPERATIONS = "financial_operations"
266
- PII_ACCESS = "pii_access"
267
- SCHEDULE_TASKS = "schedule_tasks"
268
- GENERATE_REPORTS = "generate_reports"
269
- EXECUTE_SHELL = "execute_shell"
270
-
271
-
272
- @dataclass
273
- class ActivationCondition:
274
- """Predicate that determines when a Program Function fires.
275
-
276
- An activation condition inspects the current state (context, recent
277
- outputs, BDI beliefs) and returns True when the intervention should
278
- trigger. Conditions can match on specific failure patterns, state
279
- thresholds, or domain events.
280
-
281
- Attributes:
282
- name: Identifier for this condition
283
- description: Human-readable description of when this fires
284
- predicate: Callable that takes (context, state_snapshot) and returns bool
285
- priority: Higher priority conditions are evaluated first (default 0)
286
- cooldown_seconds: Minimum time between activations (prevents loops)
287
- """
288
- name: str
289
- description: str
290
- predicate: Callable[[SkillContext, dict[str, Any]], bool]
291
- priority: int = 0
292
- cooldown_seconds: float = 0.0
293
-
294
-
295
- @dataclass
296
- class InterventionAction:
297
- """Action taken when an ActivationCondition fires.
298
-
299
- An intervention modifies the next action, injects corrective context,
300
- or redirects the execution path. It does NOT replace the skill's
301
- normal handle method — it augments it.
302
-
303
- Attributes:
304
- name: Identifier for this action
305
- description: Human-readable description of what this does
306
- action: Callable that takes (request, context, state_snapshot) and returns
307
- a modified SkillRequest or SkillResponse
308
- modifies_request: If True, the action returns a modified SkillRequest
309
- that replaces the original. If False, it returns a
310
- SkillResponse that short-circuits execution.
311
- """
312
- name: str
313
- description: str
314
- action: Callable[..., Any]
315
- modifies_request: bool = True
316
-
317
-
318
- @dataclass
319
- class ProgramFunction:
320
- """A state-action intervention function.
321
-
322
- Program Functions are the proactive complement to a skill's handle method.
323
- While handle responds to explicit user requests, Program Functions fire
324
- when the system detects a state that warrants intervention — a failure
325
- pattern, an anomaly, a drift signal, or an opportunity.
326
-
327
- Inspired by HASP (arXiv:2605.17734): skills as executable intervention
328
- functions with explicit activation conditions.
329
-
330
- Attributes:
331
- condition: When to fire
332
- intervention: What to do
333
- enabled: Whether this PF is active
334
- activation_count: How many times this PF has fired
335
- last_activated: When this PF last fired
336
- """
337
- condition: ActivationCondition
338
- intervention: InterventionAction
339
- enabled: bool = True
340
- activation_count: int = 0
341
- last_activated: Optional[datetime] = None
342
-
343
- def should_fire(self, context: SkillContext, state: dict[str, Any]) -> bool:
344
- if not self.enabled:
345
- return False
346
- if (
347
- self.last_activated is not None
348
- and self.condition.cooldown_seconds > 0
349
- ):
350
- elapsed = (datetime.now(timezone.utc) - self.last_activated).total_seconds()
351
- if elapsed < self.condition.cooldown_seconds:
352
- return False
353
- return self.condition.predicate(context, state)
354
-
355
- def fire(
356
- self,
357
- request: "SkillRequest",
358
- context: SkillContext,
359
- state: dict[str, Any],
360
- ) -> Any:
361
- self.activation_count += 1
362
- self.last_activated = datetime.now(timezone.utc)
363
- return self.intervention.action(request, context, state)
364
-
365
-
366
- class BaseSkillChip(ABC):
367
- """Abstract base class for all skill chips.
368
-
369
- Skill chips are modular handlers for specific domains of nonprofit
370
- operations. Each chip:
371
- - Handles specific intents routed by the Orchestrator
372
- - Operates within ethical guardrails defined by EFE weights
373
- - Declares required MCP tool spans for execution
374
- - Specifies which actions require consensus approval
375
-
376
- Subclasses must implement the `handle` method and should override
377
- class-level attributes to define chip identity and behavior.
378
-
379
- Class Attributes:
380
- name: Unique identifier for the chip
381
- description: Human-readable description
382
- version: Semantic version string
383
- domain: Primary skill domain
384
- efe_weights: Ethical Framing Engine weights
385
- required_spans: MCP tool spans this chip needs
386
- consensus_actions: Actions requiring approval
387
- capabilities: Declared capabilities
388
-
389
- Example:
390
- class GrantSearchChip(BaseSkillChip):
391
- name = "grant_search"
392
- description = "Search and match grant opportunities"
393
- version = "1.0.0"
394
- domain = SkillDomain.FUNDRAISING
395
-
396
- efe_weights = EFEWeights(
397
- mission_alignment=0.30,
398
- stakeholder_benefit=0.25,
399
- resource_efficiency=0.20,
400
- transparency=0.15,
401
- equity=0.10,
402
- )
403
-
404
- required_spans = ["grant_database", "org_profile"]
405
- capabilities = [SkillCapability.READ_DATA, SkillCapability.EXTERNAL_API]
406
-
407
- async def handle(self, request: SkillRequest, context: SkillContext) -> SkillResponse:
408
- # Implementation here
409
- ...
410
- """
411
-
412
- # Class-level attributes (override in subclasses)
413
- name: str = "base_chip"
414
- description: str = "Base skill chip"
415
- version: str = "1.0.0"
416
- domain: SkillDomain = SkillDomain.OPERATIONS
417
-
418
- # Default EFE weights (override per domain)
419
- efe_weights: EFEWeights | None = None
420
-
421
- # MCP tool spans this chip needs
422
- required_spans: list[str] = []
423
-
424
- # Actions that require consensus approval
425
- consensus_actions: list[str] = []
426
-
427
- # Capabilities this chip uses
428
- capabilities: list[SkillCapability] = []
429
-
430
- # Program Functions — proactive interventions (v2)
431
- program_functions: list[ProgramFunction] = []
432
-
433
- def __init__(self) -> None:
434
- """Initialize the skill chip.
435
-
436
- Sets default values for instance-level attributes if not
437
- already defined at the class level.
438
- """
439
- # Initialize instance-level if not set at class level
440
- if self.efe_weights is None:
441
- self.efe_weights = EFEWeights()
442
-
443
- # Ensure lists are instance-level to avoid shared state
444
- if not hasattr(self.__class__, 'required_spans') or self.required_spans is None:
445
- self.required_spans = []
446
- if not hasattr(self.__class__, 'consensus_actions') or self.consensus_actions is None:
447
- self.consensus_actions = []
448
- if not hasattr(self.__class__, 'capabilities') or self.capabilities is None:
449
- self.capabilities = []
450
- self.program_functions = list(self.__class__.program_functions or [])
451
-
452
- @abstractmethod
453
- async def handle(self, request: SkillRequest, context: SkillContext) -> SkillResponse:
454
- """Main execution method - receives routed request from Orchestrator.
455
-
456
- This is the primary entry point for skill chip execution. The
457
- Orchestrator routes classified intents to the appropriate chip
458
- and calls this method with the request and context.
459
-
460
- Args:
461
- request: The skill request with intent and entities
462
- context: Execution context including org, user, BDI state
463
-
464
- Returns:
465
- SkillResponse with content and metadata
466
-
467
- Raises:
468
- NotImplementedError: If subclass doesn't implement this method
469
- """
470
- ...
471
-
472
- async def get_bdi_context(
473
- self,
474
- beliefs: list[dict[str, Any]],
475
- desires: list[dict[str, Any]],
476
- intentions: list[dict[str, Any]],
477
- ) -> dict[str, Any]:
478
- """Extract relevant BDI sections for this chip's domain.
479
-
480
- Override in subclasses to filter BDI state relevant to the
481
- chip's domain. This allows chips to focus on the beliefs,
482
- desires, and intentions that matter for their operations.
483
-
484
- Default implementation returns all BDI state unfiltered.
485
-
486
- Args:
487
- beliefs: Current belief state from cognitive architecture
488
- desires: Current desires/goals
489
- intentions: Current intentions/plans
490
-
491
- Returns:
492
- Dictionary containing filtered BDI state for this chip's domain
493
-
494
- Example:
495
- # In a FundraisingChip subclass:
496
- async def get_bdi_context(self, beliefs, desires, intentions):
497
- return {
498
- 'beliefs': [b for b in beliefs if b.get('domain') == 'fundraising'],
499
- 'desires': [d for d in desires if d.get('type') == 'funding_goal'],
500
- 'intentions': intentions, # Keep all intentions
501
- }
502
- """
503
- return {
504
- 'beliefs': beliefs,
505
- 'desires': desires,
506
- 'intentions': intentions,
507
- }
508
-
509
- def requires_consensus(self, action: str) -> bool:
510
- """Check if an action requires consensus approval.
511
-
512
- Consensus actions are those that need human approval before
513
- execution, such as financial disbursements or policy changes.
514
-
515
- Args:
516
- action: The action name to check
517
-
518
- Returns:
519
- True if the action is in the consensus_actions list
520
- """
521
- return action in self.consensus_actions
522
-
523
- def register_program_function(self, pf: ProgramFunction) -> None:
524
- """Register a Program Function on this skill chip."""
525
- if not isinstance(self.program_functions, list):
526
- self.program_functions = []
527
- self.program_functions.append(pf)
528
-
529
- def evaluate_interventions(
530
- self, context: SkillContext, state: dict[str, Any]
531
- ) -> list[ProgramFunction]:
532
- """Check which Program Functions should fire given the current state.
533
-
534
- Returns PFs in priority order (highest first). The caller decides
535
- whether to execute them — this method only evaluates conditions.
536
- """
537
- if not self.program_functions:
538
- return []
539
- triggered = [
540
- pf for pf in self.program_functions
541
- if pf.should_fire(context, state)
542
- ]
543
- return sorted(triggered, key=lambda pf: pf.condition.priority, reverse=True)
544
-
545
- async def handle_with_interventions(
546
- self,
547
- request: SkillRequest,
548
- context: SkillContext,
549
- state: dict[str, Any] | None = None,
550
- ) -> SkillResponse:
551
- """Execute handle() with Program Function intervention layer.
552
-
553
- Before calling handle(), evaluates all registered PFs against the
554
- current state. PFs that fire can either:
555
- - Modify the request (modifies_request=True): the modified request
556
- is passed to handle()
557
- - Short-circuit (modifies_request=False): the PF's response is
558
- returned directly, skipping handle()
559
- """
560
- state = state or {}
561
- triggered = self.evaluate_interventions(context, state)
562
-
563
- current_request = request
564
- for pf in triggered:
565
- result = pf.fire(current_request, context, state)
566
- if pf.intervention.modifies_request:
567
- if isinstance(result, SkillRequest):
568
- current_request = result
569
- else:
570
- if isinstance(result, SkillResponse):
571
- return result
572
-
573
- return await self.handle(current_request, context)
574
-
575
- def get_info(self) -> dict[str, Any]:
576
- """Get chip metadata for registration/discovery.
577
-
578
- Returns a dictionary containing all metadata about this chip,
579
- useful for registration in the SkillRegistry and for UI display.
580
-
581
- Returns:
582
- Dictionary with chip metadata including name, description,
583
- version, domain, EFE weights, required spans, consensus
584
- actions, and capabilities.
585
- """
586
- return {
587
- 'name': self.name,
588
- 'description': self.description,
589
- 'version': self.version,
590
- 'domain': self.domain.value,
591
- 'efe_weights': self.efe_weights.to_dict() if self.efe_weights else {},
592
- 'required_spans': list(self.required_spans),
593
- 'consensus_actions': list(self.consensus_actions),
594
- 'capabilities': [c.value for c in self.capabilities],
595
- 'program_functions': [
596
- {
597
- 'condition': pf.condition.name,
598
- 'intervention': pf.intervention.name,
599
- 'enabled': pf.enabled,
600
- 'activation_count': pf.activation_count,
601
- }
602
- for pf in (self.program_functions or [])
603
- ],
604
- }
605
-
606
-
607
- # Type alias for skill handler functions
608
- SkillHandler = Callable[[SkillRequest, SkillContext], Coroutine[Any, Any, SkillResponse]]
609
- """Type alias for async skill handler functions.
610
-
611
- A skill handler is any async function that takes a SkillRequest and
612
- SkillContext and returns a SkillResponse. This type is useful for
613
- functional-style skill implementations or middleware.
614
-
615
- Example:
616
- async def my_handler(request: SkillRequest, context: SkillContext) -> SkillResponse:
617
- return SkillResponse(content="Hello!")
618
-
619
- handler: SkillHandler = my_handler
620
- """