fluxloop-cli 0.2.22__tar.gz → 0.2.24__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 (62) hide show
  1. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/PKG-INFO +4 -1
  2. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/README.md +2 -0
  3. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/__init__.py +1 -1
  4. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/commands/record.py +17 -10
  5. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/conversation_supervisor.py +40 -4
  6. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/templates.py +9 -3
  7. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli.egg-info/PKG-INFO +4 -1
  8. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli.egg-info/requires.txt +1 -0
  9. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/pyproject.toml +2 -1
  10. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/arg_binder.py +0 -0
  11. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/commands/__init__.py +0 -0
  12. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/commands/config.py +0 -0
  13. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/commands/doctor.py +0 -0
  14. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/commands/evaluate.py +0 -0
  15. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/commands/generate.py +0 -0
  16. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/commands/init.py +0 -0
  17. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/commands/parse.py +0 -0
  18. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/commands/run.py +0 -0
  19. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/commands/status.py +0 -0
  20. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/config_loader.py +0 -0
  21. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/config_schema.py +0 -0
  22. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/constants.py +0 -0
  23. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/environment.py +0 -0
  24. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/evaluation/__init__.py +0 -0
  25. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/evaluation/config.py +0 -0
  26. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/evaluation/engine/__init__.py +0 -0
  27. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/evaluation/engine/analysis.py +0 -0
  28. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/evaluation/engine/core.py +0 -0
  29. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/evaluation/engine/reporting/__init__.py +0 -0
  30. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/evaluation/engine/reporting/html.py +0 -0
  31. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/evaluation/engine/reporting/markdown.py +0 -0
  32. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/evaluation/engine/success.py +0 -0
  33. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/evaluation/llm.py +0 -0
  34. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/evaluation/prompts/__init__.py +0 -0
  35. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/evaluation/prompts/base.py +0 -0
  36. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/evaluation/prompts/information_completeness.py +0 -0
  37. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/evaluation/prompts/intent_recognition.py +0 -0
  38. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/evaluation/prompts/response_clarity.py +0 -0
  39. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/evaluation/prompts/response_consistency.py +0 -0
  40. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/evaluation/rules.py +0 -0
  41. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/input_generator.py +0 -0
  42. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/llm_generator.py +0 -0
  43. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/main.py +0 -0
  44. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/project_paths.py +0 -0
  45. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/runner.py +0 -0
  46. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/target_loader.py +0 -0
  47. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli/validators.py +0 -0
  48. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli.egg-info/SOURCES.txt +0 -0
  49. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli.egg-info/dependency_links.txt +0 -0
  50. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli.egg-info/entry_points.txt +0 -0
  51. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/fluxloop_cli.egg-info/top_level.txt +0 -0
  52. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/setup.cfg +0 -0
  53. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/tests/test_analysis_recommendations.py +0 -0
  54. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/tests/test_arg_binder.py +0 -0
  55. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/tests/test_config_command.py +0 -0
  56. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/tests/test_evaluate_command.py +0 -0
  57. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/tests/test_evaluation_llm.py +0 -0
  58. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/tests/test_input_generator.py +0 -0
  59. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/tests/test_prompt_library.py +0 -0
  60. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/tests/test_run_command.py +0 -0
  61. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/tests/test_success_criteria.py +0 -0
  62. {fluxloop_cli-0.2.22 → fluxloop_cli-0.2.24}/tests/test_target_loader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fluxloop-cli
3
- Version: 0.2.22
3
+ Version: 0.2.24
4
4
  Summary: FluxLoop CLI for running agent simulations
5
5
  Author-email: FluxLoop Team <team@fluxloop.dev>
6
6
  License: Apache-2.0
@@ -26,6 +26,7 @@ Requires-Dist: httpx>=0.24.0
26
26
  Requires-Dist: rich>=13.0
27
27
  Requires-Dist: python-dotenv>=1.0.0
28
28
  Requires-Dist: fluxloop>=0.1.0
29
+ Requires-Dist: ruamel.yaml>=0.17.0
29
30
  Provides-Extra: dev
30
31
  Requires-Dist: pytest>=7.0; extra == "dev"
31
32
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
@@ -83,6 +84,8 @@ The legacy `setting.yaml` is still supported, but new projects created with
83
84
 
84
85
  These flags override the values in `configs/simulation.yaml` (`multi_turn` block). When enabled, the runner consults the supervisor after every turn to decide whether to continue and to synthesize the next realistic user message.
85
86
 
87
+ **Scripted Playback Mode**: For deterministic multi-turn scenarios, switch `supervisor.provider` to `mock` and populate `supervisor.metadata.scripted_questions` with a list of user messages. FluxLoop will replay them sequentially and terminate when the script ends—ideal for regression testing and demos.
88
+
86
89
  Run `fluxloop --help` or `fluxloop <command> --help` for more detail.
87
90
 
88
91
  ## Quick Setup Script
@@ -43,6 +43,8 @@ The legacy `setting.yaml` is still supported, but new projects created with
43
43
 
