ouroboros-ai 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.

Potentially problematic release.


This version of ouroboros-ai might be problematic. Click here for more details.

Files changed (81) hide show
  1. ouroboros/__init__.py +15 -0
  2. ouroboros/__main__.py +9 -0
  3. ouroboros/bigbang/__init__.py +39 -0
  4. ouroboros/bigbang/ambiguity.py +464 -0
  5. ouroboros/bigbang/interview.py +530 -0
  6. ouroboros/bigbang/seed_generator.py +610 -0
  7. ouroboros/cli/__init__.py +9 -0
  8. ouroboros/cli/commands/__init__.py +7 -0
  9. ouroboros/cli/commands/config.py +79 -0
  10. ouroboros/cli/commands/init.py +425 -0
  11. ouroboros/cli/commands/run.py +201 -0
  12. ouroboros/cli/commands/status.py +85 -0
  13. ouroboros/cli/formatters/__init__.py +31 -0
  14. ouroboros/cli/formatters/panels.py +157 -0
  15. ouroboros/cli/formatters/progress.py +112 -0
  16. ouroboros/cli/formatters/tables.py +166 -0
  17. ouroboros/cli/main.py +60 -0
  18. ouroboros/config/__init__.py +81 -0
  19. ouroboros/config/loader.py +292 -0
  20. ouroboros/config/models.py +332 -0
  21. ouroboros/core/__init__.py +62 -0
  22. ouroboros/core/ac_tree.py +401 -0
  23. ouroboros/core/context.py +472 -0
  24. ouroboros/core/errors.py +246 -0
  25. ouroboros/core/seed.py +212 -0
  26. ouroboros/core/types.py +205 -0
  27. ouroboros/evaluation/__init__.py +110 -0
  28. ouroboros/evaluation/consensus.py +350 -0
  29. ouroboros/evaluation/mechanical.py +351 -0
  30. ouroboros/evaluation/models.py +235 -0
  31. ouroboros/evaluation/pipeline.py +286 -0
  32. ouroboros/evaluation/semantic.py +302 -0
  33. ouroboros/evaluation/trigger.py +278 -0
  34. ouroboros/events/__init__.py +5 -0
  35. ouroboros/events/base.py +80 -0
  36. ouroboros/events/decomposition.py +153 -0
  37. ouroboros/events/evaluation.py +248 -0
  38. ouroboros/execution/__init__.py +44 -0
  39. ouroboros/execution/atomicity.py +451 -0
  40. ouroboros/execution/decomposition.py +481 -0
  41. ouroboros/execution/double_diamond.py +1386 -0
  42. ouroboros/execution/subagent.py +275 -0
  43. ouroboros/observability/__init__.py +63 -0
  44. ouroboros/observability/drift.py +383 -0
  45. ouroboros/observability/logging.py +504 -0
  46. ouroboros/observability/retrospective.py +338 -0
  47. ouroboros/orchestrator/__init__.py +78 -0
  48. ouroboros/orchestrator/adapter.py +391 -0
  49. ouroboros/orchestrator/events.py +278 -0
  50. ouroboros/orchestrator/runner.py +597 -0
  51. ouroboros/orchestrator/session.py +486 -0
  52. ouroboros/persistence/__init__.py +23 -0
  53. ouroboros/persistence/checkpoint.py +511 -0
  54. ouroboros/persistence/event_store.py +183 -0
  55. ouroboros/persistence/migrations/__init__.py +1 -0
  56. ouroboros/persistence/migrations/runner.py +100 -0
  57. ouroboros/persistence/migrations/scripts/001_initial.sql +20 -0
  58. ouroboros/persistence/schema.py +56 -0
  59. ouroboros/persistence/uow.py +230 -0
  60. ouroboros/providers/__init__.py +28 -0
  61. ouroboros/providers/base.py +133 -0
  62. ouroboros/providers/claude_code_adapter.py +212 -0
  63. ouroboros/providers/litellm_adapter.py +316 -0
  64. ouroboros/py.typed +0 -0
  65. ouroboros/resilience/__init__.py +67 -0
  66. ouroboros/resilience/lateral.py +595 -0
  67. ouroboros/resilience/stagnation.py +727 -0
  68. ouroboros/routing/__init__.py +60 -0
  69. ouroboros/routing/complexity.py +272 -0
  70. ouroboros/routing/downgrade.py +664 -0
  71. ouroboros/routing/escalation.py +340 -0
  72. ouroboros/routing/router.py +204 -0
  73. ouroboros/routing/tiers.py +247 -0
  74. ouroboros/secondary/__init__.py +40 -0
  75. ouroboros/secondary/scheduler.py +467 -0
  76. ouroboros/secondary/todo_registry.py +483 -0
  77. ouroboros_ai-0.1.0.dist-info/METADATA +607 -0
  78. ouroboros_ai-0.1.0.dist-info/RECORD +81 -0
  79. ouroboros_ai-0.1.0.dist-info/WHEEL +4 -0
  80. ouroboros_ai-0.1.0.dist-info/entry_points.txt +2 -0
  81. ouroboros_ai-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,425 @@
