curry-leaves 1.0.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 (94) hide show
  1. curry_leaves-1.0.0/.gitignore +34 -0
  2. curry_leaves-1.0.0/CHANGELOG.md +41 -0
  3. curry_leaves-1.0.0/CONTRIBUTING.md +166 -0
  4. curry_leaves-1.0.0/LICENSE +21 -0
  5. curry_leaves-1.0.0/PKG-INFO +392 -0
  6. curry_leaves-1.0.0/README.md +357 -0
  7. curry_leaves-1.0.0/assets/logo.png +0 -0
  8. curry_leaves-1.0.0/examples/01_basic.py +47 -0
  9. curry_leaves-1.0.0/examples/02_streaming.py +63 -0
  10. curry_leaves-1.0.0/examples/03_custom_tool.py +96 -0
  11. curry_leaves-1.0.0/examples/04_structured_output.py +71 -0
  12. curry_leaves-1.0.0/examples/05_subagents.py +66 -0
  13. curry_leaves-1.0.0/examples/06_host_and_permissions.py +120 -0
  14. curry_leaves-1.0.0/examples/07_mcp_tools.py +120 -0
  15. curry_leaves-1.0.0/publish.sh +106 -0
  16. curry_leaves-1.0.0/pyproject.toml +67 -0
  17. curry_leaves-1.0.0/src/curry_leaves/__init__.py +362 -0
  18. curry_leaves-1.0.0/src/curry_leaves/agents.py +103 -0
  19. curry_leaves-1.0.0/src/curry_leaves/catalog.py +222 -0
  20. curry_leaves-1.0.0/src/curry_leaves/cli/__init__.py +0 -0
  21. curry_leaves-1.0.0/src/curry_leaves/cli/chat.py +313 -0
  22. curry_leaves-1.0.0/src/curry_leaves/cli/host.py +55 -0
  23. curry_leaves-1.0.0/src/curry_leaves/cli/render.py +132 -0
  24. curry_leaves-1.0.0/src/curry_leaves/cli/theme.py +24 -0
  25. curry_leaves-1.0.0/src/curry_leaves/cli/tui/__init__.py +0 -0
  26. curry_leaves-1.0.0/src/curry_leaves/cli/tui/app.py +564 -0
  27. curry_leaves-1.0.0/src/curry_leaves/cli/tui/controller.py +135 -0
  28. curry_leaves-1.0.0/src/curry_leaves/cli/tui/figlet.py +33 -0
  29. curry_leaves-1.0.0/src/curry_leaves/cli/tui/host.py +66 -0
  30. curry_leaves-1.0.0/src/curry_leaves/cli/tui/logo_image.py +52 -0
  31. curry_leaves-1.0.0/src/curry_leaves/cli/tui/state.py +389 -0
  32. curry_leaves-1.0.0/src/curry_leaves/cli/tui/widgets.py +370 -0
  33. curry_leaves-1.0.0/src/curry_leaves/compaction.py +216 -0
  34. curry_leaves-1.0.0/src/curry_leaves/core/__init__.py +0 -0
  35. curry_leaves-1.0.0/src/curry_leaves/core/agent.py +204 -0
  36. curry_leaves-1.0.0/src/curry_leaves/core/blobs.py +44 -0
  37. curry_leaves-1.0.0/src/curry_leaves/core/events.py +291 -0
  38. curry_leaves-1.0.0/src/curry_leaves/core/host.py +115 -0
  39. curry_leaves-1.0.0/src/curry_leaves/core/loop.py +236 -0
  40. curry_leaves-1.0.0/src/curry_leaves/core/messages.py +228 -0
  41. curry_leaves-1.0.0/src/curry_leaves/core/tools.py +395 -0
  42. curry_leaves-1.0.0/src/curry_leaves/mcp/__init__.py +41 -0
  43. curry_leaves-1.0.0/src/curry_leaves/mcp/client.py +151 -0
  44. curry_leaves-1.0.0/src/curry_leaves/mcp/config.py +132 -0
  45. curry_leaves-1.0.0/src/curry_leaves/mcp/manager.py +122 -0
  46. curry_leaves-1.0.0/src/curry_leaves/mcp/pick.py +50 -0
  47. curry_leaves-1.0.0/src/curry_leaves/mcp/server.py +183 -0
  48. curry_leaves-1.0.0/src/curry_leaves/mcp/tool.py +144 -0
  49. curry_leaves-1.0.0/src/curry_leaves/permission.py +193 -0
  50. curry_leaves-1.0.0/src/curry_leaves/presets.py +45 -0
  51. curry_leaves-1.0.0/src/curry_leaves/prompt.py +177 -0
  52. curry_leaves-1.0.0/src/curry_leaves/providers/__init__.py +0 -0
  53. curry_leaves-1.0.0/src/curry_leaves/providers/anthropic.py +297 -0
  54. curry_leaves-1.0.0/src/curry_leaves/providers/base.py +174 -0
  55. curry_leaves-1.0.0/src/curry_leaves/providers/factory.py +66 -0
  56. curry_leaves-1.0.0/src/curry_leaves/providers/openai.py +320 -0
  57. curry_leaves-1.0.0/src/curry_leaves/providers/sse.py +45 -0
  58. curry_leaves-1.0.0/src/curry_leaves/py.typed +0 -0
  59. curry_leaves-1.0.0/src/curry_leaves/runner.py +701 -0
  60. curry_leaves-1.0.0/src/curry_leaves/session/__init__.py +17 -0
  61. curry_leaves-1.0.0/src/curry_leaves/session/store.py +303 -0
  62. curry_leaves-1.0.0/src/curry_leaves/settings.py +175 -0
  63. curry_leaves-1.0.0/src/curry_leaves/skills.py +118 -0
  64. curry_leaves-1.0.0/src/curry_leaves/thinking.py +135 -0
  65. curry_leaves-1.0.0/src/curry_leaves/tools/__init__.py +0 -0
  66. curry_leaves-1.0.0/src/curry_leaves/tools/ask.py +51 -0
  67. curry_leaves-1.0.0/src/curry_leaves/tools/bash.py +120 -0
  68. curry_leaves-1.0.0/src/curry_leaves/tools/current_time.py +58 -0
  69. curry_leaves-1.0.0/src/curry_leaves/tools/edit.py +98 -0
  70. curry_leaves-1.0.0/src/curry_leaves/tools/find.py +74 -0
  71. curry_leaves-1.0.0/src/curry_leaves/tools/glob.py +78 -0
  72. curry_leaves-1.0.0/src/curry_leaves/tools/read.py +84 -0
  73. curry_leaves-1.0.0/src/curry_leaves/tools/search.py +107 -0
  74. curry_leaves-1.0.0/src/curry_leaves/tools/search_tools.py +50 -0
  75. curry_leaves-1.0.0/src/curry_leaves/tools/task.py +58 -0
  76. curry_leaves-1.0.0/src/curry_leaves/tools/tasks.py +241 -0
  77. curry_leaves-1.0.0/src/curry_leaves/tools/transfer.py +53 -0
  78. curry_leaves-1.0.0/src/curry_leaves/tools/web.py +146 -0
  79. curry_leaves-1.0.0/src/curry_leaves/tools/write.py +55 -0
  80. curry_leaves-1.0.0/src/curry_leaves/util/__init__.py +0 -0
  81. curry_leaves-1.0.0/src/curry_leaves/util/frontmatter.py +21 -0
  82. curry_leaves-1.0.0/src/curry_leaves/util/paths.py +103 -0
  83. curry_leaves-1.0.0/src/curry_leaves/util/resources.py +61 -0
  84. curry_leaves-1.0.0/src/curry_leaves/util/retry.py +56 -0
  85. curry_leaves-1.0.0/tests/mcp/conftest.py +54 -0
  86. curry_leaves-1.0.0/tests/mcp/fixtures/echo_server.py +31 -0
  87. curry_leaves-1.0.0/tests/mcp/fixtures/http_echo_server.py +32 -0
  88. curry_leaves-1.0.0/tests/mcp/test_agent_integration.py +85 -0
  89. curry_leaves-1.0.0/tests/mcp/test_client_stdio.py +55 -0
  90. curry_leaves-1.0.0/tests/mcp/test_config.py +150 -0
  91. curry_leaves-1.0.0/tests/mcp/test_manager.py +105 -0
  92. curry_leaves-1.0.0/tests/mcp/test_pick.py +88 -0
  93. curry_leaves-1.0.0/tests/mcp/test_server.py +61 -0
  94. curry_leaves-1.0.0/tests/mcp/test_tool_adapter.py +152 -0
