A correct, simple, performant, and pythonic framework for building durable AI agents
Project description
PocketJoe
LLM Agents are just agents...
- Agents are policies
- A policy reasons over observations and chooses a batch of options
- A policy can be any mix of LLM-based, human-in-the-loop, or heuristic
Semantics
An agent system using Reinforcement Learning theory with LLM semantics as first class
policy: all code/logic/llm are policiesobservations- the set of observations for the policy to reason overoptions- additional action spaces available to the policyselected_actions- the set of concurrent actions the policy chose to takeMessage: a shared dataclass forobservationsandactionsthat aligns with llm semantics
LLM semantics as platform semantics
In LLM APIs, everything is a Message. We adopt this as our universal unit:
- Input:
observations: list[Message](what the policy sees) - Output:
selected_actions- the policy's action space (owns its outputs)
Key insight: When options are provided, they expand the policy's action space. The runtime automatically invokes all option calls and injects the results back as observations.
Everything is a Policy
Universal Return Types: Policies can return any JSON-serializable type - the framework automatically wraps results when called as options.
An LLM policy that can call other policies:
@policy.tool(description="Calls LLM with tool support")
async def openai_llm_policy_v1(observations: list[Message], options: list[OptionSchema]) -> list[Message]:
"""LLM policy that calls OpenAI GPT-4 with tool support."""
openai = AsyncOpenAI()
response = await openai.chat.completions.create(
model="gpt-4",
messages=observations_to_completions_messages(observations),
tools=options_to_completions_tools(options))
return completions_response_to_messages(response)
A simple helper policy returning primitives:
@policy.tool(description="Performs a web search and returns results.")
async def web_search_policy(query: str) -> str:
"""Performs a web search and returns results."""
results = DDGS().text(query, max_results=5)
return "\n\n".join([f"Title: {r['title']}\nURL: {r['href']}\nSnippet: {r['body']}" for r in results])
A policy returning structured data:
@policy.tool(description="Transcribe YouTube video")
async def transcribe_youtube_policy(url: str) -> dict[str, str]:
"""Get video title, transcript and metadata from YouTube URL."""
video_id = _extract_video_id(url)
transcript = YouTubeTranscriptApi().fetch(video_id)
return {
"title": title,
"transcript": " ".join([snippet.text for snippet in transcript]),
"video_id": video_id
}
An orchestrator policy that coordinates LLM + search:
@policy.tool(description="Orchestrates LLM with web search tool")
async def search_agent(prompt: str, max_iterations: int = 3) -> list[Message]:
"""Orchestrator that gives the LLM access to web search."""
ctx = AppContext.get_ctx()
system_builder = MessageBuilder(policy="system", role_hint_for_llm="system")
system_builder.add_text("You are an AI assistant that can use tools.")
system_message = system_builder.to_message()
prompt_builder = MessageBuilder(policy="user", role_hint_for_llm="user")
prompt_builder.add_text(prompt)
prompt_message = prompt_builder.to_message()
history = [system_message, prompt_message]
for _ in range(max_iterations):
selected_actions = await ctx.llm(
observations=history,
options=OptionSchema.from_func([ctx.web_search])
)
history.extend(selected_actions)
if not any(msg.payload and isinstance(msg.payload, OptionCallPayload) for msg in selected_actions):
break
return history
Use AppContext for registry (gives IDE type hints):
class AppContext(BaseContext):
def __init__(self, runner):
super().__init__(runner)
self.llm = self._bind(openai_llm_policy_v1)
self.web_search = self._bind(web_seatch_ddgs_policy)
self.search_agent = self._bind(search_agent)
Enjoy:
async def main():
runner = InMemoryRunner()
ctx = AppContext(runner)
result = await ctx.search_agent(prompt="What is the latest Python version?")
# Get final text message (Message.__str__ extracts text automatically)
final_msg = next((msg for msg in reversed(result) if msg.parts), '')
print(f"\nFinal Result: {final_msg}")
Why this matters:
- Universal Composability: Decorate any function - it works like FastAPI/FastMCP endpoints
- Flexible Return Types: Return primitives (str, dict, list), or list[Message] for complex flows
- Auto-wrapping: Framework automatically wraps results in OptionResultPayload when called as options
- Type-safe: Full IDE support with typed context and message payloads
- Evolution-friendly: Start simple (primitives) → add complexity (messages) with no refactoring
A correct, simple, performant, and pythonic framework for building durable AI agents.
"There is no flow, only Policies and Actions."
Getting Started
Prerequisites
- Python 3.12+
Installation
uv add pocket-joe
Or with pip:
pip install pocket-joe
To install with example dependencies:
uv add pocket-joe --extra examples
# or
pip install pocket-joe[examples]
Development Setup
git clone https://github.com/Sohojoe/pocket-joe.git
cd pocket-joe
uv sync --dev --all-extras
Running Examples
Set your API key:
export OPENAI_API_KEY=sk-...
Search Agent (ReAct)
uv run python examples/search_agent.py
YouTube Summarizer
uv run python examples/youtube_summarizer.py
Dev Status
Still in prerelease, things will change
Initial version
- [] Tidy up code - add partly refactored code
- [] Proper tests
- [] Implement more examples from Pocket-Flow
Durable System:
- [] Ledger - Temporal style 'at least once, only one result' replay semantic
- [] Durable Storage wrapper - For long running tasks & replay
- [] Distributed - worker model
Background
Inspired by PocketFlow... I loved PocketFlow but it fell short in a couple of key areas. This is my rewrite that I can actually use.
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 pocket_joe-0.2.0.5.tar.gz.
File metadata
- Download URL: pocket_joe-0.2.0.5.tar.gz
- Upload date:
- Size: 98.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b2f409ca5ee434655a18f4cc581911d8ebe1146fc34506f25ee72589fcd904ba
|
|
| MD5 |
567ae887700c7bc75f888de74dfbc5ca
|
|
| BLAKE2b-256 |
ca22d88da42b8fdd88819d82f430b3fad837e396aa0f21d6bc04ace6048adea8
|
Provenance
The following attestation bundles were made for pocket_joe-0.2.0.5.tar.gz:
Publisher:
publish.yml on Sohojoe/pocket-joe
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pocket_joe-0.2.0.5.tar.gz -
Subject digest:
b2f409ca5ee434655a18f4cc581911d8ebe1146fc34506f25ee72589fcd904ba - Sigstore transparency entry: 764232533
- Sigstore integration time:
-
Permalink:
Sohojoe/pocket-joe@5c68ea9be8c3959cec634ada09196ad98036d34f -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Sohojoe
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5c68ea9be8c3959cec634ada09196ad98036d34f -
Trigger Event:
push
-
Statement type:
File details
Details for the file pocket_joe-0.2.0.5-py3-none-any.whl.
File metadata
- Download URL: pocket_joe-0.2.0.5-py3-none-any.whl
- Upload date:
- Size: 12.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cce67c981f4d8fd89c743adcb0c93d671ca0e92b69dac69d6b7d506b10233142
|
|
| MD5 |
335241bd10041d32c39604f8f48dcbda
|
|
| BLAKE2b-256 |
454d429618ffa299e6134ad3e270926670e21704e030cbb0713b10938a6d7330
|
Provenance
The following attestation bundles were made for pocket_joe-0.2.0.5-py3-none-any.whl:
Publisher:
publish.yml on Sohojoe/pocket-joe
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pocket_joe-0.2.0.5-py3-none-any.whl -
Subject digest:
cce67c981f4d8fd89c743adcb0c93d671ca0e92b69dac69d6b7d506b10233142 - Sigstore transparency entry: 764232537
- Sigstore integration time:
-
Permalink:
Sohojoe/pocket-joe@5c68ea9be8c3959cec634ada09196ad98036d34f -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Sohojoe
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5c68ea9be8c3959cec634ada09196ad98036d34f -
Trigger Event:
push
-
Statement type: