Metadata-Version: 2.4
Name: forceequals
Version: 0.1.0
Summary: Governance SDK for AI agents. Builders only call emit_event() and request_approval().
Author: ForceEquals
License: Proprietary
Project-URL: Homepage, https://www.forceequals.ai
Project-URL: Documentation, https://www.forceequals.ai
Project-URL: Repository, https://github.com/ForceEquals/forceequals-momentum
Keywords: ai,agents,governance,langchain
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.32.0
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.3.0; extra == "langchain"
Requires-Dist: langgraph>=0.2.0; extra == "langchain"

# ForceEquals SDK — builder integration guide

How to govern **your** coded agent with ForceEquals. Share this with customers and internal agent authors.

You write the agent. ForceEquals decides whether each step may **continue**, must **pause** for a human, or must be **blocked**.

```text
Your agent reports facts  →  ForceEquals applies org policy  →  Your agent obeys
```

You do **not** write pause/poll/resume loops. That lives in the SDK.

---

## 1. What you install vs what ForceEquals hosts

| Piece | Who runs it | What it is |
|---|---|---|
| **SDK** (`pip install forceequals`) | You, inside your agent process | Small Python library |
| **ForceEquals API** | ForceEquals (or your local `mock_server.py`) | Policy + approvals |
| **Momentum** | ForceEquals web app | Create API key, policies, approve cards |

The SDK is **not** a server. You do not deploy it. You install it. ForceEquals deploys the API and Momentum.

---

## 2. What you need (credentials)

Create these in **Momentum** while logged in (your email owns the key).

| Item | Example | Where |
|---|---|---|
| **API key** | `fe_live_...` | Momentum → API Key → Generate. Shown **once**. |
| **Agent id** | `github-agent` | Momentum → Add Agent / Connect. Must match `FORCEEQUALS_AGENT_ID`. |

Put them in your agent `.env` (never commit the key):

```env
FORCEEQUALS_API_KEY=fe_live_your_key_here
FORCEEQUALS_AGENT_ID=github-agent
```

The SDK calls the hosted ForceEquals API. You do **not** set `FORCEEQUALS_BASE_URL`. That URL is built into the SDK. (Optional local override: `FORCEEQUALS_BASE_URL=http://127.0.0.1:8787` when running `mock_server.py`.)

**You do not send your email in the SDK.** The API key **is** your identity. The API looks up the key and knows which Momentum user owns it.

**Not ForceEquals credentials** (those stay yours):

- GitHub token, OpenAI/Claude key, database passwords — your agent’s own tools.

If the key is missing, revoked, or not created under your Momentum login, SDK calls return **401**.

---

## 3. Install

```bash
pip install forceequals
```

Until the package is on PyPI, from this repo:

```bash
cd forceequals-platform
pip install -e .
```

Python 3.10+.

---

## 4. Integrate — step by step

### Step 1 — Create the client once

```python
from forceequals import (
    ForceEquals,
    GovernanceBlockedError,
    GovernanceRejectedError,
)

fe = ForceEquals()  # reads FORCEEQUALS_API_KEY + FORCEEQUALS_AGENT_ID
```

### Step 2 — Wrap each business run with `@fe.governed`

One GitHub PR, one loan application, one user message = one run.

```python
class MyAgent:
    @fe.governed
    def handle_event(self, payload: dict) -> None:
        ...
```

This allocates `execution_id` / `case_id`. Without it, `emit_event` fails.

Keep your **process** alive yourself (a `while True` or worker). ForceEquals only governs **this run**.

### Step 3 — Report facts with `emit_event`

Call this after something meaningful happened. ForceEquals policies decide continue / pause / block.

```python
decision = fe.emit_event(
    "pull_request.opened",
    {
        "repo": payload["repo"],
        "pr": payload["number"],
        "title": payload["title"],
        "base_branch": payload["base_branch"],
    },
)
# If this returns, status was continue (or pause was approved).
# If policy blocked, GovernanceBlockedError is raised.
```

