pal-runtime 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.
pal/__init__.py ADDED
@@ -0,0 +1,159 @@
1
+ """PAL Python SDK — build production AI agents with durable execution.
2
+
3
+ Quick start::
4
+
5
+ from pal import agent, step
6
+
7
+ @agent("hello-world", model="auto")
8
+ async def hello(input: dict, ctx):
9
+ greeting = await step("greet", lambda: ctx.llm.complete(
10
+ prompt=f"Say hello to {input['name']}",
11
+ capability="chat-small",
12
+ ))
13
+ return {"greeting": greeting}
14
+ """
15
+
16
+ from pal.agent import PalAgent, agent, get_agent, list_agents
17
+ from pal.client import PalClient
18
+ from pal.errors import (
19
+ PalApiError,
20
+ PalError,
21
+ PalLlmError,
22
+ PalPolicyDeniedError,
23
+ PalPolicyVersionConflictError,
24
+ PalStepError,
25
+ PalTimeoutError,
26
+ PalValidationError,
27
+ )
28
+ from pal.step import step, step_map, step_race, step_signal, step_sleep
29
+ from pal.tools import tool
30
+ from pal.types import (
31
+ AgentConfig,
32
+ AgentContext,
33
+ AgentInfo,
34
+ ApprovalRequest,
35
+ AskOptions,
36
+ BuiltContext,
37
+ Capability,
38
+ CompactionConfig,
39
+ ConnectorAction,
40
+ ConnectorInfo,
41
+ ConnectorResult,
42
+ ContextBudget,
43
+ ContextBuildOpts,
44
+ ContextConfig,
45
+ GuardrailConfig,
46
+ IsolationClass,
47
+ IsolationConfig,
48
+ LlmClient,
49
+ LlmJsonOptions,
50
+ LlmOptions,
51
+ LlmStreamOptions,
52
+ MemoryClient,
53
+ MemoryConfig,
54
+ MemoryEntry,
55
+ MemoryTier,
56
+ Message,
57
+ MessageRole,
58
+ NotifyOptions,
59
+ OptimizeTarget,
60
+ OptimizeWeights,
61
+ PrivacyLevel,
62
+ RecallConfig,
63
+ ResourceLimits,
64
+ RetryPolicy,
65
+ RouteStrategy,
66
+ Run,
67
+ RunError,
68
+ RunStatus,
69
+ Session,
70
+ SessionConfig,
71
+ SessionMessage,
72
+ StepOptions,
73
+ StreamEvent,
74
+ StreamEventKind,
75
+ ToolCallMessage,
76
+ ToolClient,
77
+ ToolDef,
78
+ TriggerKind,
79
+ )
80
+
81
+ __version__ = "0.1.0"
82
+
83
+ __all__ = [
84
+ # Agent definition
85
+ "agent",
86
+ "PalAgent",
87
+ "get_agent",
88
+ "list_agents",
89
+ # Step primitives
90
+ "step",
91
+ "step_map",
92
+ "step_race",
93
+ "step_sleep",
94
+ "step_signal",
95
+ # Client
96
+ "PalClient",
97
+ # Tools
98
+ "tool",
99
+ # Errors
100
+ "PalError",
101
+ "PalApiError",
102
+ "PalPolicyDeniedError",
103
+ "PalPolicyVersionConflictError",
104
+ "PalStepError",
105
+ "PalLlmError",
106
+ "PalTimeoutError",
107
+ "PalValidationError",
108
+ # Types — enums
109
+ "Capability",
110
+ "OptimizeTarget",
111
+ "RunStatus",
112
+ "TriggerKind",
113
+ "IsolationClass",
114
+ "MemoryTier",
115
+ "StreamEventKind",
116
+ "MessageRole",
117
+ "PrivacyLevel",
118
+ "RouteStrategy",
119
+ # Types — models
120
+ "AgentConfig",
121
+ "AgentContext",
122
+ "AgentInfo",
123
+ "Message",
124
+ "ToolCallMessage",
125
+ "ToolDef",
126
+ "RetryPolicy",
127
+ "StepOptions",
128
+ "MemoryConfig",
129
+ "ResourceLimits",
130
+ "IsolationConfig",
131
+ "RunError",
132
+ "Run",
133
+ "StreamEvent",
134
+ "MemoryEntry",
135
+ "ApprovalRequest",
136
+ "AskOptions",
137
+ "NotifyOptions",
138
+ "OptimizeWeights",
139
+ "ContextBudget",
140
+ "CompactionConfig",
141
+ "RecallConfig",
142
+ "ContextConfig",
143
+ "ContextBuildOpts",
144
+ "BuiltContext",
145
+ "LlmOptions",
146
+ "LlmJsonOptions",
147
+ "LlmStreamOptions",
148
+ "SessionConfig",
149
+ "GuardrailConfig",
150
+ "Session",
151
+ "SessionMessage",
152
+ "ConnectorAction",
153
+ "ConnectorInfo",
154
+ "ConnectorResult",
155
+ # Types — clients/interfaces
156
+ "LlmClient",
157
+ "ToolClient",
158
+ "MemoryClient",
159
+ ]
pal/agent.py ADDED
@@ -0,0 +1,234 @@
1
+ """Agent definition — decorator and class-based patterns (Google ADK-inspired).
2
+
3
+ Usage (decorator)::
4
+
5
+ @agent("my-agent", model="auto")
6
+ async def my_agent(input: dict, ctx: AgentContext) -> dict:
7
+ result = await step("greet", lambda: ctx.llm.complete(prompt="Hello"))
8
+ return {"greeting": result}
9
+
10
+ Usage (class-based)::
11
+
12
+ class MyAgent(PalAgent):
13
+ name = "my-agent"
14
+ model = "auto"
15
+
16
+ async def run(self, input: dict, ctx: AgentContext) -> dict:
17
+ ...
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import re
23
+ from typing import Any, Awaitable, Callable
24
+
25
+ from pal.errors import PalValidationError
26
+ from pal.types import (
27
+ AgentConfig,
28
+ AgentContext,
29
+ Capability,
30
+ IsolationConfig,
31
+ MemoryConfig,
32
+ ResourceLimits,
33
+ ToolDef,
34
+ )
35
+
36
+ _NAME_RE = re.compile(r"^[a-z0-9-]{1,63}$")
37
+
38
+
39
+ def _validate_name(name: str) -> None:
40
+ if not _NAME_RE.match(name):
41
+ raise PalValidationError(f'Agent name must match [a-z0-9-]{{1,63}}, got: "{name}"')
42
+
43
+
44
+ # ---------------------------------------------------------------------------
45
+ # Registered agent — the object returned by the @agent decorator
46
+ # ---------------------------------------------------------------------------
47
+
48
+
49
+ class RegisteredAgent:
50
+ """Wrapper around a user-defined agent function plus its config.
51
+
52
+ This is the object stored in the module-level registry and loaded by the
53
+ runner at startup.
54
+ """
55
+
56
+ def __init__(
57
+ self,
58
+ config: AgentConfig,
59
+ fn: Callable[[dict[str, Any], AgentContext], Awaitable[Any]],
60
+ ) -> None:
61
+ self.config = config
62
+ self._fn = fn
63
+ # Preserve the original function metadata for introspection
64
+ self.__name__ = getattr(fn, "__name__", config.name)
65
+ self.__doc__ = getattr(fn, "__doc__", None)
66
+
67
+ async def run(self, input: dict[str, Any], ctx: AgentContext) -> Any:
68
+ return await self._fn(input, ctx)
69
+
70
+ def __repr__(self) -> str:
71
+ return f"<RegisteredAgent name={self.config.name!r} version={self.config.version!r}>"
72
+
73
+
74
+ # ---------------------------------------------------------------------------
75
+ # Module-level registry — simple dict mapping name -> RegisteredAgent
76
+ # ---------------------------------------------------------------------------
77
+
78
+ _registry: dict[str, RegisteredAgent] = {}
79
+
80
+
81
+ def get_agent(name: str) -> RegisteredAgent | None:
82
+ """Retrieve a registered agent by name."""
83
+ return _registry.get(name)
84
+
85
+
86
+ def list_agents() -> list[RegisteredAgent]:
87
+ """Return all registered agents."""
88
+ return list(_registry.values())
89
+
90
+
91
+ # ---------------------------------------------------------------------------
92
+ # @agent decorator
93
+ # ---------------------------------------------------------------------------
94
+
95
+
96
+ def agent(
97
+ name: str,
98
+ *,
99
+ version: str = "0.1.0",
100
+ model: Capability | str = "auto",
101
+ description: str = "",
102
+ tools: list[ToolDef] | None = None,
103
+ memory: list[MemoryConfig] | None = None,
104
+ limits: ResourceLimits | dict[str, Any] | None = None,
105
+ isolation: IsolationConfig | dict[str, Any] | None = None,
106
+ labels: dict[str, str] | None = None,
107
+ ) -> Callable[
108
+ [Callable[[dict[str, Any], AgentContext], Awaitable[Any]]],
109
+ RegisteredAgent,
110
+ ]:
111
+ """Decorator that creates and registers a PAL agent.
112
+
113
+ Parameters
114
+ ----------
115
+ name:
116
+ DNS-compatible identifier ([a-z0-9-]{1,63}).
117
+ version:
118
+ Semver string, defaults to ``"0.1.0"``.
119
+ model:
120
+ Default model capability for LLM calls. ``"auto"`` lets the
121
+ model router decide.
122
+ description:
123
+ Human-readable description.
124
+ tools:
125
+ Built-in tool definitions to make available.
126
+ memory:
127
+ Memory configurations for the agent.
128
+ limits:
129
+ Resource limits (CPU, memory, GPU, timeout, cost, tokens, steps).
130
+ isolation:
131
+ Isolation class for the agent's runtime environment.
132
+ labels:
133
+ Arbitrary key-value labels.
134
+
135
+ Returns a :class:`RegisteredAgent` instance that wraps the decorated
136
+ function and holds its :class:`AgentConfig`.
137
+ """
138
+ _validate_name(name)
139
+
140
+ # Coerce convenience dicts into typed models
141
+ if isinstance(limits, dict):
142
+ limits = ResourceLimits(**limits)
143
+ if isinstance(isolation, dict):
144
+ isolation = IsolationConfig(**isolation)
145
+
146
+ config = AgentConfig(
147
+ name=name,
148
+ version=version,
149
+ model=model,
150
+ description=description,
151
+ tools=tools,
152
+ memory=memory,
153
+ limits=limits,
154
+ isolation=isolation,
155
+ labels=labels or {},
156
+ )
157
+
158
+ def decorator(
159
+ fn: Callable[[dict[str, Any], AgentContext], Awaitable[Any]],
160
+ ) -> RegisteredAgent:
161
+ if not callable(fn):
162
+ raise PalValidationError("Agent must wrap a callable")
163
+
164
+ registered = RegisteredAgent(config, fn)
165
+ _registry[name] = registered
166
+ return registered
167
+
168
+ return decorator
169
+
170
+
171
+ # ---------------------------------------------------------------------------
172
+ # Class-based agent (Google ADK style)
173
+ # ---------------------------------------------------------------------------
174
+
175
+
176
+ class PalAgent:
177
+ """Base class for class-based agent definitions.
178
+
179
+ Subclass and override ``run``. Class attributes map to ``AgentConfig``
180
+ fields::
181
+
182
+ class Greeter(PalAgent):
183
+ name = "greeter"
184
+ version = "1.0.0"
185
+ model = "chat-small"
186
+ description = "A friendly greeter"
187
+
188
+ async def run(self, input: dict, ctx: AgentContext) -> dict:
189
+ greeting = await ctx.llm.complete(prompt=f"Hello {input['name']}")
190
+ return {"greeting": greeting}
191
+ """
192
+
193
+ name: str = ""
194
+ version: str = "0.1.0"
195
+ model: Capability | str = "auto"
196
+ description: str = ""
197
+ tools: list[ToolDef] | None = None
198
+ memory: list[MemoryConfig] | None = None
199
+ limits: ResourceLimits | None = None
200
+ isolation: IsolationConfig | None = None
201
+ labels: dict[str, str] = {}
202
+
203
+ def __init_subclass__(cls, **kwargs: Any) -> None:
204
+ super().__init_subclass__(**kwargs)
205
+ # Only register concrete subclasses that define a name
206
+ if cls.name:
207
+ _validate_name(cls.name)
208
+ config = AgentConfig(
209
+ name=cls.name,
210
+ version=cls.version,
211
+ model=cls.model,
212
+ description=cls.description,
213
+ tools=cls.tools,
214
+ memory=cls.memory,
215
+ limits=cls.limits,
216
+ isolation=cls.isolation,
217
+ labels=cls.labels or {},
218
+ )
219
+
220
+ # Create a RegisteredAgent that delegates to an instance of the class
221
+ instance = cls()
222
+ registered = RegisteredAgent(config, instance._dispatch)
223
+ _registry[cls.name] = registered
224
+ # Store a reference so the class can be used directly too
225
+ cls._registered = registered # type: ignore[attr-defined]
226
+
227
+ async def _dispatch(self, input: dict[str, Any], ctx: AgentContext) -> Any:
228
+ return await self.run(input, ctx)
229
+
230
+ async def run(self, input: dict[str, Any], ctx: AgentContext) -> Any:
231
+ raise NotImplementedError("Subclass must implement run()")
232
+
233
+ async def init(self) -> None:
234
+ """Optional init hook called once before the first run."""