fifty-agent-sdk 1.1.0__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 (49) hide show
  1. fifty_agent_sdk-1.1.0/LICENSE +21 -0
  2. fifty_agent_sdk-1.1.0/PKG-INFO +225 -0
  3. fifty_agent_sdk-1.1.0/README.md +181 -0
  4. fifty_agent_sdk-1.1.0/pyproject.toml +101 -0
  5. fifty_agent_sdk-1.1.0/setup.cfg +4 -0
  6. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/__init__.py +191 -0
  7. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/audit/__init__.py +45 -0
  8. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/audit/console.py +69 -0
  9. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/audit/protocol.py +112 -0
  10. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/audit/sql.py +328 -0
  11. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/errors.py +119 -0
  12. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/llm/__init__.py +30 -0
  13. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/llm/openai_compat.py +345 -0
  14. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/llm/protocol.py +87 -0
  15. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/llm/types.py +149 -0
  16. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/loop.py +836 -0
  17. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/mcp/__init__.py +39 -0
  18. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/mcp/client.py +622 -0
  19. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/mcp/transport.py +177 -0
  20. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/observability/__init__.py +18 -0
  21. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/observability/hooks.py +232 -0
  22. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/parser/__init__.py +32 -0
  23. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/parser/base.py +118 -0
  24. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/parser/json_mode.py +219 -0
  25. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/parser/native_tools.py +93 -0
  26. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/parser/prose_mode.py +162 -0
  27. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/prompts.py +186 -0
  28. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/py.typed +0 -0
  29. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/runner.py +797 -0
  30. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/safety.py +134 -0
  31. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/state/__init__.py +53 -0
  32. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/state/memory.py +156 -0
  33. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/state/protocol.py +112 -0
  34. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/state/redis.py +371 -0
  35. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/state/sql.py +688 -0
  36. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/streaming.py +273 -0
  37. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/tools/__init__.py +32 -0
  38. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/tools/inproc_provider.py +232 -0
  39. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/tools/mcp_provider.py +345 -0
  40. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/tools/protocol.py +124 -0
  41. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk/tools/registry.py +165 -0
  42. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk.egg-info/PKG-INFO +225 -0
  43. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk.egg-info/SOURCES.txt +47 -0
  44. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk.egg-info/dependency_links.txt +1 -0
  45. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk.egg-info/requires.txt +21 -0
  46. fifty_agent_sdk-1.1.0/src/fifty_agent_sdk.egg-info/top_level.txt +1 -0
  47. fifty_agent_sdk-1.1.0/tests/test_e2e_example.py +144 -0
  48. fifty_agent_sdk-1.1.0/tests/test_errors.py +101 -0
  49. fifty_agent_sdk-1.1.0/tests/test_prompts.py +156 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 fifty.dev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,225 @@