44
44
  These flags override the values in `configs/simulation.yaml` (`multi_turn` block). When enabled, the runner consults the supervisor after every turn to decide whether to continue and to synthesize the next realistic user message.
45
45
 
46
+ **Scripted Playback Mode**: For deterministic multi-turn scenarios, switch `supervisor.provider` to `mock` and populate `supervisor.metadata.scripted_questions` with a list of user messages. FluxLoop will replay them sequentially and terminate when the script ends—ideal for regression testing and demos.
47
+
46
48
  Run `fluxloop --help` or `fluxloop <command> --help` for more detail.
47
49
 
48
50
  ## Quick Setup Script
@@ -2,7 +2,7 @@
2
2
  FluxLoop CLI - Command-line interface for running agent simulations.
3
3
  """
4
4
 
5
- __version__ = "0.2.22"
5
+ __version__ = "0.2.24"
6
6
 
7
7
  from .main import app
8
8
 
@@ -8,6 +8,7 @@ from typing import Dict, Optional
8
8
  import typer
9
9
  from rich.console import Console
10
10
  from rich.table import Table
11
+ from ruamel.yaml import YAML, CommentedMap
11
12
 
12
13
  from ..constants import DEFAULT_ROOT_DIR_NAME
13
14
  from ..project_paths import (
@@ -20,6 +21,9 @@ from ..config_schema import CONFIG_SECTION_FILENAMES
20
21
 
21
22
  app = typer.Typer()
22
23
  console = Console()
24
+ _yaml = YAML()
25
+ _yaml.indent(mapping=2, sequence=4, offset=2)
26
+ _yaml.preserve_quotes = True
23
27
 
24
28
 
25
29
  def _load_env(env_path: Path) -> Dict[str, str]:
@@ -44,22 +48,25 @@ def _update_simulation(recording_enabled: bool, project: Optional[str], root: Pa
44
48
  except KeyError:
45
49
  return
46
50
 
47
- data: Dict[str, object] = {}
48
51
  if simulation_path.exists():
49
- import yaml
52
+ with simulation_path.open("r", encoding="utf-8") as handle:
53
+ loaded = _yaml.load(handle) or CommentedMap()
54
+ else:
55
+ loaded = CommentedMap()
50
56
 
51
- data = yaml.safe_load(simulation_path.read_text()) or {}
57
+ if not isinstance(loaded, CommentedMap):
58
+ loaded = CommentedMap(loaded)
52
59
 
53
- replay = data.setdefault("replay_args", {})
54
- replay["enabled"] = recording_enabled
60
+ replay = loaded.get("replay_args")
61
+ if not isinstance(replay, CommentedMap):
62
+ replay = CommentedMap(replay or {})
63
+ loaded["replay_args"] = replay
55
64
 
56
- import yaml
65
+ replay["enabled"] = recording_enabled
57
66
 
58
67
  simulation_path.parent.mkdir(parents=True, exist_ok=True)
59
- simulation_path.write_text(
60
- yaml.dump(data, sort_keys=False, default_flow_style=False),
61
- encoding="utf-8",
62
- )
68
+ with simulation_path.open("w", encoding="utf-8") as handle:
69
+ _yaml.dump(loaded, handle)
63
70
 
64
71
 
65
72
  @app.command()
@@ -119,7 +119,7 @@ class ConversationSupervisor:
119
119
 
120
120
  provider = (self.config.provider or "openai").lower()
121
121
  if provider == "mock":
122
- return self._mock_decision()
122
+ return self._mock_decision(conversation_state)
123
123
  if provider == "openai":
124
124
  response_text = await self._call_openai(prompt)
125
125
  else:
@@ -127,10 +127,45 @@ class ConversationSupervisor:
127
127
 
128
128
  return self._parse_decision(response_text)
129
129
 
130
- def _mock_decision(self) -> SupervisorDecision:
131
- """Deterministic supervisor used for tests or offline runs."""
130
+ def _mock_decision(self, conversation_state: Dict[str, Any]) -> SupervisorDecision:
131
+ """Deterministic supervisor used for tests, scripted runs, or offline modes."""
132
132
 
133
133
  metadata = self.config.metadata or {}
134
+
135
+ scripted_questions = metadata.get("scripted_questions")
136
+ if isinstance(scripted_questions, list):
137
+ context = conversation_state.setdefault("context", {})
138
+ index = int(context.get("mock_script_index", 0))
139
+ script_length = len(scripted_questions)
140
+
141
+ if index < script_length:
142
+ next_message = scripted_questions[index]
143
+ context["mock_script_index"] = index + 1
144
+ return SupervisorDecision(
145
+ decision="continue",
146
+ next_user_message=str(next_message),
147
+ raw_response={
148
+ "mock": True,
149
+ "scripted": True,
150
+ "script_index": index,
151
+ "script_length": script_length,
152
+ },
153
+ )
154
+
155
+ reason = metadata.get("mock_reason", "script_complete")
156
+ closing = metadata.get("mock_closing")
157
+ return SupervisorDecision(
158
+ decision="terminate",
159
+ termination_reason=reason,
160
+ closing_user_message=closing,
161
+ raw_response={
162
+ "mock": True,
163
+ "scripted": True,
164
+ "script_complete": True,
165
+ "script_length": script_length,
166
+ },
167
+ )
168
+
134
169
  default_decision = metadata.get("mock_decision", "terminate")
135
170
  decision: SupervisorDecisionType = (
136
171
  default_decision if default_decision in {"continue", "terminate"} else "terminate"
@@ -181,8 +216,9 @@ class ConversationSupervisor:
181
216
  payload = {
182
217
  "model": self.config.model,
183
218
  "messages": messages,
184
- "temperature": self.config.temperature,
185
219
  }
220
+ if self.config.temperature is not None:
221
+ payload["temperature"] = self.config.temperature
186
222
 
187
223
  async with httpx.AsyncClient(timeout=60) as client:
188
224
  response = await client.post(
@@ -35,6 +35,8 @@ def create_project_config(project_name: str) -> str:
35
35
  metadata:
36
36
  team: development
37
37
  environment: local
38
+ service_context: ""
39
+ # Describe the overall service scenario (used by multi-turn supervisor)
38
40
  # Add any custom fields used by dashboards or automation tools.
39
41
  """
