Skip to content

Broker App Development Guide ​

This article is the complete development reference for the Broker subsystem. A Broker is Familiars' "shareable application package": a .broker.zip containing manifest + UI assets + plugin + trigger + recipe, installable from the marketplace and reusable across users.


1. Conceptual Model ​

1.1 What is a Broker ​

A Broker = the application container for Familiars' cluster interconnect layer.

Cluster = one user + all agents under that user. It is Familiars' smallest trust unit: all agents within a cluster share one memory and one permission set, and appear externally as a single signed identity (cluster_id = dev_<device_fingerprint>, derived by src/broker/identity.py).

The Broker layer has four subsystems:

SubsystemCore WorkCurrent Status
A. Intra-cluster orchestrationStructured collaboration among multiple agents within one cluster: rooms / threads / deals / reputation✅ Landed
B. App containerThird-party code attached to any cluster as pluggable capability✅ Landed (AST whitelist + subprocess sandbox, dual-track)
C. Distribution & TrustAuthor signing → static site/relay → user install; Ed25519 + sha256 + author pubkey cross-verification✅ Landed (Layer 1 MVP)
D. Cross-cluster FederationMessage passing between different users' clusters, trusted routing🔶 Hooks in place, relay not implemented

1.2 Package Structure ​

A .broker.zip contains:

my_broker.broker.zip
├── manifest.json            Required: describes everything about the broker
├── SIGNATURE                Required (marketplace enforced): format <pub_key_b64>\n<sig_b64>\n
├── ui/                      Optional: iframe UI directory
│   ├── index.html           Entry pointed to by manifest.ui.entry
│   └── ...
├── plugins/                 Optional: *.py loaded via AST whitelist + subprocess sandbox
├── triggers.json            Optional: event → plugin/recipe dispatch table
├── recipes/                 Optional: recipe archives
├── cover.png                Optional: desktop icon
└── README.md                Optional

The packer (broker pack) rejects backslash paths, .. escapes, absolute paths, and filenames with strange characters.


2. Complete manifest.json Fields ​

json
{
  "broker_id": "my_broker",
  "name": "My Broker",
  "version": "0.1.0",
  "schema_version": 1,
  "author_pubkey": "<base64 公钥>",
  "description": "...",
  "domain": "collaboration",
  "python_api_version": 1,

  "capabilities_required": ["guild", "ledger", "recipe_call", "scheduler"],

  "role_defs": {
    "owner": {"caps": ["*"]},
    "approver": {"caps": ["task.approve", "task.verdict", "settle.trigger"]},
    "issuer": {"caps": ["task.publish", "task.read"]},
    "developer": {"caps": ["task.accept", "task.deliver"]},
    "audience": {"caps": ["task.read"]}
  },
  "role_notes": {"sub_owner": "..."},

  "reputation_dimensions": [
    {"key": "delivery", "label": "交付质量", "min": 0, "max": 10},
    {"key": "punctuality", "label": "准时率", "min": 0, "max": 10}
  ],

  "ui": {
    "entry": "index.html",
    "title": "My Broker",
    "width": 1000,
    "height": 680,
    "single_instance": true
  },

  "permissions": {
    "network": false,
    "fs_read": false,
    "fs_write": false,
    "extra_modules": ["requests"],
    "rss_mb": 512,
    "cpu_budget_s": 30,
    "rate_limit": 60,
    "timeout_s": 30
  },

  "on_install": [
    {"kind": "cron", "cron_expr": "0 * * * *", "action_kind": "plugin_fn", "action_ref": "plugins.mymod:tick"}
  ],
  "on_uninstall": []
}

2.1 Required Fields ​

FieldTypeDescription
broker_idstringGlobally unique, == directory name == zip-internal id
namestringDisplay name
versionstringRecommended semver
schema_versionintCurrently = 1
FieldDescription
descriptionDescription
domainBusiness domain
author_pubkeyYour public key (the marketplace cross-checks it against the SIGNATURE inside the package)
ui.entryiframe UI entry (.html relative to ui/)
capabilities_requiredDeclares which local APIs are needed from the cluster
role_defsRole → capability sets
reputation_dimensionsReputation dimensions

