cubepi 0.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 (45) hide show
  1. cubepi-0.1.0/.github/workflows/ci.yml +51 -0
  2. cubepi-0.1.0/.github/workflows/publish.yml +55 -0
  3. cubepi-0.1.0/.gitignore +28 -0
  4. cubepi-0.1.0/.python-version +1 -0
  5. cubepi-0.1.0/LICENSE +21 -0
  6. cubepi-0.1.0/PKG-INFO +246 -0
  7. cubepi-0.1.0/README.md +225 -0
  8. cubepi-0.1.0/cubepi/__init__.py +48 -0
  9. cubepi-0.1.0/cubepi/agent/__init__.py +53 -0
  10. cubepi-0.1.0/cubepi/agent/agent.py +350 -0
  11. cubepi-0.1.0/cubepi/agent/loop.py +334 -0
  12. cubepi-0.1.0/cubepi/agent/tools.py +384 -0
  13. cubepi-0.1.0/cubepi/agent/types.py +170 -0
  14. cubepi-0.1.0/cubepi/checkpointer/__init__.py +4 -0
  15. cubepi-0.1.0/cubepi/checkpointer/base.py +17 -0
  16. cubepi-0.1.0/cubepi/checkpointer/memory.py +23 -0
  17. cubepi-0.1.0/cubepi/checkpointer/sqlite.py +118 -0
  18. cubepi-0.1.0/cubepi/middleware/__init__.py +3 -0
  19. cubepi-0.1.0/cubepi/middleware/base.py +88 -0
  20. cubepi-0.1.0/cubepi/providers/__init__.py +65 -0
  21. cubepi-0.1.0/cubepi/providers/anthropic.py +308 -0
  22. cubepi-0.1.0/cubepi/providers/base.py +154 -0
  23. cubepi-0.1.0/cubepi/providers/faux.py +333 -0
  24. cubepi-0.1.0/cubepi/providers/openai.py +281 -0
  25. cubepi-0.1.0/cubepi/py.typed +0 -0
  26. cubepi-0.1.0/pyproject.toml +45 -0
  27. cubepi-0.1.0/tests/__init__.py +0 -0
  28. cubepi-0.1.0/tests/agent/__init__.py +0 -0
  29. cubepi-0.1.0/tests/agent/test_agent.py +289 -0
  30. cubepi-0.1.0/tests/agent/test_e2e.py +257 -0
  31. cubepi-0.1.0/tests/agent/test_loop.py +376 -0
  32. cubepi-0.1.0/tests/agent/test_tools.py +281 -0
  33. cubepi-0.1.0/tests/agent/test_types.py +149 -0
  34. cubepi-0.1.0/tests/checkpointer/__init__.py +0 -0
  35. cubepi-0.1.0/tests/checkpointer/test_memory.py +52 -0
  36. cubepi-0.1.0/tests/checkpointer/test_sqlite.py +63 -0
  37. cubepi-0.1.0/tests/conftest.py +0 -0
  38. cubepi-0.1.0/tests/middleware/__init__.py +0 -0
  39. cubepi-0.1.0/tests/middleware/test_base.py +178 -0
  40. cubepi-0.1.0/tests/providers/__init__.py +0 -0
  41. cubepi-0.1.0/tests/providers/test_anthropic.py +62 -0
  42. cubepi-0.1.0/tests/providers/test_base.py +149 -0
  43. cubepi-0.1.0/tests/providers/test_faux.py +286 -0
  44. cubepi-0.1.0/tests/providers/test_openai.py +63 -0
  45. cubepi-0.1.0/uv.lock +705 -0
