Connector Author Guide
A Govern connector bridges an external system — a practice management system, document store, or billing platform — to the Govern policy engine. Agents call Govern’s MCP tools; Govern routes approved writes to your connector and records every decision to a tamper-evident ledger.
What a connector does
- Receives a governed write request (e.g. “create a billing entry for matter X”)
- Translates the canonical slot payload into the target system’s API shape
- Executes the write against the system of record
- Returns a structured result that Govern records to the tamper-evident ledger
Govern handles the policy gate, the capability envelope check, the ledger append, and the audit-pack generation. Your connector’s responsibility is the translation layer and the write itself.
The port contract
Your connector implements the Connector<TClient> port from @govern/core:
import type { Connector } from '@govern/core';
export const myConnector: Connector<MyClient> = {
// ConnectorDescriptor fields (flat — no nested 'descriptor' object).
id: 'my-system',
displayName: 'My System',
version: '1.0.0',
capabilities: [
'read.matters',
'read.contacts',
'write.expense',
'write.note',
'edit.matter',
// ... declare every operation your system supports
],
buildClient: async ({ env, firmId, log }) => {
// Return your initialized client.
// env is the Cloudflare Workers env bindings (cast to your env shape).
// firmId is the Govern firm identifier for the current request.
return new MyClient({ apiKey: (env as MyEnv).MY_API_KEY });
},
};The ConnectorDescriptor fields (all top-level on the connector object):
| Field | Type | Description |
|---|---|---|
id | string | Stable, lowercase identifier. Used as a key in config tables, KV prefixes, and admin URLs. Never rename — migration cost. |
displayName | string | Human-readable system name (displayed in the admin UI). |
version | string | Connector semver. Bump on breaking changes to the canonical projection contract. |
capabilities | ConnectorCapability[] | Declared operational capabilities (see below). The policy engine uses this to short-circuit tool dispatch. |
Operational capabilities
The capabilities field is a closed set of operational surface declarations. The policy engine uses these to short-circuit dispatch — it rejects a write tool routed to a connector that hasn’t declared the matching operation.
| Capability | Description |
|---|---|
read.matters | Read matter records |
read.contacts | Read contact records |
read.users | Read user / timekeeper records |
read.documents | Read document metadata |
read.time_entries | Read time-entry records |
read.billing_codes | Read billing-code catalog |
read.billing_entry | Read billing entries |
read.attorney_rate | Read attorney billing rates |
write.time_entry | Create time entries |
write.expense | Create expense / billing entries |
write.note | Create notes |
write.activity | Create activity records |
write.document | Create documents |
write.billing_code | Create billing codes |
edit.matter | Mutate matter fields |
edit.contact | Mutate contact fields |
edit.billing_entry | Mutate billing entries |
edit.pnc | Mutate prospective-new-client records |
diag.probe_item_fields | Wide-field probe for admin diagnostics (ADR-019 mechanism 3) |
Slot policy tiers
Separate from connector operational capabilities, every canonical slot carries a policy tier annotation that describes the level of Govern policy gate applied when an agent reads or writes that slot. These tiers appear in the Slot vocabulary table.
| Tier | Description |
|---|---|
observation-only | Read access only. No writes permitted. Used for evidence-gathering, ledger projection, and matter-context resolution. |
mediated-write | Writes gated by Govern’s policy engine. The standard tier for entity mutations (matter status, contact metadata, PNC intake). |
mediated-write-financial | Mediated-write plus explicit per-firm grant required. Applies to financial-truth slots (billing-entry money fields, billing codes) that affect customer invoices and the firm’s books. |
Canonical slots
Govern passes writes as canonical slot payloads — a standardized vocabulary that abstracts over target-system field IDs. Your connector maps slots to your system’s fields.
The three-tier slot namespace:
core.*— owned by the spec working group. Append-only after v1.0. Changes require a spec PR.connector.<id>.*— owned by the connector author. Your connector can define slots in this namespace without WG involvement.firm.custom.*— owned by firm admins. No portability guarantee across connectors.
See the Slot vocabulary for the full core.* catalog (87 slots across matter, contact, PNC, and billing-entry entities).
Runtime slot discovery
The govern.describe_slots MCP method returns the active slot catalog at runtime, including which slots your connector has bound to your system’s field IDs.
govern.describe_slots is specified in the v1 Connector spec but the MCP endpoint is not yet live. It ships in Workstream A.2 when the second connector integration opens. Connectors can rely on the static slot catalog at this URL in the interim.Mapping slots to your system
Your connector declares its slot bindings in a catalog-defaults map. For each slot you support, provide the field reference in your system’s schema:
// packages/connectors/my-system/src/canonical-slots.ts
export const CATALOG_DEFAULTS: Partial<Record<CanonicalSlot, string>> = {
'matter.name': 'title', // your system's field name
'matter.responsible_attorney': 'user_id',
'matter.status': 'status',
// Declare null for slots your system does not support.
'matter.trust_balance': null,
};Slots declared as null produce a structured slot_unavailable error from the policy engine when a tool tries to read or write them — not a runtime adapter crash.
Per-firm overrides: firm admins can remap any slot to a different field ID through the /admin/fields/slots UI. Your catalog default is the floor; the per-firm override wins when set.
Intent declaration (proactive tier)
Agents that opt into the proactive tier call govern.declare_intent before executing. This gives Govern a chance to issue a capability envelope — pre-authorizing a batch of writes within stated bounds — before the agent commits to action.
The envelope carries:
- bounds — max total cost, max invocations, allowed slot scope
- expires_at — hard expiry (Govern rejects envelope IDs past this timestamp)
- issued_by_rule_id — set when a standing rule matched; null for ad-hoc grants (conditional status — reviewer ratification required)
Your connector receives approved writes pre-authorized by a capability envelope. The envelope ID arrives on every write tool call; Govern enforces the bounds before forwarding to your connector. Your connector does not need to evaluate bounds — that’s Govern’s job.
// Example: agent calls govern.declare_intent
const result = await mcpClient.call('govern.declare_intent', {
scope: {
tool_id: 'anthropic.claude-opus-4', // the AI tool making the call
mcp_method: 'govern.create_govern_expense', // MCP method it plans to call
matter_id: 'matter_abc', // optional: specific matter
description: 'Create AI-spend expense entry',
},
forecast_cost_cents: 450, // integer cents (not decimal USD)
agent_id: 'agent-007', // optional: sub-agent identifier
task_id: 'task-xyz', // optional: orchestrator task ID for correlation
});
// Govern returns:
// {
// intent_id: string,
// status: 'approved' | 'denied' | 'conditional',
// concerns: string[], // non-empty when status === 'conditional'
// denial_reason: string | null,
// envelope: { envelope_id, bounds, expires_at, issued_by_rule_id } | null,
// }
// Agent then passes envelope_id on the write call:
if (result.status !== 'denied' && result.envelope) {
await mcpClient.call('govern.create_govern_expense', {
matter_id: 'matter_abc',
envelope_id: result.envelope.envelope_id,
// ...
});
}Conditional intents (status conditional) have concerns that go to the reviewer queue at /review for human ratification before the write can proceed. Approved intents were cleared by a matching standing rule and the agent can proceed immediately. Denied intents were blocked by policy — the agent must not proceed with the write.
Identity contract
Your connector receives an IdentityClaims object on every authenticated request. It carries the IDP-native subject identifier (attorney_oid), the Govern firm scope (firm_id), and optional role claims.
See the Identity contract for the full JWT shape, required vs. optional claims, and how to plug in a non-Entra IDP.
Audit trail
Every write Govern routes produces a ledger row linked to the capability envelope and the provenance chain. Your connector does not need to manage audit logging — Govern handles it.
The ledger row captures:
- The tool ID and slot payload
- The policy decision (allow / deny / review)
- The capability envelope ID (if the proactive tier was used)
- The decision snapshot — rule version, rule IDs, input hash — at decision time
- A SHA-256 hash linked to the prior row (tamper-evident chain)
Audit packs are exportable as signed zip files. See the Audit-pack signing spec for the protocol.
Conformance
Run the conformance suite against your implementation before declaring it spec-compliant:
npx @govern/conformance verify <your-audit-pack.zip>
All 8 golden test cases must pass. See the Conformance suite page for the full case catalog and expected outputs.
Checklist for a new connector
- Implement
Connector<TClient>from@govern/corewith a stabledescriptor.idin theconnector.<id>namespace. - Declare your capability tier(s) in
descriptor.capabilities. - Publish your
CATALOG_DEFAULTS— map every slot you support to a field reference in your system; declarenullfor unsupported slots. - Implement
buildClient— initialize and return your API client from Cloudflare Workers env bindings. Avoid constructor side-effects; the function is called once per firm per Worker lifetime. - Wire the identity contract — verify incoming tokens and map IDP claims to
IdentityClaims. The Entra adapter inpackages/adapters/src/entra-auth.tsis the reference. - Export a signed audit pack and run
npx @govern/conformance verifyagainst it. All 8 cases must pass. - Open a PR against the spec repo adding your connector to the connector registry in
docs/connectors/. The PR is where the spec WG reviews your slot bindings for correctness.