alpineagents 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 (103) hide show
  1. alpineagents-0.1.0/.github/workflows/ci.yml +29 -0
  2. alpineagents-0.1.0/.github/workflows/docs.yml +40 -0
  3. alpineagents-0.1.0/.gitignore +10 -0
  4. alpineagents-0.1.0/LICENSE +21 -0
  5. alpineagents-0.1.0/PKG-INFO +200 -0
  6. alpineagents-0.1.0/README.md +161 -0
  7. alpineagents-0.1.0/docs/api/agent.md +7 -0
  8. alpineagents-0.1.0/docs/api/errors.md +19 -0
  9. alpineagents-0.1.0/docs/api/io.md +9 -0
  10. alpineagents-0.1.0/docs/api/loops.md +17 -0
  11. alpineagents-0.1.0/docs/api/models.md +9 -0
  12. alpineagents-0.1.0/docs/api/state.md +7 -0
  13. alpineagents-0.1.0/docs/api/testing.md +9 -0
  14. alpineagents-0.1.0/docs/api/tools.md +11 -0
  15. alpineagents-0.1.0/docs/api/types.md +37 -0
  16. alpineagents-0.1.0/docs/concepts/agent.md +99 -0
  17. alpineagents-0.1.0/docs/concepts/errors.md +79 -0
  18. alpineagents-0.1.0/docs/concepts/glossary.md +26 -0
  19. alpineagents-0.1.0/docs/concepts/loops.md +101 -0
  20. alpineagents-0.1.0/docs/concepts/overview.md +78 -0
  21. alpineagents-0.1.0/docs/concepts/state.md +126 -0
  22. alpineagents-0.1.0/docs/concepts/tools.md +104 -0
  23. alpineagents-0.1.0/docs/guides/approval.md +41 -0
  24. alpineagents-0.1.0/docs/guides/async.md +46 -0
  25. alpineagents-0.1.0/docs/guides/chat.md +23 -0
  26. alpineagents-0.1.0/docs/guides/context-size.md +45 -0
  27. alpineagents-0.1.0/docs/guides/mcp.md +51 -0
  28. alpineagents-0.1.0/docs/guides/models.md +64 -0
  29. alpineagents-0.1.0/docs/guides/plan-first.md +29 -0
  30. alpineagents-0.1.0/docs/guides/progress.md +99 -0
  31. alpineagents-0.1.0/docs/guides/stop-conditions.md +56 -0
  32. alpineagents-0.1.0/docs/guides/structured-output.md +36 -0
  33. alpineagents-0.1.0/docs/guides/testing.md +64 -0
  34. alpineagents-0.1.0/docs/guides/tool-state.md +30 -0
  35. alpineagents-0.1.0/docs/guides/verify.md +25 -0
  36. alpineagents-0.1.0/docs/index.md +1 -0
  37. alpineagents-0.1.0/docs_src/approval.py +29 -0
  38. alpineagents-0.1.0/docs_src/approval_always.py +15 -0
  39. alpineagents-0.1.0/docs_src/async_agent.py +31 -0
  40. alpineagents-0.1.0/docs_src/chat.py +8 -0
  41. alpineagents-0.1.0/docs_src/coding_agent.py +39 -0
  42. alpineagents-0.1.0/docs_src/context_size.py +12 -0
  43. alpineagents-0.1.0/docs_src/custom_model.py +23 -0
  44. alpineagents-0.1.0/docs_src/human.py +14 -0
  45. alpineagents-0.1.0/docs_src/mcp_servers.py +16 -0
  46. alpineagents-0.1.0/docs_src/model_settings.py +19 -0
  47. alpineagents-0.1.0/docs_src/no_api_key.py +7 -0
  48. alpineagents-0.1.0/docs_src/plan_first.py +12 -0
  49. alpineagents-0.1.0/docs_src/quickstart.py +14 -0
  50. alpineagents-0.1.0/docs_src/reporter.py +13 -0
  51. alpineagents-0.1.0/docs_src/stop_conditions.py +12 -0
  52. alpineagents-0.1.0/docs_src/structured_output.py +18 -0
  53. alpineagents-0.1.0/docs_src/submit_tool.py +15 -0
  54. alpineagents-0.1.0/docs_src/test_example.py +29 -0
  55. alpineagents-0.1.0/docs_src/tool_object.py +23 -0
  56. alpineagents-0.1.0/docs_src/tool_state.py +14 -0
  57. alpineagents-0.1.0/docs_src/verify.py +20 -0
  58. alpineagents-0.1.0/mkdocs.yml +93 -0
  59. alpineagents-0.1.0/pyproject.toml +65 -0
  60. alpineagents-0.1.0/src/alpineagents/__init__.py +81 -0
  61. alpineagents-0.1.0/src/alpineagents/_async.py +139 -0
  62. alpineagents-0.1.0/src/alpineagents/_runner.py +552 -0
  63. alpineagents-0.1.0/src/alpineagents/_structured.py +175 -0
  64. alpineagents-0.1.0/src/alpineagents/_tokens.py +61 -0
  65. alpineagents-0.1.0/src/alpineagents/agent.py +1196 -0
  66. alpineagents-0.1.0/src/alpineagents/blocks.py +51 -0
  67. alpineagents-0.1.0/src/alpineagents/errors.py +79 -0
  68. alpineagents-0.1.0/src/alpineagents/human.py +141 -0
  69. alpineagents-0.1.0/src/alpineagents/loop.py +337 -0
  70. alpineagents-0.1.0/src/alpineagents/mcp_tools.py +507 -0
  71. alpineagents-0.1.0/src/alpineagents/models/__init__.py +8 -0
  72. alpineagents-0.1.0/src/alpineagents/models/anthropic.py +520 -0
  73. alpineagents-0.1.0/src/alpineagents/models/base.py +215 -0
  74. alpineagents-0.1.0/src/alpineagents/models/openai_compatible.py +493 -0
  75. alpineagents-0.1.0/src/alpineagents/models/resolve.py +95 -0
  76. alpineagents-0.1.0/src/alpineagents/reporter.py +89 -0
  77. alpineagents-0.1.0/src/alpineagents/state.py +1097 -0
  78. alpineagents-0.1.0/src/alpineagents/terminal.py +366 -0
  79. alpineagents-0.1.0/src/alpineagents/testing.py +236 -0
  80. alpineagents-0.1.0/src/alpineagents/tool.py +626 -0
  81. alpineagents-0.1.0/src/alpineagents/types.py +410 -0
  82. alpineagents-0.1.0/tests/mcp_demo_server.py +46 -0
  83. alpineagents-0.1.0/tests/test_agent.py +891 -0
  84. alpineagents-0.1.0/tests/test_anthropic.py +442 -0
  85. alpineagents-0.1.0/tests/test_async.py +815 -0
  86. alpineagents-0.1.0/tests/test_docs.py +331 -0
  87. alpineagents-0.1.0/tests/test_e2e.py +808 -0
  88. alpineagents-0.1.0/tests/test_integration.py +354 -0
  89. alpineagents-0.1.0/tests/test_loop.py +632 -0
  90. alpineagents-0.1.0/tests/test_mcp.py +360 -0
  91. alpineagents-0.1.0/tests/test_model.py +794 -0
  92. alpineagents-0.1.0/tests/test_model_event.py +87 -0
  93. alpineagents-0.1.0/tests/test_resolve.py +79 -0
  94. alpineagents-0.1.0/tests/test_review_agent.py +405 -0
  95. alpineagents-0.1.0/tests/test_review_decorators.py +374 -0
  96. alpineagents-0.1.0/tests/test_review_io.py +263 -0
  97. alpineagents-0.1.0/tests/test_review_models.py +545 -0
  98. alpineagents-0.1.0/tests/test_review_state.py +348 -0
  99. alpineagents-0.1.0/tests/test_state.py +887 -0
  100. alpineagents-0.1.0/tests/test_terminal.py +287 -0
  101. alpineagents-0.1.0/tests/test_testing.py +163 -0
  102. alpineagents-0.1.0/tests/test_tool.py +595 -0
  103. alpineagents-0.1.0/uv.lock +1597 -0
