Modules

Last updated October 5, 2026

On this page

Draft for review

A module is one part of the company's work, packaged. It brings:

  • collections: the kinds of records it keeps, such as tickets or leads (see The brain);
  • agents and the tools they call on outside systems;
  • triggers that start its agents on their own;
  • rules that limit what its agents may do;
  • pages people use in the sidebar.

A company switches a module on from the catalog, binds it to its own teams and tool connections, and can pause it, change it or roll a change back at any time. Its data stays when it is paused.

Each enabled module has one versioned record: its teams, agents, connections, triggers and pages as the company set them. When an admin saves a change, OrchKernel applies the record at once. It installs the module, gives its agents the delegations they need, routes its tools and shows its pages. That step is called reconciling, and every module page shows what the last one did.

This guide describes modules as they work today on the golive branch. What is not possible yet is listed at the end.

Only admins (Ada in the demo) see Modules. Anyone else who opens the page reads "Only admins enable and configure modules."; a module's own page reads "Only admins configure modules."

The steps below were run in the demo company with no model configured. The demo's stub planner then answers in place of a model (see Quickstart). Steps that need a real model say so.

What the demo has#

The demo company has every bundled module enabled already: Context, CRM, Founder, Marketing, Product, Product operations, Sales, Support, Support operations and Tech. Every trigger starts off, so nothing runs on its own until you turn one on.

The Modules page#

Sign in as ada and open Modules under Company in the sidebar. It has four tabs.

