Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

openverb_fellowship-0.1.0.tar.gz (20.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

openverb_fellowship-0.1.0-py3-none-any.whl (17.4 kB view details)

Uploaded Python 3

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

Hashes for openverb_fellowship-0.1.0.tar.gz
Algorithm Hash digest
SHA256 4cb921cd6090d8e53c8d2f1c07002b0a53ff1298deac03e9ed243263e39b8fba
MD5 f113d069b4b7076823b3d21d1f851d96
BLAKE2b-256 597c3709dd88b089ed2205b94e25f06a9347edb45853c2e31099db7ffb383f3c

See more details on using hashes here.

File details

Details for the file openverb_fellowship-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for openverb_fellowship-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 006e6887c5972aabd48a3414eea9c9a68f8fef8ecc0c6ba0d4fdf4dd652ea206
MD5 c98510445951f4ed516eef080c95ec9a
BLAKE2b-256 005e4fbb2640ec8a38491a69d4cc482f3ec28a9dbd20ffd5455d86e6d0d69921

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page