Metadata-Version: 2.4
Name: openverb-fellowship
Version: 0.1.0
Summary: The Fellowship pattern for Python AI agents — register, execute, and audit verbs with zero friction.
License: MIT
Project-URL: Homepage, https://openverb.org/the-fellowship-of-the-verbs
Project-URL: Repository, https://github.com/openverb/openverb-fellowship
Keywords: openverb,fellowship,ai,agents,verbs,actions,registry,audit
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: openverb>=0.1.0
Dynamic: license-file

# openverb-fellowship

**The Fellowship pattern for Python AI agents.**

Register, execute, and audit verbs with zero friction.  
Built on the [`openverb`](https://pypi.org/project/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](https://openverb.org/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.

```python
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

```python
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

```python
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

```python
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

```python
@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)

```python
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

```python
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.

```python
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

```python
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

```python
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

```python
# 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](https://openverb.org/the-fellowship-of-the-verbs)