Tab What it shows
Catalog Every module the company can enable, one card each: its name, source (bundled or uploaded), trust (Official for bundled ones, Reviewed by your company for the company's own), what it brings ("2 collections · 4 agents · servers apollo, google · 4 pages · requires crm"), and either Enable, or its state (enabled, paused) with Open.
Installed The enabled modules, each with its state (enabled or paused) and health (ok or degraded). The line under the name is the version and source ("v1 · bundled"), or what needs attention ("Paused", "degraded: support paused").
Connections The tool servers modules call. See Connections and integrations.
Agents Every agent the company can have, by department, with Open (its agent page), Configure (its module's Agents tab) and Fork. See Agents.

Describe a module and Upload a module, at the top, add the company's own module to the catalog (below).

Some modules build on others. Sales and Marketing require CRM, Product operations requires Product, Support operations requires Support. A module cannot be enabled until what it requires is enabled.

Enabling a module#

Enable on a catalog card opens the Enable sheet. Most fields start filled, so for a bundled module enabling is usually one click.

Section What it holds Default
Teams Each team the module names (team:support), bound to one of your teams. A bundled module's team is matched to yours by id, then by name; if none matches, the sheet offers to create one ("Create team Marketing"). A company module's teams start empty ("Pick a team…").
Agents One line per agent: who it acts for, its budget, its trust, on or off. Open a line to change it. Acts for the team it sits on; the module's budget; trust standard (sandboxed for a company module); on.
Connections Each tool server, bound to a connection. For a bundled module: a connection named after the server, or the one another module already uses for it ("as sales binds it"). A company module starts Not connected. "Not connected never blocks enabling: its tools are refused until a connection is bound."
Automation Each trigger, with a checkbox and, for a schedule, its schedule. As the module ships them.
Pages Each page, shown or not, and its sidebar section. Shown, in a section named after the module.
Reads from other modules Which of its skills read another module's collections ("field-notes-digest reads tickets, owned by support"). Information only.

Once a team is picked, the sheet says what the binding means: "field-notes-scribe joins Support and can read incidents, qa_reviews, tickets, field_notes." Enable stays greyed out until every team is bound ("Bind team:support to enable."), while a schedule is invalid, and while a required module is missing ("Field notes needs these enabled first: support").

After Enable a note says "Field notes is enabled" and you land on the module's page. If reconciling hit a problem, the Overview lists it under Needs attention ("1 tool on helpdesk needs a connection").

Who an agent acts for decides who may ask it and whose authority it uses (see Delegations). A team stands for its members. Admins are left out of a team unless you name them, so an admin's authority is never lent to an agent by default.

In the demo every bundled module is enabled already. To walk the Enable sheet, add a module of your own first (below).

The module page#

Open a module from Installed or with Open on its catalog card. The head shows its name, state and health, and All modules goes back. The tabs:

Tab What you do there
Overview See state, health, the module's own version, who set it up, what it requires and what requires it. Pause or Resume. Needs attention lists problems. Last reconcile says what the last change applied ("1 setting applied · 16 already in place · no errors"); open applied for the list. Under Advanced: the module id, the record version, the source and Reconcile now (apply the record again).
Teams Which of your teams each module team stands for, and what it can read. Change teams.
Agents Each agent: acts for, budget, trust, on, and its runs in the last 7 days. Edit per agent.
Connections Each tool server, its tools, its connection and last test. Add a connection, Change connections, Test. Also lists collections kept in another system.
Automation Each trigger: its kind, when it fires, on or off. Change automation; for a webhook, its address and signing secret.
Pages Each page: shown or hidden, its sidebar section and order. Change pages.
History Every version of the module's record, with Roll back to vN.

Each Change button (and Edit on an agent) opens a sheet, and Save writes a new version of the record. A note confirms it ("Saved teams"). If another admin changed the module since you opened it, the save is refused, the page reloads, and you make the change again; nobody's change is overwritten.

Rebinding a team#

The Support module has two teams: its Support team, where its agents sit, and a Product team that may read tickets. In the demo both are bound to your teams of the same name. To let Engineering read tickets instead of Product:

  1. As ada, open Modules, tab Installed, then Support, tab Teams. The table shows Product team bound to Product, which can read Tickets.
  2. Choose Change teams. Set team:product to Engineering (team:eng). The line under it previews what the binding means.
  3. Choose Save. A note says "Saved teams". The table now shows Engineering, which can read "Dependencies, Incidents, Pull requests, Tickets".
  4. Sign in as erin (Engineering) and open Brain, Tickets: she sees the tickets ("5 rows you can read"). As pat (Product) the same page says "You cannot read this collection."

The Overview's Last reconcile lists "tickets: teams rebound". Undo it with History.

Who an agent acts for#

  1. In Support, tab Agents, choose Edit on Support Agent. The sheet has Acts for (teams first, then people), Budget ($), Trust (sandboxed, standard, trusted) and On.
  2. Untick Support, tick Maya Patel, and choose Save. The row now reads "Maya Patel".
  3. Sign in as tom. On Threads, under Talk to an agent, Support Agent is gone: it no longer acts for him. Maya still sees it.

The Overview's applied list shows "support-agent no longer acts for tom". A person who joins or leaves a bound team gains or loses the module's delegations with it (see Delegations).

These delegations belong to the module. When Maya chooses Revoke on one herself (Governance, Delegations), she is refused with "delegation ... is managed by module support; change it on /modules/support (the module record)". The same goes for the agent's budget, its trust, and the module's triggers and tool routes: change them on the module page. An admin's own kill switch on an agent is separate and always works (see Policy, rules and kill switches).

Across modules. Marketing's lead agents write CRM's leads and contacts, and CRM's rules decide who may. If a Marketing agent acts for nobody on the team CRM is bound to, its row on the Agents tab warns. Set Lead Sourcer to act for Fiona Reyes only and the row reads "cannot write contacts: acts for nobody on Sales" and "cannot write leads: acts for nobody on Sales". One module never widens another's rules.

Automation: triggers#

A trigger starts one of the module's agents on its own. The Automation tab lists them; all are off in the demo.

Kind Fires Shown as
Schedule On a schedule "Weekdays at 8:30"
Webhook When another system posts to the module's address POST /api/webhooks/helpdesk and the state of its signing secret
Data change When a record in one of the module's collections changes "a change to tickets"

Turning on a schedule#

  1. As ada, open Support operations, tab Automation. Churn sweep reads "Schedule · Weekdays at 8:30 · Off".
  2. Choose Change automation. The sheet lists each trigger by its id. Tick support-ops-churn-sweep. Its schedule is in the box next to it.
  3. Choose Save. A note says "Saved automation" and the row reads On.
  4. Open Work, then All. If 8:30 UTC has passed today, a task "Sweep open tickets for accounts at risk" for the Churn-risk watcher is already there, created by the trigger, and has run (Done; with no model, the stub planner answers it).

Schedules the box accepts:

Schedule Means
every:30m, every:2h, every:7d Every 30 minutes, 2 hours, 7 days
hourly Every hour
daily@08:00 Every day at 08:00
weekdays@08:00 Monday to Friday at 08:00

Anything else is refused as you type ("Use every:30m, hourly, daily@08:00 or weekdays@08:00") and Save stays greyed out. Two things to know:

  • Times are UTC. The screen does not say so.
  • A schedule that has never fired fires on the next clock tick if its time today has already passed. Turning on Churn sweep at 12:05 UTC on a Monday ran it at once, not the next morning. An every:7d trigger runs right after it is turned on.

Webhooks#

Each webhook trigger has an address, shown on the Automation tab, that another system posts to. Every delivery must be signed.

Before the module has a secret of its own, the tab reads "No signing secret of its own yet: deliveries are checked with the source's OK_WEBHOOK_SECRETS entry, else the shared secret." Create secret issues one and shows it once ("Copy it now: it is not shown again"), with how to sign: HMAC-SHA256 of <timestamp>.<raw body>, sent as x-ok-signature: sha256=<hex> with x-ok-timestamp: <unix seconds>. The tab then reads "Signing secret issued 5:43 PM by Ada Park. It was shown once; rotate it if it is lost." Rotate asks "Rotate this signing secret? The current one stops working at once; senders need the new one." and shows the new one once.

Delivery Answer
Signed with the secret 202, with the tasks it created
Same x-ok-delivery id sent again 202, replayed: true, the same task, no new one
Bad signature, or signed with a secret that was rotated away 401 "bad webhook signature"
To a source with no secret at all 401 "no webhook secret is configured for this source"
To a paused module, or a trigger turned off 409 trigger_disabled; nothing is remembered, so the same delivery sent again after resume goes through

A bundled module's source with no secret of its own falls back to the server's webhook secrets (OK_WEBHOOK_SECRETS, then OK_WEBHOOK_SECRET); a company module's never does.

In the demo: no webhook secret is configured and the demo starts without OK_SECRETS_KEY, so every webhook is refused with 401, and Create secret fails with "a secret cannot be stored: OK_SECRETS_KEY is not set". To try webhooks, start the demo with OK_SECRETS_KEY set to a base64 key of 32 bytes:

sh
OK_SECRETS_KEY=$(openssl rand -base64 32) ok demo serve

Pages#

The Pages tab sets where a module's pages sit in the sidebar. Detail pages (one lead, one proposal) open from a list and never sit there.

  1. As ada, in Support operations, tab Pages, choose Change pages.
  2. Change the section of Support operations from Support to Customers, and choose Save. A note says "Saved pages".
  3. Sign in as maya: her sidebar now has a Customers section holding Support operations.

Untick a page to hide it. Edits people made to a page stay when it is hidden or the module is paused.

Pausing and resuming#

Pause a module when it is not needed for a while. Its data, records, delegations and settings stay.

  1. As ada, open Support, tab Overview, and choose Pause. Confirm "Pause Support? Its agents, tools and triggers stop and its pages hide; data and settings stay."
  2. A note says "Support is paused". The head reads Paused. On Installed, Support reads paused, and Support operations, which requires it, reads "degraded: support paused" with a degraded badge. Support operations stays enabled.
  3. Sign in as maya. Under Talk to an agent, Support Agent and Triage are greyed out with "paused"; hovering says "Paused by an admin". (In a thread they are greyed out in the agent selector; see Threads.)
  4. Back as ada, choose Resume (no confirmation). A note says "Support is running again", and Support operations is ok again.

While a module is paused:

  • Its agents do not run. An ask through the API is refused: "kill switch engaged: support is paused". The ask's task is still created, though, and runs as soon as the module is resumed (see Known problems).
  • Its triggers are off and its tools are held.
  • Its pages are hidden from the sidebar.

Resume brings back exactly the module's settings, triggers included: a trigger that was on before the pause is on again. An admin's own kill switch on one of its agents stays until the admin lifts it.

History and rollback#

Every change to a module (enable, a sheet's save, pause, resume) is a new version of its record. The History tab lists them newest first: version, who, how (direct or rollback), when, and the record itself under record.

If you followed this page in order, Support now has five versions: v1 as the demo set it up, v2 the team change, v3 the Support Agent change, v4 the pause and v5 the resume.

  1. In Support, tab History, choose Roll back to v1 and confirm "Roll Support back to version 1?".
  2. A note says "Rolled back to v1". A new version, v6, appears marked rollback; nothing is erased.
  3. Teams shows Product team bound to Product again (Pat can read tickets, Erin cannot), and Agents shows Support Agent acting for Support again.

A rollback restores the whole record, including whether the module was paused: rolling back to v4 pauses Support again. The History tab shows each version's record but not what changed between two of them.

Records kept in another system#

A module can keep its records here (native, the default) or in a system the company already runs, such as Salesforce. Then the collection is read and written live through the connection's adapter and nothing is copied here.

  • On the Enable sheet, once a tool server is bound to an adapter connection that maps one of the module's collections, a box offers "Keep records in salesforce: leads as Lead, read live and never copied here".
  • The module's Connections tab then lists the collection under Records kept in the other system.
  • Reads stop at 5,000 rows and say so; such records cannot be deleted or rolled back here. Change them in the other system.
  • Where a collection is kept can change only while nothing is stored for it here. In the demo CRM already holds leads, so moving them is refused: "leads has 7 record(s) stored here; where its records are kept can change only while it has none".

The demo has no adapter connection, so only the last point can be tried there. See Connections and integrations for adapter connections.

Adding your company's own module#

A company module enters the catalog only after a second admin reviews it. Enabling it is a separate step after that. A company module is written as a pack.yaml file; Writing packs and skills explains how.

By upload#

The demo has only one admin, and the admin who uploads a module cannot review it ("ada proposed this module; another admin reviews and promotes it"). So add a second admin first. There is no page for adding a person (see Making someone an admin); with the demo running, use the API with Ada's token and the address ok demo serve printed (127.0.0.1:8080 unless you passed --bind):

sh
curl -X POST http://127.0.0.1:8080/api/actors \
  -H "Authorization: Bearer <ada's token>" -H 'content-type: application/json' \
  -d '{"actor":{"id":"dana","name":"Dana Lee","kind":"human","role":"admin"}}'

Then, as Ada, open People, choose Issue token on Dana Lee, and Issue token again. Sign in as Dana with the token it shows once.

To follow along, save this small module as field-notes.yaml. It keeps call notes for the support team and has one agent, the Field-notes scribe, with a weekly digest:

yaml
id: field-notes
name: Field notes
version: 1
description: Notes from customer calls, kept by the support team, with a scribe that writes a weekly digest.
requires: [support]
collections:
  - name: field_notes
    storage: native
    data_class: { label: internal, sensitivity: internal }
    schema:
      base: document
      fields:
        - { name: title, type: string, required: true }
        - { name: body, type: string }
    policy:
      read_teams: ["team:support"]
      write_teams: ["team:support"]
    semantics: { description: Notes from customer calls }
tools:
  - id: field-notes.helpdesk.tickets
    name: List helpdesk tickets
    provider: { type: mcp, server: helpdesk, tool: tickets }
    risk_tier: low
skills:
  - id: field-notes-digest
    name: Weekly digest
    tools: [field-notes.helpdesk.tickets]
    permissions:
      read_collections: [field_notes, tickets]
      write_collections: [field_notes]
    code: { type: declarative }
agents:
  - id: field-notes-scribe
    name: Field-notes scribe
    kind: agent
    role: field_notes_scribe
    skills: [field-notes-digest]
    tools: [field-notes.helpdesk.tickets]
    team: "team:support"
triggers:
  - id: trigger:field-notes-weekly
    name: field-notes-weekly
    kind: cron
    schedule: "every:7d"
    template:
      title: Write this week's field-notes digest
      owner: field-notes-scribe
      assignment: { to: actor, id: field-notes-scribe }
      priority: normal
      skill_hint: field-notes-digest
  1. As ada, on Modules, choose Upload a module. Paste the file's contents or choose the file, and choose Upload for review.
  2. The sheet says "Filed module field-notes v1 (uploaded): 1 collections, 1 agents, 1 tools as proposal ... Another admin reviews the card below and promotes it from Governance › Changes; enabling it is a separate step.", and shows the review card.
  3. Sign in as dana and open Governance, tab Changes. Under Proposed the card lists every collection and how sensitive it is, every tool and its risk (with Lower to lower it), what each skill reads and writes (naming collections other modules own: "reads field_notes, tickets (owned by Support)"), each agent's team, every trigger, rule and page, what it requires, and the content hash.
  4. Choose Approve, add an optional note, and choose Approve again. A note says "Reviewed" and the change moves to Reviewed. Choose Promote: a note says "Promoted".
  5. As ada, the Catalog shows Field notes, uploaded, Reviewed by your company, with Enable.
  6. Choose Enable. Set team:support to Support (team:support). Leave helpdesk on Not connected (or pick demo helpdesk), and choose Enable. A note says "Field notes is enabled". The Overview's applied list reads "installed field-notes v1", "field-notes-scribe acts for maya", "field-notes-scribe acts for tom", and, left unconnected, Needs attention says "1 tool on helpdesk needs a connection".

A company module's triggers start as the file declares them, so a trigger the file does not turn off is on at once. An every:7d trigger in the file above ran as soon as the module was enabled.

An upload is refused, with every problem listed, when it breaks the catalog's rules:

  • More than 256 KB, or an id that is a bundled module's ("module id crm is a bundled module's id") or another module's.
  • An id that reuses an existing collection, tool, skill, rule, agent, trigger, thread template or page ("collection field_notes already exists").
  • Skills that are not declarative; tools that are not MCP tools (each is treated as external and at least high risk unless the reviewer lowers it).
  • Agents that are admins, act for someone in the file, or are not on a team the module names ("agent field-notes-scribe is on team team:nobody, which none of the module's collections or rules name"). Its agents start sandboxed.
  • Webhook sources not named <module>.<source>; data-change triggers on another module's collections.
  • Rules that loosen anything: a company module may only deny or require approval, on its own objects.
  • A re-upload that is not a higher version ("field-notes v1 is not higher than the catalog's v1").

By describing it#

Describe a module takes a sentence ("Track customer contracts and renewals for the sales team, with an agent that reminds reps 60 days before a renewal") and, with Draft it, the Architect drafts the module and files it for review. As the Architect is the proposer, any admin, the one who asked included, may review and promote it. This needs a real model.

In the demo it does not work: the sheet answers "The Architect did not draft a module: no native skill registered as architect". This is a bug and has been reported.

Retiring a company module#

ok catalog retire <id> removes an unused company module from the catalog. One a module record uses is refused ("a modules record uses field-notes; pause the module instead"); a bundled one cannot be retired ("bundled modules ship with OrchKernel and cannot be retired").

Who can change a module#

Only a human admin, on every path: the module page, the CLI, the API, a rollback. Anyone else is refused ("only human admins change a module"). A module record is never deleted; pause the module instead.

A save is checked before anything changes, and every problem is listed at once, for example: "teams.team:product: team:nope does not exist; agents.triage-agent.acts_for: pr-reviewer is not a human or a team; connections.helpdesk: conn:nope is neither a connection nor an mcp.yaml server". Other refusals: a negative budget ("must be a non-negative integer"), a schedule that is not one of the forms above ("mon@08:00 is not a schedule"), or a schedule on a trigger that is not a schedule ("is not a cron trigger, so it takes no schedule").

Not possible yet#

  • Removing a module. Pause it instead.
  • Moving an enabled module to a newer version. When a company module's version 2 is promoted, the catalog card shows newer version available, but there is no control to apply it, and a record naming version 2 is refused ("field-notes version 2 is not the installed version 1").
  • Seeing what changed between two versions on the History tab.
  • Choosing the time zone of a schedule; schedules run in UTC.
  • Rolling back from the CLI; use the History tab or the API.
  • Adding a person (such as a second admin) from a page; use the API or the CLI.
  • Describing a module to the Architect in the demo (see above).
  • Webhook signing secrets in the demo as it starts by default (no OK_SECRETS_KEY).

Known problems on this page#

  • A refused ask runs later. Asking an agent of a paused module is refused, but its task is kept and runs as soon as the module is resumed. Maya's ask of Triage while Support was paused showed Done on her Work page a few seconds after Ada chose Resume.
  • A huge budget for an agent with none. An agent whose module file sets no budget shows "$23,058,430,092,136,940.00" on the Enable sheet and the Agents tab. It means no limit.
  • Phone width. On a phone the module page's Agents and Connections tables are wider than the screen, so the page pans sideways and Edit and Test sit off-screen.
  • Raw names. The Change automation sheet and the Enable sheet list triggers by id (support-ops-churn-sweep) while the table says "Churn sweep"; an every:7d schedule shows as "every:7d", not in words; the agent save note says "Saved support-agent", not the agent's name; and the upload note says "1 collections, 1 agents". The applied list opens as raw JSON.
  • Change teams preview. For a module team with no agents, such as Support's Product team, the preview still says "Its agents would sit on Engineering and read dependencies, incidents, pull_requests", and leaves out tickets, which the binding grants.

For developers#

CLI#

ok module, ok catalog, ok tick and ok webhook work on the state file (--state, or OK_STATE), as a human admin (--as ada). A running server keeps its own copy and overwrites the file, so stop the demo first, or use the pages or the API. Even ok module list needs an admin; anyone else is refused with "only human admins change a module".

sh
S="--state orchkernel-demo/state.db --as ada"
ok $S module list                       # each module: state, health, source, name
ok $S module show support               # the module in full, as JSON
ok $S module set support '{"agents":{"triage-agent":{"budget":3000}}}'
ok $S module set tech '{"triggers":{"trigger:tech-daily-dependency-audit":{"on":true,"schedule":"daily@06:00"}}}'
ok $S module pause tech
ok $S module resume tech
ok $S module reconcile support          # apply the record again
ok $S module enable <pack> --team team:sales=team:revenue --create-team team:revenue=Revenue --connection google=conn:acme-google
ok $S module adopt <pack>               # a pack installed before modules
ok $S catalog list
ok $S catalog upload field-notes.yaml
ok $S catalog retire field-notes
ok $S tick                              # advance the clock: due schedules fire
ok $S tick --now 2026-10-07T06:01:00Z   # as if it were that time (UTC)
ok $S webhook tech.github --payload '{"pr":7}'

ok module set takes an RFC 7396 merge patch applied to the record as just read; every set, pause and resume prints the record, its new version and the reconcile report. ok tick prints a line such as fired=1 escalated=0 stalled=2 expired_knowledge=0 ran=1. ok webhook delivers without a signature and prints "created 1 task(s)", or "created 0 task(s)" when the trigger is off or the module paused. Outside ok demo serve there is no stub planner, so runs the CLI starts fail without a model.

API#

All under /api, with Authorization: Bearer <token>, for human admins only.

Method and path Purpose
GET /catalog The catalog: id, source, trust, state, health, installed_version, newer_version_available, summary
POST /catalog/uploads { yaml } File an upload; returns proposal and the review card
POST /catalog/drafts { text } Ask the Architect to draft one
GET /catalog/proposals/:id/card A proposal's review card
DELETE /catalog/:id Retire a company module
GET /modules Modules with state, health, last report, attention
GET /modules/:pack/defaults The Enable sheet's values
POST /modules/:pack/enable { record, create_teams } Enable
GET /modules/:pack record, effective (with defaults filled in), agents, servers, triggers, pages, delegations, team_reads, version
PUT /modules/:pack { record, version } Replace the record at the version read
POST /modules/:pack/pause, /resume { version } Pause, resume; version is optional here
POST /modules/:pack/reconcile Apply the record again
POST /modules/:pack/adopt Adopt a legacy install
POST /modules/:pack/webhooks/:source/secret Issue or rotate a webhook signing secret; the answer is the only place it is shown
GET /brain/collections/modules/records/:pack/history Versions
POST /brain/collections/modules/records/:pack/rollback { to_version } Roll back
POST /webhooks/:source A signed delivery (see Webhooks)

Errors: 422 invalid with problems; 428 version_required; 409 version_conflict, already_enabled, adopt_required, requires_missing, managed_by_module, storage_change, catalog_in_use, version_not_higher, trigger_disabled; 403 denied; 401 unauthorized (webhooks); 413 too_large; 503 secrets_key_missing.

The record#

Field Example
pack, version, source, state support, 1, bundled, enabled
teams {"team:product": "team:product", "team:support": "team:support"} (every module team must be bound)
agents {"support-agent": {"acts_for": ["maya"], "budget": 5000, "trust": "standard", "on": true}} (budget in cents)
connections {"helpdesk": "conn:demo-helpdesk"}
triggers {"trigger:support-ops-churn-sweep": {"on": true, "schedule": "weekdays@08:30"}}
pages {"support-ops.dashboard": {"show": true, "section": "Customers", "order": 30}}
storage {"leads": {"server": "crm", "object": "Lead"}}
owner ada: who set it up; grants nothing

A key left out takes the module's default, so the pages save only what you changed. Reconciling applies, in order: install, team rebinding, delegations (one per person in acts_for, teams expanded, scoped module:<pack>), agent budget, trust and on, triggers, tool routes, pages. Reconciling twice changes nothing the second time. The full specification is docs/modules.md in the source.