The Fellowship pattern for Python AI agents — register, execute, and audit verbs with zero friction.
Project description
openverb-fellowship
The Fellowship pattern for Python AI agents.
Register, execute, and audit verbs with zero friction.
Built on the openverb core protocol.
pip install openverb-fellowship
"In clarity, they found control. In control, they found scale. In scale, they found order."
— The Fellowship of the Verbs
The Problem
AI systems hide their capabilities. Actions are scattered across prompts, routes, and handlers.
A developer asks: "What can my AI actually do?" — and there's no clear answer.
The Solution
Declare every action as a verb. Named. Structured. Visible. Controllable.
from openverb_fellowship import Fellowship
fellowship = Fellowship("my_app")
@fellowship.verb(
name="job.create",
description="Create a new job record",
params={
"title": {"type": "string", "description": "Job title", "required": True},
"client_id": {"type": "string", "description": "Client ID", "required": True},
},
returns={"id": {"type": "string", "description": "New job ID"}},
)
def create_job(params):
job = db.jobs.create(title=params["title"], client_id=params["client_id"])
return {"verb": "job.create", "status": "success", "data": {"id": job.id}}
# See everything your AI can do
fellowship.list_verbs()
# ["job.create"]
# Execute with validation
result = fellowship.execute({"verb": "job.create", "params": {"title": "Fix roof", "client_id": "c1"}})
# {"verb": "job.create", "status": "success", "data": {"id": "job_123"}}
Quick Start
1. Define your Fellowship
from openverb_fellowship import Fellowship, FellowshipExecutor, ai_safe_policy
fellowship = Fellowship("tradie_app", description="Job management AI verbs")
@fellowship.verb(
name="job.create",
description="Create a new job",
params={
"title": {"type": "string", "description": "Job title", "required": True},
"client_id": {"type": "string", "description": "Client ID", "required": True},
},
returns={"id": {"type": "string", "description": "New job ID"}},
category="jobs",
)
def create_job(params):
return {"verb": "job.create", "status": "success", "data": {"id": "job_abc"}}
@fellowship.verb(
name="invoice.generate",
description="Generate a PDF invoice for a job",
params={"job_id": {"type": "string", "description": "Job ID", "required": True}},
returns={"url": {"type": "string", "description": "PDF download URL"}},
category="invoices",
)
def generate_invoice(params):
url = pdf_service.generate(params["job_id"])
return {"verb": "invoice.generate", "status": "success", "data": {"url": url}}
2. Execute with policy
executor = FellowshipExecutor(fellowship, policy=ai_safe_policy())
result = executor.execute({
"verb": "job.create",
"params": {"title": "Fix roof", "client_id": "c1"}
})
# {"verb": "job.create", "status": "success", "data": {"id": "job_abc"}, "duration_ms": 2.1, ...}
3. Connect to your AI model
from openverb_fellowship import generate_system_prompt, parse_ai_response, format_receipt
# Generate the system prompt
system_prompt = generate_system_prompt(fellowship)
# Call your model
response = openai.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": "Create a job to fix the roof for client C1"}
]
)
# Parse and execute
parsed = parse_ai_response(response.choices[0].message.content)
if parsed["success"]:
receipt = executor.execute_batch(parsed["actions"])
print(format_receipt(receipt))
The @verb Decorator
@fellowship.verb(
name="user.notify", # required — dot-separated namespace.action
description="...", # required — what it does
params={ # optional — input schema
"user_id": {"type": "string", "required": True, "description": "..."},
"message": {"type": "string", "required": True, "description": "..."},
},
returns={ # optional — output schema
"sent": {"type": "boolean", "description": "Whether the notification was sent"},
},
destructive=False, # marks the verb as dangerous in prompts/policy
requires_confirmation=False, # marks the verb as needing explicit user approval
category="users", # optional — groups verbs in prompts
)
def notify_user(params):
# params is a dict of the declared params
# return an ActionResult dict
return {"verb": "user.notify", "status": "success", "data": {"sent": True}}
Programmatic registration (no decorator)
fellowship.register(
"map.geocode",
handler=geocode_address,
description="Geocode a street address to coordinates",
params={
"address": {"type": "string", "required": True, "description": "Street address"},
},
returns={
"lat": {"type": "number", "description": "Latitude"},
"lng": {"type": "number", "description": "Longitude"},
},
)
Executor
from openverb_fellowship import FellowshipExecutor, format_receipt
executor = FellowshipExecutor(
fellowship,
policy=ai_safe_policy(), # policy config
dry_run=False, # if True, skips all execution
stop_on_failure=False, # if True, halts batch on first failure
on_before=lambda action: log.info(f"Executing {action['verb']}"),
on_after=lambda action, result: audit_db.write(result),
)
# Single action
result = executor.execute({"verb": "job.create", "params": {"title": "T", "client_id": "c1"}})
# Batch
receipt = executor.execute_batch([
{"verb": "job.create", "params": {"title": "Job A", "client_id": "c1"}},
{"verb": "invoice.generate", "params": {"job_id": "job_abc"}},
])
print(format_receipt(receipt))
# Fellowship Receipt — 2026-04-26T...
# Status: ✓ SUCCESS
# Summary: 2 succeeded
# Executed:
# ✓ job.create (1.2ms)
# ✓ invoice.generate (3.4ms)
# Total: 4.6ms
Policy
Control exactly what AI is allowed to do.
from openverb_fellowship import ai_safe_policy, read_only_policy, deny_list_policy, create_policy
# Built-in presets
ai_safe_policy() # requires_confirmation for destructive verbs
permissive_policy() # allow everything (trusted envs only)
read_only_policy(allowed_verbs={"job.list", "job.get"})
deny_list_policy(["db.reset", "user.delete"], reason="Disabled in this environment")
# Fluent builder
policy = (
create_policy()
.deny("db.reset", "Requires manual intervention")
.require_confirmation("invoice.generate", "Invoices need approval")
.allow("job.list")
.build()
)
Policy decisions: "allow" | "deny" | "require_confirmation"
When require_confirmation fires, the result has skipped=True and skip_reason — your UI can surface this to the user before retrying.
Prompt Generator
from openverb_fellowship import generate_system_prompt, to_tool_call_schemas
# Full system prompt (markdown, all verbs)
prompt = generate_system_prompt(fellowship)
# Category filter
prompt = generate_system_prompt(fellowship, categories=["jobs"])
# XML style (recommended for Claude)
prompt = generate_system_prompt(fellowship, style="xml")
# Compact — verb names only, no schema (for token efficiency)
prompt = generate_system_prompt(fellowship, include_schema=False, include_example=False, style="plain")
# OpenAI / Anthropic tool-call schemas
tools = to_tool_call_schemas(fellowship)
tools = to_tool_call_schemas(fellowship, verb_names=["job.create", "invoice.generate"])
Inspection
fellowship.list_verbs()
# ["job.create", "job.delete", "job.list", "invoice.generate"]
fellowship.get_verb("job.create")
# {"name": "job.create", "description": "...", "params": {...}, ...}
fellowship.get_verbs_by_category("jobs")
fellowship.get_destructive_verbs()
fellowship.get_verbs_requiring_confirmation()
print(fellowship.describe())
# Fellowship: tradie_app
# Verbs: 4
# job.create
# Create a new job
# job.delete [destructive, requires-confirmation]
# Delete a job permanently
# ...
# Export the VerbLibrary as JSON (compatible with all openverb tools)
print(fellowship.to_json())
# Use with core openverb utilities directly
from openverb import build_registry, validate_action
registry = build_registry(fellowship.library)
Loading an External Library
# Load from an existing openverb.*.json file
fellowship.load_library("openverb.ide.json")
# Then register handlers for the loaded verbs
fellowship.add_handler("create_file", my_create_file_fn)
Architecture
openverb-fellowship
├── registry.py — Fellowship class, @verb decorator, VerbLibrary construction
├── executor.py — FellowshipExecutor: validate → policy → execute → receipt
├── policy.py — allow/deny/require_confirmation rules + fluent builder
└── prompt.py — system prompt generator + tool-call schema export
Core dependency: openverb (load_library, build_registry, validate_action, create_executor)
The Fellowship produces a valid VerbLibrary dict that works with all openverb core utilities.
All types (Verb, Action, ActionResult, VerbHandler) come from the core — nothing is duplicated.
License
MIT — © 2026 OpenVerb Contributors
openverb.org/the-fellowship-of-the-verbs
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 openverb_fellowship-0.1.0.tar.gz.
File metadata
- Download URL: openverb_fellowship-0.1.0.tar.gz
- Upload date:
- Size: 20.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4cb921cd6090d8e53c8d2f1c07002b0a53ff1298deac03e9ed243263e39b8fba
|
|
| MD5 |
f113d069b4b7076823b3d21d1f851d96
|
|
| BLAKE2b-256 |
597c3709dd88b089ed2205b94e25f06a9347edb45853c2e31099db7ffb383f3c
|
File details
Details for the file openverb_fellowship-0.1.0-py3-none-any.whl.
File metadata
- Download URL: openverb_fellowship-0.1.0-py3-none-any.whl
- Upload date:
- Size: 17.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
006e6887c5972aabd48a3414eea9c9a68f8fef8ecc0c6ba0d4fdf4dd652ea206
|
|
| MD5 |
c98510445951f4ed516eef080c95ec9a
|
|
| BLAKE2b-256 |
005e4fbb2640ec8a38491a69d4cc482f3ec28a9dbd20ffd5455d86e6d0d69921
|