hiveloop 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.
hiveloop/__init__.py ADDED
@@ -0,0 +1,194 @@
1
+ """HiveLoop SDK — framework-agnostic agent instrumentation.
2
+
3
+ Usage:
4
+ import hiveloop
5
+
6
+ hb = hiveloop.init(api_key="hb_live_...", environment="production")
7
+ agent = hb.agent("my-agent", type="sales", version="1.0.0")
8
+
9
+ with agent.task("task-123", project="sales-pipeline") as task:
10
+ task.llm_call("reason", "claude-sonnet-4-20250514", tokens_in=1200, tokens_out=350, cost=0.008)
11
+ task.plan("Process lead", ["Score", "Enrich", "Route"])
12
+ task.plan_step(0, "completed", "Scored lead 42", turns=2, tokens=3200)
13
+
14
+ hiveloop.shutdown()
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import logging
20
+ from typing import Any, Callable
21
+
22
+ from ._agent import Agent, Task, HiveLoopError, SDK_VERSION
23
+ from ._transport import Transport
24
+
25
+ __all__ = [
26
+ "init",
27
+ "shutdown",
28
+ "reset",
29
+ "flush",
30
+ "HiveBoard",
31
+ "Agent",
32
+ "Task",
33
+ "HiveLoopError",
34
+ "SDK_VERSION",
35
+ ]
36
+
37
+ logger = logging.getLogger("hiveloop")
38
+
39
+ # Module-level singleton
40
+ _instance: HiveBoard | None = None
41
+
42
+
43
+ class HiveBoard:
44
+ """HiveLoop client — manages transport, agents, and global config."""
45
+
46
+ def __init__(
47
+ self,
48
+ api_key: str,
49
+ endpoint: str = "https://api.hiveboard.io",
50
+ environment: str = "production",
51
+ group: str = "default",
52
+ flush_interval: float = 5.0,
53
+ batch_size: int = 100,
54
+ max_queue_size: int = 10_000,
55
+ debug: bool = False,
56
+ ) -> None:
57
+ self._api_key = api_key
58
+ self._endpoint = endpoint
59
+ self._environment = environment
60
+ self._group = group
61
+ self._debug = debug
62
+
63
+ if debug:
64
+ logging.getLogger("hiveloop").setLevel(logging.DEBUG)
65
+
66
+ self._transport = Transport(
67
+ endpoint=endpoint,
68
+ api_key=api_key,
69
+ flush_interval=flush_interval,
70
+ batch_size=batch_size,
71
+ max_queue_size=max_queue_size,
72
+ )
73
+
74
+ # Agent registry
75
+ self._agents: dict[str, Agent] = {}
76
+
77
+ def agent(
78
+ self,
79
+ agent_id: str,
80
+ type: str = "general",
81
+ version: str | None = None,
82
+ framework: str = "custom",
83
+ heartbeat_interval: float = 30.0,
84
+ stuck_threshold: int = 300,
85
+ heartbeat_payload: Callable[[], dict[str, Any] | None] | None = None,
86
+ queue_provider: Callable[[], dict[str, Any] | None] | None = None,
87
+ ) -> Agent:
88
+ """Create or retrieve an agent.
89
+
90
+ Idempotent: same agent_id returns existing instance (updates metadata).
91
+ """
92
+ if agent_id in self._agents:
93
+ existing = self._agents[agent_id]
94
+ # Update metadata if different
95
+ existing.agent_type = type
96
+ existing.version = version
97
+ existing.framework = framework
98
+ existing._heartbeat_payload_cb = heartbeat_payload
99
+ existing._queue_provider_cb = queue_provider
100
+ return existing
101
+
102
+ ag = Agent(
103
+ agent_id=agent_id,
104
+ transport=self._transport,
105
+ agent_type=type,
106
+ version=version,
107
+ framework=framework,
108
+ heartbeat_interval=heartbeat_interval,
109
+ stuck_threshold=stuck_threshold,
110
+ heartbeat_payload=heartbeat_payload,
111
+ queue_provider=queue_provider,
112
+ environment=self._environment,
113
+ group=self._group,
114
+ )
115
+ self._agents[agent_id] = ag
116
+ ag._register()
117
+ return ag
118
+
119
+ def get_agent(self, agent_id: str) -> Agent | None:
120
+ """Look up a registered agent by ID."""
121
+ return self._agents.get(agent_id)
122
+
123
+ def flush(self) -> None:
124
+ """Trigger an immediate flush of all queued events."""
125
+ self._transport.flush()
126
+
127
+ def shutdown(self, timeout: float = 5.0) -> None:
128
+ """Shut down all agents and transport."""
129
+ for ag in self._agents.values():
130
+ ag._stop_heartbeat()
131
+ self._transport.shutdown(timeout=timeout)
132
+
133
+
134
+ def init(
135
+ api_key: str,
136
+ environment: str = "production",
137
+ group: str = "default",
138
+ endpoint: str = "https://api.hiveboard.io",
139
+ flush_interval: float = 5.0,
140
+ batch_size: int = 100,
141
+ max_queue_size: int = 10_000,
142
+ debug: bool = False,
143
+ ) -> HiveBoard:
144
+ """Initialize the HiveLoop SDK singleton.
145
+
146
+ Validates api_key starts with 'hb_'. Subsequent calls log a warning
147
+ and return the existing instance.
148
+ """
149
+ global _instance
150
+
151
+ if not api_key.startswith("hb_"):
152
+ raise HiveLoopError(
153
+ f"Invalid API key format: must start with 'hb_' (got '{api_key[:10]}...')"
154
+ )
155
+
156
+ if _instance is not None:
157
+ logger.warning(
158
+ "hiveloop.init() called again — returning existing instance. "
159
+ "Call hiveloop.reset() first to reinitialize."
160
+ )
161
+ return _instance
162
+
163
+ _instance = HiveBoard(
164
+ api_key=api_key,
165
+ endpoint=endpoint,
166
+ environment=environment,
167
+ group=group,
168
+ flush_interval=flush_interval,
169
+ batch_size=batch_size,
170
+ max_queue_size=max_queue_size,
171
+ debug=debug,
172
+ )
173
+ return _instance
174
+
175
+
176
+ def shutdown(timeout: float = 5.0) -> None:
177
+ """Shut down the HiveLoop SDK."""
178
+ global _instance
179
+ if _instance is not None:
180
+ _instance.shutdown(timeout=timeout)
181
+
182
+
183
+ def reset() -> None:
184
+ """Shut down and clear the singleton. Allows re-initialization."""
185
+ global _instance
186
+ if _instance is not None:
187
+ _instance.shutdown(timeout=5.0)
188
+ _instance = None
189
+
190
+
191
+ def flush() -> None:
192
+ """Flush all queued events immediately."""
193
+ if _instance is not None:
194
+ _instance.flush()