hyperagents 1.2.3rc5.post3__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.
Files changed (361) hide show
  1. agents/__init__.py +31 -0
  2. agents/background_monitor.py +453 -0
  3. agents/learning.py +460 -0
  4. agents/planner.py +421 -0
  5. agents/transcriber.py +341 -0
  6. civilization/__init__.py +51 -0
  7. civilization/api/__init__.py +7 -0
  8. civilization/api/civilization_api.py +361 -0
  9. civilization/graph/__init__.py +19 -0
  10. civilization/graph/knowledge_linker.py +348 -0
  11. civilization/graph/org_graph.py +348 -0
  12. civilization/graph/process_graph.py +386 -0
  13. civilization/ingestion/__init__.py +20 -0
  14. civilization/ingestion/chunker.py +273 -0
  15. civilization/ingestion/document_ingestor.py +358 -0
  16. civilization/ingestion/embedder.py +262 -0
  17. civilization/ingestion/extractor.py +224 -0
  18. civilization/interview/__init__.py +19 -0
  19. civilization/interview/gap_detector.py +380 -0
  20. civilization/interview/interviewer.py +323 -0
  21. civilization/interview/questioner.py +363 -0
  22. civilization/retrieval/__init__.py +17 -0
  23. civilization/retrieval/civilization_rag.py +316 -0
  24. civilization/retrieval/context_injector.py +342 -0
  25. civilization/retrieval/relevance_ranker.py +287 -0
  26. civilization/schema.py +299 -0
  27. civilization/store.py +177 -0
  28. civilization/sync/__init__.py +16 -0
  29. civilization/sync/confluence_sync.py +317 -0
  30. civilization/sync/gdrive_sync.py +262 -0
  31. civilization/sync/notion_sync.py +237 -0
  32. civilization/versioning/__init__.py +19 -0
  33. civilization/versioning/diff_engine.py +351 -0
  34. civilization/versioning/staleness_detector.py +303 -0
  35. civilization/versioning/version_manager.py +293 -0
  36. cli/__init__.py +1 -0
  37. cli/doctor.py +334 -0
  38. cli/hyperclaw.py +1357 -0
  39. cli/onboarding.py +469 -0
  40. core/__init__.py +1 -0
  41. core/connectors/__init__.py +119 -0
  42. core/connectors/infrastructure.py +772 -0
  43. core/connectors/messaging.py +586 -0
  44. core/connectors/productivity.py +764 -0
  45. core/connectors/smarthome.py +528 -0
  46. core/connectors/social.py +730 -0
  47. core/hyperrouter/__init__.py +23 -0
  48. core/hyperrouter/bandit.py +218 -0
  49. core/hyperrouter/fast_loop.py +68 -0
  50. core/hyperrouter/slow_loop.py +143 -0
  51. core/hyperstate/__init__.py +24 -0
  52. core/hyperstate/certifier.py +154 -0
  53. core/hyperstate/schema.py +160 -0
  54. core/hyperstate/state_manager.py +103 -0
  55. core/hyperstate/store.py +163 -0
  56. core/providers/__init__.py +121 -0
  57. core/providers/embeddings.py +389 -0
  58. core/providers/image.py +534 -0
  59. core/providers/llm.py +1935 -0
  60. core/providers/search.py +608 -0
  61. core/providers/voice.py +474 -0
  62. hyperagents-1.2.3rc5.post3.dist-info/METADATA +469 -0
  63. hyperagents-1.2.3rc5.post3.dist-info/RECORD +361 -0
  64. hyperagents-1.2.3rc5.post3.dist-info/WHEEL +5 -0
  65. hyperagents-1.2.3rc5.post3.dist-info/entry_points.txt +6 -0
  66. hyperagents-1.2.3rc5.post3.dist-info/licenses/LICENSE +21 -0
  67. hyperagents-1.2.3rc5.post3.dist-info/top_level.txt +13 -0
  68. hyperclaw/__init__.py +67 -0
  69. hyperclaw/__main__.py +220 -0
  70. hyperclaw/agent.py +775 -0
  71. hyperclaw/agent_coordinator.py +577 -0
  72. hyperclaw/anthropic_params.py +225 -0
  73. hyperclaw/api_utils.py +85 -0
  74. hyperclaw/approval_telegram.py +209 -0
  75. hyperclaw/approval_terminal.py +80 -0
  76. hyperclaw/attachments.py +177 -0
  77. hyperclaw/branding.py +55 -0
  78. hyperclaw/brief/__init__.py +11 -0
  79. hyperclaw/brief/cli.py +204 -0
  80. hyperclaw/brief/collect.py +146 -0
  81. hyperclaw/brief/compose.py +333 -0
  82. hyperclaw/brief/diff.py +110 -0
  83. hyperclaw/brief/errors.py +99 -0
  84. hyperclaw/brief/google.py +269 -0
  85. hyperclaw/brief/google_auth.py +294 -0
  86. hyperclaw/brief/links.py +124 -0
  87. hyperclaw/brief/service.py +424 -0
  88. hyperclaw/brief/store.py +205 -0
  89. hyperclaw/brief/tasks.py +200 -0
  90. hyperclaw/chat_scope.py +115 -0
  91. hyperclaw/cli_tui.py +497 -0
  92. hyperclaw/client_guard.py +717 -0
  93. hyperclaw/connection_check.py +113 -0
  94. hyperclaw/cost_guard.py +1728 -0
  95. hyperclaw/default_config/agents.yaml +534 -0
  96. hyperclaw/default_config/hyperclaw.yaml +27 -0
  97. hyperclaw/default_config/models.yaml +161 -0
  98. hyperclaw/deployment.py +39 -0
  99. hyperclaw/digest/__init__.py +9 -0
  100. hyperclaw/digest/cli.py +117 -0
  101. hyperclaw/digest/errors.py +100 -0
  102. hyperclaw/digest/gitlab.py +187 -0
  103. hyperclaw/digest/http.py +113 -0
  104. hyperclaw/digest/redact.py +32 -0
  105. hyperclaw/digest/service.py +271 -0
  106. hyperclaw/digest/slack.py +223 -0
  107. hyperclaw/digest/state.py +124 -0
  108. hyperclaw/digest/summarize.py +198 -0
  109. hyperclaw/digest/tokens.py +143 -0
  110. hyperclaw/distribution.py +46 -0
  111. hyperclaw/documents.py +351 -0
  112. hyperclaw/feeds.py +339 -0
  113. hyperclaw/google_workspace/__init__.py +5 -0
  114. hyperclaw/google_workspace/api.py +151 -0
  115. hyperclaw/google_workspace/auth.py +397 -0
  116. hyperclaw/google_workspace/calendar.py +157 -0
  117. hyperclaw/google_workspace/cli.py +72 -0
  118. hyperclaw/google_workspace/drive.py +236 -0
  119. hyperclaw/google_workspace/errors.py +58 -0
  120. hyperclaw/google_workspace/gmail.py +295 -0
  121. hyperclaw/google_workspace/meet.py +122 -0
  122. hyperclaw/google_workspace/tools.py +319 -0
  123. hyperclaw/hyperspeed.py +57 -0
  124. hyperclaw/integrations_layer.py +1427 -0
  125. hyperclaw/live_state.py +188 -0
  126. hyperclaw/managed_config.py +245 -0
  127. hyperclaw/media_hub.py +179 -0
  128. hyperclaw/memory_manager.py +584 -0
  129. hyperclaw/model_catalog.py +551 -0
  130. hyperclaw/model_policy.py +211 -0
  131. hyperclaw/model_router.py +595 -0
  132. hyperclaw/model_selector.py +72 -0
  133. hyperclaw/naming.py +284 -0
  134. hyperclaw/notices.py +65 -0
  135. hyperclaw/oai_stream.py +106 -0
  136. hyperclaw/orchestrator.py +591 -0
  137. hyperclaw/outbox.py +82 -0
  138. hyperclaw/persona.py +367 -0
  139. hyperclaw/prometheus.py +219 -0
  140. hyperclaw/prompt_assembly.py +157 -0
  141. hyperclaw/provider_setup.py +146 -0
  142. hyperclaw/providers.py +340 -0
  143. hyperclaw/qualification.py +341 -0
  144. hyperclaw/qualification_keys/hyper-d292fd127cef5a3e.pub +1 -0
  145. hyperclaw/qualification_records/anthropic--claude-sonnet-5-5.json +14 -0
  146. hyperclaw/reasoning_display.py +35 -0
  147. hyperclaw/scheduler.py +454 -0
  148. hyperclaw/server.py +847 -0
  149. hyperclaw/setup.py +627 -0
  150. hyperclaw/skills.py +717 -0
  151. hyperclaw/solomon.py +442 -0
  152. hyperclaw/swarm.py +533 -0
  153. hyperclaw/taint.py +832 -0
  154. hyperclaw/team/__init__.py +28 -0
  155. hyperclaw/team/dispatcher.py +431 -0
  156. hyperclaw/team/genesis.py +106 -0
  157. hyperclaw/team/lead.py +110 -0
  158. hyperclaw/team/model.py +135 -0
  159. hyperclaw/team/morning.py +238 -0
  160. hyperclaw/team/repos.py +66 -0
  161. hyperclaw/team/roster.py +264 -0
  162. hyperclaw/team/specialist.py +235 -0
  163. hyperclaw/team/status.py +163 -0
  164. hyperclaw/team/watchers.py +112 -0
  165. hyperclaw/teamsetup/__init__.py +17 -0
  166. hyperclaw/teamsetup/checks.py +208 -0
  167. hyperclaw/teamsetup/item.py +391 -0
  168. hyperclaw/teamsetup/oauth_loopback.py +84 -0
  169. hyperclaw/teamsetup/onboard.py +550 -0
  170. hyperclaw/teamsetup/profile.py +82 -0
  171. hyperclaw/teamsetup/secrets.py +313 -0
  172. hyperclaw/teamsetup/selfcheck.py +295 -0
  173. hyperclaw/teamsetup/service.py +495 -0
  174. hyperclaw/teamsetup/slack_oauth.py +442 -0
  175. hyperclaw/teamsetup/tty.py +62 -0
  176. hyperclaw/telegram_bot.py +1106 -0
  177. hyperclaw/telegram_intake.py +126 -0
  178. hyperclaw/telegram_pairing.py +360 -0
  179. hyperclaw/terminal_provider.py +323 -0
  180. hyperclaw/tools/__init__.py +1 -0
  181. hyperclaw/tools/approval.py +949 -0
  182. hyperclaw/tools/args.py +212 -0
  183. hyperclaw/tools/catalog.py +244 -0
  184. hyperclaw/tools/classify.py +266 -0
  185. hyperclaw/tools/effects.py +724 -0
  186. hyperclaw/tools/egress.py +210 -0
  187. hyperclaw/tools/envelope.py +188 -0
  188. hyperclaw/tools/guard.py +367 -0
  189. hyperclaw/tools/ledger.py +339 -0
  190. hyperclaw/tools/receipts.py +70 -0
  191. hyperclaw/tools/registry.py +318 -0
  192. hyperclaw/tools/service_guard.py +151 -0
  193. hyperclaw/tools/surfaces.py +54 -0
  194. hyperclaw/tools/workspace.py +87 -0
  195. hyperclaw/trading/__init__.py +77 -0
  196. hyperclaw/truth_guard.py +791 -0
  197. hyperclaw/tts.py +124 -0
  198. hyperclaw/tui.py +4255 -0
  199. hyperclaw/tui_bridge.py +938 -0
  200. hyperclaw/unattended.py +60 -0
  201. hyperclaw/updates/__init__.py +54 -0
  202. hyperclaw/updates/check.py +131 -0
  203. hyperclaw/updates/index.py +178 -0
  204. hyperclaw/updates/install.py +110 -0
  205. hyperclaw/updates/managed.py +161 -0
  206. hyperclaw/updates/manifest.py +138 -0
  207. hyperclaw/updates/notices.py +86 -0
  208. hyperclaw/updates/paths.py +53 -0
  209. hyperclaw/updates/release.py +146 -0
  210. hyperclaw/updates/upgrade.py +253 -0
  211. hyperclaw/updates/versioning.py +104 -0
  212. hyperclaw/userconfig.py +119 -0
  213. hyperclaw/xai.py +83 -0
  214. integrations/__init__.py +31 -0
  215. integrations/automation/__init__.py +1 -0
  216. integrations/automation/make.py +87 -0
  217. integrations/automation/zapier.py +87 -0
  218. integrations/base.py +476 -0
  219. integrations/communication/__init__.py +1 -0
  220. integrations/communication/twilio.py +279 -0
  221. integrations/communication/zoom.py +290 -0
  222. integrations/data/__init__.py +1 -0
  223. integrations/data/google_analytics.py +135 -0
  224. integrations/data/postgres.py +182 -0
  225. integrations/data/supabase_connector.py +202 -0
  226. integrations/data/weather_feeds.py +450 -0
  227. integrations/developer/__init__.py +9 -0
  228. integrations/developer/github.py +406 -0
  229. integrations/developer/gitlab.py +298 -0
  230. integrations/enterprise/__init__.py +13 -0
  231. integrations/enterprise/confluence.py +266 -0
  232. integrations/enterprise/hubspot.py +293 -0
  233. integrations/enterprise/jira.py +314 -0
  234. integrations/enterprise/salesforce.py +286 -0
  235. integrations/finance/__init__.py +1 -0
  236. integrations/finance/polymarket_client.py +247 -0
  237. integrations/finance/quickbooks.py +281 -0
  238. integrations/finance/stripe.py +261 -0
  239. integrations/gateway.py +238 -0
  240. integrations/messaging/__init__.py +32 -0
  241. integrations/messaging/discord.py +159 -0
  242. integrations/messaging/email.py +401 -0
  243. integrations/messaging/imessage.py +443 -0
  244. integrations/messaging/signal.py +175 -0
  245. integrations/messaging/slack.py +217 -0
  246. integrations/messaging/sms.py +175 -0
  247. integrations/messaging/teams.py +208 -0
  248. integrations/messaging/telegram.py +260 -0
  249. integrations/messaging/whatsapp.py +203 -0
  250. integrations/productivity/__init__.py +17 -0
  251. integrations/productivity/airtable.py +217 -0
  252. integrations/productivity/asana.py +248 -0
  253. integrations/productivity/google/__init__.py +19 -0
  254. integrations/productivity/google/base.py +89 -0
  255. integrations/productivity/google/calendar.py +272 -0
  256. integrations/productivity/google/docs.py +202 -0
  257. integrations/productivity/google/drive.py +268 -0
  258. integrations/productivity/google/gmail.py +279 -0
  259. integrations/productivity/google/meet.py +218 -0
  260. integrations/productivity/google/sheets.py +222 -0
  261. integrations/productivity/google/tasks.py +199 -0
  262. integrations/productivity/linear.py +279 -0
  263. integrations/productivity/microsoft/__init__.py +13 -0
  264. integrations/productivity/microsoft/base.py +93 -0
  265. integrations/productivity/microsoft/calendar.py +207 -0
  266. integrations/productivity/microsoft/onedrive.py +222 -0
  267. integrations/productivity/microsoft/outlook.py +241 -0
  268. integrations/productivity/microsoft/sharepoint.py +211 -0
  269. integrations/productivity/notion.py +243 -0
  270. integrations/productivity/todoist.py +250 -0
  271. integrations/productivity/trello.py +244 -0
  272. integrations/storage/__init__.py +1 -0
  273. integrations/storage/box.py +279 -0
  274. integrations/storage/dropbox.py +215 -0
  275. integrations/telegram_inbound.py +179 -0
  276. integrations/telegram_poller.py +151 -0
  277. memory/__init__.py +1 -0
  278. memory/causal_graph.py +125 -0
  279. memory/impact_tracker.py +88 -0
  280. memory/vector_memory.py +436 -0
  281. models/__init__.py +12 -0
  282. models/chatjimmy_client.py +210 -0
  283. models/claude_client.py +235 -0
  284. models/claude_code_subagent.py +192 -0
  285. models/openai_client.py +120 -0
  286. models/router.py +162 -0
  287. recursive/discovery_loop.py +118 -0
  288. recursive/optimization_loop.py +135 -0
  289. recursive/validation_loop.py +38 -0
  290. security/__init__.py +1 -0
  291. security/audit_logger.py +106 -0
  292. security/filesystem_guard.py +100 -0
  293. security/hypershield.py +99 -0
  294. security/network_guard.py +97 -0
  295. security/policy_engine.py +198 -0
  296. swarm/__init__.py +1 -0
  297. swarm/agents/__init__.py +0 -0
  298. swarm/agents/base.py +230 -0
  299. swarm/agents/business/__init__.py +0 -0
  300. swarm/agents/business/counsel.py +31 -0
  301. swarm/agents/business/herald.py +32 -0
  302. swarm/agents/business/ledger.py +26 -0
  303. swarm/agents/business/nexus.py +30 -0
  304. swarm/agents/business/ops.py +30 -0
  305. swarm/agents/business/pipeline.py +28 -0
  306. swarm/agents/business/revenue.py +30 -0
  307. swarm/agents/business/sovereign.py +31 -0
  308. swarm/agents/business/strategos.py +27 -0
  309. swarm/agents/business/talent.py +27 -0
  310. swarm/agents/business/venture.py +32 -0
  311. swarm/agents/comms/__init__.py +1 -0
  312. swarm/agents/comms/cipher.py +32 -0
  313. swarm/agents/comms/echo_comms.py +31 -0
  314. swarm/agents/comms/envoy.py +31 -0
  315. swarm/agents/comms/herald_scheduler.py +33 -0
  316. swarm/agents/comms/pulse.py +29 -0
  317. swarm/agents/creative/__init__.py +0 -0
  318. swarm/agents/creative/author.py +27 -0
  319. swarm/agents/creative/lens.py +25 -0
  320. swarm/agents/executive/__init__.py +1 -0
  321. swarm/agents/intelligence/__init__.py +1 -0
  322. swarm/agents/intelligence/arbiter.py +30 -0
  323. swarm/agents/intelligence/sentinel.py +30 -0
  324. swarm/agents/media/__init__.py +0 -0
  325. swarm/agents/personal/__init__.py +0 -0
  326. swarm/agents/personal/atlas.py +27 -0
  327. swarm/agents/personal/hearth.py +26 -0
  328. swarm/agents/personal/midas.py +27 -0
  329. swarm/agents/personal/navigator.py +27 -0
  330. swarm/agents/personal/nourish.py +31 -0
  331. swarm/agents/personal/vitals.py +33 -0
  332. swarm/agents/recursive/__init__.py +0 -0
  333. swarm/agents/recursive/alchemist.py +73 -0
  334. swarm/agents/recursive/calibrator.py +45 -0
  335. swarm/agents/recursive/scout.py +97 -0
  336. swarm/agents/scientific/__init__.py +0 -0
  337. swarm/agents/scientific/cosmos.py +36 -0
  338. swarm/agents/scientific/gaia.py +26 -0
  339. swarm/agents/scientific/medicus.py +33 -0
  340. swarm/agents/scientific/oracle_agent.py +39 -0
  341. swarm/agents/scientific/scribe.py +39 -0
  342. swarm/agents/talent/__init__.py +0 -0
  343. swarm/agents/talent/deal.py +28 -0
  344. swarm/agents/talent/roster.py +29 -0
  345. swarm/agents/talent/scout.py +29 -0
  346. swarm/agents/talent/stage.py +27 -0
  347. swarm/agents/tech/__init__.py +0 -0
  348. swarm/agents/tech/aegis.py +28 -0
  349. swarm/agents/tech/bridge.py +28 -0
  350. swarm/agents/tech/forge.py +28 -0
  351. swarm/agents/trading/__init__.py +1 -0
  352. swarm/agents/trading/global_prediction_engine.py +611 -0
  353. swarm/agents/trading/polymarket_trader.py +165 -0
  354. swarm/agents/trading/prediction_strategist.py +218 -0
  355. swarm/autogen_bridge.py +68 -0
  356. swarm/bid_protocol.py +196 -0
  357. swarm/dispatcher.py +527 -0
  358. swarm/nexus.py +181 -0
  359. swarm/registry.py +112 -0
  360. telegram_listener.py +403 -0
  361. ui/tui/app.py +214 -0
