How syncing works
Each manager exposes the same pair of building blocks (inunify/<manager>/custom_*.py in the
unifyai/unify repo):
- Collectors read the source files from one or more directories and return the declared entries.
sync_custom()on the manager reconciles the store against those entries.
- Every source entry carries a stable
keyand gets a content hash. An aggregate hash over all entries is compared against the last synced value — if nothing changed, sync is a no-op, so it’s cheap to run on every startup. - On a mismatch, entries are reconciled by
key: new keys are inserted, changed hashes are updated in place, and keys that disappeared from the source are deleted from the store. - Rows the assistant or user created through normal conversation are
untouched — sync only manages rows it created. The one exception: if a
manually-created row collides with a declared
key, the source definition adopts it. - Runtime-owned state survives updates. A recurring task keeps its execution history and status when you edit its definition, and a task that is actively running is skipped and picked up on the next sync.
- Invalid lines are skipped with a logged warning — one bad row never blocks the rest of the file.
Blank lines and
#-prefixed comment lines are allowed in every .jsonl
file.
The override cascade
Collectors accept a list of directories and merge them in order — when the samekey appears in more than one directory, the later directory
wins. This gives you a layered override model:
[org, me] and an entry keyed crm-runbook in me/guidance.jsonl
replaces the org-level definition of the same key. The hosted product uses
this same mechanism to cascade org → user → assistant configuration; a
local deployment can use as many or as few layers as it wants.
Source file formats
Guidance — guidance.jsonl
One JSON object per line. function_names link the guidance to custom
functions by name (resolved to ids at sync time):
Contacts — contacts.jsonl
phone_number, whatsapp_number, discord_id,
slack_user_id, bio, timezone. key may be omitted — it defaults to
the lowercased first_name|surname pair (secrets similarly default to
their name).
Secrets — secrets.jsonl
Tasks — tasks.jsonl
Declare recurring or event-triggered work. schedule and trigger are
mutually exclusive; runtime status and execution metadata stay owned by
the scheduler:
schedule holds an ISO-8601 start_at; repeat is a list of
RFC-5545-style patterns (frequency, interval, weekdays, count,
until, time_of_day); trigger names an inbound communication event
(medium such as email or sms_message, optional
from_contact_ids/omit_contact_ids, and recurring to re-arm after
each run). Other supported fields: deadline, priority,
response_policy, entrypoint_function (run a stored function instead of
re-planning), offline.
Blacklist — blacklist.jsonl
Knowledge tables — directory tree
Each table is a subdirectory holdingmeta.json (description, column
types, and the seed_key used as the per-row merge key) plus rows.jsonl.
The relative path to meta.json becomes the table name, so nesting is
allowed:
Reference data tables — directory tree
Samemeta.json + rows.jsonl shape as knowledge, for DataManager-owned
reference tables. meta.json additionally supports context (target
context path), unique_keys, and auto_counting.
Dashboards — directory tree
Tiles and layouts live undertiles/ and layouts/ namespaces, each with
the meta.json + rows.jsonl shape.
Functions and venvs — Python and TOML
Custom functions are ordinary Python decorated with@custom_function;
venvs are pyproject.toml-style files whose filename becomes the venv
name:
_ are ignored. See the
FunctionManager README
for the full decorator reference.
Running a sync
Syncing is explicit — nothing watches the files. Collect from your directory layers and callsync_custom() on each manager. Sync functions
first, since guidance and tasks resolve function names to ids:
collect_contacts_from_directories, collect_secrets_from_directories,
and so on). Because every sync is hash-guarded, running the whole pass on
every startup costs almost nothing when the files haven’t changed.
In the hosted product this reconcile runs automatically when an assistant
starts, cascading org, user, and assistant configuration layers. In a
local deployment you decide when it runs — a startup script that calls the
snippet above is the usual shape.