Metadata-Version: 2.1
Name: pawly
Version: 0.1.3
Summary: Lightweight reference implementation of the Aploy Pawly execution-boundary controller.
Requires-Python: >=3.10
License: Apache-2.0
Description-Content-Type: text/markdown
Requires-Dist: pawly-pawprint>=0.1.0

# Pawly

<p align="center">
  <img src="docs/assets/icon.png" alt="Pawly icon" width="128">
</p>

<p align="center">
  <strong>Managed, safe execution for AI agent actions.</strong>
</p>

Pawly takes over the messy part of agent execution: deciding which capability
should run, checking whether it is allowed, wrapping the call in a policy-aware
execution path, and returning a receipt you can debug or audit later. It is built
for the moment an agent is about to touch the outside world: send an email,
publish content, issue a refund, update a record, call an API, or trigger a
payment.

Instead of wiring every tool call, permission rule, fallback, and audit record by
hand, your agent delegates a goal to Pawly. Pawly manages the execution path so
your agent can act without quietly doing something unsafe, unauthorized, or
impossible to reconstruct later.

```python
from pawly import Pawly

# Register skills before executing a goal. See Quickstart for a complete example.
pawly = Pawly("./worker.yaml")
result = pawly.achieve(
    objective="safe reply to the duplicate charge question",
    context={"order_id": "123"},
    constraints={"max_cost": 2},
)
```

Pawly is not another agent framework. It is the safety and execution layer you
put behind one: your agent decides what it wants, Pawly manages how that action
is allowed to run.

This repository contains Open Pawly, the local runtime for defining action
boundaries, registering skills, running policy checks, and collecting receipts
before your agent touches external systems.

## Status

Pawly is in alpha. The goal interface, Pawprint boundary model, and local
execution receipts are the primary stable surfaces. Lower-level adapter and
gateway APIs may continue to evolve.

## Why Pawly

Building agent products gets painful and risky right after the demo works. You
start with tool calls, then quickly need routing, permission checks, blocked
actions, review paths, audit logs, reproducible receipts, and framework adapters.
The hardest bugs are not syntax errors; they are agents calling the wrong tool,
acting outside their scope, or leaving no useful trace when something goes wrong.

Pawly packages that execution work into a small runtime:

- **Stop hand-rolling tool routing.** Delegate an objective and let Pawly map it
  to a registered capability.
- **Make external actions safer.** Put policy checks before calls that can email,
  publish, refund, delete, pay, or modify user data.
- **Keep permissions out of prompt glue.** Declare allowed, review-only, and
  blocked capabilities in Pawprint instead of relying on model instructions.
- **Make execution inspectable.** Every goal attempt can return an action receipt
  with the selected capability and execution envelope.
- **Keep your existing framework.** Insert Pawly before the tool or skill
  executor instead of rebuilding your agent loop.
- **Run locally first.** Use deterministic Open Pawly policy checks offline,
  then connect a hosted project when you want managed keys, team review, and
  shared execution history.

## Core Concepts

| Concept | Meaning |
| --- | --- |
| Pawprint | The YAML contract that declares metadata, capabilities, and boundaries. |
| Capability | A named action the agent may ask Pawly to use. |
| Skill | Local Python code registered to implement a capability. |
| Objective | The goal delegated by the agent runtime. |
| Execution envelope | The scoped runtime boundary for a goal: resources, capabilities, limits, and approvals. |
| Action receipt | The auditable result of a goal attempt. |

## Install

From PyPI, after release:

```bash
pip install pawly
```

From GitHub:

```bash
pip install "git+https://github.com/dustin-aploy/pawprint.git"
pip install "git+https://github.com/dustin-aploy/open_pawly.git" --no-deps
```

From source:

```bash
git clone git@github.com:dustin-aploy/open_pawly.git
cd open_pawly
pip install -e ../pawprint
pip install --no-build-isolation --no-deps -e ".[dev]"
```

The PyPI package dependency is `pawly-pawprint`. Do not install the unrelated
package named `pawprint`.

## Quickstart

### 1. Declare the agent boundary

Create `worker.yaml`. The `capabilities` list describes what the agent may ask
Pawly to do. The `boundaries` section is the policy: allow safe work, require
review for sensitive work, and block work that should never run automatically.

```yaml
id: support-worker
name: Support Worker
role: Support action runner
summary: Handles low-risk support replies and hands off sensitive customer actions.

capabilities:
  # Capabilities are the actions your agent may delegate to Pawly.
  - safe_reply
  - issue_refund

boundaries:
  # Safe to run automatically.
  auto:
    - safe_reply
  # Must produce a review path before execution.
  ask_first:
    - issue_refund
  # Never run automatically.
  never:
    - delete_customer

handoff:
  to: support-lead
  when:
    - refund requested
    - customer asks for an exception

style:
  tone: clear and practical
  format: concise support update
```