@@ -0,0 +1,323 @@
1
+ """
2
+ Interactive interviewer for knowledge elicitation.
3
+ Guides users through structured interviews to capture organizational knowledge.
4
+ """
5
+ from __future__ import annotations
6
+ import logging
7
+ from dataclasses import dataclass, field
8
+ from datetime import datetime
9
+ from enum import Enum
10
+ from typing import Any, Callable
11
+ from uuid import UUID, uuid4
12
+
13
+ from ..schema import NodeType, CivilizationNode
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class InterviewState(str, Enum):
19
+ NOT_STARTED = "not_started"
20
+ IN_PROGRESS = "in_progress"
21
+ PAUSED = "paused"
22
+ COMPLETED = "completed"
23
+ ABANDONED = "abandoned"
24
+
25
+
26
+ @dataclass
27
+ class InterviewResponse:
28
+ """A single response in an interview."""
29
+ question_id: str
30
+ question_text: str
31
+ response_text: str
32
+ timestamp: datetime = field(default_factory=datetime.utcnow)
33
+ confidence: float | None = None
34
+ follow_up_needed: bool = False
35
+ extracted_entities: list[str] = field(default_factory=list)
36
+
37
+
38
+ @dataclass
39
+ class InterviewSession:
40
+ """Represents an interview session."""
41
+ id: UUID = field(default_factory=uuid4)
42
+ org_id: str = ""
43
+ target_node_type: NodeType = NodeType.SOP
44
+ subject: str = ""
45
+ interviewer_id: str | None = None
46
+ interviewee_id: str | None = None
47
+ state: InterviewState = InterviewState.NOT_STARTED
48
+ responses: list[InterviewResponse] = field(default_factory=list)
49
+ current_question_index: int = 0
50
+ total_questions: int = 0
51
+ started_at: datetime | None = None
52
+ completed_at: datetime | None = None
53
+ metadata: dict = field(default_factory=dict)
54
+
55
+ @property
56
+ def progress(self) -> float:
57
+ """Return progress as percentage (0-100)."""
58
+ if self.total_questions == 0:
59
+ return 0.0
60
+ return (len(self.responses) / self.total_questions) * 100
61
+
62
+ @property
63
+ def duration_minutes(self) -> float | None:
64
+ """Return interview duration in minutes."""
65
+ if not self.started_at:
66
+ return None
67
+ end = self.completed_at or datetime.utcnow()
68
+ return (end - self.started_at).total_seconds() / 60
69
+
70
+
71
+ class InterviewTemplate:
72
+ """Template for a specific type of interview."""
73
+
74
+ def __init__(self, node_type: NodeType, questions: list[dict]):
75
+ self.node_type = node_type
76
+ self.questions = questions
77
+
78
+ @classmethod
79
+ def for_sop(cls) -> "InterviewTemplate":
80
+ """Create template for SOP interviews."""
81
+ return cls(NodeType.SOP, [
82
+ {"id": "sop_name", "text": "What is the name of this procedure?", "required": True},
83
+ {"id": "sop_purpose", "text": "What is the purpose of this SOP? What problem does it solve?", "required": True},
84
+ {"id": "sop_scope", "text": "What is the scope? What does this SOP cover and not cover?", "required": True},
85
+ {"id": "sop_trigger", "text": "What triggers this procedure? When should someone start it?", "required": True},
86
+ {"id": "sop_roles", "text": "Who is involved in this procedure? What roles are needed?", "required": True},
87
+ {"id": "sop_steps", "text": "Walk me through the steps. What happens first, second, etc.?", "required": True},
88
+ {"id": "sop_tools", "text": "What tools or systems are used in this procedure?", "required": False},
89
+ {"id": "sop_duration", "text": "How long does this procedure typically take?", "required": False},
90
+ {"id": "sop_failure", "text": "What happens if something goes wrong? How do you handle failures?", "required": False},
91
+ {"id": "sop_success", "text": "How do you know the procedure was successful? What are the success criteria?", "required": True},
92
+ {"id": "sop_exceptions", "text": "Are there any exceptions or edge cases to be aware of?", "required": False},
93
+ {"id": "sop_related", "text": "Are there other procedures that relate to this one?", "required": False},
94
+ ])
95
+
96
+ @classmethod
97
+ def for_role(cls) -> "InterviewTemplate":
98
+ """Create template for role interviews."""
99
+ return cls(NodeType.ROLE, [
100
+ {"id": "role_title", "text": "What is the title of this role?", "required": True},
101
+ {"id": "role_department", "text": "Which department or team does this role belong to?", "required": True},
102
+ {"id": "role_mission", "text": "What is the primary mission or purpose of this role?", "required": True},
103
+ {"id": "role_responsibilities", "text": "What are the key responsibilities of this role?", "required": True},
104
+ {"id": "role_accountabilities", "text": "What outcomes is this role accountable for?", "required": True},
105
+ {"id": "role_authority", "text": "What decisions can this role make independently?", "required": True},
106
+ {"id": "role_escalation", "text": "Who does this role escalate issues to?", "required": True},
107
+ {"id": "role_interfaces", "text": "Which other roles or teams does this role regularly interact with?", "required": True},
108
+ {"id": "role_tools", "text": "What tools and systems does this role use daily?", "required": False},
109
+ {"id": "role_sops", "text": "What SOPs or procedures does this role own or follow?", "required": False},
110
+ ])
111
+
112
+ @classmethod
113
+ def for_checklist(cls) -> "InterviewTemplate":
114
+ """Create template for checklist interviews."""
115
+ return cls(NodeType.CHECKLIST, [
116
+ {"id": "cl_name", "text": "What is this checklist called?", "required": True},
117
+ {"id": "cl_purpose", "text": "What is the purpose of this checklist?", "required": True},
118
+ {"id": "cl_trigger", "text": "When should this checklist be used?", "required": True},
119
+ {"id": "cl_frequency", "text": "How often is this checklist used? (daily, weekly, per-event, etc.)", "required": False},
120
+ {"id": "cl_items", "text": "Walk me through each item on the checklist.", "required": True},
121
+ {"id": "cl_verification", "text": "How do you verify each item is complete?", "required": False},
122
+ {"id": "cl_responsible", "text": "Who is responsible for completing this checklist?", "required": True},
123
+ {"id": "cl_related_sop", "text": "Is this checklist part of a larger SOP?", "required": False},
124
+ ])
125
+
126
+ @classmethod
127
+ def get_template(cls, node_type: NodeType) -> "InterviewTemplate":
128
+ """Get the appropriate template for a node type."""
129
+ templates = {
130
+ NodeType.SOP: cls.for_sop,
131
+ NodeType.ROLE: cls.for_role,
132
+ NodeType.CHECKLIST: cls.for_checklist,
133
+ }
134
+ factory = templates.get(node_type, cls.for_sop)
135
+ return factory()
136
+
137
+
138
+ class Interviewer:
139
+ """
140
+ Conducts structured interviews to elicit organizational knowledge.
141
+ Supports asynchronous, multi-session interviews.
142
+ """
143
+
144
+ def __init__(
145
+ self,
146
+ org_id: str,
147
+ store=None, # CivilizationStore
148
+ llm_callback: Callable[[str], str] | None = None,
149
+ ):
150
+ self.org_id = org_id
151
+ self.store = store
152
+ self.llm_callback = llm_callback
153
+ self.sessions: dict[UUID, InterviewSession] = {}
154
+
155
+ def start_session(
156
+ self,
157
+ target_node_type: NodeType,
158
+ subject: str,
159
+ interviewee_id: str | None = None,
160
+ ) -> InterviewSession:
161
+ """Start a new interview session."""
162
+ template = InterviewTemplate.get_template(target_node_type)
163
+
164
+ session = InterviewSession(
165
+ org_id=self.org_id,
166
+ target_node_type=target_node_type,
167
+ subject=subject,
168
+ interviewee_id=interviewee_id,
169
+ state=InterviewState.IN_PROGRESS,
170
+ total_questions=len(template.questions),
171
+ started_at=datetime.utcnow(),
172
+ )
173
+
174
+ self.sessions[session.id] = session
175
+ return session
176
+
177
+ def get_current_question(self, session_id: UUID) -> dict | None:
178
+ """Get the current question for a session."""
179
+ session = self.sessions.get(session_id)
180
+ if not session or session.state != InterviewState.IN_PROGRESS:
181
+ return None
182
+
183
+ template = InterviewTemplate.get_template(session.target_node_type)
184
+ if session.current_question_index >= len(template.questions):
185
+ return None
186
+
187
+ return template.questions[session.current_question_index]
188
+
189
+ def submit_response(
190
+ self,
191
+ session_id: UUID,
192
+ response_text: str,
193
+ confidence: float | None = None,
194
+ ) -> dict:
195
+ """
196
+ Submit a response to the current question.
197
+
198
+ Returns:
199
+ Dict with: next_question, is_complete, follow_up
200
+ """
201
+ session = self.sessions.get(session_id)
202
+ if not session or session.state != InterviewState.IN_PROGRESS:
203
+ return {"error": "Invalid session or session not in progress"}
204
+
205
+ template = InterviewTemplate.get_template(session.target_node_type)
206
+ if session.current_question_index >= len(template.questions):
207
+ return {"error": "No more questions"}
208
+
209
+ current_q = template.questions[session.current_question_index]
210
+
211
+ # Record response
212
+ response = InterviewResponse(
213
+ question_id=current_q["id"],
214
+ question_text=current_q["text"],
215
+ response_text=response_text,
216
+ confidence=confidence,
217
+ )
218
+ session.responses.append(response)
219
+ session.current_question_index += 1
220
+
221
+ # Check if interview is complete
222
+ if session.current_question_index >= len(template.questions):
223
+ session.state = InterviewState.COMPLETED
224
+ session.completed_at = datetime.utcnow()
225
+ return {
226
+ "is_complete": True,
227
+ "next_question": None,
228
+ "progress": 100.0,
229
+ }
230
+
231
+ next_q = template.questions[session.current_question_index]
232
+ return {
233
+ "is_complete": False,
234
+ "next_question": next_q,
235
+ "progress": session.progress,
236
+ }
237
+
238
+ def pause_session(self, session_id: UUID) -> bool:
239
+ """Pause an interview session."""
240
+ session = self.sessions.get(session_id)
241
+ if session and session.state == InterviewState.IN_PROGRESS:
242
+ session.state = InterviewState.PAUSED
243
+ return True
244
+ return False
245
+
246
+ def resume_session(self, session_id: UUID) -> InterviewSession | None:
247
+ """Resume a paused interview session."""
248
+ session = self.sessions.get(session_id)
249
+ if session and session.state == InterviewState.PAUSED:
250
+ session.state = InterviewState.IN_PROGRESS
251
+ return session
252
+ return None
253
+
254
+ def abandon_session(self, session_id: UUID) -> bool:
255
+ """Abandon an interview session."""
256
+ session = self.sessions.get(session_id)
257
+ if session:
258
+ session.state = InterviewState.ABANDONED
259
+ return True
260
+ return False
261
+
262
+ def get_session(self, session_id: UUID) -> InterviewSession | None:
263
+ """Get a session by ID."""
264
+ return self.sessions.get(session_id)
265
+
266
+ def compile_to_node(self, session_id: UUID) -> CivilizationNode | None:
267
+ """
268
+ Compile interview responses into a CivilizationNode.
269
+ Only works for completed sessions.
270
+ """
271
+ session = self.sessions.get(session_id)
272
+ if not session or session.state != InterviewState.COMPLETED:
273
+ return None
274
+
275
+ # Build response lookup
276
+ responses = {r.question_id: r.response_text for r in session.responses}
277
+
278
+ if session.target_node_type == NodeType.SOP:
279
+ from ..schema import SOP, SOPStep
280
+ return SOP(
281
+ org_id=session.org_id,
282
+ title=responses.get("sop_name", session.subject),
283
+ purpose=responses.get("sop_purpose", ""),
284
+ scope=responses.get("sop_scope", ""),
285
+ triggers=[responses.get("sop_trigger", "")] if responses.get("sop_trigger") else [],
286
+ steps=self._parse_steps(responses.get("sop_steps", "")),
287
+ roles_involved=self._parse_list(responses.get("sop_roles", "")),
288
+ tools_required=self._parse_list(responses.get("sop_tools", "")),
289
+ success_criteria=self._parse_list(responses.get("sop_success", "")),
290
+ metadata={"source": "interview", "session_id": str(session.id)},
291
+ )
292
+
293
+ # Add more node type compilations as needed
294
+ return None
295
+
296
+ def _parse_steps(self, text: str) -> list:
297
+ """Parse step descriptions into SOPStep objects."""
298
+ from ..schema import SOPStep
299
+ if not text:
300
+ return []
301
+
302
+ # Simple parsing: split by numbered items or newlines
303
+ lines = [l.strip() for l in text.split("\n") if l.strip()]
304
+ steps = []
305
+ for i, line in enumerate(lines, 1):
306
+ # Remove leading numbers/bullets
307
+ import re
308
+ cleaned = re.sub(r"^[\d\.\)\-\*]+\s*", "", line)
309
+ if cleaned:
310
+ steps.append(SOPStep(
311
+ step_number=i,
312
+ title=f"Step {i}",
313
+ description=cleaned,
314
+ ))
315
+ return steps if steps else [SOPStep(step_number=1, title="Step 1", description=text)]
316
+
317
+ def _parse_list(self, text: str) -> list[str]:
318
+ """Parse comma or newline separated list."""
319
+ if not text:
320
+ return []
321
+ import re
322
+ items = re.split(r"[,\n]", text)
323
+ return [item.strip() for item in items if item.strip()]
@@ -0,0 +1,363 @@
1
+ """
2
+ Dynamic question generation for knowledge elicitation.
3
+ Generates contextual follow-up questions based on responses and gaps.
4
+ """
5
+ from __future__ import annotations
6
+ import logging
7
+ from dataclasses import dataclass, field
8
+ from enum import Enum
9
+ from typing import Any, Callable
10
+ from uuid import UUID, uuid4
11
+
12
+ from ..schema import NodeType, CivilizationNode
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class QuestionType(str, Enum):
18
+ OPEN = "open" # Open-ended questions
19
+ CLOSED = "closed" # Yes/No questions
20
+ MULTIPLE_CHOICE = "multiple_choice"
21
+ SCALE = "scale" # 1-5 or 1-10 ratings
22
+ LIST = "list" # Ask for a list of items
23
+ CLARIFICATION = "clarification" # Follow-up for unclear answers
24
+ CONFIRMATION = "confirmation" # Verify understanding
25
+
26
+
27
+ class QuestionPriority(str, Enum):
28
+ REQUIRED = "required"
29
+ RECOMMENDED = "recommended"
30
+ OPTIONAL = "optional"
31
+
32
+
33
+ @dataclass
34
+ class Question:
35
+ """Represents a question to ask during knowledge elicitation."""
36
+ id: str
37
+ text: str
38
+ question_type: QuestionType = QuestionType.OPEN
39
+ priority: QuestionPriority = QuestionPriority.REQUIRED
40
+ context: str | None = None
41
+ options: list[str] = field(default_factory=list) # For multiple choice
42
+ follow_up_for: str | None = None # ID of question this follows up on
43
+ validation_hint: str | None = None
44
+ examples: list[str] = field(default_factory=list)
45
+ metadata: dict = field(default_factory=dict)
46
+
47
+
48
+ @dataclass
49
+ class QuestionContext:
50
+ """Context for generating questions."""
51
+ target_node_type: NodeType
52
+ subject: str
53
+ existing_responses: dict[str, str] = field(default_factory=dict)
54
+ existing_nodes: list[CivilizationNode] = field(default_factory=list)
55
+ detected_gaps: list[str] = field(default_factory=list)
56
+ interview_depth: str = "standard" # "brief", "standard", "thorough"
57
+
58
+
59
+ class Questioner:
60
+ """
61
+ Generates dynamic questions for knowledge elicitation.
62
+ Adapts questions based on context, previous responses, and detected gaps.
63
+ """
64
+
65
+ def __init__(
66
+ self,
67
+ llm_callback: Callable[[str, str], str] | None = None,
68
+ ):
69
+ self.llm_callback = llm_callback
70
+ self._question_templates = self._load_templates()
71
+
72
+ def _load_templates(self) -> dict[NodeType, list[Question]]:
73
+ """Load question templates for each node type."""
74
+ return {
75
+ NodeType.SOP: [
76
+ Question(
77
+ id="sop_overview",
78
+ text="Can you give me a high-level overview of this procedure?",
79
+ question_type=QuestionType.OPEN,
80
+ priority=QuestionPriority.REQUIRED,
81
+ context="Start with the big picture before diving into details",
82
+ ),
83
+ Question(
84
+ id="sop_trigger",
85
+ text="What events or conditions trigger this procedure?",
86
+ question_type=QuestionType.LIST,
87
+ priority=QuestionPriority.REQUIRED,
88
+ examples=["Customer complaint received", "Monthly audit due", "New employee onboarding"],
89
+ ),
90
+ Question(
91
+ id="sop_prerequisites",
92
+ text="What needs to be in place before starting this procedure?",
93
+ question_type=QuestionType.LIST,
94
+ priority=QuestionPriority.RECOMMENDED,
95
+ ),
96
+ Question(
97
+ id="sop_first_step",
98
+ text="What is the very first thing someone does when starting this procedure?",
99
+ question_type=QuestionType.OPEN,
100
+ priority=QuestionPriority.REQUIRED,
101
+ ),
102
+ Question(
103
+ id="sop_decision_points",
104
+ text="Are there any decision points where the procedure can branch?",
105
+ question_type=QuestionType.OPEN,
106
+ priority=QuestionPriority.RECOMMENDED,
107
+ context="We want to capture if-then logic in the procedure",
108
+ ),
109
+ Question(
110
+ id="sop_handoffs",
111
+ text="Are there any handoffs between people or teams during this procedure?",
112
+ question_type=QuestionType.OPEN,
113
+ priority=QuestionPriority.RECOMMENDED,
114
+ ),
115
+ Question(
116
+ id="sop_failure_modes",
117
+ text="What are the most common ways this procedure can fail?",
118
+ question_type=QuestionType.LIST,
119
+ priority=QuestionPriority.RECOMMENDED,
120
+ ),
121
+ Question(
122
+ id="sop_time_critical",
123
+ text="Are there any time-sensitive steps or SLAs to be aware of?",
124
+ question_type=QuestionType.OPEN,
125
+ priority=QuestionPriority.OPTIONAL,
126
+ ),
127
+ ],
128
+ NodeType.ROLE: [
129
+ Question(
130
+ id="role_mission",
131
+ text="What is the primary mission or purpose of this role?",
132
+ question_type=QuestionType.OPEN,
133
+ priority=QuestionPriority.REQUIRED,
134
+ ),
135
+ Question(
136
+ id="role_daily",
137
+ text="What does a typical day look like for someone in this role?",
138
+ question_type=QuestionType.OPEN,
139
+ priority=QuestionPriority.REQUIRED,
140
+ ),
141
+ Question(
142
+ id="role_decisions",
143
+ text="What decisions can this role make independently?",
144
+ question_type=QuestionType.LIST,
145
+ priority=QuestionPriority.REQUIRED,
146
+ ),
147
+ Question(
148
+ id="role_escalation",
149
+ text="What situations require escalation? To whom?",
150
+ question_type=QuestionType.OPEN,
151
+ priority=QuestionPriority.REQUIRED,
152
+ ),
153
+ Question(
154
+ id="role_interfaces",
155
+ text="Which other roles or teams does this role interact with most?",
156
+ question_type=QuestionType.LIST,
157
+ priority=QuestionPriority.RECOMMENDED,
158
+ ),
159
+ Question(
160
+ id="role_success",
161
+ text="How is success measured for this role?",
162
+ question_type=QuestionType.LIST,
163
+ priority=QuestionPriority.RECOMMENDED,
164
+ ),
165
+ ],
166
+ NodeType.CHECKLIST: [
167
+ Question(
168
+ id="cl_purpose",
169
+ text="What is the purpose of this checklist?",
170
+ question_type=QuestionType.OPEN,
171
+ priority=QuestionPriority.REQUIRED,
172
+ ),
173
+ Question(
174
+ id="cl_when",
175
+ text="When should this checklist be used?",
176
+ question_type=QuestionType.OPEN,
177
+ priority=QuestionPriority.REQUIRED,
178
+ ),
179
+ Question(
180
+ id="cl_items",
181
+ text="Walk me through each item on the checklist.",
182
+ question_type=QuestionType.LIST,
183
+ priority=QuestionPriority.REQUIRED,
184
+ ),
185
+ Question(
186
+ id="cl_critical",
187
+ text="Are any items especially critical? What happens if they're missed?",
188
+ question_type=QuestionType.OPEN,
189
+ priority=QuestionPriority.RECOMMENDED,
190
+ ),
191
+ ],
192
+ }
193
+
194
+ def get_initial_questions(
195
+ self,
196
+ context: QuestionContext,
197
+ ) -> list[Question]:
198
+ """Get initial questions based on context."""
199
+ templates = self._question_templates.get(context.target_node_type, [])
200
+
201
+ # Filter by depth
202
+ if context.interview_depth == "brief":
203
+ return [q for q in templates if q.priority == QuestionPriority.REQUIRED]
204
+ elif context.interview_depth == "thorough":
205
+ return templates
206
+ else: # standard
207
+ return [q for q in templates if q.priority != QuestionPriority.OPTIONAL]
208
+
209
+ def generate_follow_up(
210
+ self,
211
+ question_id: str,
212
+ response: str,
213
+ context: QuestionContext,
214
+ ) -> list[Question]:
215
+ """Generate follow-up questions based on a response."""
216
+ follow_ups = []
217
+
218
+ # Check for vague responses that need clarification
219
+ vague_indicators = ["sometimes", "usually", "it depends", "maybe", "i think"]
220
+ if any(indicator in response.lower() for indicator in vague_indicators):
221
+ follow_ups.append(Question(
222
+ id=f"{question_id}_clarify",
223
+ text="Can you be more specific? In what situations does this vary?",
224
+ question_type=QuestionType.CLARIFICATION,
225
+ priority=QuestionPriority.REQUIRED,
226
+ follow_up_for=question_id,
227
+ ))
228
+
229
+ # Check for mentions of other processes/roles that aren't documented
230
+ if context.existing_nodes:
231
+ existing_titles = {n.title.lower() for n in context.existing_nodes}
232
+ # Simple heuristic: look for capitalized phrases that might be references
233
+ import re
234
+ potential_refs = re.findall(r"([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)", response)
235
+ for ref in potential_refs:
236
+ if ref.lower() not in existing_titles and len(ref) > 3:
237
+ follow_ups.append(Question(
238
+ id=f"ref_{ref.lower().replace(' ', '_')}",
239
+ text=f"You mentioned '{ref}'. Is this a process, role, or system we should document?",
240
+ question_type=QuestionType.CLOSED,
241
+ priority=QuestionPriority.OPTIONAL,
242
+ follow_up_for=question_id,
243
+ ))
244
+ break # Only ask about one reference at a time
245
+
246
+ # Check for step mentions that need elaboration
247
+ if "step" in response.lower() or any(c.isdigit() for c in response):
248
+ if len(response) < 100: # Short response about steps
249
+ follow_ups.append(Question(
250
+ id=f"{question_id}_elaborate_steps",
251
+ text="Can you walk me through these steps in more detail?",
252
+ question_type=QuestionType.OPEN,
253
+ priority=QuestionPriority.RECOMMENDED,
254
+ follow_up_for=question_id,
255
+ ))
256
+
257
+ return follow_ups
258
+
259
+ def generate_gap_questions(
260
+ self,
261
+ gaps: list[str],
262
+ context: QuestionContext,
263
+ ) -> list[Question]:
264
+ """Generate questions to fill identified knowledge gaps."""
265
+ questions = []
266
+
267
+ gap_question_map = {
268
+ "missing_owner": Question(
269
+ id="gap_owner",
270
+ text="Who is responsible for maintaining and updating this?",
271
+ question_type=QuestionType.OPEN,
272
+ priority=QuestionPriority.REQUIRED,
273
+ ),
274
+ "missing_tools": Question(
275
+ id="gap_tools",
276
+ text="What tools or systems are used in this process?",
277
+ question_type=QuestionType.LIST,
278
+ priority=QuestionPriority.REQUIRED,
279
+ ),
280
+ "missing_timeline": Question(
281
+ id="gap_timeline",
282
+ text="How long does this typically take? Are there any SLAs?",
283
+ question_type=QuestionType.OPEN,
284
+ priority=QuestionPriority.RECOMMENDED,
285
+ ),
286
+ "missing_exceptions": Question(
287
+ id="gap_exceptions",
288
+ text="Are there any exceptions or special cases to be aware of?",
289
+ question_type=QuestionType.OPEN,
290
+ priority=QuestionPriority.RECOMMENDED,
291
+ ),
292
+ "missing_metrics": Question(
293
+ id="gap_metrics",
294
+ text="How do you measure success or track performance?",
295
+ question_type=QuestionType.LIST,
296
+ priority=QuestionPriority.RECOMMENDED,
297
+ ),
298
+ }
299
+
300
+ for gap in gaps:
301
+ if gap in gap_question_map:
302
+ questions.append(gap_question_map[gap])
303
+
304
+ return questions
305
+
306
+ async def generate_smart_question(
307
+ self,
308
+ context: QuestionContext,
309
+ previous_qa: list[tuple[str, str]],
310
+ ) -> Question | None:
311
+ """
312
+ Use LLM to generate a contextually relevant follow-up question.
313
+ Requires llm_callback to be set.
314
+ """
315
+ if not self.llm_callback:
316
+ return None
317
+
318
+ # Build prompt for LLM
319
+ qa_history = "\n".join([f"Q: {q}\nA: {a}" for q, a in previous_qa[-5:]])
320
+
321
+ prompt = f"""You are helping document organizational knowledge about: {context.subject}
322
+ Document type: {context.target_node_type.value}
323
+
324
+ Previous Q&A:
325
+ {qa_history}
326
+
327
+ Based on this conversation, generate ONE follow-up question that would help complete the documentation.
328
+ The question should:
329
+ 1. Fill in gaps in the information provided
330
+ 2. Clarify any ambiguous points
331
+ 3. Explore important details not yet covered
332
+
333
+ Return ONLY the question text, nothing else."""
334
+
335
+ try:
336
+ question_text = self.llm_callback("system", prompt)
337
+ return Question(
338
+ id=f"smart_{uuid4().hex[:8]}",
339
+ text=question_text.strip(),
340
+ question_type=QuestionType.OPEN,
341
+ priority=QuestionPriority.RECOMMENDED,
342
+ metadata={"generated": True},
343
+ )
344
+ except Exception as e:
345
+ logger.warning(f"Failed to generate smart question: {e}")
346
+ return None
347
+
348
+ def format_question_for_display(self, question: Question) -> str:
349
+ """Format a question for user display."""
350
+ text = question.text
351
+
352
+ if question.context:
353
+ text = f"{text}\n\n(Context: {question.context})"
354
+
355
+ if question.examples:
356
+ examples_str = ", ".join(question.examples[:3])
357
+ text = f"{text}\n\nExamples: {examples_str}"
358
+
359
+ if question.options:
360
+ options_str = "\n".join([f" {i+1}. {opt}" for i, opt in enumerate(question.options)])
361
+ text = f"{text}\n\nOptions:\n{options_str}"
362
+
363
+ return text