A lightweight async framework for structuring and running AI agents in Python
Project description
pygents
A lightweight async framework for structuring and running AI agents in Python. Define tools, queue turns, stream results.
Install
pip install pygents
Requires Python 3.12+.
Example
import asyncio
from pygents import Agent, Turn, tool
@tool()
async def greet(name: str) -> str:
return f"Hello, {name}!"
async def main():
agent = Agent("greeter", "Greets people", [greet])
# Use kwargs:
await agent.put(Turn("greet", kwargs={"name": "World"}))
# Or positional args:
await agent.put(Turn("greet", args=["World"]))
async for turn, value in agent.run():
print(value) # "Hello, World!"
asyncio.run(main())
Tools are async functions. Turns say which tool to run and with what args. Agents process a queue of turns and stream results. The loop exits when the queue is empty.
Features
- Streaming — agents yield
(turn, value)as results are produced - Inter-agent messaging — agents can send turns to each other
- Dynamic arguments — callable positional args and kwargs evaluated at runtime
- Timeouts — per-turn, default 60s
- Per-tool locking — opt-in serialization for shared state (lock is acquired inside the tool wrapper, so turn-level hooks run outside the tool lock)
- Fixed kwargs — decorator kwargs (e.g.
@tool(permission="admin")) are merged into every invocation; call-time kwargs override - Hooks —
@hook(hook_type, lock=..., **fixed_kwargs)decorator; hooks stored as a list and selected by type; turn, agent, tool, and memory hooks; same fixed_kwargs and lock options as tools - Serialization —
to_dict()/from_dict()for turns and agents
Design decisions
-
Turn identity:
Turninstances no longer have a built-inuuid. If you need identifiers, store them yourself inmetadataor wrapTurnin a higher-level domain object. -
Turn arguments:
Turn.__init__takesargsbeforekwargs, andmetadatais the final parameter:Turn( "tool_name", args=[...], kwargs={...}, timeout=..., metadata={...}, hooks=[...], )
This keeps positional arguments explicit while reserving
metadatapurely for user-level annotations.start_time,end_time,stop_reason, andoutputare set by the framework during execution—they are not constructor parameters. -
Agent serialization and current turn:
Agent.to_dict()includes acurrent_turnkey when the agent is in the middle ofrun()(the turn being executed). That turn is already off the queue, so without it a snapshot would lose the in-flight work.from_dict()restores it; the nextrun()consumes the restored current turn first, then the queue. So you can save agent state at any time and get a faithful snapshot (config, queue, and current turn if any). -
Tool and hook metadata timing:
ToolMetadataandHookMetadataincludestart_timeandend_time(datetime | None). They are set on the metadata instance when the tool or hook runs (start at entry, end in afinally). So the same metadata object is updated each run;dict()includes ISO strings for serialization. -
Hook protocol: Registered hooks conform to a
Hookprotocol (likeTool), withmetadata(HookMetadata: name, description, and run timing),hook_type,fn, andlock. The decorator setsmetadata(from__name__and__doc__),fn, andlock;HookRegistry.register()setshook_type. Raw async callables are typed asCallable[..., Awaitable[None]]. -
Hook decorator:
@hook(hook_type, lock=False, **fixed_kwargs)mirrors the tool decorator: keyword arguments are merged into every invocation (call-time overrides), andlock=Trueuses an asyncio lock to serialize hook invocations. Pass a list of types (e.g.@hook([TurnHook.BEFORE_RUN, AgentHook.AFTER_TURN])) to reuse one hook for several events; multi-type hooks must accept*args, **kwargssince different types receive different arguments. -
Tool lock in tool layer: The tool lock is acquired inside the
@toolwrapper, not in the turn. So the lock covers only the tool’s own execution (including its BEFORE_INVOKE / ON_YIELD / AFTER_INVOKE hooks). Turn-level hooks (BEFORE_RUN, AFTER_RUN, ON_TIMEOUT, ON_ERROR) run outside the tool lock; hooks that need serialization use their ownlock=True. -
ContextQueue hooks:
ContextQueuehas no compact callback. It supportsContextQueueHook.BEFORE_APPENDandContextQueueHook.AFTER_APPENDonly. Hooks are stored aslist[Hook](like Agent/Turn), filtered by type when running. BEFORE_APPEND and AFTER_APPEND hooks receive(items,)— the current items as a list (read-only). Serialization uses the same by-type-by-name shape as Agent/Turn;from_dict()resolves names fromHookRegistry.
Docs
Full documentation: uv run mkdocs serve. MkDocs is an optional dependency—install with pip install -e ".[docs]" (or use uv run as above) so the library itself does not depend on it.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pygents-0.4.2.tar.gz.
File metadata
- Download URL: pygents-0.4.2.tar.gz
- Upload date:
- Size: 17.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
edce2ded9fbdf10726b974fd5d1f7127293cc91c9fe9f65341a14e9733b7bcc8
|
|
| MD5 |
661344b0783e93803636ff8b1091f0a9
|
|
| BLAKE2b-256 |
dec1133effcf67a00204d2c306decb24dba05876341c2f52793a7ed368916504
|
File details
Details for the file pygents-0.4.2-py3-none-any.whl.
File metadata
- Download URL: pygents-0.4.2-py3-none-any.whl
- Upload date:
- Size: 20.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
18e714ed2d5b0082cd360d07c070fa11148acdba88a132b5f2361c9d161eab06
|
|
| MD5 |
a2e731cc01bafafd1f5ae8bdd1ce4b53
|
|
| BLAKE2b-256 |
3f97ed2869cabb00a24e56a7bcc7ef46c1c185cab60181add12b31b57823842f
|