loopengt 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.
Files changed (87) hide show
  1. loopengt/__init__.py +31 -0
  2. loopengt/adapters/__init__.py +1 -0
  3. loopengt/adapters/antigravity/__init__.py +1 -0
  4. loopengt/adapters/antigravity/adapter.py +55 -0
  5. loopengt/adapters/antigravity/commands.py +21 -0
  6. loopengt/adapters/base.py +51 -0
  7. loopengt/adapters/claude_code/__init__.py +1 -0
  8. loopengt/adapters/claude_code/adapter.py +55 -0
  9. loopengt/adapters/claude_code/commands.py +16 -0
  10. loopengt/adapters/codex/__init__.py +1 -0
  11. loopengt/adapters/codex/adapter.py +52 -0
  12. loopengt/adapters/codex/commands.py +16 -0
  13. loopengt/adapters/cursor/__init__.py +1 -0
  14. loopengt/adapters/cursor/adapter.py +56 -0
  15. loopengt/adapters/cursor/commands.py +29 -0
  16. loopengt/adapters/generic/__init__.py +1 -0
  17. loopengt/adapters/generic/terminal.py +82 -0
  18. loopengt/cli/__init__.py +1 -0
  19. loopengt/cli/commands/__init__.py +1 -0
  20. loopengt/cli/commands/design.py +171 -0
  21. loopengt/cli/commands/doctor.py +110 -0
  22. loopengt/cli/commands/eval.py +105 -0
  23. loopengt/cli/commands/init.py +131 -0
  24. loopengt/cli/commands/mcp_serve.py +57 -0
  25. loopengt/cli/commands/run.py +99 -0
  26. loopengt/cli/commands/template.py +145 -0
  27. loopengt/cli/commands/trace.py +114 -0
  28. loopengt/cli/formatters.py +125 -0
  29. loopengt/cli/main.py +66 -0
  30. loopengt/core/__init__.py +1 -0
  31. loopengt/core/evals/__init__.py +1 -0
  32. loopengt/core/evals/judges.py +216 -0
  33. loopengt/core/evals/metrics.py +119 -0
  34. loopengt/core/evals/regression.py +157 -0
  35. loopengt/core/memory/__init__.py +1 -0
  36. loopengt/core/memory/retrieval.py +124 -0
  37. loopengt/core/memory/store.py +184 -0
  38. loopengt/core/memory/summarizer.py +97 -0
  39. loopengt/core/models/__init__.py +43 -0
  40. loopengt/core/models/agent.py +126 -0
  41. loopengt/core/models/loop_spec.py +251 -0
  42. loopengt/core/models/policy.py +131 -0
  43. loopengt/core/models/state.py +271 -0
  44. loopengt/core/models/tool.py +105 -0
  45. loopengt/core/runtime/__init__.py +1 -0
  46. loopengt/core/runtime/checkpoint.py +152 -0
  47. loopengt/core/runtime/executor.py +463 -0
  48. loopengt/core/runtime/handoff.py +139 -0
  49. loopengt/core/runtime/scheduler.py +168 -0
  50. loopengt/core/tracing/__init__.py +1 -0
  51. loopengt/core/tracing/events.py +95 -0
  52. loopengt/core/tracing/exporters.py +158 -0
  53. loopengt/core/tracing/store.py +202 -0
  54. loopengt/mcp/__init__.py +1 -0
  55. loopengt/mcp/client/__init__.py +1 -0
  56. loopengt/mcp/client/manager.py +118 -0
  57. loopengt/mcp/client/tools.py +107 -0
  58. loopengt/mcp/server/__init__.py +1 -0
  59. loopengt/mcp/server/prompts.py +82 -0
  60. loopengt/mcp/server/resources.py +75 -0
  61. loopengt/mcp/server/server.py +50 -0
  62. loopengt/mcp/server/tools.py +214 -0
  63. loopengt/mcp/shared/__init__.py +1 -0
  64. loopengt/mcp/shared/schemas.py +91 -0
  65. loopengt/plugins/__init__.py +1 -0
  66. loopengt/plugins/base.py +90 -0
  67. loopengt/plugins/loader.py +130 -0
  68. loopengt/plugins/manifest.py +70 -0
  69. loopengt/plugins/registry.py +146 -0
  70. loopengt/prompts/LOOPENGT.md +60 -0
  71. loopengt/prompts/__init__.py +1 -0
  72. loopengt/storage/__init__.py +1 -0
  73. loopengt/storage/jsonl.py +84 -0
  74. loopengt/storage/sqlite.py +102 -0
  75. loopengt/templates/__init__.py +1 -0
  76. loopengt/templates/builtins/handoff_loop/LOOPENGS.md +10 -0
  77. loopengt/templates/builtins/planner_executor/LOOPENGS.md +29 -0
  78. loopengt/templates/builtins/research_architect/LOOPENGS.md +17 -0
  79. loopengt/templates/builtins/reviewer_retry/LOOPENGS.md +29 -0
  80. loopengt/templates/builtins/supervisor_workers/LOOPENGS.md +29 -0
  81. loopengt/templates/loader.py +38 -0
  82. loopengt/templates/registry.py +85 -0
  83. loopengt-0.1.0.dist-info/METADATA +275 -0
  84. loopengt-0.1.0.dist-info/RECORD +87 -0
  85. loopengt-0.1.0.dist-info/WHEEL +4 -0
  86. loopengt-0.1.0.dist-info/entry_points.txt +8 -0
  87. loopengt-0.1.0.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,126 @@
