maque-core 1.3.4__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 maque-core might be problematic. Click here for more details.

maque_core/__init__.py ADDED
@@ -0,0 +1,28 @@
1
+ """maque_core — a lightweight, extensible Agent framework."""
2
+
3
+ from .contracts.llm import LLMProvider
4
+ from .contracts.messages import LLMResponse, Message, Role, ToolCall
5
+ from .contracts.tool import ToolSpec
6
+ from .factory import create_agent
7
+ from .kernel import Agent, AgentRunError
8
+ from .llm import MockLLM, OpenAICompatibleProvider
9
+ from .tools import ToolRegistry, tool
10
+
11
+ __version__ = "1.3.4"
12
+
13
+ __all__ = [
14
+ "create_agent",
15
+ "Agent",
16
+ "AgentRunError",
17
+ "tool",
18
+ "LLMProvider",
19
+ "Message",
20
+ "Role",
21
+ "ToolCall",
22
+ "LLMResponse",
23
+ "ToolSpec",
24
+ "ToolRegistry",
25
+ "OpenAICompatibleProvider",
26
+ "MockLLM",
27
+ "__version__",
28
+ ]
maque_core/config.py ADDED
@@ -0,0 +1,80 @@
1
+ """Configuration for the agent framework.
2
+
3
+ All settings can be overridden via environment variables with the
4
+ ``MAQUE_`` prefix, e.g. ``MAQUE_LLM_MODEL=qwen3-turbo``.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from dataclasses import dataclass, field
11
+ from typing import Optional
12
+
13
+
14
+ @dataclass
15
+ class LLMConfig:
16
+ """LLM backend configuration."""
17
+
18
+ model: str = "qwen-turbo"
19
+ base_url: str = "https://dashscope.aliyuncs.com/compatible-mode/v1"
20
+ api_key: str = ""
21
+ temperature: float = 0.7
22
+ timeout: int = 60
23
+ max_tokens: int = 2048
24
+
25
+ @classmethod
26
+ def from_env(cls) -> "LLMConfig":
27
+ return cls(
28
+ model=os.environ.get("MAQUE_LLM_MODEL", cls.model),
29
+ base_url=os.environ.get("MAQUE_LLM_BASE_URL", cls.base_url),
30
+ api_key=os.environ.get("MAQUE_LLM_API_KEY", cls.api_key),
31
+ temperature=float(os.environ.get("MAQUE_LLM_TEMPERATURE", cls.temperature)),
32
+ timeout=int(os.environ.get("MAQUE_LLM_TIMEOUT", cls.timeout)),
33
+ max_tokens=int(os.environ.get("MAQUE_LLM_MAX_TOKENS", cls.max_tokens)),
34
+ )
35
+
36
+
37
+ @dataclass
38
+ class AgentConfig:
39
+ """Agent runtime configuration."""
40
+
41
+ system_prompt: str = "You are a helpful assistant with access to tools. Use tools when needed to answer questions accurately."
42
+ max_iterations: int = 10
43
+ max_tool_calls: Optional[int] = None
44
+ verbose: bool = False
45
+ max_history_turns: int = 20
46
+ include_reasoning_in_history: bool = True
47
+
48
+ @classmethod
49
+ def from_env(cls) -> "AgentConfig":
50
+ max_tool_calls_str = os.environ.get("MAQUE_MAX_TOOL_CALLS", "")
51
+ max_tool_calls = int(max_tool_calls_str) if max_tool_calls_str else None
52
+ max_history_str = os.environ.get("MAQUE_MAX_HISTORY_TURNS", "")
53
+ max_history_turns = int(max_history_str) if max_history_str else cls.max_history_turns
54
+ return cls(
55
+ system_prompt=os.environ.get("MAQUE_SYSTEM_PROMPT", cls.system_prompt),
56
+ max_iterations=int(os.environ.get("MAQUE_MAX_ITERATIONS", cls.max_iterations)),
57
+ max_tool_calls=max_tool_calls,
58
+ verbose=os.environ.get("MAQUE_VERBOSE", "").lower() in ("1", "true", "yes"),
59
+ max_history_turns=max_history_turns,
60
+ include_reasoning_in_history=os.environ.get(
61
+ "MAQUE_INCLUDE_REASONING_IN_HISTORY", "true"
62
+ ).lower() in ("1", "true", "yes"),
63
+ )
64
+
65
+
66
+ @dataclass
67
+ class AppConfig:
68
+ """Top-level application configuration."""
69
+
70
+ llm: LLMConfig = field(default_factory=LLMConfig)
71
+ agent: AgentConfig = field(default_factory=AgentConfig)
72
+ tools_package: str = "maque_core.tools.impl"
73
+
74
+ @classmethod
75
+ def from_env(cls) -> "AppConfig":
76
+ return cls(
77
+ llm=LLMConfig.from_env(),
78
+ agent=AgentConfig.from_env(),
79
+ tools_package=os.environ.get("MAQUE_TOOLS_PACKAGE", cls.tools_package),
80
+ )
@@ -0,0 +1,17 @@
1
+ """Core contracts (interfaces and data structures) for the agent framework."""
2
+
3
+ from .messages import Role, ToolCall, Message, LLMResponse, Observation
4
+ from .llm import LLMProvider
5
+ from .tool import ToolSpec, ToolRegistry, CallBudget
6
+
7
+ __all__ = [
8
+ "Role",
9
+ "ToolCall",
10
+ "Message",
11
+ "LLMResponse",
12
+ "Observation",
13
+ "LLMProvider",
14
+ "ToolSpec",
15
+ "ToolRegistry",
16
+ "CallBudget",
17
+ ]
@@ -0,0 +1,47 @@
1
+ """LLM provider protocol — the interface every LLM backend must implement.
2
+
3
+ Using a Protocol (structural subtyping) means providers don't need to
4
+ inherit from a base class — they just need the right method signature.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING, Any, List, Optional, Protocol, runtime_checkable
10
+
11
+ if TYPE_CHECKING:
12
+ from .messages import LLMResponse, Message, ToolSpec
13
+
14
+
15
+ @runtime_checkable
16
+ class LLMProvider(Protocol):
17
+ """Protocol for LLM backends.
18
+
19
+ Any class with a compatible ``chat`` method is automatically an
20
+ LLMProvider — no explicit inheritance required.
21
+ """
22
+
23
+ def chat(
24
+ self,
25
+ messages: List["Message"],
26
+ tools: Optional[List["ToolSpec"]] = None,
27
+ max_tokens: Optional[int] = None,
28
+ raise_on_error: bool = False,
29
+ **kwargs: Any,
30
+ ) -> "LLMResponse":
31
+ """Send a chat completion request and return the response.
32
+
33
+ Args:
34
+ messages: The conversation history.
35
+ tools: Optional list of tool specs the model may call.
36
+ max_tokens: Maximum number of tokens to generate. ``None``
37
+ means "use the provider's configured default".
38
+ raise_on_error: When ``False`` (default), API failures are
39
+ returned as an :class:`LLMResponse` with ``error`` set.
40
+ When ``True``, they are raised as an
41
+ :class:`~maque_core.llm.errors.LLMError` subclass instead.
42
+ **kwargs: Backend-specific parameters (temperature, model, etc.).
43
+
44
+ Returns:
45
+ An LLMResponse with content and/or tool_calls.
46
+ """
47
+ ...
@@ -0,0 +1,264 @@
1
+ """Core message data structures for agent communication.
2
+
3
+ Defines the universal message types used across all layers:
4
+ - Role: who sent a message
5
+ - ToolCall: a requested tool invocation
6
+ - Message: a chat message (system / user / assistant / tool)
7
+ - LLMResponse: what the LLM returns
8
+ - Observation: the result of a tool call
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ from dataclasses import dataclass, field
15
+ from enum import Enum
16
+ from typing import Any, Dict, List, Optional
17
+
18
+
19
+ class Role(str, Enum):
20
+ """Message role — who sent the message."""
21
+
22
+ SYSTEM = "system"
23
+ USER = "user"
24
+ ASSISTANT = "assistant"
25
+ TOOL = "tool"
26
+
27
+ def __str__(self) -> str:
28
+ return self.value
29
+
30
+
31
+ @dataclass
32
+ class ToolCall:
33
+ """A single tool invocation requested by the model.
34
+
35
+ Attributes:
36
+ id: Unique identifier for this tool call (used to match results).
37
+ name: Name of the tool to invoke.
38
+ arguments: Dict of argument name → value to pass to the tool.
39
+ """
40
+
41
+ id: str
42
+ name: str
43
+ arguments: Dict[str, Any] = field(default_factory=dict)
44
+
45
+ def to_dict(self) -> Dict[str, Any]:
46
+ return {
47
+ "id": self.id,
48
+ "name": self.name,
49
+ "arguments": self.arguments,
50
+ }
51
+
52
+ @classmethod
53
+ def from_dict(cls, data: Dict[str, Any]) -> "ToolCall":
54
+ return cls(
55
+ id=data["id"],
56
+ name=data["name"],
57
+ arguments=data.get("arguments", {}),
58
+ )
59
+
60
+
61
+ @dataclass
62
+ class Message:
63
+ """A single chat message.
64
+
65
+ Use factory methods for readability:
66
+ Message.system("You are helpful.")
67
+ Message.user("Hello")
68
+ Message.assistant("Hi there!", tool_calls=[...])
69
+ Message.tool_result("call_1", "echo", "output text")
70
+
71
+ Attributes:
72
+ reasoning: Optional reasoning / chain-of-thought content
73
+ associated with this message. Used for multi-turn
74
+ conversations so that subsequent rounds can see the
75
+ model's previous reasoning basis. None when the message
76
+ has no reasoning (system / user / tool messages, or
77
+ when ``include_reasoning_in_history`` is False).
78
+ """
79
+
80
+ role: Role
81
+ content: str = ""
82
+ tool_calls: List[ToolCall] = field(default_factory=list)
83
+ tool_call_id: Optional[str] = None
84
+ tool_name: Optional[str] = None
85
+ reasoning: Optional[str] = None
86
+
87
+ # ---- factory methods ----
88
+
89
+ @classmethod
90
+ def system(cls, content: str) -> "Message":
91
+ return cls(role=Role.SYSTEM, content=content)
92
+
93
+ @classmethod
94
+ def user(cls, content: str) -> "Message":
95
+ return cls(role=Role.USER, content=content)
96
+
97
+ @classmethod
98
+ def assistant(
99
+ cls,
100
+ content: str = "",
101
+ tool_calls: Optional[List[ToolCall]] = None,
102
+ ) -> "Message":
103
+ return cls(
104
+ role=Role.ASSISTANT,
105
+ content=content,
106
+ tool_calls=tool_calls or [],
107
+ )
108
+
109
+ @classmethod
110
+ def tool_result(
111
+ cls,
112
+ tool_call_id: str,
113
+ tool_name: str,
114
+ content: str,
115
+ ) -> "Message":
116
+ return cls(
117
+ role=Role.TOOL,
118
+ content=content,
119
+ tool_call_id=tool_call_id,
120
+ tool_name=tool_name,
121
+ )
122
+
123
+ # ---- serialization ----
124
+
125
+ def to_dict(self) -> Dict[str, Any]:
126
+ data: Dict[str, Any] = {"role": str(self.role), "content": self.content}
127
+ if self.tool_calls:
128
+ data["tool_calls"] = [tc.to_dict() for tc in self.tool_calls]
129
+ if self.tool_call_id is not None:
130
+ data["tool_call_id"] = self.tool_call_id
131
+ if self.tool_name is not None:
132
+ data["name"] = self.tool_name
133
+ if self.reasoning is not None:
134
+ data["reasoning"] = self.reasoning
135
+ return data
136
+
137
+ @classmethod
138
+ def from_dict(cls, data: Dict[str, Any]) -> "Message":
139
+ role = Role(data["role"])
140
+ tool_calls = [
141
+ ToolCall.from_dict(tc) for tc in data.get("tool_calls", [])
142
+ ]
143
+ return cls(
144
+ role=role,
145
+ content=data.get("content", ""),
146
+ tool_calls=tool_calls,
147
+ tool_call_id=data.get("tool_call_id"),
148
+ tool_name=data.get("name"),
149
+ reasoning=data.get("reasoning"),
150
+ )
151
+
152
+
153
+ @dataclass
154
+ class LLMResponse:
155
+ """The result of an LLM chat completion call.
156
+
157
+ Attributes:
158
+ content: The text response (may be empty if only tool calls).
159
+ When the call failed and the adapter returned instead of
160
+ raising, ``content`` holds the human-readable error text.
161
+ reasoning: The model's internal reasoning / chain-of-thought.
162
+ None if the model did not produce reasoning content.
163
+ tool_calls: Tool invocations requested by the model.
164
+ model: Name of the model that generated the response.
165
+ usage: Optional token usage info.
166
+ finish_reason: Why the model stopped generating. Passed through
167
+ from the API verbatim (``stop`` / ``length`` / ``tool_calls`` /
168
+ ``content_filter`` ...). ``"length"`` means the answer was
169
+ truncated by the token budget — it is NOT a complete answer.
170
+ None when the provider did not report one (e.g. on failure).
171
+ error: Error description when the call failed. ``None`` means the
172
+ response is a genuine model output. When set, ``content`` also
173
+ carries the message so it can still be displayed, but callers
174
+ MUST NOT treat it as an answer — check this field, or
175
+ ``response.is_error``.
176
+ """
177
+
178
+ content: str = ""
179
+ reasoning: Optional[str] = None
180
+ tool_calls: List[ToolCall] = field(default_factory=list)
181
+ model: str = ""
182
+ usage: Optional[Dict[str, int]] = None
183
+ finish_reason: Optional[str] = None
184
+ error: Optional[str] = None
185
+
186
+ @property
187
+ def is_error(self) -> bool:
188
+ """True when this response represents a failed LLM call."""
189
+ return self.error is not None
190
+
191
+ @property
192
+ def is_truncated(self) -> bool:
193
+ """True when the model stopped because it hit the token limit."""
194
+ return self.finish_reason == "length"
195
+
196
+ def to_dict(self) -> Dict[str, Any]:
197
+ data = {
198
+ "content": self.content,
199
+ "reasoning": self.reasoning,
200
+ "tool_calls": [tc.to_dict() for tc in self.tool_calls],
201
+ "model": self.model,
202
+ "usage": self.usage,
203
+ "finish_reason": self.finish_reason,
204
+ "error": self.error,
205
+ }
206
+ return data
207
+
208
+ @classmethod
209
+ def from_dict(cls, data: Dict[str, Any]) -> "LLMResponse":
210
+ return cls(
211
+ content=data.get("content", ""),
212
+ reasoning=data.get("reasoning"),
213
+ tool_calls=[ToolCall.from_dict(tc) for tc in data.get("tool_calls", [])],
214
+ model=data.get("model", ""),
215
+ usage=data.get("usage"),
216
+ finish_reason=data.get("finish_reason"),
217
+ error=data.get("error"),
218
+ )
219
+
220
+
221
+ @dataclass
222
+ class Observation:
223
+ """The result of executing a tool call.
224
+
225
+ Attributes:
226
+ tool_call_id: Matches the id of the originating ToolCall.
227
+ tool_name: Name of the tool that was executed.
228
+ content: String output of the tool.
229
+ is_error: Whether the tool call resulted in an error.
230
+ metadata: Extra info like execution duration, etc.
231
+ """
232
+
233
+ tool_call_id: str
234
+ tool_name: str
235
+ content: str
236
+ is_error: bool = False
237
+ metadata: Dict[str, Any] = field(default_factory=dict)
238
+
239
+ def to_dict(self) -> Dict[str, Any]:
240
+ return {
241
+ "tool_call_id": self.tool_call_id,
242
+ "tool_name": self.tool_name,
243
+ "content": self.content,
244
+ "is_error": self.is_error,
245
+ "metadata": self.metadata,
246
+ }
247
+
248
+ @classmethod
249
+ def from_dict(cls, data: Dict[str, Any]) -> "Observation":
250
+ return cls(
251
+ tool_call_id=data["tool_call_id"],
252
+ tool_name=data["tool_name"],
253
+ content=data["content"],
254
+ is_error=data.get("is_error", False),
255
+ metadata=data.get("metadata", {}),
256
+ )
257
+
258
+ def to_message(self) -> Message:
259
+ """Convert to a tool-role Message for feeding back to the LLM."""
260
+ return Message.tool_result(
261
+ tool_call_id=self.tool_call_id,
262
+ tool_name=self.tool_name,
263
+ content=self.content,
264
+ )
@@ -0,0 +1,121 @@
1
+ """Tool-related contracts: ToolSpec, ToolRegistry protocol, and CallBudget.
2
+
3
+ ToolSpec describes a tool's interface (name, description, parameters).
4
+ ToolRegistry is the protocol for registering and executing tools.
5
+ CallBudget tracks how many tool calls remain in a session.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Protocol, runtime_checkable
12
+
13
+ if TYPE_CHECKING:
14
+ from .messages import Observation
15
+
16
+
17
+ @dataclass
18
+ class ToolSpec:
19
+ """Specification of a tool's interface.
20
+
21
+ Attributes:
22
+ name: Unique tool name.
23
+ description: Human-readable description for the LLM.
24
+ parameters: JSON Schema dict describing the tool's arguments.
25
+ func: The callable that implements the tool.
26
+ """
27
+
28
+ name: str
29
+ description: str
30
+ parameters: Dict[str, Any] = field(default_factory=dict)
31
+ func: Any = None # callable, stored for execution
32
+
33
+ def to_openai_schema(self) -> Dict[str, Any]:
34
+ """Convert to OpenAI-compatible tool schema format."""
35
+ return {
36
+ "type": "function",
37
+ "function": {
38
+ "name": self.name,
39
+ "description": self.description,
40
+ "parameters": self.parameters,
41
+ },
42
+ }
43
+
44
+
45
+ @runtime_checkable
46
+ class ToolRegistry(Protocol):
47
+ """Protocol for tool registries.
48
+
49
+ A registry knows about available tools, can return their schemas,
50
+ and can execute a tool call by name.
51
+ """
52
+
53
+ def register(self, spec: ToolSpec) -> None:
54
+ """Register a tool spec."""
55
+ ...
56
+
57
+ def get_schemas(self) -> List[Dict[str, Any]]:
58
+ """Return OpenAI-compatible schemas for all registered tools."""
59
+ ...
60
+
61
+ def execute(
62
+ self,
63
+ tool_name: str,
64
+ arguments: Dict[str, Any],
65
+ tool_call_id: str = "",
66
+ ) -> "Observation":
67
+ """Execute a tool by name with the given arguments.
68
+
69
+ Returns:
70
+ An Observation with the result (or error).
71
+ """
72
+ ...
73
+
74
+ @property
75
+ def tool_names(self) -> List[str]:
76
+ """Return list of registered tool names."""
77
+ ...
78
+
79
+ def __len__(self) -> int:
80
+ """Number of registered tools."""
81
+ ...
82
+
83
+
84
+ @dataclass
85
+ class CallBudget:
86
+ """Tracks remaining tool call budget.
87
+
88
+ Attributes:
89
+ max_calls: Maximum number of tool calls allowed. None = unlimited.
90
+ used: Number of tool calls already consumed.
91
+ """
92
+
93
+ max_calls: Optional[int] = None
94
+ used: int = 0
95
+
96
+ def consume(self, count: int = 1) -> bool:
97
+ """Consume ``count`` calls. Returns True if within budget, False if exhausted."""
98
+ if self.max_calls is None:
99
+ self.used += count
100
+ return True
101
+ if self.used + count > self.max_calls:
102
+ # Still count the attempted calls so ``exhausted`` stays True
103
+ # but we report that we're over budget
104
+ self.used += count
105
+ return False
106
+ self.used += count
107
+ return True
108
+
109
+ @property
110
+ def exhausted(self) -> bool:
111
+ """Whether the budget has been fully consumed."""
112
+ if self.max_calls is None:
113
+ return False
114
+ return self.used >= self.max_calls
115
+
116
+ @property
117
+ def remaining(self) -> Optional[int]:
118
+ """Number of remaining calls, or None if unlimited."""
119
+ if self.max_calls is None:
120
+ return None
121
+ return max(0, self.max_calls - self.used)
maque_core/factory.py ADDED
@@ -0,0 +1,105 @@
1
+ """Agent factory — convenient entry point for creating agents.
2
+
3
+ Provides ``create_agent`` which builds an :class:`Agent` from
4
+ explicit parameters, environment variables (via ``AppConfig.from_env``),
5
+ or a pre-built LLM provider.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from .config import AppConfig
11
+ from .kernel.agent import Agent
12
+ from .llm.openai_compatible import OpenAICompatibleProvider
13
+ from .tools.discovery import discover_tools
14
+ from .tools.registry import ToolRegistry
15
+
16
+
17
+ def create_agent(
18
+ *,
19
+ model: str | None = None,
20
+ base_url: str | None = None,
21
+ api_key: str | None = None,
22
+ system_prompt: str | None = None,
23
+ max_iterations: int | None = None,
24
+ max_tool_calls: int | None = None,
25
+ max_history_turns: int | None = None,
26
+ include_reasoning_in_history: bool | None = None,
27
+ tools_package: str | list[str] | None = None,
28
+ llm=None,
29
+ ) -> Agent:
30
+ """Create an Agent from explicit parameters and environment defaults.
31
+
32
+ Parameters
33
+ ----------
34
+ model:
35
+ Model name. Overrides the environment default when provided.
36
+ base_url:
37
+ Base URL for the OpenAI-compatible endpoint.
38
+ api_key:
39
+ API key for the LLM endpoint.
40
+ system_prompt:
41
+ System prompt to prepend to the message history.
42
+ max_iterations:
43
+ Maximum ReAct iterations per ``ask()`` call.
44
+ max_tool_calls:
45
+ Maximum tool calls per ``ask()`` call.
46
+ max_history_turns:
47
+ Maximum conversation turns to keep in history.
48
+ include_reasoning_in_history:
49
+ Whether to include reasoning content in the message history.
50
+ tools_package:
51
+ Package name(s) to scan for ``@tool``-decorated functions.
52
+ ``None`` means no tools are loaded by default.
53
+ A string loads one package; a list loads multiple packages
54
+ into the same registry.
55
+ llm:
56
+ Pre-built LLM provider. When provided, ``model``/``base_url``/
57
+ ``api_key`` are ignored and this provider is used directly.
58
+
59
+ Returns
60
+ -------
61
+ Agent
62
+ A configured Agent instance ready to use.
63
+ """
64
+ config = AppConfig.from_env()
65
+
66
+ # LLM
67
+ if llm is None:
68
+ llm = OpenAICompatibleProvider(
69
+ model=model if model is not None else config.llm.model,
70
+ base_url=base_url if base_url is not None else config.llm.base_url,
71
+ api_key=api_key if api_key is not None else config.llm.api_key,
72
+ temperature=config.llm.temperature,
73
+ timeout=config.llm.timeout,
74
+ max_tokens=config.llm.max_tokens,
75
+ )
76
+
77
+ # Tools
78
+ registry = ToolRegistry()
79
+ if tools_package is not None:
80
+ if isinstance(tools_package, str):
81
+ discover_tools(registry, tools_package)
82
+ else:
83
+ for pkg in tools_package:
84
+ discover_tools(registry, pkg)
85
+
86
+ # Config overrides
87
+ resolved_system_prompt = system_prompt if system_prompt is not None else config.agent.system_prompt
88
+ resolved_max_iterations = max_iterations if max_iterations is not None else config.agent.max_iterations
89
+ resolved_max_tool_calls = max_tool_calls if max_tool_calls is not None else config.agent.max_tool_calls
90
+ resolved_max_history = max_history_turns if max_history_turns is not None else config.agent.max_history_turns
91
+ resolved_include_reasoning = (
92
+ include_reasoning_in_history
93
+ if include_reasoning_in_history is not None
94
+ else config.agent.include_reasoning_in_history
95
+ )
96
+
97
+ return Agent(
98
+ llm=llm,
99
+ registry=registry,
100
+ system_prompt=resolved_system_prompt,
101
+ max_iterations=resolved_max_iterations,
102
+ max_tool_calls=resolved_max_tool_calls,
103
+ max_history_turns=resolved_max_history,
104
+ include_reasoning_in_history=resolved_include_reasoning,
105
+ )
@@ -0,0 +1,8 @@
1
+ """Kernel layer — the agent's reasoning and acting loop."""
2
+
3
+ from .agent import Agent
4
+ from .budget import RunBudget
5
+ from .errors import AgentRunError
6
+ from .loop import react_loop
7
+
8
+ __all__ = ["Agent", "AgentRunError", "RunBudget", "react_loop"]