1
+ Metadata-Version: 2.4
2
+ Name: fifty-agent-sdk
3
+ Version: 1.1.0
4
+ Summary: Production-grade reusable agent loop SDK — custom ReACT, JSON-mode tool calls, MCP client, pluggable LLM/state/tools.
5
+ Author: fifty.dev
6
+ Maintainer: fifty.dev
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/fiftynotai/fifty-agent-sdk
9
+ Project-URL: Repository, https://github.com/fiftynotai/fifty-agent-sdk
10
+ Project-URL: Issues, https://github.com/fiftynotai/fifty-agent-sdk/issues
11
+ Project-URL: Changelog, https://github.com/fiftynotai/fifty-agent-sdk/blob/main/CHANGELOG.md
12
+ Keywords: agent,llm,react,mcp,tool-calling,openai,ai,sdk
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.11
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: pydantic>=2.5.0
26
+ Requires-Dist: httpx>=0.26.0
27
+ Requires-Dist: structlog>=24.1.0
28
+ Requires-Dist: openai<3.0.0,>=1.30.0
29
+ Requires-Dist: mcp<2.0.0,>=1.27.0
30
+ Provides-Extra: sql
31
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.0; extra == "sql"
32
+ Provides-Extra: redis
33
+ Requires-Dist: redis>=5.0.0; extra == "redis"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=7.4.0; extra == "dev"
36
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
37
+ Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
38
+ Requires-Dist: pytest-httpx>=0.30.0; extra == "dev"
39
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
40
+ Requires-Dist: mypy>=1.8.0; extra == "dev"
41
+ Requires-Dist: aiosqlite>=0.19.0; extra == "dev"
42
+ Requires-Dist: fakeredis>=2.20.0; extra == "dev"
43
+ Dynamic: license-file
44
+
45
+ <p align="center">
46
+ <img src=".github/banner.png" alt="fifty-agent-sdk — an embeddable ReACT loop. any endpoint. no infra." width="100%">
47
+ </p>
48
+
49
+ # fifty-agent-sdk
50
+
51
+ [![PyPI](https://img.shields.io/pypi/v/fifty-agent-sdk)](https://pypi.org/project/fifty-agent-sdk/)
52
+ [![Python](https://img.shields.io/pypi/pyversions/fifty-agent-sdk)](https://pypi.org/project/fifty-agent-sdk/)
53
+ [![CI](https://github.com/fiftynotai/fifty-agent-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/fiftynotai/fifty-agent-sdk/actions/workflows/ci.yml)
54
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
55
+
56
+ an embeddable ReACT agent loop you drop into your own service. it talks to
57
+ any OpenAI-compatible Chat Completions endpoint — OpenAI, Google Distributed
58
+ Cloud, a local OSS server — by changing one `base_url`. transport-free,
59
+ infra-free by default, and pluggable behind protocols the whole way down.
60
+
61
+ ## At a glance
62
+
63
+ - endpoint-agnostic — one `base_url` points the loop at OpenAI, GDC, or a local OSS server; the loop doesn't care which.
64
+ - pluggable tools — `@tool` derives a JSON Schema from type hints; MCP discovery adds remote tools to the same registry.
65
+ - pluggable state — conversation state lives behind a `StateStore` protocol: in-memory, SQL, or Redis.
66
+ - typed event stream — every ReACT step emits exactly one `AgentEvent`; every run ends with exactly one `FinalEvent`.
67
+ - safety caps — iteration ceiling, per-tool timeouts, and a fallback answer on error or cap.
68
+ - zero-infra default — memory backends ship in core; run an agent with no extra dependency.
69
+
70
+ ## Installation
71
+
72
+ ```
73
+ pip install fifty-agent-sdk
74
+ ```
75
+
76
+ The SDK requires Python 3.11 or newer. The core install ships with the
77
+ in-memory backends, so an agent can run with no infrastructure dependency.
78
+
79
+ Two optional extras add durable backends:
80
+
81
+ - `pip install 'fifty-agent-sdk[sql]'` — pulls SQLAlchemy and enables
82
+ `SqlStateStore` (durable conversation state) and `SqlAuditSink` (durable
83
+ audit log).
84
+ - `pip install 'fifty-agent-sdk[redis]'` — pulls redis-py and enables
85
+ `RedisStateStore` (Redis-backed conversation state).
86
+
87
+ Importing `fifty_agent_sdk` itself pulls neither SQLAlchemy nor redis-py. The
88
+ extra symbols are re-exported lazily; first access to one without the
89
+ relevant extra installed raises a clear `ImportError`.
90
+
91
+ ## Quickstart
92
+
93
+ A complete agent fits in a handful of lines. The example below defines a
94
+ tool, wires the loop and runner, and drains the event stream:
95
+
96
+ ```python
97
+ import asyncio
98
+ from typing import Any
99
+
100
+ from fifty_agent_sdk import (
101
+ JSON_MODE_OUTPUT_FORMAT,
102
+ AgentLoop,
103
+ AgentRunner,
104
+ JsonModeParser,
105
+ MemoryStateStore,
106
+ OpenAICompatibleClient,
107
+ PromptSections,
108
+ Registry,
109
+ SafetyConfig,
110
+ tool,
111
+ )
112
+
113
+
114
+ @tool()
115
+ async def get_weather(city: str) -> dict[str, Any]:
116
+ """Return the current weather for a city."""
117
+ return {"city": city, "temp_c": 21}
118
+
119
+
120
+ async def main() -> None:
121
+ # 1. An LLM client — points at any OpenAI-compatible endpoint.
122
+ llm = OpenAICompatibleClient(api_key="sk-...")
123
+
124
+ # 2. A tool registry — register the decorated tool.
125
+ registry = Registry()
126
+ registry.register(get_weather)
127
+
128
+ # 3. The ReACT loop — LLM + registry + parser + prompts + safety.
129
+ # `output_format` shows the model the JSON envelope the parser
130
+ # expects; without it JsonModeParser raises ParserError on every turn.
131
+ loop = AgentLoop(
132
+ llm=llm,
133
+ registry=registry,
134
+ parser=JsonModeParser(),
135
+ prompts=PromptSections(persona="You are helpful."),
136
+ safety=SafetyConfig(),
137
+ model="gpt-4o",
138
+ output_format=JSON_MODE_OUTPUT_FORMAT,
139
+ )
140
+
141
+ # 4. The runner — wraps the loop with conversation-state persistence.
142
+ runner = AgentRunner(
143
+ loop=loop,
144
+ state=MemoryStateStore(),
145
+ system_prompt="You are a helpful weather assistant.",
146
+ )
147
+
148
+ # 5. Drive a turn and consume the event stream.
149
+ async for event in runner.run("session-1", "What's the weather in Paris?"):
150
+ print(event)
151
+
152
+
153
+ asyncio.run(main())
154
+ ```
155
+
156
+ ## Core concepts
157
+
158
+ ### Tools
159
+
160
+ The `@tool` decorator turns an async function into a `Tool`: it derives a
161
+ JSON Schema for the arguments from the function's type annotations and
162
+ docstring. A `Registry` is the dispatch table the loop talks to —
163
+ `Registry().register(my_tool)` adds a tool by name. `InProcProvider` is a
164
+ convenience helper for bulk-registering a batch of decorated callables.
165
+
166
+ ### LLM clients
167
+
168
+ `LLMClient` is the protocol the loop depends on — anything implementing it
169
+ can drive the agent. `OpenAICompatibleClient` is the shipped implementation;
170
+ it works against any OpenAI-compatible Chat Completions endpoint. Point it at
171
+ GDC, a local OSS server, or OpenAI itself by passing `base_url` — the
172
+ provider differences are absorbed entirely by that one argument.
173
+
174
+ ### State stores
175
+
176
+ `StateStore` is the protocol for conversation-state persistence across turns.
177
+ `MemoryStateStore` is the default in-memory implementation and needs no
178
+ infrastructure. `SqlStateStore` and `RedisStateStore` are durable backends
179
+ behind the `sql` and `redis` extras respectively.
180
+
181
+ ### The event stream
182
+
183
+ Every step of the ReACT cycle emits exactly one event from the `AgentEvent`
184
+ union: `ThoughtEvent`, `ActionEvent`, `ToolStartedEvent`,
185
+ `ToolProgressEvent` (reserved — never emitted by the v1 loop),
186
+ `ObservationEvent`, `ToolFailedEvent`, `TokenEvent`, `FinalEvent`, and
187
+ `ErrorEvent`. Events carry a monotonic `sequence` counter and a `timestamp`
188
+ so consumers can detect drops or reorders. Every run ends with exactly one
189
+ `FinalEvent` — consumers can rely on it as the "iteration done" signal.
190
+
191
+ ### Safety
192
+
193
+ `SafetyConfig` bounds a run: it caps the iteration count, sets per-tool
194
+ timeouts, and supplies the fallback answer used when a run terminates on an
195
+ error or safety cap.
196
+
197
+ ### Audit & observability
198
+
199
+ Two optional collaborators plug into the runner. An `AuditSink` records an
200
+ `AuditEvent` at session start, each tool invocation, the final answer, and
201
+ any error. A `Hooks` instance fires lifecycle callbacks for logging, metrics,
202
+ and tracing. Both are best-effort and isolated — a raising sink or hook
203
+ never aborts a live run.
204
+
205
+ ## Design principles
206
+
207
+ - **Pluggable everything.** LLM clients, tool sources, state stores, audit
208
+ sinks, observability hooks all sit behind protocols.
209
+ - **Transport-free.** No HTTP, no WebSocket, no auth. Consumers wrap the
210
+ `Runner` in whatever transport makes sense.
211
+ - **Production-first.** Iter caps, tool timeouts, structured errors,
212
+ graceful fallbacks, full-fidelity event stream.
213
+ - **Standalone usable.** Memory backends ship by default so you can run an
214
+ agent without any infrastructure dependency.
215
+
216
+ ## Links
217
+
218
+ - package — https://pypi.org/project/fifty-agent-sdk/
219
+ - source & issues — https://github.com/fiftynotai/fifty-agent-sdk
220
+ - changelog — [CHANGELOG.md](CHANGELOG.md)
221
+ - contributing — issues and PRs welcome.
222
+
223
+ ## License
224
+
225
+ [MIT](LICENSE) © fifty.dev
@@ -0,0 +1,181 @@
1
+ <p align="center">
2
+ <img src=".github/banner.png" alt="fifty-agent-sdk — an embeddable ReACT loop. any endpoint. no infra." width="100%">
3
+ </p>
4
+
5
+ # fifty-agent-sdk
6
+
7
+ [![PyPI](https://img.shields.io/pypi/v/fifty-agent-sdk)](https://pypi.org/project/fifty-agent-sdk/)
8
+ [![Python](https://img.shields.io/pypi/pyversions/fifty-agent-sdk)](https://pypi.org/project/fifty-agent-sdk/)
9
+ [![CI](https://github.com/fiftynotai/fifty-agent-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/fiftynotai/fifty-agent-sdk/actions/workflows/ci.yml)
10
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
11
+
12
+ an embeddable ReACT agent loop you drop into your own service. it talks to
13
+ any OpenAI-compatible Chat Completions endpoint — OpenAI, Google Distributed
14
+ Cloud, a local OSS server — by changing one `base_url`. transport-free,
15
+ infra-free by default, and pluggable behind protocols the whole way down.
16
+
17
+ ## At a glance
18
+
19
+ - endpoint-agnostic — one `base_url` points the loop at OpenAI, GDC, or a local OSS server; the loop doesn't care which.
20
+ - pluggable tools — `@tool` derives a JSON Schema from type hints; MCP discovery adds remote tools to the same registry.
21
+ - pluggable state — conversation state lives behind a `StateStore` protocol: in-memory, SQL, or Redis.
22
+ - typed event stream — every ReACT step emits exactly one `AgentEvent`; every run ends with exactly one `FinalEvent`.
23
+ - safety caps — iteration ceiling, per-tool timeouts, and a fallback answer on error or cap.
24
+ - zero-infra default — memory backends ship in core; run an agent with no extra dependency.
25
+
26
+ ## Installation
27
+
28
+ ```
29
+ pip install fifty-agent-sdk
30
+ ```
31
+
32
+ The SDK requires Python 3.11 or newer. The core install ships with the
33
+ in-memory backends, so an agent can run with no infrastructure dependency.
34
+
35
+ Two optional extras add durable backends:
36
+
37
+ - `pip install 'fifty-agent-sdk[sql]'` — pulls SQLAlchemy and enables
38
+ `SqlStateStore` (durable conversation state) and `SqlAuditSink` (durable
39
+ audit log).
40
+ - `pip install 'fifty-agent-sdk[redis]'` — pulls redis-py and enables
41
+ `RedisStateStore` (Redis-backed conversation state).
42
+
43
+ Importing `fifty_agent_sdk` itself pulls neither SQLAlchemy nor redis-py. The
44
+ extra symbols are re-exported lazily; first access to one without the
45
+ relevant extra installed raises a clear `ImportError`.
46
+
47
+ ## Quickstart
48
+
49
+ A complete agent fits in a handful of lines. The example below defines a
50
+ tool, wires the loop and runner, and drains the event stream:
51
+
52
+ ```python
53
+ import asyncio
54
+ from typing import Any
55
+
56
+ from fifty_agent_sdk import (
57
+ JSON_MODE_OUTPUT_FORMAT,
58
+ AgentLoop,
59
+ AgentRunner,
60
+ JsonModeParser,
61
+ MemoryStateStore,
62
+ OpenAICompatibleClient,
63
+ PromptSections,
64
+ Registry,
65
+ SafetyConfig,
66
+ tool,
67
+ )
68
+
69
+
70
+ @tool()
71
+ async def get_weather(city: str) -> dict[str, Any]:
72
+ """Return the current weather for a city."""
73
+ return {"city": city, "temp_c": 21}
74
+
75
+
76
+ async def main() -> None:
77
+ # 1. An LLM client — points at any OpenAI-compatible endpoint.
78
+ llm = OpenAICompatibleClient(api_key="sk-...")
79
+
80
+ # 2. A tool registry — register the decorated tool.
81
+ registry = Registry()
82
+ registry.register(get_weather)
83
+
84
+ # 3. The ReACT loop — LLM + registry + parser + prompts + safety.
85
+ # `output_format` shows the model the JSON envelope the parser
86
+ # expects; without it JsonModeParser raises ParserError on every turn.
87
+ loop = AgentLoop(
88
+ llm=llm,
89
+ registry=registry,
90
+ parser=JsonModeParser(),
91
+ prompts=PromptSections(persona="You are helpful."),
92
+ safety=SafetyConfig(),
93
+ model="gpt-4o",
94
+ output_format=JSON_MODE_OUTPUT_FORMAT,
95
+ )
96
+
97
+ # 4. The runner — wraps the loop with conversation-state persistence.
98
+ runner = AgentRunner(
99
+ loop=loop,
100
+ state=MemoryStateStore(),
101
+ system_prompt="You are a helpful weather assistant.",
102
+ )
103
+
104
+ # 5. Drive a turn and consume the event stream.
105
+ async for event in runner.run("session-1", "What's the weather in Paris?"):
106
+ print(event)
107
+
108
+
109
+ asyncio.run(main())
110
+ ```
111
+
112
+ ## Core concepts
113
+
114
+ ### Tools
115
+
116
+ The `@tool` decorator turns an async function into a `Tool`: it derives a
117
+ JSON Schema for the arguments from the function's type annotations and
118
+ docstring. A `Registry` is the dispatch table the loop talks to —
119
+ `Registry().register(my_tool)` adds a tool by name. `InProcProvider` is a
120
+ convenience helper for bulk-registering a batch of decorated callables.
121
+
122
+ ### LLM clients
123
+
124
+ `LLMClient` is the protocol the loop depends on — anything implementing it
125
+ can drive the agent. `OpenAICompatibleClient` is the shipped implementation;
126
+ it works against any OpenAI-compatible Chat Completions endpoint. Point it at
127
+ GDC, a local OSS server, or OpenAI itself by passing `base_url` — the
128
+ provider differences are absorbed entirely by that one argument.
129
+
130
+ ### State stores
131
+
132
+ `StateStore` is the protocol for conversation-state persistence across turns.
133
+ `MemoryStateStore` is the default in-memory implementation and needs no
134
+ infrastructure. `SqlStateStore` and `RedisStateStore` are durable backends
135
+ behind the `sql` and `redis` extras respectively.
136
+
137
+ ### The event stream
138
+
139
+ Every step of the ReACT cycle emits exactly one event from the `AgentEvent`
140
+ union: `ThoughtEvent`, `ActionEvent`, `ToolStartedEvent`,
141
+ `ToolProgressEvent` (reserved — never emitted by the v1 loop),
142
+ `ObservationEvent`, `ToolFailedEvent`, `TokenEvent`, `FinalEvent`, and
143
+ `ErrorEvent`. Events carry a monotonic `sequence` counter and a `timestamp`
144
+ so consumers can detect drops or reorders. Every run ends with exactly one
145
+ `FinalEvent` — consumers can rely on it as the "iteration done" signal.
146
+
147
+ ### Safety
148
+
149
+ `SafetyConfig` bounds a run: it caps the iteration count, sets per-tool
150
+ timeouts, and supplies the fallback answer used when a run terminates on an
151
+ error or safety cap.
152
+
153
+ ### Audit & observability
154
+
155
+ Two optional collaborators plug into the runner. An `AuditSink` records an
156
+ `AuditEvent` at session start, each tool invocation, the final answer, and
157
+ any error. A `Hooks` instance fires lifecycle callbacks for logging, metrics,
158
+ and tracing. Both are best-effort and isolated — a raising sink or hook
159
+ never aborts a live run.
160
+
161
+ ## Design principles
162
+
163
+ - **Pluggable everything.** LLM clients, tool sources, state stores, audit
164
+ sinks, observability hooks all sit behind protocols.
165
+ - **Transport-free.** No HTTP, no WebSocket, no auth. Consumers wrap the
166
+ `Runner` in whatever transport makes sense.
167
+ - **Production-first.** Iter caps, tool timeouts, structured errors,
168
+ graceful fallbacks, full-fidelity event stream.
169
+ - **Standalone usable.** Memory backends ship by default so you can run an
170
+ agent without any infrastructure dependency.
171
+
172
+ ## Links
173
+
174
+ - package — https://pypi.org/project/fifty-agent-sdk/
175
+ - source & issues — https://github.com/fiftynotai/fifty-agent-sdk
176
+ - changelog — [CHANGELOG.md](CHANGELOG.md)
177
+ - contributing — issues and PRs welcome.
178
+
179
+ ## License
180
+
181
+ [MIT](LICENSE) © fifty.dev
@@ -0,0 +1,101 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "fifty-agent-sdk"
7
+ version = "1.1.0"
8
+ description = "Production-grade reusable agent loop SDK — custom ReACT, JSON-mode tool calls, MCP client, pluggable LLM/state/tools."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = {text = "MIT"}
12
+ authors = [{name = "fifty.dev"}]
13
+ maintainers = [{name = "fifty.dev"}]
14
+ keywords = ["agent", "llm", "react", "mcp", "tool-calling", "openai", "ai", "sdk"]
15
+ classifiers = [
16
+ "Development Status :: 5 - Production/Stable",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
24
+ "Typing :: Typed",
25
+ ]
26
+
27
+ dependencies = [
28
+ "pydantic>=2.5.0",
29
+ "httpx>=0.26.0",
30
+ "structlog>=24.1.0",
31
+ "openai>=1.30.0,<3.0.0",
32
+ # Official MCP SDK. Standard-only: the MCP path is the ONLY MCP path, so
33
+ # this is a core dependency, not an optional extra. Floor pinned at the
34
+ # probed 1.27.0; <2.0.0 ceiling because FastMCP/transport surfaces drift
35
+ # across minors (re-probe the live API before coding against new minors).
36
+ "mcp>=1.27.0,<2.0.0",
37
+ ]
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/fiftynotai/fifty-agent-sdk"
41
+ Repository = "https://github.com/fiftynotai/fifty-agent-sdk"
42
+ Issues = "https://github.com/fiftynotai/fifty-agent-sdk/issues"
43
+ Changelog = "https://github.com/fiftynotai/fifty-agent-sdk/blob/main/CHANGELOG.md"
44
+
45
+ [project.optional-dependencies]
46
+ sql = [
47
+ "sqlalchemy[asyncio]>=2.0.0",
48
+ ]
49
+ redis = [
50
+ "redis>=5.0.0",
51
+ ]
52
+ dev = [
53
+ "pytest>=7.4.0",
54
+ "pytest-asyncio>=0.23.0",
55
+ "pytest-cov>=4.1.0",
56
+ "pytest-httpx>=0.30.0",
57
+ "ruff>=0.1.0",
58
+ "mypy>=1.8.0",
59
+ "aiosqlite>=0.19.0",
60
+ # fakeredis must track the redis major version (redis>=5.0.0 above):
61
+ # the SDK relies on 5.x async semantics — aclose(), pipeline(transaction=True).
62
+ "fakeredis>=2.20.0",
63
+ ]
64
+
65
+ [tool.setuptools.packages.find]
66
+ where = ["src"]
67
+ include = ["fifty_agent_sdk*"]
68
+
69
+ [tool.setuptools.package-data]
70
+ # Ship the PEP 561 marker so downstream type-checkers see fifty-agent-sdk as typed.
71
+ fifty_agent_sdk = ["py.typed"]
72
+
73
+ [tool.ruff]
74
+ target-version = "py311"
75
+ line-length = 100
76
+
77
+ [tool.ruff.lint]
78
+ select = ["E", "W", "F", "I", "B", "UP", "SIM"]
79
+ ignore = ["E501"]
80
+
81
+ [tool.ruff.lint.per-file-ignores]
82
+ "__init__.py" = ["F401"]
83
+
84
+ [tool.ruff.lint.isort]
85
+ known-first-party = ["fifty_agent_sdk"]
86
+
87
+ [tool.mypy]
88
+ python_version = "3.11"
89
+ strict = true
90
+ exclude = ["tests/", "build/", ".venv/"]
91
+
92
+ [tool.pytest.ini_options]
93
+ asyncio_mode = "auto"
94
+ testpaths = ["tests"]
95
+ python_files = ["test_*.py"]
96
+ addopts = ["-v", "--tb=short", "--strict-markers", "--import-mode=importlib"]
97
+ markers = [
98
+ "integration: Integration tests requiring external services",
99
+ "postgres: Integration tests against a real Postgres database (skipped unless POSTGRES_TEST_URL is set)",
100
+ "redis: Integration tests against a real Redis instance (skipped unless REDIS_TEST_URL is set)",
101
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+