1
+ """Agent role and capability definitions for loop participants."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import StrEnum
6
+ from typing import Any
7
+
8
+ from pydantic import BaseModel, ConfigDict, Field
9
+
10
+
11
+ class AgentCapability(StrEnum):
12
+ """Standard capabilities an agent may declare."""
13
+
14
+ CODE_GENERATION = "code_generation"
15
+ CODE_REVIEW = "code_review"
16
+ PLANNING = "planning"
17
+ RESEARCH = "research"
18
+ TESTING = "testing"
19
+ EVALUATION = "evaluation"
20
+ SUMMARIZATION = "summarization"
21
+ TOOL_USE = "tool_use"
22
+ DECISION_MAKING = "decision_making"
23
+ FILE_OPERATIONS = "file_operations"
24
+ WEB_SEARCH = "web_search"
25
+ CUSTOM = "custom"
26
+
27
+
28
+ class RetryConfig(BaseModel):
29
+ """Retry configuration for an individual agent."""
30
+
31
+ model_config = ConfigDict(frozen=True)
32
+
33
+ max_retries: int = Field(default=3, ge=0, description="Maximum retry attempts")
34
+ backoff_seconds: float = Field(
35
+ default=1.0, gt=0, description="Initial backoff duration in seconds"
36
+ )
37
+ backoff_multiplier: float = Field(
38
+ default=2.0, ge=1.0, description="Backoff multiplier per retry"
39
+ )
40
+
41
+
42
+ class AgentConfig(BaseModel):
43
+ """Configuration block for agent-specific settings.
44
+
45
+ Holds provider, model, temperature, and arbitrary provider-specific
46
+ parameters that do not belong in the role definition itself.
47
+ """
48
+
49
+ model_config = ConfigDict(frozen=True)
50
+
51
+ provider: str = Field(
52
+ default="openai",
53
+ description="LLM provider identifier (openai, anthropic, ollama, …)",
54
+ )
55
+ model: str = Field(
56
+ default="gpt-4o",
57
+ description="Model name within the provider",
58
+ )
59
+ temperature: float = Field(
60
+ default=0.7, ge=0.0, le=2.0, description="Sampling temperature"
61
+ )
62
+ max_tokens: int | None = Field(
63
+ default=None, ge=1, description="Max tokens to generate"
64
+ )
65
+ extra: dict[str, Any] = Field(
66
+ default_factory=dict,
67
+ description="Arbitrary provider-specific parameters",
68
+ )
69
+
70
+
71
+ class AgentRole(BaseModel):
72
+ """Defines a single agent participant in a loop.
73
+
74
+ Each agent has a unique name, a set of declared capabilities, optional
75
+ input/output schemas, timeout, retry, and provider configuration.
76
+ """
77
+
78
+ model_config = ConfigDict(frozen=True)
79
+
80
+ name: str = Field(
81
+ ..., min_length=1, max_length=128, description="Unique agent identifier"
82
+ )
83
+ description: str = Field(
84
+ default="", max_length=2048, description="Human-readable role description"
85
+ )
86
+ system_prompt: str = Field(
87
+ default="",
88
+ description="System prompt that defines this agent's persona and behaviour",
89
+ )
90
+ capabilities: list[AgentCapability] = Field(
91
+ default_factory=list,
92
+ description="Declared capabilities of this agent",
93
+ )
94
+ input_schema: dict[str, Any] | None = Field(
95
+ default=None,
96
+ description="JSON Schema for the data this agent receives",
97
+ )
98
+ output_schema: dict[str, Any] | None = Field(
99
+ default=None,
100
+ description="JSON Schema for the data this agent produces",
101
+ )
102
+ tools: list[str] = Field(
103
+ default_factory=list,
104
+ description="Names of tools this agent is allowed to use",
105
+ )
106
+ timeout_seconds: float = Field(
107
+ default=300.0,
108
+ gt=0,
109
+ description="Maximum execution time for a single invocation",
110
+ )
111
+ retry: RetryConfig = Field(
112
+ default_factory=RetryConfig,
113
+ description="Retry behaviour on failure",
114
+ )
115
+ config: AgentConfig = Field(
116
+ default_factory=AgentConfig,
117
+ description="LLM provider and model configuration",
118
+ )
119
+ metadata: dict[str, Any] = Field(
120
+ default_factory=dict,
121
+ description="Arbitrary key-value metadata",
122
+ )
123
+
124
+ def has_capability(self, capability: AgentCapability) -> bool:
125
+ """Check whether this agent declares the given capability."""
126
+ return capability in self.capabilities
@@ -0,0 +1,251 @@
1
+ """Loop specification models — the declarative blueprint for a loop."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import StrEnum
6
+ from typing import Any
7
+
8
+ from pydantic import BaseModel, ConfigDict, Field
9
+
10
+ from loopengt.core.models.agent import AgentRole
11
+ from loopengt.core.models.policy import Policy
12
+ from loopengt.core.models.tool import ToolSpec
13
+
14
+
15
+ class LoopPattern(StrEnum):
16
+ """Supported orchestration patterns."""
17
+
18
+ SEQUENTIAL = "sequential"
19
+ SUPERVISOR_WORKER = "supervisor_worker"
20
+ PARALLEL_FAN_OUT = "parallel_fan_out"
21
+ HANDOFF = "handoff"
22
+ EVALUATOR_OPTIMIZER = "evaluator_optimizer"
23
+ CUSTOM = "custom"
24
+
25
+
26
+ class StopConditionType(StrEnum):
27
+ """How a stop condition is evaluated."""
28
+
29
+ MAX_TURNS = "max_turns"
30
+ GOAL_MET = "goal_met"
31
+ QUALITY_THRESHOLD = "quality_threshold"
32
+ TIMEOUT = "timeout"
33
+ MANUAL = "manual"
34
+ CUSTOM = "custom"
35
+
36
+
37
+ class StopCondition(BaseModel):
38
+ """A condition under which the loop should terminate."""
39
+
40
+ model_config = ConfigDict(frozen=True)
41
+
42
+ condition_type: StopConditionType = Field(
43
+ ..., description="Category of stop condition"
44
+ )
45
+ value: Any = Field(
46
+ default=None,
47
+ description=(
48
+ "Threshold or expression. Meaning depends on condition_type: "
49
+ "int for max_turns, float for quality_threshold, str expression "
50
+ "for goal_met / custom."
51
+ ),
52
+ )
53
+ description: str = Field(
54
+ default="", description="Human-readable explanation"
55
+ )
56
+
57
+
58
+ class OutputSchema(BaseModel):
59
+ """Schema describing the expected loop output."""
60
+
61
+ model_config = ConfigDict(frozen=True)
62
+
63
+ schema_def: dict[str, Any] = Field(
64
+ default_factory=dict,
65
+ description="JSON Schema for the loop's final output",
66
+ )
67
+ required_fields: list[str] = Field(
68
+ default_factory=list,
69
+ description="Fields that must be present in the output",
70
+ )
71
+ description: str = Field(
72
+ default="", description="Human-readable output description"
73
+ )
74
+
75
+
76
+ class StepDependency(BaseModel):
77
+ """Declares a dependency between steps."""
78
+
79
+ model_config = ConfigDict(frozen=True)
80
+
81
+ step_name: str = Field(
82
+ ..., description="Name of the step that must complete first"
83
+ )
84
+ condition: str | None = Field(
85
+ default=None,
86
+ description="Optional condition on the dependency step's result",
87
+ )
88
+
89
+
90
+ class StepSpec(BaseModel):
91
+ """Specification for a single step within a loop.
92
+
93
+ Steps bind an agent to a task with optional tool access, dependencies,
94
+ and verification gates.
95
+ """
96
+
97
+ model_config = ConfigDict(frozen=True)
98
+
99
+ name: str = Field(
100
+ ..., min_length=1, max_length=128, description="Unique step identifier"
101
+ )
102
+ description: str = Field(
103
+ default="", max_length=2048, description="What this step accomplishes"
104
+ )
105
+ agent: str = Field(
106
+ ..., description="Name of the agent responsible for this step"
107
+ )
108
+ prompt_template: str = Field(
109
+ default="",
110
+ description=(
111
+ "Prompt template with ``{variable}`` placeholders resolved "
112
+ "from the loop context at execution time"
113
+ ),
114
+ )
115
+ tools: list[str] = Field(
116
+ default_factory=list,
117
+ description="Tool names available during this step",
118
+ )
119
+ dependencies: list[StepDependency] = Field(
120
+ default_factory=list,
121
+ description="Steps that must complete before this one starts",
122
+ )
123
+ verification_gates: list[str] = Field(
124
+ default_factory=list,
125
+ description="Names of verification gates to evaluate after this step",
126
+ )
127
+ timeout_seconds: float | None = Field(
128
+ default=None,
129
+ gt=0,
130
+ description="Per-step timeout override (falls back to policy default)",
131
+ )
132
+ allow_retry: bool = Field(
133
+ default=True, description="Whether this step can be retried on failure"
134
+ )
135
+ metadata: dict[str, Any] = Field(
136
+ default_factory=dict,
137
+ description="Arbitrary step metadata",
138
+ )
139
+
140
+
141
+ class LoopTrigger(BaseModel):
142
+ """Defines how a loop can be triggered."""
143
+
144
+ model_config = ConfigDict(frozen=True)
145
+
146
+ trigger_type: str = Field(
147
+ default="manual",
148
+ description="Trigger type: manual, schedule, webhook, event",
149
+ )
150
+ config: dict[str, Any] = Field(
151
+ default_factory=dict,
152
+ description="Trigger-specific configuration",
153
+ )
154
+
155
+
156
+ class LoopSpec(BaseModel):
157
+ """Top-level declarative specification for an agent loop.
158
+
159
+ A ``LoopSpec`` is the single source of truth for loop configuration.
160
+ It is serializable to/from YAML and JSON, and validated by Pydantic v2.
161
+
162
+ Example YAML::
163
+
164
+ name: code-review
165
+ version: "1.0"
166
+ goal: Review pull request for correctness and style
167
+ pattern: supervisor_worker
168
+ agents:
169
+ - name: reviewer
170
+ capabilities: [code_review]
171
+ steps:
172
+ - name: review
173
+ agent: reviewer
174
+ policy:
175
+ max_turns: 10
176
+ """
177
+
178
+ model_config = ConfigDict(frozen=True)
179
+
180
+ name: str = Field(
181
+ ..., min_length=1, max_length=128, description="Loop identifier"
182
+ )
183
+ version: str = Field(
184
+ default="1.0", description="Semantic version of this spec"
185
+ )
186
+ description: str = Field(
187
+ default="", max_length=4096, description="Human-readable description"
188
+ )
189
+ goal: str = Field(
190
+ ..., min_length=1, description="The objective this loop achieves"
191
+ )
192
+ pattern: LoopPattern = Field(
193
+ default=LoopPattern.SEQUENTIAL,
194
+ description="Orchestration pattern",
195
+ )
196
+ trigger: LoopTrigger = Field(
197
+ default_factory=LoopTrigger,
198
+ description="How this loop is invoked",
199
+ )
200
+ agents: list[AgentRole] = Field(
201
+ ..., min_length=1, description="Agent participants"
202
+ )
203
+ tools: list[ToolSpec] = Field(
204
+ default_factory=list, description="Tools available in this loop"
205
+ )
206
+ steps: list[StepSpec] = Field(
207
+ ..., min_length=1, description="Ordered sequence of execution steps"
208
+ )
209
+ policy: Policy = Field(
210
+ default_factory=Policy,
211
+ description="Execution policy (turns, retries, gates, concurrency)",
212
+ )
213
+ stop_conditions: list[StopCondition] = Field(
214
+ default_factory=list,
215
+ description="Conditions that terminate the loop early",
216
+ )
217
+ output_schema: OutputSchema | None = Field(
218
+ default=None, description="Expected output schema"
219
+ )
220
+ context: dict[str, Any] = Field(
221
+ default_factory=dict,
222
+ description="Initial context / variables injected into the loop",
223
+ )
224
+ metadata: dict[str, Any] = Field(
225
+ default_factory=dict,
226
+ description="Arbitrary spec-level metadata",
227
+ )
228
+
229
+ # ------------------------------------------------------------------
230
+ # Convenience helpers
231
+ # ------------------------------------------------------------------
232
+
233
+ def get_agent(self, name: str) -> AgentRole | None:
234
+ """Look up an agent by name."""
235
+ return next((a for a in self.agents if a.name == name), None)
236
+
237
+ def get_step(self, name: str) -> StepSpec | None:
238
+ """Look up a step by name."""
239
+ return next((s for s in self.steps if s.name == name), None)
240
+
241
+ def get_tool(self, name: str) -> ToolSpec | None:
242
+ """Look up a tool by name."""
243
+ return next((t for t in self.tools if t.name == name), None)
244
+
245
+ def agent_names(self) -> list[str]:
246
+ """Return the list of agent names in definition order."""
247
+ return [a.name for a in self.agents]
248
+
249
+ def step_names(self) -> list[str]:
250
+ """Return the list of step names in definition order."""
251
+ return [s.name for s in self.steps]
@@ -0,0 +1,131 @@
1
+ """Policy, retry, timeout, and stop-condition definitions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import StrEnum
6
+ from typing import Any
7
+
8
+ from pydantic import BaseModel, ConfigDict, Field
9
+
10
+
11
+ class BackoffStrategy(StrEnum):
12
+ """Backoff strategy for retries."""
13
+
14
+ CONSTANT = "constant"
15
+ LINEAR = "linear"
16
+ EXPONENTIAL = "exponential"
17
+
18
+
19
+ class RetryPolicy(BaseModel):
20
+ """Retry policy with configurable backoff."""
21
+
22
+ model_config = ConfigDict(frozen=True)
23
+
24
+ max_retries: int = Field(default=3, ge=0, description="Maximum number of retries")
25
+ initial_delay_seconds: float = Field(
26
+ default=1.0, gt=0, description="Delay before the first retry"
27
+ )
28
+ backoff: BackoffStrategy = Field(
29
+ default=BackoffStrategy.EXPONENTIAL,
30
+ description="Backoff strategy between retries",
31
+ )
32
+ max_delay_seconds: float = Field(
33
+ default=60.0, gt=0, description="Maximum delay cap"
34
+ )
35
+ jitter: bool = Field(
36
+ default=True, description="Add random jitter to delay"
37
+ )
38
+ retryable_errors: list[str] = Field(
39
+ default_factory=lambda: ["TimeoutError", "ConnectionError"],
40
+ description="Error class names eligible for retry",
41
+ )
42
+
43
+ def compute_delay(self, attempt: int) -> float:
44
+ """Compute the delay for a given attempt number (0-indexed).
45
+
46
+ Returns the raw delay *before* jitter is applied.
47
+ """
48
+ if self.backoff == BackoffStrategy.CONSTANT:
49
+ delay = self.initial_delay_seconds
50
+ elif self.backoff == BackoffStrategy.LINEAR:
51
+ delay = self.initial_delay_seconds * (attempt + 1)
52
+ else: # exponential
53
+ delay = self.initial_delay_seconds * (2**attempt)
54
+ return min(delay, self.max_delay_seconds)
55
+
56
+
57
+ class VerificationGate(BaseModel):
58
+ """A gate that must pass before the loop proceeds past a given step.
59
+
60
+ Gates are evaluated after a step completes. If the gate fails and
61
+ ``on_failure`` is ``"retry"``, the step is re-run up to the retry
62
+ budget. If ``"abort"``, the loop is stopped.
63
+ """
64
+
65
+ model_config = ConfigDict(frozen=True)
66
+
67
+ name: str = Field(
68
+ ..., min_length=1, max_length=128, description="Gate identifier"
69
+ )
70
+ description: str = Field(default="", description="What this gate checks")
71
+ condition: str = Field(
72
+ ...,
73
+ description=(
74
+ "A Python expression or JMESPath query evaluated against "
75
+ "the step result. Must evaluate to a truthy value to pass."
76
+ ),
77
+ )
78
+ on_failure: str = Field(
79
+ default="retry",
80
+ description="Action on failure: 'retry' or 'abort'",
81
+ )
82
+ metadata: dict[str, Any] = Field(
83
+ default_factory=dict,
84
+ description="Arbitrary metadata",
85
+ )
86
+
87
+
88
+ class Policy(BaseModel):
89
+ """Top-level policy governing loop execution behaviour.
90
+
91
+ Controls how many turns are allowed, retry budgets, timeouts, and
92
+ verification gates that must pass between steps.
93
+ """
94
+
95
+ model_config = ConfigDict(frozen=True)
96
+
97
+ max_turns: int = Field(
98
+ default=50,
99
+ ge=1,
100
+ description="Hard limit on total loop turns across all agents",
101
+ )
102
+ max_total_time_seconds: float = Field(
103
+ default=3600.0,
104
+ gt=0,
105
+ description="Maximum wall-clock time for the entire loop",
106
+ )
107
+ retry: RetryPolicy = Field(
108
+ default_factory=RetryPolicy,
109
+ description="Default retry policy for steps without their own",
110
+ )
111
+ verification_gates: list[VerificationGate] = Field(
112
+ default_factory=list,
113
+ description="Ordered list of verification gates",
114
+ )
115
+ allow_parallel: bool = Field(
116
+ default=False,
117
+ description="Whether the scheduler may execute independent steps concurrently",
118
+ )
119
+ max_concurrency: int = Field(
120
+ default=4,
121
+ ge=1,
122
+ description="Maximum concurrent steps when parallel execution is enabled",
123
+ )
124
+ fail_fast: bool = Field(
125
+ default=True,
126
+ description="Abort the loop on the first unrecoverable error",
127
+ )
128
+ metadata: dict[str, Any] = Field(
129
+ default_factory=dict,
130
+ description="Arbitrary policy metadata",
131
+ )