glia-agents 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.
- glia_agents-0.1.0/.gitattributes +4 -0
- glia_agents-0.1.0/.github/workflows/ci.yml +40 -0
- glia_agents-0.1.0/.github/workflows/publish.yml +38 -0
- glia_agents-0.1.0/.gitignore +31 -0
- glia_agents-0.1.0/CHANGELOG.md +26 -0
- glia_agents-0.1.0/CONTRIBUTING.md +46 -0
- glia_agents-0.1.0/LICENSE +21 -0
- glia_agents-0.1.0/PKG-INFO +179 -0
- glia_agents-0.1.0/README.md +148 -0
- glia_agents-0.1.0/docs/ARCHITECTURE.md +78 -0
- glia_agents-0.1.0/docs/ROADMAP.md +42 -0
- glia_agents-0.1.0/docs/STRATEGY.md +136 -0
- glia_agents-0.1.0/examples/01_hello_agent.py +33 -0
- glia_agents-0.1.0/examples/02_tools.py +38 -0
- glia_agents-0.1.0/examples/03_structured_output.py +38 -0
- glia_agents-0.1.0/examples/04_subagents.py +43 -0
- glia_agents-0.1.0/examples/05_checkpoint_resume.py +37 -0
- glia_agents-0.1.0/examples/06_evals.py +39 -0
- glia_agents-0.1.0/examples/_common.py +26 -0
- glia_agents-0.1.0/glia/__init__.py +102 -0
- glia_agents-0.1.0/glia/agent.py +251 -0
- glia_agents-0.1.0/glia/checkpoint.py +59 -0
- glia_agents-0.1.0/glia/errors.py +49 -0
- glia_agents-0.1.0/glia/evals.py +141 -0
- glia_agents-0.1.0/glia/guardrails.py +77 -0
- glia_agents-0.1.0/glia/llm.py +71 -0
- glia_agents-0.1.0/glia/memory.py +140 -0
- glia_agents-0.1.0/glia/providers/__init__.py +25 -0
- glia_agents-0.1.0/glia/providers/anthropic.py +162 -0
- glia_agents-0.1.0/glia/providers/echo.py +98 -0
- glia_agents-0.1.0/glia/py.typed +0 -0
- glia_agents-0.1.0/glia/structured.py +101 -0
- glia_agents-0.1.0/glia/tools.py +232 -0
- glia_agents-0.1.0/glia/trajectory.py +245 -0
- glia_agents-0.1.0/glia/types.py +171 -0
- glia_agents-0.1.0/pyproject.toml +59 -0
- glia_agents-0.1.0/tests/conftest.py +18 -0
- glia_agents-0.1.0/tests/test_agent.py +82 -0
- glia_agents-0.1.0/tests/test_guardrails_memory.py +66 -0
- glia_agents-0.1.0/tests/test_structured_evals.py +69 -0
- glia_agents-0.1.0/tests/test_tools.py +81 -0
- glia_agents-0.1.0/tests/test_trajectory_checkpoint.py +68 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install (dev extras)
|
|
25
|
+
run: |
|
|
26
|
+
python -m pip install --upgrade pip
|
|
27
|
+
pip install -e ".[dev]"
|
|
28
|
+
|
|
29
|
+
- name: Lint (ruff)
|
|
30
|
+
run: python -m ruff check .
|
|
31
|
+
|
|
32
|
+
- name: Tests (pytest, fully offline)
|
|
33
|
+
run: python -m pytest -q
|
|
34
|
+
|
|
35
|
+
- name: Examples run offline
|
|
36
|
+
run: |
|
|
37
|
+
for f in examples/0*.py; do
|
|
38
|
+
echo "--- $f ---"
|
|
39
|
+
python "$f"
|
|
40
|
+
done
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI via Trusted Publishing (OIDC) — no API token needed.
|
|
4
|
+
# Configure the trusted publisher once on PyPI:
|
|
5
|
+
# https://pypi.org/manage/account/publishing/
|
|
6
|
+
# Project: glia-agents | Owner: DenisDrobyshev | Repo: glia | Workflow: publish.yml
|
|
7
|
+
# Then publishing runs automatically whenever a GitHub Release is published
|
|
8
|
+
# (or manually via the "Run workflow" button / workflow_dispatch).
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
release:
|
|
12
|
+
types: [published]
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
publish:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
permissions:
|
|
19
|
+
id-token: write # required for Trusted Publishing (OIDC)
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: "3.12"
|
|
26
|
+
|
|
27
|
+
- name: Build sdist and wheel
|
|
28
|
+
run: |
|
|
29
|
+
python -m pip install --upgrade build
|
|
30
|
+
python -m build
|
|
31
|
+
|
|
32
|
+
- name: Check metadata
|
|
33
|
+
run: |
|
|
34
|
+
python -m pip install --upgrade twine
|
|
35
|
+
python -m twine check dist/*
|
|
36
|
+
|
|
37
|
+
- name: Publish to PyPI
|
|
38
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
*.egg
|
|
9
|
+
|
|
10
|
+
# Virtual envs
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
env/
|
|
14
|
+
|
|
15
|
+
# Tooling caches
|
|
16
|
+
.pytest_cache/
|
|
17
|
+
.ruff_cache/
|
|
18
|
+
.mypy_cache/
|
|
19
|
+
.coverage
|
|
20
|
+
htmlcov/
|
|
21
|
+
|
|
22
|
+
# Editors / OS
|
|
23
|
+
.vscode/
|
|
24
|
+
.idea/
|
|
25
|
+
.DS_Store
|
|
26
|
+
Thumbs.db
|
|
27
|
+
|
|
28
|
+
# Local secrets / scratch
|
|
29
|
+
.env
|
|
30
|
+
*.local
|
|
31
|
+
glia_run.json
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to glia are documented here. Format loosely follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/); this project uses
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] — 2026-07-22
|
|
8
|
+
|
|
9
|
+
Initial release. Proves the glass-box thesis end-to-end.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Transparent agent loop: `Agent.run()` and `Agent.run_events()` with a full
|
|
13
|
+
event stream (`RunStarted`, `ModelCall`, `ModelResponse`, `ToolCalled`,
|
|
14
|
+
`ToolReturned`, `Compacted`, `RunFinished`).
|
|
15
|
+
- `@tool` decorator deriving JSON schemas from type hints (scalars, `Literal`,
|
|
16
|
+
`Optional`/PEP 604 unions, `list`/`dict`, `Annotated` descriptions).
|
|
17
|
+
- `LLM` provider protocol with two adapters: `ClaudeLLM` (Anthropic SDK,
|
|
18
|
+
optional dependency) and `EchoLLM` (deterministic, offline, for tests/CI).
|
|
19
|
+
- Serialisable `Trajectory` with checkpoint/resume (`glia.checkpoint`).
|
|
20
|
+
- Context engineering: `SummarizingCompactor` and `TrimmingCompactor`.
|
|
21
|
+
- Input/output guardrails (`glia.guardrails`).
|
|
22
|
+
- Provider-agnostic structured outputs (`generate_structured`).
|
|
23
|
+
- Subagents via `Agent.as_tool(...)`.
|
|
24
|
+
- Evals-as-tests harness (`glia.evals`).
|
|
25
|
+
- Zero-dependency core, `py.typed`, six runnable offline examples, 26 tests,
|
|
26
|
+
green CI.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Contributing to glia
|
|
2
|
+
|
|
3
|
+
Thanks for your interest. glia has one guiding principle, and contributions are
|
|
4
|
+
judged against it:
|
|
5
|
+
|
|
6
|
+
> **No hidden control flow.** A new feature must be inspectable — if you can't
|
|
7
|
+
> see it happen in the event stream or read it in one file, it doesn't fit.
|
|
8
|
+
|
|
9
|
+
## Setup
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
git clone https://github.com/DenisDrobyshev/glia
|
|
13
|
+
cd glia
|
|
14
|
+
pip install -e ".[anthropic,dev]"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Checks (all must pass)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
python -m pytest # tests — run fully offline, no API key
|
|
21
|
+
python -m ruff check . # lint
|
|
22
|
+
python -m mypy glia # types (best-effort)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The whole test suite runs against the offline `EchoLLM`, so it's fast and
|
|
26
|
+
deterministic. New behaviour should come with a test that uses `EchoLLM` — if a
|
|
27
|
+
feature can't be tested offline, that's usually a design smell worth discussing
|
|
28
|
+
first.
|
|
29
|
+
|
|
30
|
+
## Guidelines
|
|
31
|
+
|
|
32
|
+
- **Keep the core dependency-free.** New runtime deps go behind an optional
|
|
33
|
+
extra (like `[anthropic]`), imported lazily.
|
|
34
|
+
- **Prefer a primitive over a feature.** A small composable piece (a new
|
|
35
|
+
`Compactor`, a new guardrail, a new provider) beats a bespoke flag on `Agent`.
|
|
36
|
+
- **Match the surrounding style.** Readable code with comments that explain
|
|
37
|
+
_why_, not _what_. The codebase is meant to be read.
|
|
38
|
+
- **New provider?** Implement the `LLM` protocol in ~40 lines; see
|
|
39
|
+
`glia/providers/echo.py` for the minimal shape.
|
|
40
|
+
|
|
41
|
+
## Opening a PR
|
|
42
|
+
|
|
43
|
+
1. Branch from `master`.
|
|
44
|
+
2. Add/adjust tests.
|
|
45
|
+
3. Make sure `pytest` and `ruff` are green.
|
|
46
|
+
4. Describe the change and how it stays inside the glass box.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Denis Drobyshev
|
|
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,179 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: glia-agents
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A glass-box, minimal library for building LLM agents — modern techniques as opt-in primitives, no hidden control flow.
|
|
5
|
+
Project-URL: Homepage, https://github.com/DenisDrobyshev/glia
|
|
6
|
+
Project-URL: Repository, https://github.com/DenisDrobyshev/glia
|
|
7
|
+
Project-URL: Issues, https://github.com/DenisDrobyshev/glia/issues
|
|
8
|
+
Author: Denis Drobyshev
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agent-framework,agents,ai,anthropic,claude,evals,llm,tools
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Provides-Extra: anthropic
|
|
23
|
+
Requires-Dist: anthropic>=0.40; extra == 'anthropic'
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
28
|
+
Provides-Extra: pydantic
|
|
29
|
+
Requires-Dist: pydantic>=2; extra == 'pydantic'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# glia
|
|
33
|
+
|
|
34
|
+
**A glass-box, minimal library for building LLM agents.** Every model call, tool
|
|
35
|
+
call, and state transition is a plain object you can log, snapshot, and replay.
|
|
36
|
+
No hidden control flow. The whole loop fits in one file you can read in an
|
|
37
|
+
afternoon.
|
|
38
|
+
|
|
39
|
+
> _glia_ (n.): the cells that support and connect neurons. This is the connective
|
|
40
|
+
> tissue for LLM agents — not a framework you submit to, a small library you
|
|
41
|
+
> build on.
|
|
42
|
+
|
|
43
|
+
[](https://github.com/DenisDrobyshev/glia/actions/workflows/ci.yml)
|
|
44
|
+
Python 3.10+ · MIT · zero required dependencies · typed
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Why another agent library?
|
|
49
|
+
|
|
50
|
+
The 2026 agent-framework field is crowded, and the loudest, most consistent
|
|
51
|
+
complaint about the incumbents is the same: **too much abstraction, hidden
|
|
52
|
+
control flow, painful to debug.** Developers keep stripping the framework out to
|
|
53
|
+
call the model API directly, just to see what's happening.
|
|
54
|
+
|
|
55
|
+
glia is the opposite bet. It ships the modern techniques — tools, structured
|
|
56
|
+
outputs, context compaction, durable checkpoints, guardrails, subagents,
|
|
57
|
+
evals-as-tests — as **opt-in primitives you can read**, not a monolith you must
|
|
58
|
+
trust. The design goal is understandability and control, not feature count.
|
|
59
|
+
|
|
60
|
+
If you want a graph engine, use [LangGraph](https://github.com/langchain-ai/langgraph).
|
|
61
|
+
If you want role-play crews, use [CrewAI](https://github.com/crewAIInc/crewAI).
|
|
62
|
+
If you want a small, transparent loop you fully understand — glia.
|
|
63
|
+
|
|
64
|
+
See [docs/STRATEGY.md](docs/STRATEGY.md) for the full market analysis.
|
|
65
|
+
|
|
66
|
+
## Install
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install glia-agents # core — no dependencies
|
|
70
|
+
pip install "glia-agents[anthropic]" # + the Claude provider
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
> The distribution is `glia-agents` (the bare name `glia` was taken on PyPI);
|
|
74
|
+
> the import stays `import glia`. For development: `git clone` then
|
|
75
|
+
> `pip install -e ".[anthropic,dev]"`.
|
|
76
|
+
|
|
77
|
+
## 30-second tour
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import asyncio
|
|
81
|
+
from glia import Agent, tool
|
|
82
|
+
from glia.providers import ClaudeLLM
|
|
83
|
+
|
|
84
|
+
@tool
|
|
85
|
+
async def get_weather(city: str) -> str:
|
|
86
|
+
"""Get the current weather for a city."""
|
|
87
|
+
return {"Paris": "18°C, cloudy"}.get(city, "unknown")
|
|
88
|
+
|
|
89
|
+
async def main():
|
|
90
|
+
agent = Agent(ClaudeLLM(), tools=[get_weather], system="Be concise.")
|
|
91
|
+
result = await agent.run("What's the weather in Paris?")
|
|
92
|
+
print(result.output) # the answer
|
|
93
|
+
print(result.usage) # what it cost
|
|
94
|
+
|
|
95
|
+
asyncio.run(main())
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
No API key handy? Every example runs offline with the deterministic `EchoLLM`
|
|
99
|
+
provider — same code, no network:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from glia.providers import EchoLLM, call
|
|
103
|
+
llm = EchoLLM([call("get_weather", {"city": "Paris"}), "It's 18°C and cloudy."])
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## See the whole glass box
|
|
107
|
+
|
|
108
|
+
Because the loop emits an event for everything it does, you can watch it work:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
async for event in agent.run_events("What's the weather in Paris?"):
|
|
112
|
+
print(event.kind)
|
|
113
|
+
# run_started → model_call → model_response → tool_called → tool_returned → model_call → ... → run_finished
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
And the entire run state is one serialisable object:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from glia.checkpoint import save, load
|
|
120
|
+
save(result.trajectory, "run.json") # durable execution: it's just JSON
|
|
121
|
+
resumed = load("run.json")
|
|
122
|
+
await agent.run("follow-up question", trajectory=resumed) # pick up where you left off
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## What's in the box
|
|
126
|
+
|
|
127
|
+
| Primitive | What it gives you |
|
|
128
|
+
|---|---|
|
|
129
|
+
| **Transparent loop** | `agent.run()` / `agent.run_events()` — no hidden control flow |
|
|
130
|
+
| **Typed tools** | `@tool` on a plain function; JSON schema derived from type hints |
|
|
131
|
+
| **Provider boundary** | one ~40-line `LLM` protocol; Claude + offline adapters |
|
|
132
|
+
| **Trajectory** | the full, JSON-serialisable run state and event log |
|
|
133
|
+
| **Structured output** | `generate_structured(...)` → a dataclass / Pydantic model / dict |
|
|
134
|
+
| **Context engineering** | `SummarizingCompactor`, `TrimmingCompactor` |
|
|
135
|
+
| **Durable execution** | checkpoint & resume — a run is a JSON file |
|
|
136
|
+
| **Guardrails** | `(text) -> None` validators for input and output |
|
|
137
|
+
| **Subagents** | `agent.as_tool(...)` — any agent becomes a tool |
|
|
138
|
+
| **Evals-as-tests** | a pytest-style regression harness for agent behaviour |
|
|
139
|
+
|
|
140
|
+
## Examples
|
|
141
|
+
|
|
142
|
+
Runnable, offline, no API key needed:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
python examples/01_hello_agent.py # basic agent + event stream
|
|
146
|
+
python examples/02_tools.py # tools
|
|
147
|
+
python examples/03_structured_output.py # typed output
|
|
148
|
+
python examples/04_subagents.py # subagent as a tool
|
|
149
|
+
python examples/05_checkpoint_resume.py # durable execution
|
|
150
|
+
python examples/06_evals.py # eval suite
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Design in one picture
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
Agent.run() → [ call model → (tools? run them, loop) : done ]
|
|
157
|
+
│ │
|
|
158
|
+
every step emits an Event you can see, log, and replay
|
|
159
|
+
│
|
|
160
|
+
all state lives in one serialisable Trajectory
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Full details in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
164
|
+
|
|
165
|
+
## Project docs
|
|
166
|
+
|
|
167
|
+
- [Strategy & market analysis](docs/STRATEGY.md) — why glia, and who it competes with
|
|
168
|
+
- [Architecture](docs/ARCHITECTURE.md) — how the whole thing works
|
|
169
|
+
- [Roadmap](docs/ROADMAP.md) — where it's going
|
|
170
|
+
- [Contributing](CONTRIBUTING.md)
|
|
171
|
+
|
|
172
|
+
## Status
|
|
173
|
+
|
|
174
|
+
**v0.1 — alpha.** The core thesis is proven end-to-end with a full test suite and
|
|
175
|
+
green CI. APIs may still change before 1.0. Feedback and issues welcome.
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# glia
|
|
2
|
+
|
|
3
|
+
**A glass-box, minimal library for building LLM agents.** Every model call, tool
|
|
4
|
+
call, and state transition is a plain object you can log, snapshot, and replay.
|
|
5
|
+
No hidden control flow. The whole loop fits in one file you can read in an
|
|
6
|
+
afternoon.
|
|
7
|
+
|
|
8
|
+
> _glia_ (n.): the cells that support and connect neurons. This is the connective
|
|
9
|
+
> tissue for LLM agents — not a framework you submit to, a small library you
|
|
10
|
+
> build on.
|
|
11
|
+
|
|
12
|
+
[](https://github.com/DenisDrobyshev/glia/actions/workflows/ci.yml)
|
|
13
|
+
Python 3.10+ · MIT · zero required dependencies · typed
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Why another agent library?
|
|
18
|
+
|
|
19
|
+
The 2026 agent-framework field is crowded, and the loudest, most consistent
|
|
20
|
+
complaint about the incumbents is the same: **too much abstraction, hidden
|
|
21
|
+
control flow, painful to debug.** Developers keep stripping the framework out to
|
|
22
|
+
call the model API directly, just to see what's happening.
|
|
23
|
+
|
|
24
|
+
glia is the opposite bet. It ships the modern techniques — tools, structured
|
|
25
|
+
outputs, context compaction, durable checkpoints, guardrails, subagents,
|
|
26
|
+
evals-as-tests — as **opt-in primitives you can read**, not a monolith you must
|
|
27
|
+
trust. The design goal is understandability and control, not feature count.
|
|
28
|
+
|
|
29
|
+
If you want a graph engine, use [LangGraph](https://github.com/langchain-ai/langgraph).
|
|
30
|
+
If you want role-play crews, use [CrewAI](https://github.com/crewAIInc/crewAI).
|
|
31
|
+
If you want a small, transparent loop you fully understand — glia.
|
|
32
|
+
|
|
33
|
+
See [docs/STRATEGY.md](docs/STRATEGY.md) for the full market analysis.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install glia-agents # core — no dependencies
|
|
39
|
+
pip install "glia-agents[anthropic]" # + the Claude provider
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
> The distribution is `glia-agents` (the bare name `glia` was taken on PyPI);
|
|
43
|
+
> the import stays `import glia`. For development: `git clone` then
|
|
44
|
+
> `pip install -e ".[anthropic,dev]"`.
|
|
45
|
+
|
|
46
|
+
## 30-second tour
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import asyncio
|
|
50
|
+
from glia import Agent, tool
|
|
51
|
+
from glia.providers import ClaudeLLM
|
|
52
|
+
|
|
53
|
+
@tool
|
|
54
|
+
async def get_weather(city: str) -> str:
|
|
55
|
+
"""Get the current weather for a city."""
|
|
56
|
+
return {"Paris": "18°C, cloudy"}.get(city, "unknown")
|
|
57
|
+
|
|
58
|
+
async def main():
|
|
59
|
+
agent = Agent(ClaudeLLM(), tools=[get_weather], system="Be concise.")
|
|
60
|
+
result = await agent.run("What's the weather in Paris?")
|
|
61
|
+
print(result.output) # the answer
|
|
62
|
+
print(result.usage) # what it cost
|
|
63
|
+
|
|
64
|
+
asyncio.run(main())
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
No API key handy? Every example runs offline with the deterministic `EchoLLM`
|
|
68
|
+
provider — same code, no network:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from glia.providers import EchoLLM, call
|
|
72
|
+
llm = EchoLLM([call("get_weather", {"city": "Paris"}), "It's 18°C and cloudy."])
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## See the whole glass box
|
|
76
|
+
|
|
77
|
+
Because the loop emits an event for everything it does, you can watch it work:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
async for event in agent.run_events("What's the weather in Paris?"):
|
|
81
|
+
print(event.kind)
|
|
82
|
+
# run_started → model_call → model_response → tool_called → tool_returned → model_call → ... → run_finished
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
And the entire run state is one serialisable object:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from glia.checkpoint import save, load
|
|
89
|
+
save(result.trajectory, "run.json") # durable execution: it's just JSON
|
|
90
|
+
resumed = load("run.json")
|
|
91
|
+
await agent.run("follow-up question", trajectory=resumed) # pick up where you left off
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## What's in the box
|
|
95
|
+
|
|
96
|
+
| Primitive | What it gives you |
|
|
97
|
+
|---|---|
|
|
98
|
+
| **Transparent loop** | `agent.run()` / `agent.run_events()` — no hidden control flow |
|
|
99
|
+
| **Typed tools** | `@tool` on a plain function; JSON schema derived from type hints |
|
|
100
|
+
| **Provider boundary** | one ~40-line `LLM` protocol; Claude + offline adapters |
|
|
101
|
+
| **Trajectory** | the full, JSON-serialisable run state and event log |
|
|
102
|
+
| **Structured output** | `generate_structured(...)` → a dataclass / Pydantic model / dict |
|
|
103
|
+
| **Context engineering** | `SummarizingCompactor`, `TrimmingCompactor` |
|
|
104
|
+
| **Durable execution** | checkpoint & resume — a run is a JSON file |
|
|
105
|
+
| **Guardrails** | `(text) -> None` validators for input and output |
|
|
106
|
+
| **Subagents** | `agent.as_tool(...)` — any agent becomes a tool |
|
|
107
|
+
| **Evals-as-tests** | a pytest-style regression harness for agent behaviour |
|
|
108
|
+
|
|
109
|
+
## Examples
|
|
110
|
+
|
|
111
|
+
Runnable, offline, no API key needed:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
python examples/01_hello_agent.py # basic agent + event stream
|
|
115
|
+
python examples/02_tools.py # tools
|
|
116
|
+
python examples/03_structured_output.py # typed output
|
|
117
|
+
python examples/04_subagents.py # subagent as a tool
|
|
118
|
+
python examples/05_checkpoint_resume.py # durable execution
|
|
119
|
+
python examples/06_evals.py # eval suite
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Design in one picture
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
Agent.run() → [ call model → (tools? run them, loop) : done ]
|
|
126
|
+
│ │
|
|
127
|
+
every step emits an Event you can see, log, and replay
|
|
128
|
+
│
|
|
129
|
+
all state lives in one serialisable Trajectory
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Full details in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
133
|
+
|
|
134
|
+
## Project docs
|
|
135
|
+
|
|
136
|
+
- [Strategy & market analysis](docs/STRATEGY.md) — why glia, and who it competes with
|
|
137
|
+
- [Architecture](docs/ARCHITECTURE.md) — how the whole thing works
|
|
138
|
+
- [Roadmap](docs/ROADMAP.md) — where it's going
|
|
139
|
+
- [Contributing](CONTRIBUTING.md)
|
|
140
|
+
|
|
141
|
+
## Status
|
|
142
|
+
|
|
143
|
+
**v0.1 — alpha.** The core thesis is proven end-to-end with a full test suite and
|
|
144
|
+
green CI. APIs may still change before 1.0. Feedback and issues welcome.
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
glia has one rule: **no hidden control flow**. If you want to know what the agent
|
|
4
|
+
does, you read `agent.py`. If you want to know what state it holds, you read
|
|
5
|
+
`trajectory.py`. That's the whole system.
|
|
6
|
+
|
|
7
|
+
## The pieces
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
┌─────────────────────────────────────────────┐
|
|
11
|
+
│ Agent │
|
|
12
|
+
│ the loop: call model → run tools → repeat │
|
|
13
|
+
└───────┬───────────────┬──────────────┬───────┘
|
|
14
|
+
│ │ │
|
|
15
|
+
┌───────▼──────┐ ┌──────▼──────┐ ┌─────▼───────┐
|
|
16
|
+
│ LLM │ │ ToolRegistry│ │ Trajectory │
|
|
17
|
+
│ (protocol) │ │ (@tool fns)│ │ state+events│
|
|
18
|
+
└───────┬──────┘ └─────────────┘ └─────────────┘
|
|
19
|
+
│
|
|
20
|
+
┌─────────┴─────────┐
|
|
21
|
+
┌────▼─────┐ ┌─────▼──────┐
|
|
22
|
+
│ ClaudeLLM│ │ EchoLLM │
|
|
23
|
+
│ (Anthropic) │(offline/CI)│
|
|
24
|
+
└──────────┘ └────────────┘
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| Module | Responsibility | Lines you'd read to understand it |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `types.py` | Content blocks, `Message`, `Usage`. Immutable, JSON-serialisable. | ~170 |
|
|
30
|
+
| `llm.py` | The provider boundary: `LLMRequest`, `LLMResponse`, and the `LLM` protocol. | ~90 |
|
|
31
|
+
| `tools.py` | `@tool` decorator (schema from type hints), `ToolRegistry`. | ~230 |
|
|
32
|
+
| `trajectory.py` | The run state + the `Event` types. The glass box itself. | ~250 |
|
|
33
|
+
| `agent.py` | The loop. `run()` and `run_events()`. | ~230 |
|
|
34
|
+
| `memory.py` | Context engineering: compactors. | ~140 |
|
|
35
|
+
| `guardrails.py` | Input/output validators. | ~90 |
|
|
36
|
+
| `structured.py` | Structured output via a forced tool call. | ~110 |
|
|
37
|
+
| `checkpoint.py` | Save/load a trajectory; a checkpointer hook. | ~70 |
|
|
38
|
+
| `evals.py` | Evals-as-tests harness. | ~140 |
|
|
39
|
+
| `providers/` | `EchoLLM` (offline) and `ClaudeLLM` (Anthropic). | ~120 each |
|
|
40
|
+
|
|
41
|
+
## The loop, precisely
|
|
42
|
+
|
|
43
|
+
`Agent.run_events()` is the single source of behaviour. Each iteration:
|
|
44
|
+
|
|
45
|
+
1. **(optional) compact** — if a `Compactor` says the trajectory is too big,
|
|
46
|
+
shrink it first, emitting a `Compacted` event.
|
|
47
|
+
2. **build an `LLMRequest`** from the current trajectory + tool schemas.
|
|
48
|
+
3. **call the provider** (`ModelCall` → `ModelResponse` events), appending the
|
|
49
|
+
assistant message and its `Usage` to the trajectory.
|
|
50
|
+
4. **if the model asked for tools:** run each (`ToolCalled` → `ToolReturned`),
|
|
51
|
+
append all results as one user turn, and loop.
|
|
52
|
+
5. **otherwise:** run output guardrails and finish (`RunFinished`).
|
|
53
|
+
|
|
54
|
+
`run()` is just `run_events()` drained to completion. That's the whole thing.
|
|
55
|
+
|
|
56
|
+
## Why these choices
|
|
57
|
+
|
|
58
|
+
- **Blocks are a closed union, not a class hierarchy.** You `isinstance`/match on
|
|
59
|
+
four types; there are no subclasses to discover or plugins to register.
|
|
60
|
+
- **Tool results are a `user` turn.** That's the Anthropic convention, and it
|
|
61
|
+
keeps our message model and the wire format aligned with no translation debt.
|
|
62
|
+
- **Events are records, not commands.** Hooks observe; they never mutate the
|
|
63
|
+
loop. This keeps behaviour readable and hooks safe.
|
|
64
|
+
- **The provider boundary is tiny on purpose.** Adapting to a new model or an
|
|
65
|
+
API change touches one file. Vendor churn can't ripple through the codebase.
|
|
66
|
+
- **`EchoLLM` is a first-class citizen.** Deterministic, offline testing of the
|
|
67
|
+
entire loop is a design goal, not an afterthought.
|
|
68
|
+
|
|
69
|
+
## Extending it
|
|
70
|
+
|
|
71
|
+
- **New provider:** implement `async def generate(request) -> LLMResponse`. ~40
|
|
72
|
+
lines. See `providers/echo.py` for the simplest possible example.
|
|
73
|
+
- **New context strategy:** implement the `Compactor` protocol
|
|
74
|
+
(`should_compact` + `compact`). See `memory.py`.
|
|
75
|
+
- **New guardrail:** write a `(text) -> None` function that raises
|
|
76
|
+
`GuardrailTripped`. That's the whole interface.
|
|
77
|
+
- **Observability:** add a hook. Every event flows through it; export to logs,
|
|
78
|
+
a tracer, or a UI.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
glia follows one principle when adding features: **a new primitive must not add
|
|
4
|
+
hidden control flow.** If a feature can't be made inspectable, it doesn't ship.
|
|
5
|
+
|
|
6
|
+
## v0.1 — thesis proven (this release)
|
|
7
|
+
- Transparent agent loop (`run` / `run_events`) with a full event stream
|
|
8
|
+
- Tools from typed functions (`@tool`) + `ToolRegistry`
|
|
9
|
+
- Provider boundary + `ClaudeLLM` (Anthropic) and `EchoLLM` (offline)
|
|
10
|
+
- Serialisable `Trajectory`; checkpoint/resume (durable execution)
|
|
11
|
+
- Context engineering: `SummarizingCompactor`, `TrimmingCompactor`
|
|
12
|
+
- Guardrails (input/output validators)
|
|
13
|
+
- Structured outputs (provider-agnostic, via forced tool call)
|
|
14
|
+
- Subagents (any agent → a tool)
|
|
15
|
+
- Evals-as-tests harness
|
|
16
|
+
- Zero-dependency core, `py.typed`, green CI, runnable offline examples
|
|
17
|
+
|
|
18
|
+
## v0.2 — ergonomics & throughput
|
|
19
|
+
- [ ] Parallel tool execution (`asyncio.gather`) with preserved event ordering
|
|
20
|
+
- [ ] Streaming token output through the event stream (`ModelDelta` events)
|
|
21
|
+
- [ ] Retry/backoff policy as an explicit, inspectable wrapper (not hidden magic)
|
|
22
|
+
- [ ] `RunResult` niceties: per-tool timings, cost summary helpers
|
|
23
|
+
|
|
24
|
+
## v0.3 — interop
|
|
25
|
+
- [ ] MCP tool bridge: expose MCP servers as glia tools, and glia tools as MCP
|
|
26
|
+
- [ ] OpenTelemetry span exporter driven off the event stream
|
|
27
|
+
- [ ] Prompt-caching hints on the Claude adapter (stable-prefix breakpoints)
|
|
28
|
+
|
|
29
|
+
## v0.4 — reliability
|
|
30
|
+
- [ ] Pluggable persistence backends for checkpoints (file, sqlite, redis)
|
|
31
|
+
- [ ] Deterministic replay: re-run a saved trajectory against a recorded provider
|
|
32
|
+
- [ ] Human-in-the-loop tool approval as a first-class, inspectable gate
|
|
33
|
+
|
|
34
|
+
## Later
|
|
35
|
+
- [ ] TypeScript port once the Python core stabilises (same glass-box contract)
|
|
36
|
+
- [ ] A tiny local trace viewer that renders a `Trajectory` as a timeline
|
|
37
|
+
|
|
38
|
+
## Non-goals (on purpose)
|
|
39
|
+
- A graph/DSL orchestration engine — use LangGraph.
|
|
40
|
+
- A hosted runtime/sandbox — use the Claude Agent SDK / Managed Agents.
|
|
41
|
+
- A role-play "crew" abstraction — use CrewAI.
|
|
42
|
+
- Anything that requires the core to grow a heavy dependency.
|