Validate it:

```bash
python -m pawprint.validate ./worker.yaml
```

If validation reports different boundary field names, update `pawprint` and
`pawly-pawprint` together. The Pawprint file, the local runtime, and any hosted
project connection should all use the same schema version.

### 2. Register the skills Pawly is allowed to run

Pawly only executes skills you register. This keeps prompt output separate from
real system actions.

```python
from pawly import HeuristicPolicy, Pawly, PolicyService, SkillService

skills = SkillService.local({
    "safe_reply": lambda args, context: {
        "message": "We checked your order and will follow up safely.",
        "objective": args["objective"],
        "order_id": context.get("order_id"),
    },
})

pawly = Pawly(
    "./worker.yaml",
    skills=skills,
    policy=PolicyService.local(routing=HeuristicPolicy()),
)
```

### 3. Delegate a goal and inspect the receipt

```python
result = pawly.achieve(
    # Your agent delegates an objective; Pawly chooses an allowed skill.
    objective="safe reply to the duplicate charge question",
    # Context becomes the resource scope recorded in the receipt.
    context={"order_id": "123", "channel": "chat"},
    # Constraints become execution limits or approval policy inputs.
    constraints={"max_cost": 2},
)

print(result.status)
print(result.result)
print(result.action_receipt["execution_envelope"])
```

If the objective matches a registered skill and the policy allows it, Pawly runs
the skill. If the objective needs review or is blocked, the receipt tells you
which boundary stopped it.

### 4. Choose policy and audit services

Open Pawly and hosted Pawly use the same constructor shape. Your Pawprint stays
the source of capabilities and boundaries. `PolicyService` decides how a run is
reviewed and routed. `AuditService` decides where action records go. A hosted
key already identifies the project.

```bash
# Paste the one-time hosted key from the web console.
export PAWLY_API_KEY="paste_the_project_key"
```

Local policy and local audit:

```python
from pawly import AuditService, HeuristicPolicy, Pawly, PolicyService

local = Pawly(
    "./worker.yaml",
    skills=skills,
    policy=PolicyService.local(routing=HeuristicPolicy()),
    audit=AuditService.local("./pawly-audit.jsonl"),
)
```

Cloud audit, local policy:

```python
import os
from pawly import AuditService, HeuristicPolicy, Pawly, PolicyService

cloud_audit = Pawly(
    "./worker.yaml",
    skills=skills,
    policy=PolicyService.local(routing=HeuristicPolicy()),
    audit=AuditService.cloud(api_key=os.getenv("PAWLY_API_KEY")),
)

result = cloud_audit.achieve(
    objective="safe reply to the duplicate charge question",
    context={"order_id": "123"},
)
print(result.action_receipt["audit"]["alerts"])
```

Cloud audit plus local audit file:

```python
cloud_and_file = Pawly(
    "./worker.yaml",
    skills=skills,
    policy=PolicyService.local(routing=HeuristicPolicy()),
    audit=AuditService.cloud(
        api_key=os.getenv("PAWLY_API_KEY"),
        local_path="./pawly-audit.jsonl",
    ),
)
```

Cloud skills added from the dashboard:

```python
cloud_skills = Pawly(
    "./worker.yaml",
    # Search and add marketplace skills in the dashboard, then call them by id.
    skills=SkillService.cloud(
        api_key=os.getenv("PAWLY_API_KEY"),
        skill_ids=["safe_reply", "summarize_ticket"],
    ),
    policy=PolicyService.local(routing=HeuristicPolicy()),
    audit=AuditService.cloud(api_key=os.getenv("PAWLY_API_KEY")),
)
```

Hosted policy review:

```python
cloud_policy = Pawly(
    "./worker.yaml",
    skills=skills,
    policy=PolicyService.cloud(
        api_key=os.getenv("PAWLY_API_KEY"),
        routing=HeuristicPolicy(),
    ),
    audit=AuditService.cloud(api_key=os.getenv("PAWLY_API_KEY")),
)
```

The public API intentionally uses one `PolicyService`. Internally, Open Pawly
bridges that service to boundary review and action routing, so you do not need
to decide between similarly named policy hooks. If hosted policy is unavailable
for the current key or environment, the receipt includes a dashboard entry and
the local development path remains usable.

### 5. Batch-wrap existing OpenAI tools

You do not need to rewrite tools your agent already uses. If you already have
OpenAI-style tools with a name and executor, register them as a `SkillService`
and keep your existing executor code.