40
42
  ).strip() + "\n"
@@ -140,19 +142,23 @@ def create_simulation_config(project_name: str) -> str:
140
142
  override_param_path: data.content
141
143
 
142
144
  multi_turn:
143
- enabled: false # Enable to drive conversations via supervisor LLM
145
+ enabled: false # Enable to drive conversations via supervisor
144
146
  max_turns: 8 # Safety cap on total turns per conversation
145
147
  auto_approve_tools: true # Automatically approve tool calls when supported
146
148
  persona_override: null # Force a specific persona id (optional)
147
149
  supervisor:
148
- provider: openai
150
+ provider: openai # openai (LLM generated) | mock (scripted playback)
149
151
  model: gpt-5-mini
150
- temperature: 0.2
151
152
  system_prompt: |
152
153
  You supervise an AI assistant supporting customers.
153
154
  Review the entire transcript and decide whether to continue.
154
155
  When continuing, craft the next user message consistent with the persona.
155
156
  When terminating, explain the reason and provide any closing notes.
157
+ metadata:
158
+ scripted_questions: [] # Array of user utterances for sequential playback (e.g., ["First question", "Second question", ...])
159
+ mock_decision: terminate # Fallback when no scripted questions remain
160
+ mock_reason: script_complete # Termination reason for scripted runs
161
+ mock_closing: "Thanks for the help. I have no further questions."
156
162
 
157
163
  output_directory: experiments
158
164
  save_traces: true
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fluxloop-cli
3
- Version: 0.2.22
3
+ Version: 0.2.24
4
4
  Summary: FluxLoop CLI for running agent simulations
5
5
  Author-email: FluxLoop Team <team@fluxloop.dev>
6
6
  License: Apache-2.0
@@ -26,6 +26,7 @@ Requires-Dist: httpx>=0.24.0
26
26
  Requires-Dist: rich>=13.0
27
27
  Requires-Dist: python-dotenv>=1.0.0
28
28
  Requires-Dist: fluxloop>=0.1.0
29
+ Requires-Dist: ruamel.yaml>=0.17.0
29
30
  Provides-Extra: dev
30
31
  Requires-Dist: pytest>=7.0; extra == "dev"
31
32
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
@@ -83,6 +84,8 @@ The legacy `setting.yaml` is still supported, but new projects created with
83
84
 
84
85
  These flags override the values in `configs/simulation.yaml` (`multi_turn` block). When enabled, the runner consults the supervisor after every turn to decide whether to continue and to synthesize the next realistic user message.
85
86
 
87
+ **Scripted Playback Mode**: For deterministic multi-turn scenarios, switch `supervisor.provider` to `mock` and populate `supervisor.metadata.scripted_questions` with a list of user messages. FluxLoop will replay them sequentially and terminate when the script ends—ideal for regression testing and demos.
88
+
86
89
  Run `fluxloop --help` or `fluxloop <command> --help` for more detail.
87
90
 
88
91
  ## Quick Setup Script
@@ -5,6 +5,7 @@ httpx>=0.24.0
5
5
  rich>=13.0
6
6
  python-dotenv>=1.0.0
7
7
  fluxloop>=0.1.0
8
+ ruamel.yaml>=0.17.0
8
9
 
9
10
  [anthropic]
10
11
  anthropic>=0.7.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fluxloop-cli"
7
- version = "0.2.22"
7
+ version = "0.2.24"
8
8
  description = "FluxLoop CLI for running agent simulations"
9
9
  authors = [
10
10
  {name = "FluxLoop Team", email = "team@fluxloop.dev"}
@@ -32,6 +32,7 @@ dependencies = [
32
32
  "rich>=13.0",
33
33
  "python-dotenv>=1.0.0",
34
34
  "fluxloop>=0.1.0",
35
+ "ruamel.yaml>=0.17.0",
35
36
  ]
36
37
 
37
38
  [project.optional-dependencies]
File without changes