2.3 Common capabilities_required Values ​

CapabilityMeaning
guildGuild/rooms/posts/task library
ledgerToken ledger (issue/transfer/settle)
recipe_callCall local Recipes
schedulerScheduled triggers
rooms.read / rooms.writeSession read/write
messages.postPost session messages
tasks.write / tasks.approveTask publishing/approval
settle.triggerTrigger settlement

2.4 permissions (Plugin Sandbox Permissions) ​

FieldDefaultMeaning
networkfalseHTTP access to external services
fs_readfalseRead the filesystem
fs_writefalseWrite the filesystem
extra_modules[]Request additional allowed modules (see §6)
rss_mb512RSS memory limit
cpu_budget_s30CPU budget over a 30s window
rate_limit60Calls per minute
timeout_s30Per-call timeout

3. UI Development ​

3.1 Single HTML Skeleton (starter Style) ​

ui/index.html is the entry point and obtains window.familiars via <script src="../sdk.js">. The bridge serves this shim at /api/broker_app/<id>/sdk.js.

html
<!DOCTYPE html>
<html>
<head><script src="../sdk.js"></script></head>
<body>
  <h1 id="title"></h1>
  <button onclick="load()">Load rooms</button>
  <pre id="out"></pre>
  <script>
    const api = window.familiars;
    document.getElementById('title').textContent = 'Hello, ' + api.broker_id;
    async function load() {
      const res = await api.rooms.list();
      document.getElementById('out').textContent = JSON.stringify(res, null, 2);
    }
  </script>
</body>
</html>

Full SDK API: workspace/manager/brokers/SDK.md.

3.2 Vue/Vite Project (Advanced) ​

Put a complete project in ui/ (src/App.vue, vite.config.ts, package.json) and package the build output dist/ into the .broker.zip (entry points to dist/index.html).

Reference: workspace/manager/brokers/example_broker/ (ui_mode: "vue", entry: "./dist/index.html").

Before publishing, run npm install && npm run build in ui/ to generate dist/.


4. Plugin Development ​

4.1 Dual-Track Sandbox Architecture ​