```python
from pawly import AuditService, Pawly, PolicyService, SkillService

openai_tools = [
    {
        "tool_name": "safe_reply",
        "executor": lambda payload: ticket_system.reply(
            ticket_id=payload["payload"]["ticket_id"],
            body=f"We checked this safely: {payload['payload']['objective']}",
        ),
    },
    {
        "tool_name": "summarize_ticket",
        "executor": lambda payload: ticket_system.summarize(payload["payload"]["ticket_id"]),
    },
]

pawly = Pawly(
    "./worker.yaml",
    skills=SkillService.from_openai_tools(openai_tools),
    policy=PolicyService.local(),
    audit=AuditService.cloud(api_key=os.getenv("PAWLY_API_KEY")),
)
```

Think of the constructor as three replaceable pieces behind the same Pawprint:

| Piece | Local mode | Hosted mode |
| --- | --- | --- |
| `skills` | Local callables, a `SkillRegistry`, or existing OpenAI/framework tools through adapters | Marketplace and protected skills added to the project in the dashboard |
| `policy` | Rule-based review plus optional local routing | Hosted policy review when selected |
| `audit` | JSONL file or custom sink | Hosted dashboard sync, optionally also local JSONL |

When `PAWLY_API_KEY` is missing, Pawly returns a configuration-required result
with a link to the developer console instead of failing with an unclear error.
When a cloud key is provided without a Pawprint path, Pawly returns
`missing_pawprint` because services are runtime wiring, not the local execution
contract.

## Public API

The recommended integration surface is goal-oriented:

```python
Pawly(...).achieve(objective=..., context=..., constraints=...)
```

Lower-level APIs are available for adapters and migration work:

| API | Use when |
| --- | --- |
| `achieve(...)` | You want the top-level helper around `Pawly(...).achieve(...)`. |
| `DecisionEngine.run_actions(...)` | You already have explicit `Action` objects. |
| `run_actions(...)` | You want the top-level explicit-action helper. |
| `decide(...)` | You only need decision output, not execution. |
| `run(...)` | You need the legacy task/action evaluation helper. |
| `wrap_*` adapters | You are inserting Pawly into an existing tool executor. |

## Receipts

`achieve(...)` returns `GoalExecutionResult`.

```python
{
    "status": "completed",
    "objective": "safe reply to the duplicate charge question",
    "selected_capability": "safe_reply",
    "execution_envelope": {
        "resource_scope": {"order_id": "123", "channel": "chat"},
        "allowed_capabilities": ["safe_reply"],
        "financial_limits": {"max_cost": 2},
        "execution_limits": {},
        "approval_policy": {},
    },
}
```

Common statuses:

| Status | Meaning |
| --- | --- |
| `completed` | A matching local skill ran successfully. |
| `unsupported_goal` | No registered skill matched the delegated objective. |
| `configuration_required` | A Pawprint path or hosted key is missing; the receipt includes the next step. |
| `failed` | Local execution failed or was blocked. |

## Architecture

Pawly keeps the core runtime small:

```text
Agent runtime
    |
    | objective + context + constraints
    v
Pawly
    |-- Pawprint boundary
    |-- Skill registry
    |-- Policy engine
    |-- Execution gateway
    v
Local skill executor
```

The package intentionally has no dependency on hosted services. Managed
planning, credential brokering, marketplace access, and organization governance
are optional integrations, not Open Pawly runtime requirements.

## Adapters

Pawly can be inserted at the point where an existing framework is about to run a
tool, transition, or skill:

- OpenAI Agents
- Claude Skills
- LangGraph
- CrewAI
- OpenClaw-style loops
- self-hosted HTTP workers

See [`src/pawly/adapters/README.md`](src/pawly/adapters/README.md) and
[`adapters/`](adapters/).

## Documentation

- [Architecture](docs/architecture.md)
- [Execution gateway](docs/execution_gateway.md)
- [Run actions](docs/run_actions.md)
- [Approval flow](docs/approval_flow.md)
- [Audit and replay](docs/audit_and_replay.md)
- [Pawprint policy engine](docs/pawprint_policy_engine.md)
- [Protected skills](docs/protected_skills.md)
- [Project status](docs/status.md)

## Development

```bash
pip install -e ../pawprint
pip install --no-build-isolation --no-deps -e ".[dev]"
python -m pytest
```

Focused smoke tests:

```bash
python -m pytest tests/test_goal_interface.py tests/test_run_actions.py tests/test_runtime_smoke.py
```

## Contributing

Issues and pull requests are welcome. For code changes, include focused tests and
keep hosted-service behavior out of the Open Pawly runtime. If a change affects
the Pawprint contract, update the sibling `pawprint` package and relevant docs
in the same patch.

## Repository Layout

```text
src/pawly/       core runtime package
examples/        runnable examples
docs/            architecture and runtime notes
tests/           package tests
test-suite/      local conformance suite
adapters/        adapter docs and stubs
scripts/         bootstrap and smoke-test helpers
```

## License

Apache-2.0. See [LICENSE](LICENSE).
