All four platforms available
Reference · all four platforms availableDocs v1.0 · KernelPort v1 call names and shapes are stable

Get started

Quickstart: a first governed write

The quickstart walks through a first governed write: an AI agent sends a write intent, elan1 Govern checks it on the Right Path, returns an allow or a typed refusal, and writes a proof record you can read. It uses the HTTP API or the MCP server and needs a console invitation.

Take one process from “a person signs off today” to a write that climbs the Right Path in elan1 Govern, ends in a committed change, a typed refusal or a hold for a named approver, and leaves a 16-field proof record either way.

Docs v1.0Examples are illustrative specimens, not customer data

Docs menu
elan1 Govern, Work, Decide and Run are available. This quickstart describes the integration path customers follow, with illustrative requests and responses. Customers receive sandbox credentials and the full reference with their workspace; production keys are issued by elan1 after an onboarding checklist, never self-serve.

Before you start

A first governed write is a configuration job before it is a coding job. The work is to name one process, the people who own it, and the rules it already follows. Have these ready:

  • One process where a person already signs off: month-end journal entries, purchase-invoice posting, a credit-limit change. Pick one where a wrong write is costly and the rule is already written down somewhere.
  • A named owner and a named approver. The approver is never the asker. If an agent proposes the write, a person approves it; if a person proposes it, a different person approves it.
  • The system of record and who holds its write credential. This single fact decides your mode in step 1.
  • Your tenant and residency domain. Today there is one residency domain, in (Mumbai). Chains never cross tenants.

You will touch four of the six KernelPort calls: define to register the rules, assert to send a write intent, verify to check the record, and indirectly seal, which only the kernel calls. KernelPort v1 documents all six.

Who holds the credential

1 · Choose a mode

The mode is not a setting you pick for convenience. It follows from one fact: who holds the credential that writes to your system of record. It also decides what elan1 may truthfully say about your writes.

Mode A and Mode B compared
ModeHow you connectWho writesWhat we may say
Mode A · EnforcedIn-process SDK. elan1 Work hosted on ERPNext; third parties by agreement.The single writer in the kernel, with a single-use credential that lasts 10 minutes.No write reaches the system of record without a gate decision and a proof record.
Mode B · Attested advisoryThe MCP server (34 tools) or the HTTP API.Your system, with its own credential, after it has called elan1 first.Evaluated against your rules and recorded.

Most integrations start in Mode B: your code calls assert before it writes, and acts on the outcome. Mode B records the write; it cannot stop it, so we never describe a Mode B write as blocked. A write type can move to Mode A once elan1 can hold the write credential, after at least 30 days of recorded Mode B traffic, a bypass test against the real system and a review. Modes and assurance levels covers the difference in full.

define

2 · Declare the rules

A declaration is your process’s rules as data, in seven blocks: identity, shape, permissions, write_types, gates, proof_binding and channels. You register it with define, at an explicit version. Every proof record will later name the declaration and the version it was checked against (fields F06 and F07).

jsonDeclaration body registered with defineJSON code: Declaration body registered with define
{
  "id": "decl_month_end_close",
  "version": 7,
  "identity": {
    "name": "Month-end close",
    "owner_role": "financial_controller",
    "residency_domain": "in",
    "system_of_record": "erpnext"
  },
  "shape": {
    "record_type": "journal_entry",
    "fields": { "date": "business", "lines": "business", "narration": "business" }
  },
  "permissions": {
    "propose": ["agent", "human"],
    "approve": ["human"],
    "approver_is_never_the_asker": true
  },
  "write_types": [
    {
      "name": "journal_entry.post",
      "blast_radius": { "records": 1 },
      "dual_control": false,
      "reversal": "journal_entry.reverse"
    }
  ],
  "gates": { "rate": { "per_actor_per_hour": 20 }, "schedule": { "window": "business_hours" } },
  "proof_binding": { "seal": "elan1.proof.seal.v6" },
  "channels": { "api": "B", "mcp": "B" }
}
Illustrative specimen. Not customer data.Field names are illustrative; the exact declaration schema is in the KernelPort v1 reference.