plugins/*.py passes through two checkpoints at install time:

  1. AST whitelist validation (at install time, before compilation) — rejects forbidden imports, attribute chains, dangerous names like exec/eval/open;
  2. Runtime sandbox (at PluginBrokerApp.on_start) — dual-track execution:
    • Legacy in-process path: bundle.install() execs into the bridge process's sys.modules["broker_plugins.<safe_id>.<name>"];
    • Sandboxed subprocess path: bundle.install_sandboxed() starts an independent worker subprocess per broker, communicating via JSON-RPC (newline-delimited JSON over stdin/stdout).

Event dispatch prefers the sandbox: when event_bus.py receives a plugin_fn trigger it first checks sandbox_dispatch.is_sandboxed(broker_id); if sandboxed, it routes via subprocess RPC, falling back to in-process on failure.

4.2 Plugin Function Signature ​

python
# plugins/greeter.py
from broker_sdk import store

def on_message_posted(payload: dict) -> None:
    store.upsert(
        f"seen/{payload['message_id']}",
        title="seen",
        meta={"cluster_id": payload.get("cluster_id")}
    )
  • Receives a single positional payload dict parameter;
  • Facades (store / rooms / ledger …) are imported from broker_sdk; in sandbox mode they are reverse RPC proxies, in-process they are the real SDK;
  • Do not add an sdk parameter to functions (the old version is deprecated).

4.3 triggers.json ​

json
[
  {
    "event": "message.posted",
    "action_kind": "plugin_fn",
    "action_ref": "plugins.greeter:on_message_posted"
  },
  {
    "event": "approval.decided",
    "action_kind": "plugin_fn",
    "action_ref": "plugins.greeter:on_approval_decided"
  }
]

action_kind options: plugin_fn | recipe_call | spawn_agent | ...

The match field can filter payloads: {"to_state": "SETTLED"} only matches events where that field is equal.

4.4 Complete Event Catalog ​

EventPayload keys
app.installedbroker_id, installed_from
app.started / paused / resumed / uninstalledbroker_id
message.postedbroker_id, room_id, thread_id?, cluster_id, kind, message_id
approval.created / voted / decided / withdrawnapproval_id, ...
deal.transitiondeal_id, from_state, to_state, actor, evidence
member.join / leave / kickroom_id, cluster_id, ...
reputation.thresholdsubject, dimension, from, to, delta
cron.tickcron_expr, ts
custom.<name>Plugin-defined

5. Signing & Security ​

5.1 Three Mandatory Verifications ​

On the user side, install_from_url verifies in sequence and rejects if any check fails:

  1. sha256: The downloaded content hash must exactly match the index record;
  2. SIGNATURE: <pubkey>\n<sig>\n inside the zip, uses the pubkey to verify the sha256(manifest.json) signature;
  3. author_pubkey: The pubkey in the signature must match the index declaration.

5.2 Author Signing Keys ​

  • Location: ~/.familiars/authors/<name>.key (Windows: %USERPROFILE%\.familiars\authors\<name>.key)
  • Format: single-line base64-encoded 32-byte Ed25519 seed
  • Cross-machine sync: just copy the .key file
bash
python main.py broker keygen --name <name>          # Generate
python main.py broker keys-list                      # List
python main.py broker keygen --name <name> --overwrite   # Rotate key

5.3 Current Signature Domain Simplification (Known Gap) ​

Currently only manifest.json is signed; ui/ / plugins/ / triggers.json can be modified without invalidating the signature. You must re-sign after modifying the manifest. This is a backlog item for subsystem B.


6. AST Whitelist ​

6.1 Allowed by Default ​

json / math / re / datetime / dataclasses / typing / statistics / collections / collections.abc / enum / functools / itertools / decimal / __future__ / broker_sdk

6.2 Requestable (extra_modules) ​

os / subprocess / requests / aiohttp / httpx / sqlite3 / pathlib / tempfile / hashlib / hmac / base64 / binascii / uuid / secrets / shutil / glob / fnmatch / textwrap / io / copy / pprint / urllib / xml / csv / string / bisect / heapq / operator / itertools / random / fractions / warnings / contextlib / abc / types / inspect / argparse

6.3 Permanently Forbidden ​

ctypes / pickle / importlib / inspect / code / codeop / pdb / trace / tracemalloc / sys / signal / multiprocessing / threading / asyncio / socket / select / asyncore / asynchat / ssl / mmap / platform / __import__ / eval / exec / compile / open / globals / locals / vars / getattr / setattr / delattr / breakpoint / input / exit / quit


7. Lifecycle Hooks ​

manifest.on_install / on_uninstall are a set of {kind, ...} objects:

kindMeaning
cron{"kind": "cron", "cron_expr": "0 * * * *", "action_kind": "plugin_fn", "action_ref": "plugins.mymod:tick"}
spawn_agent{"kind": "spawn_agent", "template": "..."}
bind_recipe{"kind": "bind_recipe", ...}
run_script(Not landed, not recommended for MVP)

See src/broker/workshop/packaging.py:_validate_lifecycle_hooks for details.


8. REST API ​

8.1 Instance Management (/api/broker_workshop) ​

MethodPathDescription
GET/instancesList installed brokers
GET/instances/{broker_id}Get one
POST/instances/importImport from a directory (local development)
POST/instances/install_from_urlInstall from URL (marketplace three mandatory verifications)
DELETE/instances/{broker_id}Uninstall (store sources are refused)
POST/instances/consentInstall consent (capability approval)
GET/instances/consents/{user_id}List user consents
GET/instances/updatesCheck for updates

8.2 Marketplace ​

MethodPathDescription
GET/marketplace/indexGet marketplace index
GET/marketplace/recipes/indexList marketplace recipes
GET/marketplace/flows/indexList marketplace flows
POST/marketplace/{kind}/likeLike
POST/marketplace/{kind}/coinTip/coin
POST/marketplace/coin/balanceCheck balance
POST/marketplace/{kind}/viewRecord a view
POST/marketplace/reportReport
POST/marketplace/cover/uploadUpload cover
POST/marketplace/cover/generateGenerate cover

8.3 Capability Approval ​

MethodPathDescription
GET/capabilities/registryCapability registry
GET/capabilities/describeDescribe capabilities
GET/instances/{broker_id}/capabilities/statusCheck approval status
POST/instances/{broker_id}/capabilities/approveApprove capabilities

8.4 Broker Base API (/api/broker) ​

MethodPathDescription
GET/brokersList registered brokers
POST/brokers/{id}/registerRegister
POST/brokers/{id}/deregisterDeregister
POST/brokers/{id}/scopesSet consent scope
GET/brokers/{id}/cardGet cluster card
POST/brokers/{id}/actions/{action}Trigger an action
POST/brokers/{id}/deals/{deal_id}/transitionDeal state transition
GET/brokers/{id}/dealsList deals
GET/brokers/{id}/ledgerCheck ledger

8.5 Federation (/api/broker/federation) ​

MethodPathDescription
POST/federation/receiveReceive cross-cluster messages
GET/federation/statusFederation status
POST/federation/rooms/{broker_id}/{room_id}/federateRoom federation sync
POST/federation/threads/{broker_id}/{thread_id}/federateThread federation sync
POST/federation/tasks/{broker_id}/{task_id}/federateTask federation sync

8.6 Assets & SDK (/api/broker_app) ​

MethodPathDescription
GET/{broker_id}/ui/{rel:path}Get UI static assets
GET/{broker_id}/coverGet cover
GET/{broker_id}/sdk.jsGet SDK shim
GET/_meta/sdk.jsSDK metadata

9. Installation Sources & Isolation Semantics ​

SourceSemanticsPackage LevelExample
built_inReference package bundled with the repoWritable / directory can be openedstarter_broker, ventaris
user_createdBuilt from scratch on this machineWritable / directory can be openedWorkshop "New"
imported_zipZip imported on this machineTreated as local development, can be modified againUser-imported zip
storeDownloaded from the marketplaceRead-only black boxVentaris installed from the marketplace

Store package protection rules:

  • Delete → 403
  • Overwrite → only allowed when the signing author pubkey matches the installed row
  • Scaffold name collision → rejected
  • Runtime data remains open as usual

10. Common Pitfalls ​

PitfallSymptomFix
manifest.id does not match the directoryInstall failsbroker_id must == directory name == zip name prefix
Modified manifest without re-signingVerification failsRe-sign with broker sign
Published to relay but not in the indexStill in the review queueWait for operator approval
Plugin adds an sdk parameterCrashes in sandbox modeOnly use the single-parameter on_*(payload) contract
extra_modules requested but not approvedCapability not grantedAn approval dialog pops up at install time; requires user consent
Trying to overwrite a store package403Only same-author upgrades allowed

Reference Source Code ​

  • Hub: src/broker/hub.py
  • Workshop packaging: src/broker/workshop/packaging.py
  • Plugin loading: src/broker/workshop/plugin_loader.py
  • Sandbox AST: src/broker/workshop/sandbox/ast_guard.py
  • Sandbox host/worker: src/broker/workshop/sandbox/{host,worker}.py
  • REST: src/bridge/routes/broker_workshop.py, src/bridge/routes/broker.py, src/bridge/routes/broker_app.py
  • Template: workspace/manager/brokers/starter_broker/
  • SDK documentation: workspace/manager/brokers/SDK.md
  • Full development manual: BROKER_DEVELOPMENT.md

Familiars · 多 Agent AI 桌面应用