sibyl-dev 0.2.2__tar.gz → 0.2.4__tar.gz

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 (33) hide show
  1. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/PKG-INFO +1 -1
  2. sibyl_dev-0.2.4/src/sibyl_cli/agent.py +431 -0
  3. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/client.py +116 -0
  4. sibyl_dev-0.2.4/src/sibyl_cli/debug.py +277 -0
  5. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/local.py +2 -1
  6. sibyl_dev-0.2.4/src/sibyl_cli/logs.py +245 -0
  7. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/main.py +9 -2
  8. sibyl_dev-0.2.4/src/sibyl_cli/runner.py +285 -0
  9. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/.gitignore +0 -0
  10. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/README.md +0 -0
  11. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/pyproject.toml +0 -0
  12. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/__init__.py +0 -0
  13. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/auth.py +0 -0
  14. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/auth_store.py +0 -0
  15. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/common.py +0 -0
  16. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/config_cmd.py +0 -0
  17. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/config_store.py +0 -0
  18. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/context.py +0 -0
  19. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/crawl.py +0 -0
  20. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/document.py +0 -0
  21. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/entity.py +0 -0
  22. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/epic.py +0 -0
  23. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/explore.py +0 -0
  24. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/onboarding.py +0 -0
  25. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/org.py +0 -0
  26. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/project.py +0 -0
  27. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/source.py +0 -0
  28. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/state.py +0 -0
  29. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/src/sibyl_cli/task.py +0 -0
  30. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/tests/__init__.py +0 -0
  31. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/tests/test_auth_store.py +0 -0
  32. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/tests/test_config_store.py +0 -0
  33. {sibyl_dev-0.2.2 → sibyl_dev-0.2.4}/tests/test_epic.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sibyl-dev
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: CLI for Sibyl - Collective Intelligence Runtime for AI agents
5
5
  Project-URL: Homepage, https://github.com/hyperb1iss/sibyl
6
6
  Project-URL: Repository, https://github.com/hyperb1iss/sibyl