Three things to get right on the first declaration:

  1. One write type per kind of change. There is no generic write. journal_entry.post and journal_entry.reverse are separate write types, and an undo takes the Right Path like any other change.
  2. A bounded blast radius. Rung 5 compares each write with the declared radius, so “update one invoice” cannot arrive as an unbounded filter.
  3. Only the optional gates are parameters. Six of the nine pre-gates — consent, residency, retention, dual control, counterparty and precondition — always run and can never be declared off. Rate, budget and schedule can be tuned, or switched off with a decision record.

Registering a declaration is itself a governed write under dual control: it takes a control-plane token and a second named person. Changing the rules moves the version; records written under version 7 keep pointing at version 7. See Declarations for every block.

assert

3 · Assert a write intent

A write intent is a typed proposal to change one record. It names the write type, the declaration it claims to fall under, the actor, the record and its expected version, and the payload. Your agent, the elan1 Decide Router or a person in the console may produce one. Only elan1 Govern turns it into an allow.

httpRequest · kernelport/v1 · assertHTTP code: Request · kernelport/v1 · assert
POST /v1/assert
Authorization: Bearer <tenant token, act scope>
X-Tenant: tnt_specimen_01
Idempotency-Key: wr_specimen_7c2
Content-Type: application/json

{
  "write_id": "wr_specimen_7c2",
  "write_type": "journal_entry.post",
  "declaration": { "id": "decl_month_end_close", "version": 7 },
  "actor": { "id": "agt_specimen_gl02", "kind": "agent" },
  "record": { "id": "je_specimen_5531", "expected_version": 1 },
  "payload": {
    "date": "2027-07-14",
    "lines": [
      { "account": "5100 Freight inward", "debit": "18400.00" },
      { "account": "2100 Accrued expenses", "credit": "18400.00" }
    ]
  }
}
Illustrative specimen. Not customer data.
Write intent fields
NameTypeDescription
write_idstringYour identifier for this intent. Sent again as Idempotency-Key; replaying it returns the original outcome. Becomes F04 on the record.
write_typestringMust be listed in the declaration's write_types block, or rung 2 refuses.
declaration{ id, version }The declaration and the explicit version the intent claims to fall under.
actor{ id, kind }Who proposes. The kernel sets actor_kind on the record from the token, never from the caller.
record{ id, expected_version }The record that would change, and the version you read. A stale version fails the precondition gate.
payloadobjectThe change itself. The record keeps only its hash (F14), so personal data stays out of evidence.

assert runs the whole Right Path in order and answers synchronously. You do not call the rungs one by one, and you cannot skip one.

committed · refused · escalated

4 · Handle the outcome

Every assert ends in one of three outcomes, and every outcome writes a proof record. A platform failure is a fourth case, but it is ours, not a rule, and it is never billed.