@@ -0,0 +1,34 @@
1
+ # Python bytecode and caches
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Build and packaging artifacts
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ .eggs/
11
+
12
+ # Virtual environments
13
+ .venv/
14
+ venv/
15
+ env/
16
+
17
+ # Test and coverage artifacts
18
+ .pytest_cache/
19
+ .coverage
20
+ .coverage.*
21
+ htmlcov/
22
+
23
+ # Type checker and linter caches
24
+ .mypy_cache/
25
+ .ruff_cache/
26
+ .pyre/
27
+
28
+ # IDE and OS files
29
+ .vscode/
30
+ .idea/
31
+ .DS_Store
32
+
33
+ # Local agent-tooling state
34
+ .claude/
@@ -0,0 +1,41 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] - 2026-07-08
9
+
10
+ ### Added
11
+
12
+ - Initial release of Curry Leaves for Python — a provider-agnostic, multi-agent
13
+ kernel for building AI agents: streaming tool-use loop, subagents, skills,
14
+ thinking, permissions, sessions, and compaction. A faithful port of the
15
+ [TypeScript kernel](https://github.com/ilayanambi-ponramu/curry-leaves-ts),
16
+ module-for-module.
17
+ - Core engine (`agent_loop`), stateful driver (`Runner`), and stateless agent
18
+ definitions (`Agent`), with agent-as-tool delegation (`task`) and one-way
19
+ handoff (`transfer`).
20
+ - Providers for Anthropic, OpenAI, and Ollama (OpenAI-compatible), with
21
+ automatic provider inference from model id.
22
+ - Tool system with pydantic schemas, risk-based permission fallback, deferred
23
+ tool discovery via `search_tools`, and blob-backed result capping.
24
+ - **MCP (Model Context Protocol) client support** — connect stdio or HTTP MCP
25
+ servers (`McpServerStdio` / `McpServerHttp` / `McpServerManager`), pick
26
+ specific tools with `mcp_tools()`, or load server definitions from
27
+ `settings.json`'s `mcpServers` key. Goes beyond the TypeScript sibling, which
28
+ lists MCP as a non-goal.
29
+ - Permission engine with per-call resolution (deny > per-tool allow > standing
30
+ approval > per-tool ask > default > risk fallback).
31
+ - Auto-thinking (reasoning-effort classifier) and auto-compaction near context
32
+ limits.
33
+ - Skills system with progressive disclosure from `~/.curry-leaves/skills/` and
34
+ `.curry-leaves/skills/`.
35
+ - Model catalog sourced from models.dev (context windows, pricing).
36
+ - Session recording to `<home>/sessions/<id>/`.
37
+ - Two bundled CLIs: a full-screen Textual TUI (`curry-leaves`, alias `curry`)
38
+ and a line REPL (`curry-leaves-repl`).
39
+ - Example scripts covering basic usage, streaming, custom tools, structured
40
+ output, subagents, host/permissions, and MCP tools.
41
+ - Test suite for the MCP subsystem (`tests/mcp`); strict `mypy` across `src/`.
@@ -0,0 +1,166 @@
1
+ # Contributing to curry-leaves
2
+
3
+ Thanks for your interest in improving **curry-leaves** — bug reports, features, docs, and tests are all
4
+ welcome. This guide covers how to get set up, the conventions that keep the codebase coherent, and
5
+ how to extend the kernel.
6
+
7
+ By participating, you agree to keep interactions respectful and constructive.
8
+
9
+ ## Table of contents
10
+
11
+ - [Ways to contribute](#ways-to-contribute)
12
+ - [Development setup](#development-setup)
13
+ - [Project layout](#project-layout)
14
+ - [Pull request workflow](#pull-request-workflow)
15
+ - [Code conventions](#code-conventions)
16
+ - [Extending the kernel](#extending-the-kernel)
17
+ - [Commit messages](#commit-messages)
18
+ - [Reporting bugs](#reporting-bugs)
19
+ - [License](#license)
20
+
21
+ ## Ways to contribute
22
+
23
+ - **Report a bug** — open an issue with a minimal reproduction (see [Reporting bugs](#reporting-bugs)).
24
+ - **Propose a feature** — open an issue describing the use case *before* writing code, so we can
25
+ agree on the approach.
26
+ - **Improve docs** — READMEs, code comments, and examples all count.
27
+ - **Add tests** — the MCP subsystem has a suite under `tests/mcp`; extending coverage to the core
28
+ loop, providers, and tools is a high-value contribution.
29
+
30
+ ## Development setup
31
+
32
+ Requires **Python 3.11+**.
33
+
34
+ ```bash
35
+ git clone https://github.com/ilayanambi-ponramu/curry-leaves-py.git
36
+ cd curry-leaves-py
37
+ python3 -m venv .venv && source .venv/bin/activate
38
+ pip install -e ".[dev]"
39
+ ```
40
+
41
+ Common commands:
42
+
43
+ | Command | What it does |
44
+ |---|---|
45
+ | `mypy src` | Strict type check — **the correctness gate** |
46
+ | `pytest` | Run the test suite (`tests/`) |
47
+ | `curry-leaves` | Launch the full-screen Textual TUI (needs a TTY) |
48
+ | `curry-leaves-repl` | Launch the line REPL (works with piped input) |
49
+ | `python3 examples/01_basic.py "..."` | Run an example end-to-end |
50
+
51
+ To exercise a real turn you need a provider key:
52
+
53
+ ```bash
54
+ export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY=sk-...
55
+ curry-leaves
56
+ ```
57
+
58
+ Or run fully local with [Ollama](https://ollama.com) — no key required:
59
+
60
+ ```bash
61
+ ollama pull qwen3
62
+ CURRY_LEAVES_MODEL=qwen3 curry-leaves-repl
63
+ ```
64
+
65
+ > **Testing status:** `mypy src` under `strict` is the gate every change must pass, plus `pytest`
66
+ > for the MCP suite. Providers are written so their request builders and stream parsers are plain
67
+ > module functions (not client methods) specifically to be unit-testable — contributions that add
68
+ > a test suite around them are especially welcome.
69
+
70
+ ## Project layout
71
+
72
+ ```
73
+ src/curry_leaves/
74
+ core/ # messages, events, loop, tools, agent, host, blobs — the kernel
75
+ providers/ # anthropic, openai/ollama, factory, sse, base
76
+ tools/ # read, write, edit, find, search, bash, tasks, ask, web, …
77
+ mcp/ # MCP client: stdio/HTTP servers, manager, settings loader, tool adapter
78
+ session/ # session store + recording
79
+ cli/ # chat REPL + Textual TUI
80
+ util/ # paths, retry, frontmatter, resources
81
+ runner.py prompt.py permission.py thinking.py skills.py compaction.py catalog.py settings.py
82
+ __init__.py # public API surface
83
+ examples/ # runnable examples
84
+ tests/ # pytest suite (MCP subsystem)
85
+ ```
86
+
87
+ The architecture is a strict layering — a **stateless definition** (`Agent`) on a **stateful
88
+ driver** (`Runner`) on a **pure engine** (`agent_loop`) — with all I/O pushed to swappable seams
89
+ (`Provider`, `Host`, `Tool`). Keep that separation in mind: behavior is added at a seam, not by
90
+ branching inside the loop. See the [README architecture section](./README.md#architecture) for the
91
+ big picture.
92
+
93
+ ## Pull request workflow
94
+
95
+ 1. **Open an issue first** for anything non-trivial, so we can agree on scope and approach.
96
+ 2. **Fork and branch** off `main`:
97
+ ```bash
98
+ git checkout -b feat/short-description # or fix/… , docs/… , test/…
99
+ ```
100
+ 3. **Make the change.** Keep the diff focused on one concern.
101
+ 4. **Verify it passes:**
102
+ ```bash
103
+ mypy src && pytest
104
+ ```
105
+ For a change with a runtime surface, also drive the affected flow (a TUI turn, an example, the
106
+ REPL) and confirm it behaves as intended — don't rely on typecheck alone.
107
+ 5. **Update docs** (`README.md`, code comments, `examples/`) when behavior or the public API changes.
108
+ 6. **Open a pull request** with a clear description of *what* changed and *why*. Link the issue.
109
+
110
+ Keep PRs small and reviewable. A large PR is easier to land when split into focused commits or
111
+ separate PRs.
112
+
113
+ ## Code conventions
114
+
115
+ - **`src/curry_leaves/__init__.py` is the public API.** When you add an exported symbol consumers
116
+ should see, re-export it there and add it to `__all__`.
117
+ - **Keep new source under `src/curry_leaves/`** — the package uses a `src/` layout.
118
+ - **Match the surrounding style** — small, single-purpose modules; clear names; comments that explain
119
+ *why*, not *what*. Prefer boring, direct solutions.
120
+ - **Type strictly.** `mypy --strict` is on and must stay green. No new `Any` where a real type fits;
121
+ public functions carry full annotations.
122
+ - **Tools describe their `risk`** (`read` / `write` / `exec` / `network`) — it drives the permission
123
+ fallback, so set it accurately.
124
+ - **Complete the change** — types, docs, and (where practical) a runnable check, not just the happy path.
125
+
126
+ ## Extending the kernel
127
+
128
+ Common extension points, each done at a seam rather than by editing the loop:
129
+
130
+ - **Add a tool** — implement the `Tool` protocol (a pydantic args model + `run`), add a factory under
131
+ `src/curry_leaves/tools/`, export it from `__init__.py`, and add it to a preset in `presets.py` if
132
+ it belongs in the default kit. Set `risk` correctly.
133
+ - **Add a provider** — implement `Provider.stream` in `src/curry_leaves/providers/`, keep all
134
+ wire-format translation at that edge (nowhere else), and register it in `providers/factory.py`.
135
+ - **Add a frontend capability** — add a `Request` kind in `src/curry_leaves/core/host.py` (with a
136
+ default value), **not** a new `Host` method — so headless hosts keep working by returning the
137
+ default.
138
+ - **Connect an MCP server** — usually no code change needed: construct `McpServerStdio`/`McpServerHttp`
139
+ (or declare it in `settings.json`'s `mcpServers`) and pick tools with `mcp_tools()`. Framework-level
140
+ MCP changes live under `src/curry_leaves/mcp/` and are covered by `tests/mcp`.
141
+
142
+ ## Commit messages
143
+
144
+ Use short, imperative summaries. Conventional-commit prefixes are appreciated but not required:
145
+
146
+ ```
147
+ feat: add web_search deferred tool
148
+ fix: retry only on transient provider errors
149
+ docs: clarify Ollama setup
150
+ test: cover the anthropic stream parser
151
+ ```
152
+
153
+ ## Reporting bugs
154
+
155
+ Open an issue that includes:
156
+
157
+ - What you did (a minimal code snippet or the exact CLI command).
158
+ - What you expected vs. what happened (include the full error / stack).
159
+ - Your environment: `python --version`, curry-leaves version, provider + model id, OS.
160
+
161
+ A minimal reproduction is the fastest path to a fix.
162
+
163
+ ## License
164
+
165
+ By contributing, you agree that your contributions are licensed under the project's
166
+ [MIT License](./LICENSE).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ilayanambi Ponramu
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,392 @@
1
+ Metadata-Version: 2.4
2
+ Name: curry-leaves
3
+ Version: 1.0.0
4
+ Summary: Curry Leaves — a small, provider-agnostic, multi-agent kernel for building AI agents of any kind: streaming tool-use loop, sub-agents, skills, thinking. The Python port.
5
+ Project-URL: Homepage, https://github.com/ilayanambi-ponramu/curry-leaves-py
6
+ Project-URL: Repository, https://github.com/ilayanambi-ponramu/curry-leaves-py
7
+ Project-URL: Issues, https://github.com/ilayanambi-ponramu/curry-leaves-py/issues
8
+ Project-URL: Changelog, https://github.com/ilayanambi-ponramu/curry-leaves-py/blob/main/CHANGELOG.md
9
+ Author: Ilayanambi Ponramu
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent,ai-agent,anthropic,coding-agent,llm,mcp,multi-agent,ollama,openai,subagents,tool-use
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
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 :: Python Modules
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: httpx>=0.27
24
+ Requires-Dist: mcp>=1.9
25
+ Requires-Dist: pydantic>=2.7
26
+ Requires-Dist: rich>=13.7
27
+ Requires-Dist: textual>=0.58
28
+ Provides-Extra: dev
29
+ Requires-Dist: build>=1.2; extra == 'dev'
30
+ Requires-Dist: mypy>=1.10; extra == 'dev'
31
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
32
+ Requires-Dist: pytest>=8.2; extra == 'dev'
33
+ Requires-Dist: twine>=5.0; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ <p align="center">
37
+ <img src="assets/logo.png" alt="Curry Leaves logo" width="128" height="128">
38
+ </p>
39
+
40
+ <h1 align="center">Curry Leaves Agent Loop</h1>
41
+
42
+ <p align="center">A small, provider-agnostic, multi-agent kernel for building AI agents of any kind — in clean, readable Python.</p>
43
+
44
+ <p align="center">
45
+ <a href="https://pypi.org/project/curry-leaves/"><img src="https://img.shields.io/pypi/v/curry-leaves.svg" alt="PyPI version"></a>
46
+ <a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="license: MIT"></a>
47
+ <a href="https://www.python.org"><img src="https://img.shields.io/badge/python-%3E%3D3.11-brightgreen.svg" alt="python: >=3.11"></a>
48
+ <a href="./src/curry_leaves/py.typed"><img src="https://img.shields.io/badge/types-included-blue.svg" alt="types included"></a>
49
+ <a href="https://github.com/ilayanambi-ponramu/curry-leaves-py"><img src="https://img.shields.io/badge/github-repo-181717.svg?logo=github" alt="GitHub repo"></a>
50
+ </p>
51
+
52
+ **Curry Leaves** is a general-purpose agent kernel small enough to read in an afternoon. At its core
53
+ is a streaming tool-use loop — call the model, run the tools it asks for, feed the results back,
54
+ repeat — with everything a real agent needs built around it: **sub-agents**, **skills**, **MCP
55
+ tools**, **permission gating**, **session recording**, automatic **context compaction**, and
56
+ per-turn **reasoning-effort sizing**. Point it at any tools and any task; the kernel is
57
+ domain-agnostic — it just happens to ship a batteries-included coding toolset and CLIs on top.
58
+
59
+ One engine, any provider, any UI. The loop knows nothing about a specific LLM wire format — each
60
+ provider translates at its own boundary — so Anthropic, OpenAI, and Ollama are a *config choice, not
61
+ branching logic*. Use it as a **library** (below), or launch either bundled CLI: a full-screen
62
+ terminal UI or a line REPL.
63
+
64
+ ```python
65
+ from curry_leaves import Agent, Runner, coding_tools
66
+
67
+ agent = Agent(model="claude-sonnet-4-5", tools=coding_tools())
68
+ result = await Runner(agent).run("Summarize README.md in three bullets.")
69
+ print(result.output_text)
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Table of contents
75
+
76
+ - [Features](#features)
77
+ - [Install](#install)
78
+ - [Quick start](#quick-start)
79
+ - [Configure a provider](#configure-a-provider)
80
+ - [Terminal UI & REPL](#terminal-ui--repl)
81
+ - [Library usage](#library-usage)
82
+ - [Environment variables](#environment-variables)
83
+ - [Architecture](#architecture)
84
+ - [Project layout](#project-layout)
85
+ - [Development](#development)
86
+ - [Contributing](#contributing)
87
+ - [Non-goals](#non-goals)
88
+ - [Acknowledgements](#acknowledgements)
89
+ - [License](#license)
90
+
91
+ ## Features
92
+
93
+ - **Multi-provider.** Anthropic, OpenAI, and Ollama (any OpenAI-compatible gateway) implement one
94
+ `Provider.stream()` interface; the loop never changes. Streaming SSE is assembled into a neutral
95
+ `AssistantMessage` at the provider boundary and nowhere else.
96
+ - **Rich toolset.** `read`, `write`, `edit`, `find`, `search`, `bash`,
97
+ `task_create`/`task_update`/`task_list`/`task_get`, `ask`, `current_time`, `web_fetch`,
98
+ `web_search`. Tool args are [pydantic](https://docs.pydantic.dev) models → JSON Schema for free.
99
+ Oversized output is offloaded to an artifact store the model can page through with `read`.
100
+ - **MCP tools.** Connect any [Model Context Protocol](https://modelcontextprotocol.io) server —
101
+ stdio subprocess or HTTP — and pick specific tools by name with `mcp_tools()`; the result is a
102
+ plain `list[Tool]` spliced into `Agent(tools=[...])` like any preset. Servers can also be declared
103
+ in `settings.json`'s `mcpServers` key. MCP tools default to `risk="exec"`, so they always go
104
+ through the permission gate.
105
+ - **Deferred tools + `search_tools`.** Keep the advertised list lean; the model discovers more
106
+ tools by keyword and activates them for the next turn — works across providers.
107
+ - **Sub-agents.** Declare `subagents=[...]`; the parent gets a `task` tool (delegation that returns
108
+ a result) and a `transfer` tool (one-way handoff). Bounded recursion depth.
109
+ - **Structured output.** Give an agent an `output_type` (a pydantic model); the Runner injects the
110
+ schema, validates the final reply, and retries on mismatch. `result.output` is typed.
111
+ - **Auto-thinking.** `auto_thinking=True` sizes reasoning effort (Anthropic thinking budget /
112
+ OpenAI reasoning effort) per turn with a cheap classifier.
113
+ - **Skills.** Drop a `SKILL.md` under `.curry-leaves/skills/<name>/`; its teaser goes into the prompt and
114
+ the model pulls the full body via `read skill://<name>` only when relevant (progressive disclosure).
115
+ - **Permissions.** An opt-in gate authorizes each tool call (`allow` / `ask` / `deny`), with standing
116
+ approvals and contained-change auto-approval. Off by default — headless runs never hang.
117
+ - **Sessions.** Each run can be recorded to `<home>/sessions/<id>/` (`meta.json` + `transcript.jsonl`).
118
+ - **Compaction.** Long conversations are summarized as they near the context window — automatic, or
119
+ on demand via `Runner.compact()` / the `/compact` command.
120
+ - **Typed, async, dependency-light.** Python 3.11+, `mypy --strict`, ships `py.typed`. The library
121
+ core rides on `pydantic` + `httpx`; the CLIs add [Textual](https://textual.textualize.io) + Rich,
122
+ and MCP support uses the official `mcp` SDK.
123
+
124
+ ## Install
125
+
126
+ ```bash
127
+ pip install curry-leaves
128
+ ```
129
+
130
+ Then launch a CLI:
131
+
132
+ ```bash
133
+ curry-leaves # full-screen terminal UI (alias: curry)
134
+ curry-leaves-repl # line REPL (works with piped input)
135
+ ```
136
+
137
+ Requires **Python 3.11+**.
138
+
139
+ ## Quick start
140
+
141
+ ```python
142
+ import asyncio
143
+ from curry_leaves import Agent, Runner, coding_tools
144
+
145
+ async def main() -> None:
146
+ agent = Agent(
147
+ model="claude-sonnet-4-5",
148
+ instructions="You are a concise coding assistant.",
149
+ tools=coding_tools(),
150
+ )
151
+ result = await Runner(agent).run("What does src/curry_leaves/runner.py do?")
152
+ print(result.output_text)
153
+
154
+ asyncio.run(main())
155
+ ```
156
+
157
+ `Agent` is a stateless **definition** (model, tools, instructions); `Runner` holds the live
158
+ conversation and drives the streaming loop. Set an API key first (see below).
159
+
160
+ ## Configure a provider
161
+
162
+ ```bash
163
+ export ANTHROPIC_API_KEY=sk-ant-... # or
164
+ export OPENAI_API_KEY=sk-...
165
+ ```
166
+
167
+ The provider is inferred from the model id (`claude-*` → Anthropic, `gpt-*`/`o1-*` → OpenAI,
168
+ `gemma*`/`llama*`/`qwen*`/… → Ollama), or set `CURRY_LEAVES_PROVIDER` / pass an explicit `provider`
169
+ to the `Agent`.
170
+
171
+ ### Local models via Ollama
172
+
173
+ Ollama speaks the OpenAI wire format, so any pulled tag works with no API key:
174
+
175
+ ```bash
176
+ ollama pull qwen3
177
+ CURRY_LEAVES_MODEL=qwen3 curry-leaves-repl
178
+ ```
179
+
180
+ ```python
181
+ from curry_leaves import Agent, Runner, coding_tools
182
+ agent = Agent(model="qwen3", tools=coding_tools()) # provider → Ollama
183
+ ```
184
+
185
+ Set `OLLAMA_HOST` to point at a non-default server (`http://host:port`). Tool use needs a model with
186
+ the `tools` capability (qwen3, llama3.x, gemma3, …). Reasoning-effort knobs are dropped for Ollama,
187
+ and usage cost is `$0` (local). You can point the same `OpenAIProvider` at any OpenAI-compatible
188
+ gateway via `OPENAI_BASE_URL` or explicit provider options.
189
+
190
+ ## Terminal UI & REPL
191
+
192
+ A full-screen terminal UI (built on [Textual](https://textual.textualize.io)) ships with the
193
+ package — a header bar, a streaming transcript, live thinking blocks, spinner-tracked tool calls,
194
+ indented sub-agent activity, a status bar, and a persistent input box. Finished turns land in real
195
+ terminal scrollback; the active turn streams in place.
196
+
197
+ ```bash
198
+ curry-leaves
199
+ ```
200
+
201
+ ```
202
+ ╭──────────────────────────────────────────────────────────╮
203
+ │ curry-leaves · claude-sonnet-4-5 (anthropic) │
204
+ │ /repo · 11 tools · subagents: explore, plan │
205
+ ╰──────────────────────────────────────────────────────────╯
206
+
207
+ you › what does src/curry_leaves/runner.py do?
208
+ ai ›
209
+ → read({"path":"src/curry_leaves/runner.py"})
210
+ 1 """The Runner — holds one live conversation … """
211
+ The Runner composes an Agent with conversation state and drives the loop …
212
+
213
+ ● ready in 4213 · out 187 · $0.0155
214
+ ╭──────────────────────────────────────────────────────────╮
215
+ │ › ask anything — /help for commands │
216
+ ╰──────────────────────────────────────────────────────────╯
217
+ ```
218
+
219
+ **Slash commands:** `/help`, `/reset`, `/tools`, `/skills`, `/model`, `/stats`, `/clear`,
220
+ `/compact [focus]`, `/auto` (toggle contained-change auto-approve), `/autonomous` (toggle self-drive
221
+ mode), `/exit`. The TUI needs an interactive terminal (a TTY).
222
+
223
+ The line-streaming REPL is available as `curry-leaves-repl` — handy for piped / non-TTY input.
224
+
225
+ ## Library usage
226
+
227
+ ### Stream events instead of awaiting
228
+
229
+ ```python
230
+ async for event in runner.stream("Refactor the parser"):
231
+ if event.type == "message_update" and event.delta and event.delta.kind == "text":
232
+ print(event.delta.value, end="", flush=True)
233
+ ```
234
+
235
+ ### Structured output
236
+
237
+ ```python
238
+ import pydantic
239
+ from curry_leaves import Agent, Runner
240
+
241
+ class Summary(pydantic.BaseModel):
242
+ title: str
243
+ bullets: list[str]
244
+
245
+ agent = Agent(model="claude-sonnet-4-5", output_type=Summary)
246
+
247
+ result = await Runner(agent).run("Summarize this repo.")
248
+ report = result.output # validated Summary instance; the Runner retries on a schema mismatch
249
+ ```
250
+
251
+ ### Sub-agents
252
+
253
+ ```python
254
+ from curry_leaves import Agent, Runner, coding_tools, explore_agent, plan_agent
255
+
256
+ agent = Agent(
257
+ model="claude-sonnet-4-5",
258
+ tools=coding_tools(),
259
+ subagents=[explore_agent("claude-sonnet-4-5"), plan_agent("claude-sonnet-4-5")],
260
+ )
261
+ # The parent can `task` (delegate → result) or `transfer` (one-way handoff) to these.
262
+ ```
263
+
264
+ ### MCP tools
265
+
266
+ ```python
267
+ from curry_leaves import Agent, Runner, coding_tools
268
+ from curry_leaves.mcp import McpServerStdio, mcp_tools
269
+
270
+ async with McpServerStdio(name="github", command="npx", args=["-y", "@modelcontextprotocol/server-github"]) as gh:
271
+ agent = Agent(
272
+ model="claude-sonnet-4-5",
273
+ tools=[*coding_tools(), *await mcp_tools(gh, "search_issues")],
274
+ )
275
+ result = await Runner(agent).run("Find open issues about streaming.")
276
+ ```
277
+
278
+ Or declare servers once in `.curry-leaves/settings.json` under `mcpServers` and load them with
279
+ `load_mcp_servers()` + `McpServerManager`. See
280
+ [`examples/07_mcp_tools.py`](examples/07_mcp_tools.py) for a complete, self-contained program.
281
+
282
+ See [`examples/01_basic.py`](examples/01_basic.py) for the smallest runnable program, and
283
+ [`examples/06_host_and_permissions.py`](examples/06_host_and_permissions.py) for a self-contained
284
+ illustration of the host / permission model.
285
+
286
+ ## Environment variables
287
+
288
+ | Variable | Purpose | Default |
289
+ |---|---|---|
290
+ | `ANTHROPIC_API_KEY` | Anthropic auth | — |
291
+ | `OPENAI_API_KEY` | OpenAI auth | — |
292
+ | `CURRY_LEAVES_MODEL` | Model id for the CLIs | auto-detected |
293
+ | `CURRY_LEAVES_PROVIDER` | Force a provider (`anthropic` \| `openai` \| `ollama`) | inferred from model id |
294
+ | `CURRY_LEAVES_HOME` | Base dir for settings / skills / sessions | `~/.curry-leaves` |
295
+ | `CURRY_LEAVES_NO_RECORD` | Disable session recording when set | recording on |
296
+ | `OPENAI_BASE_URL` | Point `OpenAIProvider` at any OpenAI-compatible gateway | `https://api.openai.com/v1` |
297
+ | `OLLAMA_HOST` | Ollama server URL | `http://localhost:11434` |
298
+ | `NO_COLOR` | Disable ANSI color | color on |
299
+
300
+ ## Architecture
301
+
302
+ The design is a strict layering — a **stateless definition** on a **stateful driver** on a **pure
303
+ engine** — with all I/O pushed to swappable seams (Provider, Host, Tool).
304
+
305
+ | Layer | File | Responsibility |
306
+ |---|---|---|
307
+ | **Message model** | `core/messages.py` | Provider-neutral `Message`/`Content` types — the one thing everything agrees on. |
308
+ | **Events** | `core/events.py` | What the loop yields; a small structural set + a streaming `delta` payload. |
309
+ | **Loop** | `core/loop.py` | The pure engine: `stream → run tools → stream`, while tools are called. Yields events. |
310
+ | **Tools** | `core/tools.py` | A registry of pydantic-typed tools + a concurrent executor with a universal large-result guard. |
311
+ | **Agent** | `core/agent.py` | A stateless definition (model, tools, instructions, sub-agents, `output_type`). |
312
+ | **Runner** | `runner.py` | Live conversation state; builds the `Context` each turn; wires sub-agents, handoff, permissions, compaction. |
313
+ | **Providers** | `providers/*` | The only place that knows a wire format. Anthropic / OpenAI / Ollama behind one `Provider`. |
314
+ | **MCP** | `mcp/*` | MCP client: stdio/HTTP server connections, a manager, a settings loader, and a tool adapter. |
315
+ | **Host** | `core/host.py` | The frontend seam: `emit(event)` + `request(req)`. Headless by default. |
316
+ | **Prompt** | `prompt.py` | Layered system prompt (identity → instructions → env → context → tools), cache-friendly. |
317
+ | **Permission** | `permission.py` | Per-call `allow` / `ask` / `deny` gate with standing approvals. |
318
+ | **Thinking** | `thinking.py` | A tiny classifier that sizes reasoning effort per task. |
319
+ | **Skills** | `skills.py` | Progressive-disclosure capability packages via `skill://` refs. |
320
+ | **Compaction** | `compaction.py` | Summarizes old history as the context window fills. |
321
+
322
+ The one decision that drives the whole loop:
323
+
324
+ ```
325
+ runnable = stop_reason in ("tool_use", "stop") AND tool_calls exist
326
+ ```
327
+
328
+ Tools were called → loop again. None → stop.
329
+
330
+ ## Project layout
331
+
332
+ ```
333
+ src/curry_leaves/
334
+ core/ # messages, events, loop, tools, agent, host, blobs — the kernel
335
+ providers/ # anthropic, openai/ollama, factory, sse, base
336
+ tools/ # read, write, edit, find, search, bash, tasks, ask, web, …
337
+ mcp/ # MCP client: servers, manager, config, tool adapter
338
+ session/ # session store + recording
339
+ cli/ # chat REPL + Textual TUI
340
+ util/ # paths, retry, frontmatter, resources
341
+ runner.py prompt.py permission.py thinking.py skills.py compaction.py catalog.py settings.py
342
+ __init__.py # public API surface
343
+ examples/ # runnable examples
344
+ tests/ # pytest suite (MCP subsystem)
345
+ ```
346
+
347
+ ## Development
348
+
349
+ ```bash
350
+ git clone https://github.com/ilayanambi-ponramu/curry-leaves-py.git
351
+ cd curry-leaves-py
352
+ python3 -m venv .venv && source .venv/bin/activate
353
+ pip install -e ".[dev]"
354
+
355
+ mypy src # strict type check (the correctness gate)
356
+ pytest # run the test suite
357
+ curry-leaves # launch the Textual TUI (needs a TTY)
358
+ curry-leaves-repl # launch the REPL (works with piped input)
359
+ python3 examples/01_basic.py "What is this project?"
360
+ ```
361
+
362
+ `mypy --strict` is the gate every change must pass; `pytest` covers the MCP subsystem.
363
+ Contributions extending the test suite are welcome.
364
+
365
+ ## Contributing
366
+
367
+ Contributions are very welcome — bug reports, features, docs, and tests. In short:
368
+
369
+ 1. **Open an issue first** for anything non-trivial, so we can agree on the approach.
370
+ 2. **Fork & branch** off `main` (`git checkout -b feat/short-description`).
371
+ 3. **Make the change**, keep the diff focused, and ensure `mypy src` and `pytest` pass.
372
+ 4. **Open a pull request** describing the what and why.
373
+
374
+ See **[CONTRIBUTING.md](./CONTRIBUTING.md)** for the full guide — dev setup, code conventions, how to
375
+ add a tool / provider / frontend capability, commit style, and bug-reporting.
376
+
377
+ ## Non-goals
378
+
379
+ curry-leaves is deliberately small. It does **not** include LSP integration, vector stores, or a
380
+ plugin marketplace. If you need those, they belong in a layer built on top of the kernel, not inside
381
+ it. (Unlike the TypeScript sibling, MCP *is* included here — as a thin client layer that feeds the
382
+ existing `Tool` seam, not a change to the kernel.)
383
+
384
+ ## Acknowledgements
385
+
386
+ curry-leaves for Python is the sibling of
387
+ [curry-leaves-ts](https://github.com/ilayanambi-ponramu/curry-leaves-ts), a TypeScript kernel of the
388
+ same design; the two ports mirror each other module-for-module.
389
+
390
+ ## License
391
+
392
+ [MIT](./LICENSE) © Ilayanambi Ponramu