Three calls. That's it
Your agent keeps its tools, prompts, and LLM. Momentum answers one question: may this step continue, must a person look, or must this case stop?
fe.governed(fn)
One case = one run. This is required before the other two calls.
await fe.emitEvent(...)
Send what happened. Policy decides continue, pause, or block.
await fe.requestApproval(...)
Always pauses. The next line runs only after Approve on Feed.
Before you start
Two values from Momentum. Put both in the environment — never in git.
- Register the agent on Feed → Add Agent (Code agent). Copy the agent_id.
- Create a live key on API Key (fe_live_…). It is shown once.
.env
FORCEEQUALS_API_KEY=fe_live_paste_your_key_here
FORCEEQUALS_AGENT_ID=loan-agentInstall
Requires Node.js 18+. The package is forceequals on npmjs.com/package/forceequals. Run this in the agent folder.
Terminal
npm install forceequalsQuick start
Create the client, wrap the run, report facts, ask a human when needed. Paste this, swap in your event names, and run it.
agent.mjs
import {
ForceEquals,
GovernanceBlockedError,
GovernanceRejectedError,
} from "forceequals";
const fe = new ForceEquals({
apiKey: process.env.FORCEEQUALS_API_KEY,
agentId: process.env.FORCEEQUALS_AGENT_ID,
});
const handleApplication = fe.governed(async (payload) => {
await fe.emitEvent("loan.risk.calculated", {
loan_amount: payload.amount,
risk_score: payload.riskScore,
});
await fe.requestApproval({
title: "Approve disbursement",
context: { amount: payload.amount },
});
// your side-effect after Approve
return { ok: true };
});
const result = await handleApplication({ amount: 12000, riskScore: 0.4 });
if (result instanceof GovernanceBlockedError) {
console.error("Blocked:", result.reason);
} else if (result instanceof GovernanceRejectedError) {
console.error("Rejected:", result.reason);
} else {
console.log("Done", result);
}What each call does
| Call | When to use it |
|---|---|
| fe.governed(fn) | Wrap one business run (one loan, one ticket, one PR). |
| fe.emitEvent(name, data) | Report a fact. Policy decides continue, pause, or block. |
| fe.requestApproval({ title, context }) | Ask a human on Momentum Feed. Your process waits until they resolve it. |
If emitEvent is blocked, the SDK throws GovernanceBlockedError. If a pause is rejected, it throws GovernanceRejectedError. Approve resumes the next line.
Review on Feed
- Open Momentum → Feed while the agent is waiting.
- Approve resumes the next line (disburse, merge, send). Reject closes this case. Request change stops this run so you can resubmit.
Every decision is also stored on History.
Environment
| Variable | Purpose |
|---|---|
| FORCEEQUALS_API_KEY | Live key from Momentum (fe_live_…). |
| FORCEEQUALS_AGENT_ID | Same id you registered in Connect Agent. |
| FORCEEQUALS_BASE_URL | Optional override. Defaults to the hosted API — you can omit this. |
| FORCEEQUALS_POLL_SECONDS | How often to check a paused run (default 2). |
You can pass apiKey and agentId in the constructor instead of using env vars.
If something fails
| What you see | What to do |
|---|---|
| Cannot find package forceequals | Run npm install forceequals in the agent folder. |
| 401 / invalid or revoked key | Generate a new key on API Key. Confirm FORCEEQUALS_API_KEY has no extra spaces. |
| execution_id required | Wrap the run with fe.governed(...) before emitEvent. |
| Nothing appears on Feed | agent_id in code must match Connect Agent. A silent continue will not create a card — pause or await fe.requestApproval(...) will. |
Built the agent on a canvas instead? Use the no-code guide. To put this org's policies into Cursor, Claude, or Codex — without emitting events — connect on the MCP tab.