jsonResponse · committedJSON code: Response · committed
{
  "outcome": "committed",
  "rung": 9,
  "record": { "id": "je_specimen_5531", "version": 2 },
  "proof": { "seq": 1042, "digest": "sha256:9f3c…e41a" },
  "meter": { "governed_actions": 1 }
}
Illustrative specimen. Not customer data.
committedThe write passed every rungIn Mode A the single writer has made the change, sandbox first. One governed action is counted at rung 9. Keep the proof sequence and digest with your own record.
refusedA typed “no” from one rungThe response names the rung and the code. The refusal is recorded and metered at 0.4 of a governed action. Retrying the same intent unchanged will be refused again; fix the cause or route it to a person.
escalatedA named person decidesThe intent sits above the actor’s autonomy ceiling. An obligation goes to a named approver who is not the asker. If nobody answers before it expires, expiry is a refusal.
platform errorOurs, never billedRetry with the same write_id. If elan1 cannot be reached, do not write: allows stop, and changes wait for a person.
tsHandling the three outcomesTS code: Handling the three outcomes
// Illustrative. Field names follow the specimens on this site; exact names are in the KernelPort v1 reference.
const res = await fetch(`${BASE_URL}/v1/assert`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${TENANT_TOKEN}`,
    "X-Tenant": TENANT_ID,
    "Idempotency-Key": intent.write_id,   // the same write_id on every retry
    "Content-Type": "application/json",
  },
  body: JSON.stringify(intent),
});

if (!res.ok) {
  // A platform failure, not a rule. Retry with the SAME write_id; never mint a new one.
  // If elan1 cannot be reached, do not write: fail toward a human.
  return holdForPerson(intent, "elan1 unreachable");
}

const out = await res.json();
switch (out.outcome) {
  case "committed":
    // Mode A: the single writer has made the change. Store the proof reference.
    return saveProofRef(intent.write_id, out.proof.seq, out.proof.digest);
  case "refused":
    // A typed "no". It was recorded and metered at 0.4 of a governed action. Show the code; do not retry unchanged.
    return showRefusal(out.refusal.code, out.rung);
  case "escalated":
    // A named approver decides. Track the obligation; expiry means refusal.
    return awaitApproval(out.obligation);
}
Illustrative. Not a published SDK.

In Mode B the same three outcomes come back, and your code is the one that acts on them: write on committed, do not write on refused, wait on escalated. How a Mode B caller reports the write it then made is part of the HTTP API; see HTTP API. Approval results can be delivered on a webhook, HMAC-authenticated and carrying seq, so a gap in delivery is detectable. Errors and refusals sets out the refusal envelope.

verify

5 · Read and verify the record

Each outcome above returned a proof reference: a sequence number and a digest. The record behind it has 16 fields under Seal v6, hashed with SHA-256 over a length-prefixed preimage and chained to the record before it through prev_digest.

At L1, checking a record means recomputing its digest and comparing it with prev_digest on the next record, and confirming that seq only ever goes up by one. A changed or missing record breaks the chain. That is what tamper-evident means at L1: a change shows, to you. It is not signed, and someone who does not trust elan1 cannot check it without us.

pyShape of an L1 chain checkPY code: Shape of an L1 chain check
# Illustrative. The exact preimage encoding is set out on the Proof record page.
# At L1 this check is tamper-evident to you; it is not a signature.
def chain_is_unbroken(records, seal_digest):
    for prev, cur in zip(records, records[1:]):
        if cur["seq"] != prev["seq"] + 1:
            return False, f"gap after seq {prev['seq']}"
        if cur["prev_digest"] != seal_digest(prev):
            return False, f"record {prev['seq']} does not match its successor"
    return True, "chain unbroken"
Illustrative. Not the verifier.

From L2, each record is also signed with Ed25519 and anchored to two RFC 3161 timestamp authorities, and an evidence bundle can be checked offline with the free verifier. The Proof record page sets out all 16 fields and the exact recompute.

6 · Before production

A first governed write in a sandbox is the start of onboarding, not the end. Before production keys are issued, expect to work through this checklist with us:

  1. Refusal paths exercised. Show that your code handles a refused and an escalated outcome, including an approval that expires.
  2. Idempotency proven. Retry a timed-out request with the same write_id and confirm you receive the original outcome, not a second write.
  3. Fail-closed behaviour. Cut the connection to elan1 and confirm your system stops writing and hands the change to a person.
  4. Approvers named. Every write type that can escalate has a named approver who is not the asker, and the owner knows who covers when that person is away.
  5. For Mode A: credential custody. At least 30 days of recorded Mode B traffic, a bypass test against the real system and a review before the kernel takes the credential.

What the customer reference covers

The six call names, the 16-field record and Seal v6 are fixed, and so is the rest of KernelPort v1. The examples on this page are illustrative; the full reference customers receive with their workspace sets out:

  • Exact argument and response field names, including the shape of the outcome envelope.
  • Refusal codes. Real codes come from one closed register. Codes on this site, such as GATE.PRECONDITION_FAILED, are illustrative.
  • The declaration schema’s field-level syntax. The seven block names are stable.
  • The languages the in-process SDK supports.

The changelog records each change to these docs.

// BUILD WITH US

Put one real process on the governed path.

Customers receive sandbox credentials and the full KernelPort v1 reference with their workspace. Early-adopter design partners also help shape what comes next.