1
+ """Init command for starting interactive interview.
2
+
3
+ This command initiates the Big Bang phase interview process.
4
+ Supports both LiteLLM (external API) and Claude Code (Max Plan) modes.
5
+ """
6
+
7
+ import asyncio
8
+ from pathlib import Path
9
+ from typing import Annotated
10
+
11
+ from rich.prompt import Confirm, Prompt
12
+ import typer
13
+
14
+ from ouroboros.bigbang.ambiguity import AmbiguityScorer
15
+ from ouroboros.bigbang.interview import MAX_INTERVIEW_ROUNDS, InterviewEngine, InterviewState
16
+ from ouroboros.bigbang.seed_generator import SeedGenerator
17
+ from ouroboros.cli.formatters import console
18
+ from ouroboros.cli.formatters.panels import print_error, print_info, print_success, print_warning
19
+ from ouroboros.providers.base import LLMAdapter
20
+ from ouroboros.providers.litellm_adapter import LiteLLMAdapter
21
+
22
+ app = typer.Typer(
23
+ name="init",
24
+ help="Start interactive interview to refine requirements.",
25
+ no_args_is_help=False,
26
+ )
27
+
28
+
29
+ def _get_adapter(use_orchestrator: bool) -> LLMAdapter:
30
+ """Get the appropriate LLM adapter.
31
+
32
+ Args:
33
+ use_orchestrator: If True, use Claude Code (Max Plan). Otherwise LiteLLM.
34
+
35
+ Returns:
36
+ LLM adapter instance.
37
+ """
38
+ if use_orchestrator:
39
+ from ouroboros.providers.claude_code_adapter import ClaudeCodeAdapter
40
+
41
+ return ClaudeCodeAdapter()
42
+ else:
43
+ return LiteLLMAdapter()
44
+
45
+
46
+ async def _run_interview(
47
+ initial_context: str,
48
+ resume_id: str | None = None,
49
+ state_dir: Path | None = None,
50
+ use_orchestrator: bool = False,
51
+ ) -> None:
52
+ """Run the interview process.
53
+
54
+ Args:
55
+ initial_context: Initial context or idea for the interview.
56
+ resume_id: Optional interview ID to resume.
57
+ state_dir: Optional custom state directory.
58
+ use_orchestrator: If True, use Claude Code (Max Plan) instead of LiteLLM.
59
+ """
60
+ # Initialize components
61
+ llm_adapter = _get_adapter(use_orchestrator)
62
+ engine = InterviewEngine(
63
+ llm_adapter=llm_adapter,
64
+ state_dir=state_dir or Path.home() / ".ouroboros" / "data",
65
+ )
66
+
67
+ # Load or start interview
68
+ if resume_id:
69
+ print_info(f"Resuming interview: {resume_id}")
70
+ state_result = await engine.load_state(resume_id)
71
+ if state_result.is_err:
72
+ print_error(f"Failed to load interview: {state_result.error.message}")
73
+ raise typer.Exit(code=1)
74
+ state = state_result.value
75
+ else:
76
+ print_info("Starting new interview session...")
77
+ state_result = await engine.start_interview(initial_context)
78
+ if state_result.is_err:
79
+ print_error(f"Failed to start interview: {state_result.error.message}")
80
+ raise typer.Exit(code=1)
81
+ state = state_result.value
82
+
83
+ console.print()
84
+ console.print(
85
+ f"[bold cyan]Interview Session: {state.interview_id}[/]",
86
+ )
87
+ console.print(f"[muted]Max rounds: {MAX_INTERVIEW_ROUNDS}[/]")
88
+ console.print()
89
+
90
+ # Interview loop
91
+ while not state.is_complete:
92
+ current_round = state.current_round_number
93
+ console.print(
94
+ f"[bold]Round {current_round}/{MAX_INTERVIEW_ROUNDS}[/]",
95
+ )
96
+
97
+ # Generate question
98
+ with console.status(
99
+ "[cyan]Generating question...[/]",
100
+ spinner="dots",
101
+ ):
102
+ question_result = await engine.ask_next_question(state)
103
+
104
+ if question_result.is_err:
105
+ print_error(f"Failed to generate question: {question_result.error.message}")
106
+ should_retry = Confirm.ask("Retry?", default=True)
107
+ if not should_retry:
108
+ break
109
+ continue
110
+
111
+ question = question_result.value
112
+
113
+ # Display question
114
+ console.print()
115
+ console.print(f"[bold yellow]Q:[/] {question}")
116
+ console.print()
117
+
118
+ # Get user response
119
+ response = Prompt.ask("[bold green]Your response[/]")
120
+
121
+ if not response.strip():
122
+ print_error("Response cannot be empty. Please try again.")
123
+ continue
124
+
125
+ # Record response
126
+ record_result = await engine.record_response(state, response, question)
127
+ if record_result.is_err:
128
+ print_error(f"Failed to record response: {record_result.error.message}")
129
+ continue
130
+
131
+ state = record_result.value
132
+
133
+ # Save state
134
+ save_result = await engine.save_state(state)
135
+ if save_result.is_err:
136
+ print_error(f"Warning: Failed to save state: {save_result.error.message}")
137
+
138
+ console.print()
139
+
140
+ # Check if user wants to continue or finish early
141
+ if not state.is_complete and current_round >= 3:
142
+ should_continue = Confirm.ask(
143
+ "Continue with more questions?",
144
+ default=True,
145
+ )
146
+ if not should_continue:
147
+ complete_result = await engine.complete_interview(state)
148
+ if complete_result.is_ok:
149
+ state = complete_result.value
150
+ await engine.save_state(state)
151
+ break
152
+
153
+ # Interview complete
154
+ console.print()
155
+ print_success("Interview completed!")
156
+ console.print(f"[muted]Total rounds: {len(state.rounds)}[/]")
157
+ console.print(f"[muted]Interview ID: {state.interview_id}[/]")
158
+
159
+ # Save final state
160
+ save_result = await engine.save_state(state)
161
+ if save_result.is_ok:
162
+ console.print(f"[muted]State saved to: {save_result.value}[/]")
163
+
164
+ console.print()
165
+
166
+ # Ask if user wants to proceed to Seed generation
167
+ should_generate_seed = Confirm.ask(
168
+ "[bold cyan]Proceed to generate Seed specification?[/]",
169
+ default=True,
170
+ )
171
+
172
+ if not should_generate_seed:
173
+ console.print(
174
+ "[muted]You can resume later with:[/] "
175
+ f"[bold]ouroboros init start --resume {state.interview_id}[/]"
176
+ )
177
+ return
178
+
179
+ # Generate Seed
180
+ seed_path = await _generate_seed_from_interview(state, llm_adapter)
181
+
182
+ if seed_path is None:
183
+ return
184
+
185
+ # Ask if user wants to start workflow
186
+ console.print()
187
+ should_start_workflow = Confirm.ask(
188
+ "[bold cyan]Start workflow now?[/]",
189
+ default=True,
190
+ )
191
+
192
+ if should_start_workflow:
193
+ await _start_workflow(seed_path, use_orchestrator)
194
+
195
+
196
+ async def _generate_seed_from_interview(
197
+ state: InterviewState,
198
+ llm_adapter: LLMAdapter,
199
+ ) -> Path | None:
200
+ """Generate Seed from completed interview.
201
+
202
+ Args:
203
+ state: Completed interview state.
204
+ llm_adapter: LLM adapter for scoring and generation.
205
+
206
+ Returns:
207
+ Path to generated seed file, or None if failed.
208
+ """
209
+ console.print()
210
+ console.print("[bold cyan]Generating Seed specification...[/]")
211
+
212
+ # Step 1: Calculate ambiguity score
213
+ with console.status("[cyan]Calculating ambiguity score...[/]", spinner="dots"):
214
+ scorer = AmbiguityScorer(llm_adapter=llm_adapter)
215
+ score_result = await scorer.score(state)
216
+
217
+ if score_result.is_err:
218
+ print_error(f"Failed to calculate ambiguity: {score_result.error.message}")
219
+ return None
220
+
221
+ ambiguity_score = score_result.value
222
+ console.print(f"[muted]Ambiguity score: {ambiguity_score.overall_score:.2f}[/]")
223
+
224
+ if not ambiguity_score.is_ready_for_seed:
225
+ print_warning(
226
+ f"Ambiguity score ({ambiguity_score.overall_score:.2f}) is too high. "
227
+ "Consider more interview rounds to clarify requirements."
228
+ )
229
+ should_force = Confirm.ask(
230
+ "[yellow]Generate Seed anyway?[/]",
231
+ default=False,
232
+ )
233
+ if not should_force:
234
+ return None
235
+
236
+ # Step 2: Generate Seed
237
+ with console.status("[cyan]Generating Seed from interview...[/]", spinner="dots"):
238
+ generator = SeedGenerator(llm_adapter=llm_adapter)
239
+ # For forced generation, we need to bypass the threshold check
240
+ if ambiguity_score.is_ready_for_seed:
241
+ seed_result = await generator.generate(state, ambiguity_score)
242
+ else:
243
+ # Create a modified score that passes threshold for forced generation
244
+ from ouroboros.bigbang.ambiguity import AmbiguityScore as AmbScore
245
+
246
+ forced_score = AmbScore(
247
+ overall_score=0.19, # Just under threshold
248
+ breakdown=ambiguity_score.breakdown,
249
+ )
250
+ seed_result = await generator.generate(state, forced_score)
251
+
252
+ if seed_result.is_err:
253
+ print_error(f"Failed to generate Seed: {seed_result.error.message}")
254
+ return None
255
+
256
+ seed = seed_result.value
257
+
258
+ # Step 3: Save Seed
259
+ seed_path = Path.home() / ".ouroboros" / "seeds" / f"{seed.metadata.seed_id}.yaml"
260
+ save_result = await generator.save_seed(seed, seed_path)
261
+
262
+ if save_result.is_err:
263
+ print_error(f"Failed to save Seed: {save_result.error.message}")
264
+ return None
265
+
266
+ print_success(f"Seed generated: {seed_path}")
267
+ return seed_path
268
+
269
+
270
+ async def _start_workflow(seed_path: Path, use_orchestrator: bool = False) -> None:
271
+ """Start workflow from generated seed.
272
+
273
+ Args:
274
+ seed_path: Path to the seed YAML file.
275
+ use_orchestrator: Whether to use Claude Code orchestrator.
276
+ """
277
+ console.print()
278
+ console.print("[bold cyan]Starting workflow...[/]")
279
+
280
+ if use_orchestrator:
281
+ # Direct function call instead of subprocess
282
+ from ouroboros.cli.commands.run import _run_orchestrator
283
+
284
+ try:
285
+ await _run_orchestrator(seed_path, resume_session=None)
286
+ except typer.Exit:
287
+ pass # Normal exit
288
+ except KeyboardInterrupt:
289
+ print_info("Workflow interrupted.")
290
+ else:
291
+ # Standard workflow (placeholder for now)
292
+ print_info(f"Would execute workflow from: {seed_path}")
293
+ print_info("Standard workflow execution not yet implemented.")
294
+
295
+
296
+ @app.command()
297
+ def start(
298
+ context: Annotated[
299
+ str | None,
300
+ typer.Argument(
301
+ help="Initial context or idea (interactive prompt if not provided)."
302
+ ),
303
+ ] = None,
304
+ resume: Annotated[
305
+ str | None,
306
+ typer.Option(
307
+ "--resume",
308
+ "-r",
309
+ help="Resume an existing interview by ID.",
310
+ ),
311
+ ] = None,
312
+ state_dir: Annotated[
313
+ Path | None,
314
+ typer.Option(
315
+ "--state-dir",
316
+ help="Custom directory for interview state files.",
317
+ exists=True,
318
+ file_okay=False,
319
+ dir_okay=True,
320
+ ),
321
+ ] = None,
322
+ orchestrator: Annotated[
323
+ bool,
324
+ typer.Option(
325
+ "--orchestrator",
326
+ "-o",
327
+ help="Use Claude Code (Max Plan) instead of LiteLLM. No API key required.",
328
+ ),
329
+ ] = False,
330
+ ) -> None:
331
+ """Start an interactive interview to refine your requirements.
332
+
333
+ This command initiates the Big Bang phase, which transforms vague ideas
334
+ into clear, executable requirements through iterative questioning.
335
+
336
+ Example:
337
+ ouroboros init start "I want to build a task management CLI tool"
338
+
339
+ ouroboros init start --orchestrator "Build a REST API"
340
+
341
+ ouroboros init start --resume interview_20260116_120000
342
+
343
+ ouroboros init start
344
+ """
345
+ # Get initial context if not provided
346
+ if not resume and not context:
347
+ console.print(
348
+ "[bold cyan]Welcome to Ouroboros Interview![/]",
349
+ )
350
+ console.print()
351
+ console.print(
352
+ "This interactive process will help refine your ideas into clear requirements.",
353
+ )
354
+ console.print(
355
+ f"You'll be asked up to {MAX_INTERVIEW_ROUNDS} questions to reduce ambiguity.",
356
+ )
357
+ console.print()
358
+
359
+ context = Prompt.ask(
360
+ "[bold]What would you like to build?[/]",
361
+ )
362
+
363
+ if not resume and not context:
364
+ print_error("Initial context is required when not resuming.")
365
+ raise typer.Exit(code=1)
366
+
367
+ # Show mode info
368
+ if orchestrator:
369
+ print_info("Using Claude Code (Max Plan) - no API key required")
370
+ else:
371
+ print_info("Using LiteLLM - API key required")
372
+
373
+ # Run interview
374
+ try:
375
+ asyncio.run(_run_interview(context or "", resume, state_dir, orchestrator))
376
+ except KeyboardInterrupt:
377
+ console.print()
378
+ print_info("Interview interrupted. Progress has been saved.")
379
+ raise typer.Exit(code=0)
380
+ except Exception as e:
381
+ print_error(f"Interview failed: {e}")
382
+ raise typer.Exit(code=1)
383
+
384
+
385
+ @app.command("list")
386
+ def list_interviews(
387
+ state_dir: Annotated[
388
+ Path | None,
389
+ typer.Option(
390
+ "--state-dir",
391
+ help="Custom directory for interview state files.",
392
+ exists=True,
393
+ file_okay=False,
394
+ dir_okay=True,
395
+ ),
396
+ ] = None,
397
+ ) -> None:
398
+ """List all interview sessions."""
399
+ llm_adapter = LiteLLMAdapter()
400
+ engine = InterviewEngine(
401
+ llm_adapter=llm_adapter,
402
+ state_dir=state_dir or Path.home() / ".ouroboros" / "data",
403
+ )
404
+
405
+ interviews = asyncio.run(engine.list_interviews())
406
+
407
+ if not interviews:
408
+ print_info("No interviews found.")
409
+ return
410
+
411
+ console.print("[bold cyan]Interview Sessions:[/]")
412
+ console.print()
413
+
414
+ for interview in interviews:
415
+ status_color = "green" if interview["status"] == "completed" else "yellow"
416
+ console.print(
417
+ f"[bold]{interview['interview_id']}[/] "
418
+ f"[{status_color}]{interview['status']}[/] "
419
+ f"({interview['rounds']} rounds)"
420
+ )
421
+ console.print(f" Updated: {interview['updated_at']}")
422
+ console.print()
423
+
424
+
425
+ __all__ = ["app"]
@@ -0,0 +1,201 @@
1
+ """Run command group for Ouroboros.
2
+
3
+ Execute workflows and manage running operations.
4
+ Supports both standard workflow execution and orchestrator mode (Claude Agent SDK).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ from pathlib import Path
11
+ from typing import Annotated
12
+
13
+ import typer
14
+ import yaml
15
+
16
+ from ouroboros.cli.formatters import console
17
+ from ouroboros.cli.formatters.panels import print_error, print_info, print_success
18
+
19
+ app = typer.Typer(
20
+ name="run",
21
+ help="Execute Ouroboros workflows.",
22
+ no_args_is_help=True,
23
+ )
24
+
25
+
26
+ def _load_seed_from_yaml(seed_file: Path) -> dict:
27
+ """Load seed configuration from YAML file.
28
+
29
+ Args:
30
+ seed_file: Path to the seed YAML file.
31
+
32
+ Returns:
33
+ Seed configuration dictionary.
34
+
35
+ Raises:
36
+ typer.Exit: If file cannot be loaded.
37
+ """
38
+ try:
39
+ with open(seed_file) as f:
40
+ return yaml.safe_load(f)
41
+ except Exception as e:
42
+ print_error(f"Failed to load seed file: {e}")
43
+ raise typer.Exit(1) from e
44
+
45
+
46
+ async def _run_orchestrator(
47
+ seed_file: Path,
48
+ resume_session: str | None = None,
49
+ ) -> None:
50
+ """Run workflow via orchestrator mode (Claude Agent SDK).
51
+
52
+ Args:
53
+ seed_file: Path to seed YAML file.
54
+ resume_session: Optional session ID to resume.
55
+ """
56
+ from ouroboros.core.seed import Seed
57
+ from ouroboros.orchestrator import ClaudeAgentAdapter, OrchestratorRunner
58
+ from ouroboros.persistence.event_store import EventStore
59
+
60
+ # Load seed
61
+ seed_data = _load_seed_from_yaml(seed_file)
62
+
63
+ try:
64
+ seed = Seed.from_dict(seed_data)
65
+ except Exception as e:
66
+ print_error(f"Invalid seed format: {e}")
67
+ raise typer.Exit(1) from e
68
+
69
+ print_info(f"Loaded seed: {seed.goal[:80]}...")
70
+ print_info(f"Acceptance criteria: {len(seed.acceptance_criteria)}")
71
+
72
+ # Initialize components
73
+ import os
74
+ db_path = os.path.expanduser("~/.ouroboros/ouroboros.db")
75
+ os.makedirs(os.path.dirname(db_path), exist_ok=True)
76
+ event_store = EventStore(f"sqlite+aiosqlite:///{db_path}")
77
+ await event_store.initialize()
78
+
79
+ adapter = ClaudeAgentAdapter()
80
+ runner = OrchestratorRunner(adapter, event_store, console)
81
+
82
+ # Execute
83
+ if resume_session:
84
+ print_info(f"Resuming session: {resume_session}")
85
+ result = await runner.resume_session(resume_session, seed)
86
+ else:
87
+ print_info("Starting new orchestrator execution...")
88
+ result = await runner.execute_seed(seed)
89
+
90
+ # Handle result
91
+ if result.is_ok:
92
+ res = result.value
93
+ if res.success:
94
+ print_success("Execution completed successfully!")
95
+ print_info(f"Session ID: {res.session_id}")
96
+ print_info(f"Messages processed: {res.messages_processed}")
97
+ print_info(f"Duration: {res.duration_seconds:.1f}s")
98
+ else:
99
+ print_error("Execution failed")
100
+ print_info(f"Session ID: {res.session_id}")
101
+ console.print(f"[dim]Error: {res.final_message[:200]}[/dim]")
102
+ raise typer.Exit(1)
103
+ else:
104
+ print_error(f"Orchestrator error: {result.error}")
105
+ raise typer.Exit(1)
106
+
107
+
108
+ @app.command()
109
+ def workflow(
110
+ seed_file: Annotated[
111
+ Path,
112
+ typer.Argument(
113
+ help="Path to the seed YAML file.",
114
+ exists=True,
115
+ file_okay=True,
116
+ dir_okay=False,
117
+ readable=True,
118
+ ),
119
+ ],
120
+ orchestrator: Annotated[
121
+ bool,
122
+ typer.Option(
123
+ "--orchestrator",
124
+ "-o",
125
+ help="Use Claude Agent SDK for execution (Epic 8 mode).",
126
+ ),
127
+ ] = False,
128
+ resume_session: Annotated[
129
+ str | None,
130
+ typer.Option(
131
+ "--resume",
132
+ "-r",
133
+ help="Resume a previous orchestrator session by ID.",
134
+ ),
135
+ ] = None,
136
+ dry_run: Annotated[
137
+ bool,
138
+ typer.Option("--dry-run", "-n", help="Validate seed without executing."),
139
+ ] = False,
140
+ verbose: Annotated[
141
+ bool,
142
+ typer.Option("--verbose", "-v", help="Enable verbose output."),
143
+ ] = False,
144
+ ) -> None:
145
+ """Execute a workflow from a seed file.
146
+
147
+ Reads the seed YAML configuration and runs the Ouroboros workflow.
148
+
149
+ Use --orchestrator to execute via Claude Agent SDK (Epic 8).
150
+ Use --resume with --orchestrator to continue a previous session.
151
+
152
+ Examples:
153
+
154
+ # Standard workflow execution (placeholder)
155
+ ouroboros run workflow seed.yaml
156
+
157
+ # Orchestrator mode (Claude Agent SDK)
158
+ ouroboros run workflow --orchestrator seed.yaml
159
+
160
+ # Resume a previous orchestrator session
161
+ ouroboros run workflow --orchestrator --resume orch_abc123 seed.yaml
162
+ """
163
+ if orchestrator or resume_session:
164
+ # Orchestrator mode
165
+ if resume_session and not orchestrator:
166
+ console.print(
167
+ "[yellow]Warning: --resume requires --orchestrator flag. "
168
+ "Enabling orchestrator mode.[/yellow]"
169
+ )
170
+ asyncio.run(_run_orchestrator(seed_file, resume_session))
171
+ else:
172
+ # Standard workflow (placeholder)
173
+ print_info(f"Would execute workflow from: {seed_file}")
174
+ if dry_run:
175
+ console.print("[muted]Dry run mode - no changes will be made[/]")
176
+ if verbose:
177
+ console.print("[muted]Verbose mode enabled[/]")
178
+
179
+
180
+ @app.command()
181
+ def resume(
182
+ execution_id: Annotated[
183
+ str | None,
184
+ typer.Argument(help="Execution ID to resume. Uses latest if not specified."),
185
+ ] = None,
186
+ ) -> None:
187
+ """Resume a paused or failed execution.
188
+
189
+ If no execution ID is provided, resumes the most recent execution.
190
+
191
+ Note: For orchestrator sessions, use:
192
+ ouroboros run workflow --orchestrator --resume <session_id> seed.yaml
193
+ """
194
+ # Placeholder implementation
195
+ if execution_id:
196
+ print_info(f"Would resume execution: {execution_id}")
197
+ else:
198
+ print_info("Would resume most recent execution")
199
+
200
+
201
+ __all__ = ["app"]
@@ -0,0 +1,85 @@
1
+ """Status command group for Ouroboros.
2
+
3
+ Check system status and execution history.
4
+ """
5
+
6
+ from typing import Annotated
7
+
8
+ import typer
9
+
10
+ from ouroboros.cli.formatters.panels import print_info
11
+ from ouroboros.cli.formatters.tables import create_status_table, print_table
12
+
13
+ app = typer.Typer(
14
+ name="status",
15
+ help="Check Ouroboros system status.",
16
+ no_args_is_help=True,
17
+ )
18
+
19
+
20
+ @app.command()
21
+ def executions(
22
+ limit: Annotated[
23
+ int,
24
+ typer.Option("--limit", "-n", help="Number of executions to show."),
25
+ ] = 10,
26
+ all_: Annotated[
27
+ bool,
28
+ typer.Option("--all", "-a", help="Show all executions."),
29
+ ] = False,
30
+ ) -> None:
31
+ """List recent executions.
32
+
33
+ Shows execution history with status information.
34
+ """
35
+ # Placeholder implementation with example data
36
+ example_data = [
37
+ {"name": "exec-001", "status": "complete"},
38
+ {"name": "exec-002", "status": "running"},
39
+ {"name": "exec-003", "status": "failed"},
40
+ ]
41
+ table = create_status_table(example_data, "Recent Executions")
42
+ print_table(table)
43
+
44
+ if not all_:
45
+ print_info(f"Showing last {limit} executions. Use --all to see more.")
46
+
47
+
48
+ @app.command()
49
+ def execution(
50
+ execution_id: Annotated[
51
+ str,
52
+ typer.Argument(help="Execution ID to inspect."),
53
+ ],
54
+ events: Annotated[
55
+ bool,
56
+ typer.Option("--events", "-e", help="Show execution events."),
57
+ ] = False,
58
+ ) -> None:
59
+ """Show details for a specific execution.
60
+
61
+ Displays execution metadata, progress, and optionally events.
62
+ """
63
+ # Placeholder implementation
64
+ print_info(f"Would show details for execution: {execution_id}")
65
+ if events:
66
+ print_info("Would include event history")
67
+
68
+
69
+ @app.command()
70
+ def health() -> None:
71
+ """Check system health.
72
+
73
+ Verifies database connectivity, provider configuration, and system resources.
74
+ """
75
+ # Placeholder implementation with example data
76
+ health_data = [
77
+ {"name": "Database", "status": "ok"},
78
+ {"name": "Configuration", "status": "ok"},
79
+ {"name": "Providers", "status": "warning"},
80
+ ]
81
+ table = create_status_table(health_data, "System Health")
82
+ print_table(table)
83
+
84
+
85
+ __all__ = ["app"]