@@ -0,0 +1,51 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.11", "3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v6
18
+
19
+ - uses: astral-sh/setup-uv@v8.1.0
20
+ with:
21
+ version: "latest"
22
+
23
+ - name: Set up Python ${{ matrix.python-version }}
24
+ run: uv python install ${{ matrix.python-version }}
25
+
26
+ - name: Install dependencies
27
+ run: uv sync --all-extras --dev
28
+
29
+ - name: Run tests
30
+ run: uv run pytest tests/ -v --cov=cubepi --cov-report=term-missing
31
+
32
+ - name: Check import
33
+ run: uv run python -c "import cubepi; print(cubepi.__all__)"
34
+
35
+ lint:
36
+ runs-on: ubuntu-latest
37
+ steps:
38
+ - uses: actions/checkout@v6
39
+
40
+ - uses: astral-sh/setup-uv@v8.1.0
41
+ with:
42
+ version: "latest"
43
+
44
+ - name: Install dependencies
45
+ run: uv sync --all-extras --dev
46
+
47
+ - name: Ruff check
48
+ run: uv run ruff check cubepi/ tests/
49
+
50
+ - name: Ruff format check
51
+ run: uv run ruff format --check cubepi/ tests/
@@ -0,0 +1,55 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v6
12
+
13
+ - uses: astral-sh/setup-uv@v8.1.0
14
+ with:
15
+ version: "latest"
16
+
17
+ - name: Build package
18
+ run: uv build
19
+
20
+ - uses: actions/upload-artifact@v7
21
+ with:
22
+ name: dist
23
+ path: dist/
24
+
25
+ publish-testpypi:
26
+ needs: build
27
+ runs-on: ubuntu-latest
28
+ environment: testpypi
29
+ permissions:
30
+ id-token: write
31
+ steps:
32
+ - uses: actions/download-artifact@v8
33
+ with:
34
+ name: dist
35
+ path: dist/
36
+
37
+ - name: Publish to TestPyPI
38
+ uses: pypa/gh-action-pypi-publish@release/v1
39
+ with:
40
+ repository-url: https://test.pypi.org/legacy/
41
+
42
+ publish-pypi:
43
+ needs: publish-testpypi
44
+ runs-on: ubuntu-latest
45
+ environment: pypi
46
+ permissions:
47
+ id-token: write
48
+ steps:
49
+ - uses: actions/download-artifact@v8
50
+ with:
51
+ name: dist
52
+ path: dist/
53
+
54
+ - name: Publish to PyPI
55
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,28 @@
1
+ docs/specs
2
+ docs/plans
3
+
4
+ # Python
5
+ __pycache__/
6
+ *.pyc
7
+ *.pyo
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+
12
+ # Virtual environments
13
+ .venv/
14
+
15
+ # Testing / coverage
16
+ .coverage
17
+ .pytest_cache/
18
+ htmlcov/
19
+
20
+ # IDE
21
+ .idea/
22
+ .vscode/
23
+ *.swp
24
+ *.swo
25
+
26
+ # OS
27
+ .DS_Store
28
+ Thumbs.db
@@ -0,0 +1 @@
1
+ 3.13
cubepi-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xf gong
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.
cubepi-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,246 @@
1
+ Metadata-Version: 2.4
2
+ Name: cubepi
3
+ Version: 0.1.0
4
+ Summary: Pythonic async-native agent framework
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Framework :: AsyncIO
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Typing :: Typed
14
+ Requires-Python: >=3.11
15
+ Requires-Dist: anthropic>=0.100.0
16
+ Requires-Dist: openai>=2.36.0
17
+ Requires-Dist: pydantic>=2.13.4
18
+ Provides-Extra: sqlite
19
+ Requires-Dist: aiosqlite>=0.22.1; extra == 'sqlite'
20
+ Description-Content-Type: text/markdown
21
+
22
+ # cubepi
23
+
24
+ Pythonic async-native agent framework. Built to replace [langgraph](https://github.com/langchain-ai/langgraph) with something simpler, faster, and easier to reason about.
25
+
26
+ Inspired by [pi-agent-core](https://github.com/anthropics/pi-agent-core) (TypeScript), redesigned for Python.
27
+
28
+ ## Why cubepi
29
+
30
+ ### vs langgraph
31
+
32
+ | | langgraph | cubepi |
33
+ |---|---|---|
34
+ | **Abstraction** | Graph nodes + edges + channels — you model your agent as a state machine | Plain async functions — `run_agent_loop` is a while loop you can read in 5 minutes |
35
+ | **Streaming** | Callback-based, multiple handler types | `async for event in stream` — one pattern everywhere |
36
+ | **Checkpointing** | Full snapshot per step — serializes entire message list on every channel change | Append-only — writes only new messages, O(1) DB I/O regardless of conversation length |
37
+ | **Dependencies** | Pulls in langchain-core, langgraph-sdk, and transitive deps | 3 core deps: `pydantic`, `anthropic`, `openai` |
38
+ | **Tool execution** | Tools are graph nodes with manual wiring | Declare tools as functions, framework handles routing and parallel execution |
39
+ | **Multi-provider** | Via langchain chat model adapters | Native Provider protocol — Anthropic, OpenAI built in, add your own with one class |
40
+ | **Middleware** | Graph-level middleware on node entry/exit | Agent-level middleware with 5 typed hooks and declarative composition rules |
41
+ | **Observability** | LangSmith / Langfuse integration, full trace visualization | Events + middleware hooks — bring your own tracing |
42
+
43
+ ### vs pi-agent-core
44
+
45
+ cubepi is a Python port of pi's architecture with Pythonic improvements:
46
+
47
+ | | pi-agent-core | cubepi |
48
+ |---|---|---|
49
+ | **Language** | TypeScript | Python (async-native) |
50
+ | **Type system** | Zod schemas | Pydantic v2 — validation, serialization, JSON Schema generation in one |
51
+ | **Cancel signal** | `AbortSignal` (Web API) | `asyncio.Event` — same semantics, native to Python |
52
+ | **Middleware** | Hooks only (callbacks on Agent) | Hooks + composable Middleware protocol with `compose_middleware()` |
53
+ | **Checkpointing** | Not built in | Built-in `MemoryCheckpointer` + `SQLiteCheckpointer` |
54
+ | **Test utility** | Internal test helpers | `FauxProvider` as public API — ship it, use it in your tests |
55
+
56
+ ## Install
57
+
58
+ ```bash
59
+ pip install cubepi
60
+
61
+ # With SQLite checkpointer
62
+ pip install cubepi[sqlite]
63
+ ```
64
+
65
+ Or with [uv](https://github.com/astral-sh/uv):
66
+
67
+ ```bash
68
+ uv add cubepi
69
+ uv add cubepi[sqlite]
70
+ ```
71
+
72
+ ## Quick Start
73
+
74
+ ```python
75
+ import asyncio
76
+ from cubepi import Agent, AgentTool, Model
77
+ from cubepi.providers import AnthropicProvider
78
+
79
+ provider = AnthropicProvider(api_key="sk-...")
80
+
81
+ def get_weather(city: str) -> str:
82
+ """Get current weather for a city."""
83
+ return f"72°F and sunny in {city}"
84
+
85
+ agent = Agent(
86
+ model=Model(provider=provider, model="claude-sonnet-4-20250514"),
87
+ tools=[
88
+ AgentTool(
89
+ name="get_weather",
90
+ description="Get current weather for a city",
91
+ parameters={
92
+ "type": "object",
93
+ "properties": {"city": {"type": "string"}},
94
+ "required": ["city"],
95
+ },
96
+ execute=get_weather,
97
+ ),
98
+ ],
99
+ system_prompt="You are a helpful weather assistant.",
100
+ )
101
+
102
+ async def main():
103
+ stream = await agent.prompt("What's the weather in Tokyo?")
104
+ async for event in stream:
105
+ if event.type == "text_delta":
106
+ print(event.delta, end="", flush=True)
107
+ print()
108
+
109
+ asyncio.run(main())
110
+ ```
111
+
112
+ ## Architecture
113
+
114
+ ```
115
+ cubepi/
116
+ ├── providers/ # LLM provider abstraction
117
+ │ ├── base.py # Provider protocol, message types, MessageStream
118
+ │ ├── anthropic.py # Anthropic provider
119
+ │ ├── openai.py # OpenAI provider
120
+ │ └── faux.py # Test utility — pre-configured responses with realistic streaming
121
+ ├── agent/ # Agent runtime
122
+ │ ├── agent.py # Stateful Agent class
123
+ │ ├── loop.py # Stateless core loop (the actual algorithm)
124
+ │ ├── tools.py # Tool execution engine (sequential + parallel)
125
+ │ └── types.py # Events, AgentTool, AgentContext, hook types
126
+ ├── middleware/ # Composable middleware protocol
127
+ │ └── base.py # 5 hooks with distinct composition rules
128
+ └── checkpointer/ # Persistence
129
+ ├── base.py # Checkpointer protocol
130
+ ├── memory.py # In-memory (dev/test)
131
+ └── sqlite.py # SQLite (lightweight persistence)
132
+ ```
133
+
134
+ ## Core Concepts
135
+
136
+ ### Providers
137
+
138
+ Abstract LLM interaction behind a `Provider` protocol. All providers return `MessageStream` — an async iterator of `StreamEvent`s.
139
+
140
+ ```python
141
+ from cubepi.providers import AnthropicProvider, OpenAIProvider, FauxProvider
142
+
143
+ # Real providers
144
+ anthropic = AnthropicProvider(api_key="...")
145
+ openai = OpenAIProvider(api_key="...")
146
+
147
+ # Test provider — no API calls, fully deterministic
148
+ faux = FauxProvider()
149
+ faux.set_responses(["Hello!", "How can I help?"])
150
+ ```
151
+
152
+ ### Tools
153
+
154
+ Declare tools with a name, JSON Schema parameters, and a sync or async execute function. The framework handles argument parsing, parallel execution, and error wrapping.
155
+
156
+ ```python
157
+ from cubepi import AgentTool
158
+
159
+ tool = AgentTool(
160
+ name="search",
161
+ description="Search the web",
162
+ parameters={
163
+ "type": "object",
164
+ "properties": {"query": {"type": "string"}},
165
+ "required": ["query"],
166
+ },
167
+ execute=lambda query: f"Results for: {query}",
168
+ sequential=False, # allow parallel execution (default)
169
+ )
170
+ ```
171
+
172
+ ### Middleware
173
+
174
+ Composable hooks that modify behavior without touching the core loop:
175
+
176
+ ```python
177
+ from cubepi import Middleware, compose_middleware
178
+
179
+ class LoggingMiddleware(Middleware):
180
+ async def transform_context(self, messages, *, signal=None):
181
+ print(f"Context has {len(messages)} messages")
182
+ return messages
183
+
184
+ class SafetyMiddleware(Middleware):
185
+ async def before_tool_call(self, ctx, *, signal=None):
186
+ if ctx.tool_call.name == "dangerous_tool":
187
+ return BeforeToolCallResult(block=True, content="Blocked by policy")
188
+ return None
189
+
190
+ hooks = compose_middleware([LoggingMiddleware(), SafetyMiddleware()])
191
+ ```
192
+
193
+ **Composition rules:**
194
+
195
+ | Hook | Rule |
196
+ |------|------|
197
+ | `transform_context` | Chained — each receives previous result |
198
+ | `convert_to_llm` | Last implementation wins |
199
+ | `before_tool_call` | Any block stops execution |
200
+ | `after_tool_call` | Later overrides earlier |
201
+ | `should_stop_after_turn` | Any true stops |
202
+
203
+ ### Checkpointer
204
+
205
+ Persist conversation state with append-only semantics:
206
+
207
+ ```python
208
+ from cubepi.checkpointer import MemoryCheckpointer, SQLiteCheckpointer
209
+
210
+ # In-memory for dev/test
211
+ cp = MemoryCheckpointer()
212
+
213
+ # SQLite for lightweight persistence
214
+ async with SQLiteCheckpointer("agent.db") as cp:
215
+ agent = Agent(model=model, checkpointer=cp, thread_id="conv-1")
216
+ ```
217
+
218
+ ### FauxProvider for Testing
219
+
220
+ Ship your agent tests without API keys:
221
+
222
+ ```python
223
+ from cubepi.providers import FauxProvider, faux_text, faux_tool_call, faux_assistant_message
224
+
225
+ provider = FauxProvider()
226
+ provider.set_responses([
227
+ faux_assistant_message([
228
+ faux_tool_call("search", {"query": "python"}),
229
+ ]),
230
+ faux_assistant_message("Here are the results..."),
231
+ ])
232
+
233
+ agent = Agent(model=Model(provider=provider, model="test"), tools=[search_tool])
234
+ stream = await agent.prompt("Search for python")
235
+ # Streams realistic deltas — content_block_start, text_delta, etc.
236
+ ```
237
+
238
+ ## Requirements
239
+
240
+ - Python >= 3.11
241
+ - Core: `pydantic`, `anthropic`, `openai`
242
+ - Optional: `aiosqlite` (for `SQLiteCheckpointer`)
243
+
244
+ ## License
245
+
246
+ MIT
cubepi-0.1.0/README.md ADDED
@@ -0,0 +1,225 @@
1
+ # cubepi
2
+
3
+ Pythonic async-native agent framework. Built to replace [langgraph](https://github.com/langchain-ai/langgraph) with something simpler, faster, and easier to reason about.
4
+
5
+ Inspired by [pi-agent-core](https://github.com/anthropics/pi-agent-core) (TypeScript), redesigned for Python.
6
+
7
+ ## Why cubepi
8
+
9
+ ### vs langgraph
10
+
11
+ | | langgraph | cubepi |
12
+ |---|---|---|
13
+ | **Abstraction** | Graph nodes + edges + channels — you model your agent as a state machine | Plain async functions — `run_agent_loop` is a while loop you can read in 5 minutes |
14
+ | **Streaming** | Callback-based, multiple handler types | `async for event in stream` — one pattern everywhere |
15
+ | **Checkpointing** | Full snapshot per step — serializes entire message list on every channel change | Append-only — writes only new messages, O(1) DB I/O regardless of conversation length |
16
+ | **Dependencies** | Pulls in langchain-core, langgraph-sdk, and transitive deps | 3 core deps: `pydantic`, `anthropic`, `openai` |
17
+ | **Tool execution** | Tools are graph nodes with manual wiring | Declare tools as functions, framework handles routing and parallel execution |
18
+ | **Multi-provider** | Via langchain chat model adapters | Native Provider protocol — Anthropic, OpenAI built in, add your own with one class |
19
+ | **Middleware** | Graph-level middleware on node entry/exit | Agent-level middleware with 5 typed hooks and declarative composition rules |
20
+ | **Observability** | LangSmith / Langfuse integration, full trace visualization | Events + middleware hooks — bring your own tracing |
21
+
22
+ ### vs pi-agent-core
23
+
24
+ cubepi is a Python port of pi's architecture with Pythonic improvements:
25
+
26
+ | | pi-agent-core | cubepi |
27
+ |---|---|---|
28
+ | **Language** | TypeScript | Python (async-native) |
29
+ | **Type system** | Zod schemas | Pydantic v2 — validation, serialization, JSON Schema generation in one |
30
+ | **Cancel signal** | `AbortSignal` (Web API) | `asyncio.Event` — same semantics, native to Python |
31
+ | **Middleware** | Hooks only (callbacks on Agent) | Hooks + composable Middleware protocol with `compose_middleware()` |
32
+ | **Checkpointing** | Not built in | Built-in `MemoryCheckpointer` + `SQLiteCheckpointer` |
33
+ | **Test utility** | Internal test helpers | `FauxProvider` as public API — ship it, use it in your tests |
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install cubepi
39
+
40
+ # With SQLite checkpointer
41
+ pip install cubepi[sqlite]
42
+ ```
43
+
44
+ Or with [uv](https://github.com/astral-sh/uv):
45
+
46
+ ```bash
47
+ uv add cubepi
48
+ uv add cubepi[sqlite]
49
+ ```
50
+
51
+ ## Quick Start
52
+
53
+ ```python
54
+ import asyncio
55
+ from cubepi import Agent, AgentTool, Model
56
+ from cubepi.providers import AnthropicProvider
57
+
58
+ provider = AnthropicProvider(api_key="sk-...")
59
+
60
+ def get_weather(city: str) -> str:
61
+ """Get current weather for a city."""
62
+ return f"72°F and sunny in {city}"
63
+
64
+ agent = Agent(
65
+ model=Model(provider=provider, model="claude-sonnet-4-20250514"),
66
+ tools=[
67
+ AgentTool(
68
+ name="get_weather",
69
+ description="Get current weather for a city",
70
+ parameters={
71
+ "type": "object",
72
+ "properties": {"city": {"type": "string"}},
73
+ "required": ["city"],
74
+ },
75
+ execute=get_weather,
76
+ ),
77
+ ],
78
+ system_prompt="You are a helpful weather assistant.",
79
+ )
80
+
81
+ async def main():
82
+ stream = await agent.prompt("What's the weather in Tokyo?")
83
+ async for event in stream:
84
+ if event.type == "text_delta":
85
+ print(event.delta, end="", flush=True)
86
+ print()
87
+
88
+ asyncio.run(main())
89
+ ```
90
+
91
+ ## Architecture
92
+
93
+ ```
94
+ cubepi/
95
+ ├── providers/ # LLM provider abstraction
96
+ │ ├── base.py # Provider protocol, message types, MessageStream
97
+ │ ├── anthropic.py # Anthropic provider
98
+ │ ├── openai.py # OpenAI provider
99
+ │ └── faux.py # Test utility — pre-configured responses with realistic streaming
100
+ ├── agent/ # Agent runtime
101
+ │ ├── agent.py # Stateful Agent class
102
+ │ ├── loop.py # Stateless core loop (the actual algorithm)
103
+ │ ├── tools.py # Tool execution engine (sequential + parallel)
104
+ │ └── types.py # Events, AgentTool, AgentContext, hook types
105
+ ├── middleware/ # Composable middleware protocol
106
+ │ └── base.py # 5 hooks with distinct composition rules
107
+ └── checkpointer/ # Persistence
108
+ ├── base.py # Checkpointer protocol
109
+ ├── memory.py # In-memory (dev/test)
110
+ └── sqlite.py # SQLite (lightweight persistence)
111
+ ```
112
+
113
+ ## Core Concepts
114
+
115
+ ### Providers
116
+
117
+ Abstract LLM interaction behind a `Provider` protocol. All providers return `MessageStream` — an async iterator of `StreamEvent`s.
118
+
119
+ ```python
120
+ from cubepi.providers import AnthropicProvider, OpenAIProvider, FauxProvider
121
+
122
+ # Real providers
123
+ anthropic = AnthropicProvider(api_key="...")
124
+ openai = OpenAIProvider(api_key="...")
125
+
126
+ # Test provider — no API calls, fully deterministic
127
+ faux = FauxProvider()
128
+ faux.set_responses(["Hello!", "How can I help?"])
129
+ ```
130
+
131
+ ### Tools
132
+
133
+ Declare tools with a name, JSON Schema parameters, and a sync or async execute function. The framework handles argument parsing, parallel execution, and error wrapping.
134
+
135
+ ```python
136
+ from cubepi import AgentTool
137
+
138
+ tool = AgentTool(
139
+ name="search",
140
+ description="Search the web",
141
+ parameters={
142
+ "type": "object",
143
+ "properties": {"query": {"type": "string"}},
144
+ "required": ["query"],
145
+ },
146
+ execute=lambda query: f"Results for: {query}",
147
+ sequential=False, # allow parallel execution (default)
148
+ )
149
+ ```
150
+
151
+ ### Middleware
152
+
153
+ Composable hooks that modify behavior without touching the core loop:
154
+
155
+ ```python
156
+ from cubepi import Middleware, compose_middleware
157
+
158
+ class LoggingMiddleware(Middleware):
159
+ async def transform_context(self, messages, *, signal=None):
160
+ print(f"Context has {len(messages)} messages")
161
+ return messages
162
+
163
+ class SafetyMiddleware(Middleware):
164
+ async def before_tool_call(self, ctx, *, signal=None):
165
+ if ctx.tool_call.name == "dangerous_tool":
166
+ return BeforeToolCallResult(block=True, content="Blocked by policy")
167
+ return None
168
+
169
+ hooks = compose_middleware([LoggingMiddleware(), SafetyMiddleware()])
170
+ ```
171
+
172
+ **Composition rules:**
173
+
174
+ | Hook | Rule |
175
+ |------|------|
176
+ | `transform_context` | Chained — each receives previous result |
177
+ | `convert_to_llm` | Last implementation wins |
178
+ | `before_tool_call` | Any block stops execution |
179
+ | `after_tool_call` | Later overrides earlier |
180
+ | `should_stop_after_turn` | Any true stops |
181
+
182
+ ### Checkpointer
183
+
184
+ Persist conversation state with append-only semantics:
185
+
186
+ ```python
187
+ from cubepi.checkpointer import MemoryCheckpointer, SQLiteCheckpointer
188
+
189
+ # In-memory for dev/test
190
+ cp = MemoryCheckpointer()
191
+
192
+ # SQLite for lightweight persistence
193
+ async with SQLiteCheckpointer("agent.db") as cp:
194
+ agent = Agent(model=model, checkpointer=cp, thread_id="conv-1")
195
+ ```
196
+
197
+ ### FauxProvider for Testing
198
+
199
+ Ship your agent tests without API keys:
200
+
201
+ ```python
202
+ from cubepi.providers import FauxProvider, faux_text, faux_tool_call, faux_assistant_message
203
+
204
+ provider = FauxProvider()
205
+ provider.set_responses([
206
+ faux_assistant_message([
207
+ faux_tool_call("search", {"query": "python"}),
208
+ ]),
209
+ faux_assistant_message("Here are the results..."),
210
+ ])
211
+
212
+ agent = Agent(model=Model(provider=provider, model="test"), tools=[search_tool])
213
+ stream = await agent.prompt("Search for python")
214
+ # Streams realistic deltas — content_block_start, text_delta, etc.
215
+ ```
216
+
217
+ ## Requirements
218
+
219
+ - Python >= 3.11
220
+ - Core: `pydantic`, `anthropic`, `openai`
221
+ - Optional: `aiosqlite` (for `SQLiteCheckpointer`)
222
+
223
+ ## License
224
+
225
+ MIT
@@ -0,0 +1,48 @@
1
+ """cubepi — Pythonic async-native agent framework."""
2
+
3
+ from cubepi.agent import (
4
+ Agent,
5
+ AgentState,
6
+ AgentTool,
7
+ AgentToolResult,
8
+ run_agent_loop,
9
+ run_agent_loop_continue,
10
+ )
11
+ from cubepi.middleware import Middleware, compose_middleware
12
+ from cubepi.providers import (
13
+ AssistantMessage,
14
+ Message,
15
+ MessageStream,
16
+ Model,
17
+ Provider,
18
+ StreamEvent,
19
+ TextContent,
20
+ ThinkingLevel,
21
+ ToolCall,
22
+ ToolDefinition,
23
+ ToolResultMessage,
24
+ UserMessage,
25
+ )
26
+
27
+ __all__ = [
28
+ "Agent",
29
+ "AgentState",
30
+ "AgentTool",
31
+ "AgentToolResult",
32
+ "AssistantMessage",
33
+ "Message",
34
+ "MessageStream",
35
+ "Middleware",
36
+ "Model",
37
+ "Provider",
38
+ "StreamEvent",
39
+ "TextContent",
40
+ "ThinkingLevel",
41
+ "ToolCall",
42
+ "ToolDefinition",
43
+ "ToolResultMessage",
44
+ "UserMessage",
45
+ "compose_middleware",
46
+ "run_agent_loop",
47
+ "run_agent_loop_continue",
48
+ ]