Do not check policy yourself. Send facts only.

### Step 4 — Explicit human gate with `request_approval`

Use this when **you already know** a human must approve (merge to `main`, pay out money). Always pauses.

```python
fe.request_approval(
    title=f"Merge PR #{payload['number']} into {payload['base_branch']}",
    context={"pr": payload["number"], "repo": payload["repo"]},
)
```

The SDK waits until a reviewer approves, denies, or requests changes in Momentum (or `approve.py` locally).

### Step 5 — Catch block / reject so the process stays up

```python
def run_one(agent, payload):
    try:
        agent.handle_event(payload)
    except GovernanceBlockedError as exc:
        print(f"Blocked by policy: {exc}")
    except GovernanceRejectedError as exc:
        print(f"Rejected by reviewer: {exc}")
```

Blocked/rejected stops **this run**, not the agent process.

### Step 6 — Keep doing your work

LLM review, GitHub API, tools — that is your code. After a successful `request_approval`, continue (comment, merge, notify). ForceEquals does not merge GitHub for you.

---

## 5. Minimal example

```python
from forceequals import ForceEquals, GovernanceBlockedError, GovernanceRejectedError

fe = ForceEquals()

class GitHubAgent:
    @fe.governed
    def handle_pull_request(self, pr: dict) -> None:
        fe.emit_event("pull_request.opened", {
            "repo": pr["repo"],
            "pr": pr["number"],
            "title": pr["title"],
        })
        # your review / tools here
        fe.request_approval(
            title=f"Merge PR #{pr['number']}",
            context={"pr": pr["number"]},
        )
        print("Governance allowed this merge (you still perform it).")

agent = GitHubAgent()
try:
    agent.handle_pull_request({"repo": "acme/app", "number": 42, "title": "Fix"})
except GovernanceBlockedError as exc:
    print("Blocked:", exc)
except GovernanceRejectedError as exc:
    print("Rejected:", exc)
```

---

## 6. What ForceEquals returns

| Status | Meaning | What the SDK does |
|---|---|---|
| `continue` | Allowed | Returns; your next line runs |
| `paused` | Human needed | Waits; card appears in Momentum |
| `blocked` | Hard no | Raises `GovernanceBlockedError` |

`request_approval` always pauses. `emit_event` is adaptive (policies + Policy LLM).

Optional: a policy may return `override_result` (forced answer/tool output). The SDK applies that; you do not.

---

## 7. Local test (before production URL)

1. Log into Momentum locally → create API key → copy `fe_live_...` (or use `FE_LOCAL_KEY` for a local-only demo).
2. Start the ForceEquals API: `python mock_server.py` (port **8787**).
3. Optional: `FORCEEQUALS_BASE_URL=http://127.0.0.1:8787` so a `fe_live_` key hits the local mock instead of the hosted API. `FE_LOCAL_KEY` already defaults to localhost.
4. Run your agent. On pause, approve in Momentum when wired, or:

```bash
python approve.py <approval_id>
```

5. Agent should print that it resumed.

---

## 8. Checklist

- [ ] Momentum account and API key (`fe_live_...`)
- [ ] `FORCEEQUALS_AGENT_ID` matches the agent registered in Momentum
- [ ] `pip install forceequals`
- [ ] `ForceEquals()` once at process start
- [ ] `@fe.governed` on each run
- [ ] `emit_event` after real work facts
- [ ] `request_approval` on irreversible steps
- [ ] Catch `GovernanceBlockedError` / `GovernanceRejectedError`
- [ ] Your own loop keeps the agent online for the next event

---

## 9. Related

| Doc | Audience |
|---|---|
| [PUBLISH_AND_DEPLOY.md](./PUBLISH_AND_DEPLOY.md) | ForceEquals team — PyPI + API hosting |
| [FLOW_GUIDE.md](./FLOW_GUIDE.md) | Internal product flow |
| [NO_CODE_INTEGRATION.md](./NO_CODE_INTEGRATION.md) | n8n / Agentforce (HTTP, no Python SDK) |