@@ -0,0 +1,29 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
15
+ steps:
16
+ - uses: actions/checkout@v5
17
+ - uses: astral-sh/setup-uv@v6
18
+ with:
19
+ python-version: ${{ matrix.python-version }}
20
+ activate-environment: true
21
+ - run: uv pip install -e ".[dev]"
22
+ - run: python -m pytest -q
23
+
24
+ lint:
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@v5
28
+ - uses: astral-sh/setup-uv@v6
29
+ - run: uvx ruff@0.13.2 check .
@@ -0,0 +1,40 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+ pages: write
11
+ id-token: write
12
+
13
+ concurrency:
14
+ group: pages
15
+ cancel-in-progress: true
16
+
17
+ jobs:
18
+ build:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v5
22
+ - uses: astral-sh/setup-uv@v6
23
+ with:
24
+ python-version: "3.13"
25
+ activate-environment: true
26
+ - run: uv pip install -e ".[docs]"
27
+ - run: mkdocs build --strict
28
+ - uses: actions/upload-pages-artifact@v4
29
+ with:
30
+ path: site
31
+
32
+ deploy:
33
+ needs: build
34
+ runs-on: ubuntu-latest
35
+ environment:
36
+ name: github-pages
37
+ url: ${{ steps.deployment.outputs.page_url }}
38
+ steps:
39
+ - id: deployment
40
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,10 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .ruff_cache/
9
+ .scratch/
10
+ site/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 JAEGYUN JUNG
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,200 @@
1
+ Metadata-Version: 2.5
2
+ Name: alpineagents
3
+ Version: 0.1.0
4
+ Summary: An agent framework that keeps configuration (Agent) and history (State) apart, with a plain Python function as the loop
5
+ Project-URL: Homepage, https://github.com/TGoddessana/alpineagents
6
+ Project-URL: Source, https://github.com/TGoddessana/alpineagents
7
+ Project-URL: Issues, https://github.com/TGoddessana/alpineagents/issues
8
+ Author: JAEGYUN JUNG
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: agent-framework,agents,ai-agents,anthropic,claude,llm,mcp,openai,tool-calling
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.11
24
+ Requires-Dist: anthropic
25
+ Requires-Dist: openai
26
+ Requires-Dist: pydantic>=2
27
+ Requires-Dist: typing-extensions>=4.6
28
+ Provides-Extra: dev
29
+ Requires-Dist: mcp>=2.2; extra == 'dev'
30
+ Requires-Dist: pytest; extra == 'dev'
31
+ Requires-Dist: pytest-asyncio; extra == 'dev'
32
+ Provides-Extra: docs
33
+ Requires-Dist: mkdocs-material; extra == 'docs'
34
+ Requires-Dist: mkdocs<2; extra == 'docs'
35
+ Requires-Dist: mkdocstrings[python]; extra == 'docs'
36
+ Provides-Extra: mcp
37
+ Requires-Dist: mcp>=2.2; extra == 'mcp'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # alpineagents
41
+
42
+ [![CI](https://github.com/TGoddessana/alpineagents/actions/workflows/ci.yml/badge.svg)](https://github.com/TGoddessana/alpineagents/actions/workflows/ci.yml)
43
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/TGoddessana/alpineagents/blob/main/LICENSE)
44
+ [![Docs](https://img.shields.io/badge/docs-tgoddessana.github.io-blue.svg)](https://tgoddessana.github.io/alpineagents/)
45
+
46
+ A Python agent framework where the agent loop is a function you write.
47
+
48
+ - `Agent` holds the settings: the model, the system prompt and the tools.
49
+ - `State` holds one task: the messages so far and the answer.
50
+ - A loop takes both and repeats one turn until a stop condition is true.
51
+
52
+ ## Why
53
+
54
+ In most frameworks the loop lives inside the library. LangGraph has you declare it as a graph. The OpenAI Agents SDK
55
+ and PydanticAI run it for you.
56
+
57
+ In alpineagents the loop is a few lines of Python in your own code:
58
+
59
+ - `until=` decides when it stops.
60
+ - `limit=` sets the maximum number of turns.
61
+ - Compaction runs only where you call it.
62
+
63
+ The default loop, `alpineagents.default_loop`, is written the same way. Read it, or copy it as a starting point.
64
+ Misuse raises an error that says how to fix it.
65
+
66
+ ## Install
67
+
68
+ Requires Python 3.11 or later.
69
+
70
+ ```bash
71
+ pip install alpineagents # or: uv add alpineagents
72
+ ```
73
+
74
+ Anthropic models read `ANTHROPIC_API_KEY`. OpenAI-compatible servers read `OPENAI_API_KEY`.
75
+
76
+ ## Quick start
77
+
78
+ Define a tool, create an Agent, run a task:
79
+
80
+ ```python
81
+ from pathlib import Path
82
+
83
+ from alpineagents import Agent, tool
84
+
85
+
86
+ @tool
87
+ def read_file(path: str) -> str:
88
+ """Read a text file"""
89
+ file = Path(path)
90
+ return file.read_text() if file.exists() else f"No such file: {path}"
91
+
92
+
93
+ agent = Agent(model="claude-sonnet-5", tools=[read_file])
94
+ print(agent.run("Summarize README.md in three lines"))
95
+ ```
96
+
97
+ - `@tool` turns the function into a tool. The type hints and the docstring tell the model how to call it.
98
+ - `agent.run` repeats turns until the model answers, then returns the answer.
99
+ - Progress is printed to the terminal while it runs.
100
+
101
+ ### Without an API key
102
+
103
+ `FakeModel` returns prepared replies in order. It needs no API key and no network. Replace the `print` line above with:
104
+
105
+ ```python
106
+ from alpineagents.testing import FakeModel, tool_call
107
+
108
+ fake = FakeModel([
109
+ tool_call("read_file", path="README.md"),
110
+ "README.md describes a Python agent framework.",
111
+ ])
112
+ print(agent.copy(model=fake).run("Summarize README.md"))
113
+ ```
114
+
115
+ The first reply asks for `read_file`. The Agent runs the tool, and the second reply is the answer.
116
+
117
+ ## Write your own loop
118
+
119
+ The Agent above uses `default_loop`. To change what happens in a turn, write the loop yourself:
120
+
121
+ ```python
122
+ from pathlib import Path
123
+
124
+ from alpineagents import Agent, State, compact_if_full, loop, tool
125
+
126
+
127
+ @tool
128
+ def list_files(folder: str = ".") -> list[str]:
129
+ """List the files in a folder"""
130
+ return sorted(p.name for p in Path(folder).iterdir())
131
+
132
+
133
+ @tool
134
+ def read_file(path: str) -> str:
135
+ """Read a file"""
136
+ file = Path(path)
137
+ return file.read_text() if file.exists() else f"No such file: {path}"
138
+
139
+
140
+ @tool
141
+ def write_file(path: str, content: str) -> None:
142
+ """Create a file, or replace its content"""
143
+ Path(path).write_text(content)
144
+
145
+
146
+ @loop(until=State.is_answered, limit=30)
147
+ def coding(agent: Agent, state: State):
148
+ compact_if_full(agent, state)
149
+ agent.think(state)
150
+ if state.wants_tools():
151
+ agent.use_tools(state)
152
+
153
+
154
+ agent = Agent(
155
+ model="claude-sonnet-5",
156
+ system="You are a coding assistant. Read a file before you change it.",
157
+ tools=[list_files, read_file, write_file],
158
+ loop=coding,
159
+ )
160
+ print(agent.run("Add a test for the add() function in calc.py"))
161
+ ```
162
+
163
+ - `coding` is one turn: summarize the context if it is more than 60% full, ask the model, run the tools it asked for.
164
+ - `@loop` repeats the turn. It stops when `State.is_answered` is true, or after 30 turns.
165
+ - To change the agent, add, remove or reorder lines in `coding`.
166
+
167
+ ## Documentation
168
+
169
+ [tgoddessana.github.io/alpineagents](https://tgoddessana.github.io/alpineagents/). Read it in this order:
170
+
171
+ 1. [Concepts](https://tgoddessana.github.io/alpineagents/concepts/overview/): how a run works, then Agent, State,
172
+ loops and tools. About 15 minutes.
173
+ 2. [Guides](https://tgoddessana.github.io/alpineagents/guides/stop-conditions/): one task per page, for example
174
+ [asking before a tool runs](https://tgoddessana.github.io/alpineagents/guides/approval/) or
175
+ [testing an agent](https://tgoddessana.github.io/alpineagents/guides/testing/).
176
+ 3. [API reference](https://tgoddessana.github.io/alpineagents/api/agent/): every class and method.
177
+
178
+ ## Roadmap
179
+
180
+ - OpenAI adapter, LiteLLM adapter
181
+ - Subagents (`tools=[researcher]`). Passing an Agent in `tools=` raises `NotImplementedError` for now
182
+ - Skills (`skills=`). Passing `skills=` raises `NotImplementedError` for now
183
+ - `agent.run_tool`, `agent.load_skill`
184
+ - `state.save()` / `State.load()`
185
+ - Multimodal tool results (`Image`, `File`)
186
+ - `alpineagents[prices]` (cost calculation with genai-prices; for now `usage.cost` is filled only when you pass
187
+ `price=Price(...)`)
188
+ - `alpineagents add` CLI (copies the default loop and block sources into your project)
189
+ - Per-adapter server-side compaction optimizations
190
+
191
+ ## Development
192
+
193
+ ```bash
194
+ uv venv && uv pip install -e ".[dev]"
195
+ .venv/bin/python -m pytest -q
196
+ ```
197
+
198
+ ## License
199
+
200
+ MIT. See [LICENSE](https://github.com/TGoddessana/alpineagents/blob/main/LICENSE).
@@ -0,0 +1,161 @@
1
+ # alpineagents
2
+
3
+ [![CI](https://github.com/TGoddessana/alpineagents/actions/workflows/ci.yml/badge.svg)](https://github.com/TGoddessana/alpineagents/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/TGoddessana/alpineagents/blob/main/LICENSE)
5
+ [![Docs](https://img.shields.io/badge/docs-tgoddessana.github.io-blue.svg)](https://tgoddessana.github.io/alpineagents/)
6
+
7
+ A Python agent framework where the agent loop is a function you write.
8
+
9
+ - `Agent` holds the settings: the model, the system prompt and the tools.
10
+ - `State` holds one task: the messages so far and the answer.
11
+ - A loop takes both and repeats one turn until a stop condition is true.
12
+
13
+ ## Why
14
+
15
+ In most frameworks the loop lives inside the library. LangGraph has you declare it as a graph. The OpenAI Agents SDK
16
+ and PydanticAI run it for you.
17
+
18
+ In alpineagents the loop is a few lines of Python in your own code:
19
+
20
+ - `until=` decides when it stops.
21
+ - `limit=` sets the maximum number of turns.
22
+ - Compaction runs only where you call it.
23
+
24
+ The default loop, `alpineagents.default_loop`, is written the same way. Read it, or copy it as a starting point.
25
+ Misuse raises an error that says how to fix it.
26
+
27
+ ## Install
28
+
29
+ Requires Python 3.11 or later.
30
+
31
+ ```bash
32
+ pip install alpineagents # or: uv add alpineagents
33
+ ```
34
+
35
+ Anthropic models read `ANTHROPIC_API_KEY`. OpenAI-compatible servers read `OPENAI_API_KEY`.
36
+
37
+ ## Quick start
38
+
39
+ Define a tool, create an Agent, run a task:
40
+
41
+ ```python
42
+ from pathlib import Path
43
+
44
+ from alpineagents import Agent, tool
45
+
46
+
47
+ @tool
48
+ def read_file(path: str) -> str:
49
+ """Read a text file"""
50
+ file = Path(path)
51
+ return file.read_text() if file.exists() else f"No such file: {path}"
52
+
53
+
54
+ agent = Agent(model="claude-sonnet-5", tools=[read_file])
55
+ print(agent.run("Summarize README.md in three lines"))
56
+ ```
57
+
58
+ - `@tool` turns the function into a tool. The type hints and the docstring tell the model how to call it.
59
+ - `agent.run` repeats turns until the model answers, then returns the answer.
60
+ - Progress is printed to the terminal while it runs.
61
+
62
+ ### Without an API key
63
+
64
+ `FakeModel` returns prepared replies in order. It needs no API key and no network. Replace the `print` line above with:
65
+
66
+ ```python
67
+ from alpineagents.testing import FakeModel, tool_call
68
+
69
+ fake = FakeModel([
70
+ tool_call("read_file", path="README.md"),
71
+ "README.md describes a Python agent framework.",
72
+ ])
73
+ print(agent.copy(model=fake).run("Summarize README.md"))
74
+ ```
75
+
76
+ The first reply asks for `read_file`. The Agent runs the tool, and the second reply is the answer.
77
+
78
+ ## Write your own loop
79
+
80
+ The Agent above uses `default_loop`. To change what happens in a turn, write the loop yourself:
81
+
82
+ ```python
83
+ from pathlib import Path
84
+
85
+ from alpineagents import Agent, State, compact_if_full, loop, tool
86
+
87
+
88
+ @tool
89
+ def list_files(folder: str = ".") -> list[str]:
90
+ """List the files in a folder"""
91
+ return sorted(p.name for p in Path(folder).iterdir())
92
+
93
+
94
+ @tool
95
+ def read_file(path: str) -> str:
96
+ """Read a file"""
97
+ file = Path(path)
98
+ return file.read_text() if file.exists() else f"No such file: {path}"
99
+
100
+
101
+ @tool
102
+ def write_file(path: str, content: str) -> None:
103
+ """Create a file, or replace its content"""
104
+ Path(path).write_text(content)
105
+
106
+
107
+ @loop(until=State.is_answered, limit=30)
108
+ def coding(agent: Agent, state: State):
109
+ compact_if_full(agent, state)
110
+ agent.think(state)
111
+ if state.wants_tools():
112
+ agent.use_tools(state)
113
+
114
+
115
+ agent = Agent(
116
+ model="claude-sonnet-5",
117
+ system="You are a coding assistant. Read a file before you change it.",
118
+ tools=[list_files, read_file, write_file],
119
+ loop=coding,
120
+ )
121
+ print(agent.run("Add a test for the add() function in calc.py"))
122
+ ```
123
+
124
+ - `coding` is one turn: summarize the context if it is more than 60% full, ask the model, run the tools it asked for.
125
+ - `@loop` repeats the turn. It stops when `State.is_answered` is true, or after 30 turns.
126
+ - To change the agent, add, remove or reorder lines in `coding`.
127
+
128
+ ## Documentation
129
+
130
+ [tgoddessana.github.io/alpineagents](https://tgoddessana.github.io/alpineagents/). Read it in this order:
131
+
132
+ 1. [Concepts](https://tgoddessana.github.io/alpineagents/concepts/overview/): how a run works, then Agent, State,
133
+ loops and tools. About 15 minutes.
134
+ 2. [Guides](https://tgoddessana.github.io/alpineagents/guides/stop-conditions/): one task per page, for example
135
+ [asking before a tool runs](https://tgoddessana.github.io/alpineagents/guides/approval/) or
136
+ [testing an agent](https://tgoddessana.github.io/alpineagents/guides/testing/).
137
+ 3. [API reference](https://tgoddessana.github.io/alpineagents/api/agent/): every class and method.
138
+
139
+ ## Roadmap
140
+
141
+ - OpenAI adapter, LiteLLM adapter
142
+ - Subagents (`tools=[researcher]`). Passing an Agent in `tools=` raises `NotImplementedError` for now
143
+ - Skills (`skills=`). Passing `skills=` raises `NotImplementedError` for now
144
+ - `agent.run_tool`, `agent.load_skill`
145
+ - `state.save()` / `State.load()`
146
+ - Multimodal tool results (`Image`, `File`)
147
+ - `alpineagents[prices]` (cost calculation with genai-prices; for now `usage.cost` is filled only when you pass
148
+ `price=Price(...)`)
149
+ - `alpineagents add` CLI (copies the default loop and block sources into your project)
150
+ - Per-adapter server-side compaction optimizations
151
+
152
+ ## Development
153
+
154
+ ```bash
155
+ uv venv && uv pip install -e ".[dev]"
156
+ .venv/bin/python -m pytest -q
157
+ ```
158
+
159
+ ## License
160
+
161
+ MIT. See [LICENSE](https://github.com/TGoddessana/alpineagents/blob/main/LICENSE).
@@ -0,0 +1,7 @@
1
+ # Agent
2
+
3
+ Concepts: [Agent](../concepts/agent.md).
4
+
5
+ ::: alpineagents.Agent
6
+ options:
7
+ filters: ["!^_", "!^(run_tool|load_skill|skills)$"]
@@ -0,0 +1,19 @@
1
+ # Errors
2
+
3
+ Concepts: [Errors and interruptions](../concepts/errors.md).
4
+
5
+ ::: alpineagents.AlpineAgentsError
6
+
7
+ ::: alpineagents.ProviderError
8
+
9
+ ::: alpineagents.RateLimitError
10
+
11
+ ::: alpineagents.ContextTooLongError
12
+
13
+ ::: alpineagents.AuthError
14
+
15
+ ::: alpineagents.OutputError
16
+
17
+ ::: alpineagents.NoHumanError
18
+
19
+ ::: alpineagents.MCPConnectionError
@@ -0,0 +1,9 @@
1
+ # Reporter, Human, Terminal
2
+
3
+ Guide: [Progress and questions](../guides/progress.md).
4
+
5
+ ::: alpineagents.Reporter
6
+
7
+ ::: alpineagents.Human
8
+
9
+ ::: alpineagents.Terminal
@@ -0,0 +1,17 @@
1
+ # Loops and blocks
2
+
3
+ Concepts: [Loops](../concepts/loops.md).
4
+
5
+ ::: alpineagents.loop.loop
6
+
7
+ ::: alpineagents.Loop
8
+
9
+ ::: alpineagents.default_loop
10
+
11
+ ::: alpineagents.adefault_loop
12
+
13
+ ::: alpineagents.CompactIfFull
14
+
15
+ ::: alpineagents.compact_if_full
16
+
17
+ ::: alpineagents.acompact_if_full
@@ -0,0 +1,9 @@
1
+ # Models
2
+
3
+ Guide: [Models](../guides/models.md).
4
+
5
+ ::: alpineagents.Model
6
+
7
+ ::: alpineagents.Anthropic
8
+
9
+ ::: alpineagents.OpenAICompatible
@@ -0,0 +1,7 @@
1
+ # State
2
+
3
+ Concepts: [State](../concepts/state.md).
4
+
5
+ ::: alpineagents.State
6
+ options:
7
+ filters: ["!^_", "!^(save|load|parent|root|depth)$"]
@@ -0,0 +1,9 @@
1
+ # Testing
2
+
3
+ Guide: [Testing](../guides/testing.md).
4
+
5
+ ::: alpineagents.testing.FakeModel
6
+
7
+ ::: alpineagents.testing.FakeHuman
8
+
9
+ ::: alpineagents.testing.tool_call
@@ -0,0 +1,11 @@
1
+ # Tools and MCP
2
+
3
+ Concepts: [Tools](../concepts/tools.md). Guide: [MCP servers](../guides/mcp.md).
4
+
5
+ ::: alpineagents.tool.tool
6
+
7
+ ::: alpineagents.Tool
8
+ options:
9
+ filters: ["!^_", "!^(prepare|invoke|format_result|is_error_result|fn|needs_self|bound_to)$"]
10
+
11
+ ::: alpineagents.MCP
@@ -0,0 +1,37 @@
1
+ # Data types
2
+
3
+ The data objects that the other classes take and return.
4
+
5
+ ::: alpineagents.Message
6
+
7
+ ::: alpineagents.ToolCall
8
+
9
+ ::: alpineagents.ToolSpec
10
+
11
+ ::: alpineagents.Request
12
+
13
+ ::: alpineagents.Reply
14
+
15
+ ::: alpineagents.Usage
16
+
17
+ ::: alpineagents.Price
18
+
19
+ ::: alpineagents.HistoryEntry
20
+ options:
21
+ filters: ["!^_", "!^(substate)$"]
22
+
23
+ ::: alpineagents.ContextChange
24
+
25
+ ::: alpineagents.ModelEvent
26
+
27
+ ::: alpineagents.ToolOutcome
28
+
29
+ ## Content blocks
30
+
31
+ The parts of a `Message`. `ToolCall` above is also one.
32
+
33
+ ::: alpineagents.types.TextBlock
34
+
35
+ ::: alpineagents.types.ToolResultBlock
36
+
37
+ ::: alpineagents.types.RawBlock