@@ -0,0 +1,431 @@
1
+ """Agent communication CLI commands.
2
+
3
+ Commands for inter-agent messaging during distributed execution:
4
+ - progress: Report progress to parent agent
5
+ - blocker: Signal a blocker
6
+ - query: Ask another agent a question
7
+ - delegate: Delegate work to another agent
8
+ - review: Request code review
9
+ - inbox: Check pending messages
10
+ - respond: Respond to a message
11
+ - conversation: View conversation history with another agent
12
+
13
+ These commands enable Claude Code agents to communicate and coordinate through Sibyl.
14
+ """
15
+
16
+ from typing import Annotated
17
+
18
+ import typer
19
+
20
+ from sibyl_cli.client import SibylClientError, get_client
21
+ from sibyl_cli.common import (
22
+ CORAL,
23
+ ELECTRIC_PURPLE,
24
+ NEON_CYAN,
25
+ console,
26
+ create_table,
27
+ handle_client_error,
28
+ info,
29
+ print_json,
30
+ run_async,
31
+ success,
32
+ )
33
+
34
+ app = typer.Typer(
35
+ name="agent",
36
+ help="Inter-agent communication for distributed execution",
37
+ no_args_is_help=True,
38
+ )
39
+
40
+
41
+ def _validate_agent_id(agent_id: str) -> str:
42
+ """Validate agent ID format."""
43
+ if not agent_id.startswith("agent_"):
44
+ raise SibylClientError(
45
+ f"Invalid agent ID format: {agent_id}. Expected format: agent_<hex>",
46
+ status_code=400,
47
+ detail=f"Invalid agent ID: {agent_id}",
48
+ )
49
+ return agent_id
50
+
51
+
52
+ def _format_message_type(msg_type: str) -> str:
53
+ """Format message type for display."""
54
+ type_colors = {
55
+ "progress": "green",
56
+ "query": "cyan",
57
+ "response": "blue",
58
+ "review_request": "yellow",
59
+ "review_result": "yellow",
60
+ "blocker": "red",
61
+ "delegation": "magenta",
62
+ "system": "dim",
63
+ }
64
+ color = type_colors.get(msg_type, "white")
65
+ return f"[{color}]{msg_type}[/{color}]"
66
+
67
+
68
+ @app.command()
69
+ def progress(
70
+ agent_id: Annotated[str, typer.Argument(help="Your agent ID")],
71
+ message: Annotated[str, typer.Argument(help="Progress message")],
72
+ percent: Annotated[
73
+ int | None, typer.Option("--percent", "-p", help="Progress percentage (0-100)")
74
+ ] = None,
75
+ to: Annotated[
76
+ str | None, typer.Option("--to", "-t", help="Target agent (default: orchestrator)")
77
+ ] = None,
78
+ json_out: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
79
+ ) -> None:
80
+ """Report progress to orchestrator or another agent.
81
+
82
+ Example:
83
+ sibyl agent progress agent_abc123 "Completed code review" --percent 75
84
+ """
85
+
86
+ @run_async
87
+ async def _run() -> None:
88
+ client = get_client()
89
+ context = {}
90
+ if percent is not None:
91
+ context["progress_percent"] = percent
92
+
93
+ result = await client.send_agent_message(
94
+ from_agent_id=agent_id,
95
+ message_type="progress",
96
+ subject=f"Progress: {percent}%" if percent else "Progress update",
97
+ content=message,
98
+ to_agent_id=to,
99
+ context=context if context else None,
100
+ )
101
+
102
+ if json_out:
103
+ print_json(result)
104
+ else:
105
+ success(f"Progress reported: {message[:50]}{'...' if len(message) > 50 else ''}")
106
+
107
+ try:
108
+ _run()
109
+ except SibylClientError as e:
110
+ handle_client_error(e)
111
+
112
+
113
+ @app.command()
114
+ def blocker(
115
+ agent_id: Annotated[str, typer.Argument(help="Your agent ID")],
116
+ subject: Annotated[str, typer.Argument(help="Short blocker description")],
117
+ details: Annotated[str, typer.Argument(help="Full details about the blocker")],
118
+ resource: Annotated[
119
+ str | None, typer.Option("--resource", "-r", help="Blocking resource (file, API, etc.)")
120
+ ] = None,
121
+ json_out: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
122
+ ) -> None:
123
+ """Signal a blocker to the orchestrator.
124
+
125
+ Example:
126
+ sibyl agent blocker agent_abc123 "API rate limit" "Hit GitHub API rate limit, need to wait"
127
+ """
128
+
129
+ @run_async
130
+ async def _run() -> None:
131
+ client = get_client()
132
+ context = {}
133
+ if resource:
134
+ context["blocking_resource"] = resource
135
+
136
+ result = await client.send_agent_message(
137
+ from_agent_id=agent_id,
138
+ message_type="blocker",
139
+ subject=subject,
140
+ content=details,
141
+ priority=7, # Blockers are high priority
142
+ context=context if context else None,
143
+ )
144
+
145
+ if json_out:
146
+ print_json(result)
147
+ else:
148
+ console.print(f"[red]Blocker signaled:[/red] {subject}")
149
+
150
+ try:
151
+ _run()
152
+ except SibylClientError as e:
153
+ handle_client_error(e)
154
+
155
+
156
+ @app.command()
157
+ def query(
158
+ agent_id: Annotated[str, typer.Argument(help="Your agent ID")],
159
+ to_agent: Annotated[str, typer.Argument(help="Target agent ID to query")],
160
+ subject: Annotated[str, typer.Argument(help="Query subject")],
161
+ question: Annotated[str, typer.Argument(help="Your question")],
162
+ json_out: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
163
+ ) -> None:
164
+ """Ask another agent a question (requires response).
165
+
166
+ Example:
167
+ sibyl agent query agent_abc123 agent_def456 "Auth approach" "How should I handle token refresh?"
168
+ """
169
+
170
+ @run_async
171
+ async def _run() -> None:
172
+ client = get_client()
173
+ result = await client.send_agent_message(
174
+ from_agent_id=agent_id,
175
+ message_type="query",
176
+ subject=subject,
177
+ content=question,
178
+ to_agent_id=to_agent,
179
+ requires_response=True,
180
+ priority=5,
181
+ )
182
+
183
+ if json_out:
184
+ print_json(result)
185
+ else:
186
+ msg_id = result.get("id", "unknown")
187
+ console.print(f"[{NEON_CYAN}]Query sent to {to_agent}[/{NEON_CYAN}]")
188
+ console.print(f"Message ID: [{CORAL}]{msg_id}[/{CORAL}]")
189
+ info("Check inbox later for response")
190
+
191
+ try:
192
+ _run()
193
+ except SibylClientError as e:
194
+ handle_client_error(e)
195
+
196
+
197
+ @app.command()
198
+ def delegate(
199
+ agent_id: Annotated[str, typer.Argument(help="Your agent ID")],
200
+ to_agent: Annotated[str, typer.Argument(help="Agent to delegate to")],
201
+ subject: Annotated[str, typer.Argument(help="Delegation title")],
202
+ work: Annotated[str, typer.Argument(help="Work description")],
203
+ task_id: Annotated[str | None, typer.Option("--task", "-t", help="Associated task ID")] = None,
204
+ json_out: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
205
+ ) -> None:
206
+ """Delegate work to another agent.
207
+
208
+ Example:
209
+ sibyl agent delegate agent_abc123 agent_def456 "Write tests" "Add unit tests for auth module"
210
+ """
211
+
212
+ @run_async
213
+ async def _run() -> None:
214
+ client = get_client()
215
+ context = {}
216
+ if task_id:
217
+ context["task_id"] = task_id
218
+
219
+ result = await client.send_agent_message(
220
+ from_agent_id=agent_id,
221
+ message_type="delegation",
222
+ subject=subject,
223
+ content=work,
224
+ to_agent_id=to_agent,
225
+ priority=5,
226
+ context=context if context else None,
227
+ )
228
+
229
+ if json_out:
230
+ print_json(result)
231
+ else:
232
+ console.print(f"[{ELECTRIC_PURPLE}]Work delegated to {to_agent}[/{ELECTRIC_PURPLE}]")
233
+ console.print(f"Subject: {subject}")
234
+
235
+ try:
236
+ _run()
237
+ except SibylClientError as e:
238
+ handle_client_error(e)
239
+
240
+
241
+ @app.command()
242
+ def review(
243
+ agent_id: Annotated[str, typer.Argument(help="Your agent ID")],
244
+ to_agent: Annotated[str, typer.Argument(help="Agent to request review from")],
245
+ subject: Annotated[str, typer.Argument(help="Review request title")],
246
+ description: Annotated[str, typer.Argument(help="What to review and why")],
247
+ files: Annotated[
248
+ str | None, typer.Option("--files", "-f", help="Comma-separated list of files")
249
+ ] = None,
250
+ json_out: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
251
+ ) -> None:
252
+ """Request code review from another agent.
253
+
254
+ Example:
255
+ sibyl agent review agent_abc123 agent_def456 "Auth changes" "Please review the OAuth implementation" --files "auth.py,login.py"
256
+ """
257
+
258
+ @run_async
259
+ async def _run() -> None:
260
+ client = get_client()
261
+ context = {}
262
+ if files:
263
+ context["files"] = [f.strip() for f in files.split(",")]
264
+
265
+ result = await client.send_agent_message(
266
+ from_agent_id=agent_id,
267
+ message_type="review_request",
268
+ subject=subject,
269
+ content=description,
270
+ to_agent_id=to_agent,
271
+ requires_response=True,
272
+ priority=5,
273
+ context=context if context else None,
274
+ )
275
+
276
+ if json_out:
277
+ print_json(result)
278
+ else:
279
+ msg_id = result.get("id", "unknown")
280
+ console.print(f"[yellow]Review requested from {to_agent}[/yellow]")
281
+ console.print(f"Message ID: [{CORAL}]{msg_id}[/{CORAL}]")
282
+
283
+ try:
284
+ _run()
285
+ except SibylClientError as e:
286
+ handle_client_error(e)
287
+
288
+
289
+ @app.command()
290
+ def inbox(
291
+ agent_id: Annotated[str, typer.Argument(help="Your agent ID")],
292
+ limit: Annotated[int, typer.Option("--limit", "-l", help="Max messages to show")] = 20,
293
+ include_read: Annotated[
294
+ bool, typer.Option("--all", "-a", help="Include read messages")
295
+ ] = False,
296
+ digest: Annotated[
297
+ bool, typer.Option("--digest", "-d", help="Output as formatted digest")
298
+ ] = False,
299
+ json_out: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
300
+ ) -> None:
301
+ """Check pending messages for an agent.
302
+
303
+ Example:
304
+ sibyl agent inbox agent_abc123
305
+ sibyl agent inbox agent_abc123 --digest # For Claude Code injection
306
+ """
307
+
308
+ @run_async
309
+ async def _run() -> None:
310
+ client = get_client()
311
+
312
+ if digest:
313
+ result = await client.get_message_digest(agent_id, limit=limit)
314
+ if json_out:
315
+ print_json(result)
316
+ else:
317
+ digest_text = result.get("digest", "")
318
+ if digest_text:
319
+ console.print(digest_text)
320
+ else:
321
+ info("No pending messages")
322
+ return
323
+
324
+ result = await client.get_pending_messages(agent_id, limit=limit, include_read=include_read)
325
+
326
+ if json_out:
327
+ print_json(result)
328
+ return
329
+
330
+ messages = result.get("messages", [])
331
+ if not messages:
332
+ info("No pending messages")
333
+ return
334
+
335
+ table = create_table(f"Messages for {agent_id}", "Subject", "From", "Type", "Priority")
336
+ for msg in messages:
337
+ table.add_row(
338
+ msg.get("subject", "")[:40],
339
+ msg.get("from_agent_id", "")[:15],
340
+ _format_message_type(msg.get("message_type", "")),
341
+ str(msg.get("priority", 0)),
342
+ )
343
+ console.print(table)
344
+ console.print(f"\nTotal: {result.get('count', 0)} messages")
345
+
346
+ try:
347
+ _run()
348
+ except SibylClientError as e:
349
+ handle_client_error(e)
350
+
351
+
352
+ @app.command()
353
+ def respond(
354
+ agent_id: Annotated[str, typer.Argument(help="Your agent ID")],
355
+ message_id: Annotated[str, typer.Argument(help="Message ID to respond to")],
356
+ response: Annotated[str, typer.Argument(help="Your response")],
357
+ json_out: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
358
+ ) -> None:
359
+ """Respond to a message.
360
+
361
+ Example:
362
+ sibyl agent respond agent_abc123 <message-uuid> "Use the retry pattern with exponential backoff"
363
+ """
364
+
365
+ @run_async
366
+ async def _run() -> None:
367
+ client = get_client()
368
+ result = await client.respond_to_message(
369
+ message_id=message_id,
370
+ from_agent_id=agent_id,
371
+ content=response,
372
+ )
373
+
374
+ if json_out:
375
+ print_json(result)
376
+ else:
377
+ success(f"Response sent (ID: {result.get('id', 'unknown')[:12]}...)")
378
+
379
+ try:
380
+ _run()
381
+ except SibylClientError as e:
382
+ handle_client_error(e)
383
+
384
+
385
+ @app.command()
386
+ def conversation(
387
+ agent_id: Annotated[str, typer.Argument(help="Your agent ID")],
388
+ other_agent: Annotated[str, typer.Argument(help="Other agent ID")],
389
+ limit: Annotated[int, typer.Option("--limit", "-l", help="Max messages to show")] = 50,
390
+ json_out: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
391
+ ) -> None:
392
+ """View conversation history with another agent.
393
+
394
+ Example:
395
+ sibyl agent conversation agent_abc123 agent_def456
396
+ """
397
+
398
+ @run_async
399
+ async def _run() -> None:
400
+ client = get_client()
401
+ result = await client.get_agent_conversation(agent_id, other_agent, limit=limit)
402
+
403
+ if json_out:
404
+ print_json(result)
405
+ return
406
+
407
+ messages = result.get("messages", [])
408
+ if not messages:
409
+ info(f"No conversation history with {other_agent}")
410
+ return
411
+
412
+ console.print(f"\n[bold]Conversation: {agent_id} <-> {other_agent}[/bold]\n")
413
+ for msg in messages:
414
+ sender = msg.get("from_agent_id", "")
415
+ is_you = sender == agent_id
416
+ color = NEON_CYAN if is_you else CORAL
417
+ label = "You" if is_you else sender[:12]
418
+
419
+ console.print(f"[{color}]{label}[/{color}] [{msg.get('message_type', '')}]")
420
+ console.print(f" {msg.get('subject', '')}")
421
+ content = msg.get("content", "")
422
+ if len(content) > 100:
423
+ content = content[:100] + "..."
424
+ console.print(f" [dim]{content}[/dim]\n")
425
+
426
+ console.print(f"Total: {result.get('count', 0)} messages")
427
+
428
+ try:
429
+ _run()
430
+ except SibylClientError as e:
431
+ handle_client_error(e)
@@ -863,6 +863,122 @@ class SibylClient:
863
863
  """
864
864
  return await self._request("GET", "/sources/link-graph/status")
865
865
 
866
+ # =========================================================================
867
+ # Inter-Agent Messaging
868
+ # =========================================================================
869
+
870
+ async def send_agent_message(
871
+ self,
872
+ from_agent_id: str,
873
+ message_type: str,
874
+ subject: str,
875
+ content: str,
876
+ *,
877
+ to_agent_id: str | None = None,
878
+ requires_response: bool = False,
879
+ priority: int = 0,
880
+ context: dict[str, Any] | None = None,
881
+ ) -> dict[str, Any]:
882
+ """Send an inter-agent message.
883
+
884
+ Args:
885
+ from_agent_id: Sender agent ID
886
+ message_type: Type of message (progress, query, blocker, etc.)
887
+ subject: Short subject line
888
+ content: Full message content
889
+ to_agent_id: Target agent (None = orchestrator)
890
+ requires_response: True if sender waits for response
891
+ priority: 0-10, higher = more urgent
892
+ context: Additional context data
893
+ """
894
+ data = {
895
+ "from_agent_id": from_agent_id,
896
+ "message_type": message_type,
897
+ "subject": subject,
898
+ "content": content,
899
+ "requires_response": requires_response,
900
+ "priority": priority,
901
+ }
902
+ if to_agent_id:
903
+ data["to_agent_id"] = to_agent_id
904
+ if context:
905
+ data["context"] = context
906
+ return await self._request("POST", "/agent-messages", json=data)
907
+
908
+ async def get_pending_messages(
909
+ self,
910
+ agent_id: str,
911
+ *,
912
+ limit: int = 50,
913
+ include_read: bool = False,
914
+ ) -> dict[str, Any]:
915
+ """Get pending messages for an agent.
916
+
917
+ Args:
918
+ agent_id: Agent to get messages for
919
+ limit: Max messages to return
920
+ include_read: Include already-read messages
921
+ """
922
+ params = {"limit": limit, "include_read": str(include_read).lower()}
923
+ return await self._request("GET", f"/agent-messages/pending/{agent_id}", params=params)
924
+
925
+ async def get_message_digest(
926
+ self,
927
+ agent_id: str,
928
+ *,
929
+ limit: int = 50,
930
+ ) -> dict[str, Any]:
931
+ """Get pending messages formatted as digest for injection.
932
+
933
+ Args:
934
+ agent_id: Agent to get messages for
935
+ limit: Max messages to return
936
+ """
937
+ params = {"limit": limit}
938
+ return await self._request(
939
+ "GET", f"/agent-messages/pending/{agent_id}/digest", params=params
940
+ )
941
+
942
+ async def respond_to_message(
943
+ self,
944
+ message_id: str,
945
+ from_agent_id: str,
946
+ content: str,
947
+ *,
948
+ context: dict[str, Any] | None = None,
949
+ ) -> dict[str, Any]:
950
+ """Respond to an inter-agent message.
951
+
952
+ Args:
953
+ message_id: Message to respond to
954
+ from_agent_id: Agent sending response
955
+ content: Response content
956
+ context: Additional context
957
+ """
958
+ data: dict[str, Any] = {
959
+ "from_agent_id": from_agent_id,
960
+ "content": content,
961
+ }
962
+ if context:
963
+ data["context"] = context
964
+ return await self._request("POST", f"/agent-messages/{message_id}/respond", json=data)
965
+
966
+ async def mark_message_read(self, message_id: str) -> dict[str, Any]:
967
+ """Mark a message as read."""
968
+ return await self._request("POST", f"/agent-messages/{message_id}/read")
969
+
970
+ async def get_agent_conversation(
971
+ self,
972
+ agent_id: str,
973
+ other_agent_id: str,
974
+ *,
975
+ limit: int = 100,
976
+ ) -> dict[str, Any]:
977
+ """Get conversation history between two agents."""
978
+ params = {"limit": limit}
979
+ return await self._request(
980
+ "GET", f"/agent-messages/conversation/{agent_id}/{other_agent_id}", params=params
981
+ )
866
982
 
867
983
  # Client cache by context name (None = default/active context)
868
984
  _clients: dict[str | None, SibylClient] = {}