Docs: Signals & rules
Signals & rules
On this page
A map answers questions. Signals say what changed, so neither you nor your agents have to re-read the whole map to find work. A scan writes them as typed events, each with a severity, the objects it concerns and a payload of counts and structure — never rows.
What a signal can be
| Kind | What happened |
|---|---|
fix_approved | A person approved a finding's suggested fix: carry it out (get_task_context gives the finding and the fix) |
finding_added | No longer raised: findings reach agents as fix_approved once a person approves the fix. Kept so older signals read |
finding_resolved | No longer raised: a finding the previous scan had and this one does not is news, and the scan's findings hold it (see below). Earlier ones stay readable |
finding_changed | No longer raised: a person's change to a finding's status is in your audit trail. Earlier ones stay readable |
process_deviation | A step now takes at least twice as long as in the previous scan |
process_silent | A process that was discovered before and is not any more. A process whose object was only renamed raises none; where the map only suggests where the cases went, the signal names those processes and leaves the call to you |
structure_changed | A connected system offers a different set of resources than it did |
metric_threshold | A tracked number crossed the line you drew |
metric_trend | A tracked number moved more than a quarter in the direction you called bad |
link_dead | Something the map depends on stopped working, with the steps that repair it |
external | Something happened outside Maplith that an agent has to act on — today, a GitHub issue labelled agent-fix or agent-pr. A comment, any other label and a pull request opened or closed are news: kept with the delivery, never a signal |
rule_fired | A rule you wrote holds for one group — one product, one order |
rule_cleared | A group stopped matching the conditions of a live check your assistant reports to. A rule over your map raises none: when its group stops matching, or a rule that could not be judged can be again, the rule keeps that itself and the earlier signal is answered |
rule_broken | A rule cannot be judged on the map as it now stands, with the reason and the steps that repair it |
watch_silent | A live check you set up has stopped reporting: nothing arrived from your assistant or your no-code sender within three times its expected interval, so its silence no longer means nothing happened. Not raised yet: until the check that raises it ships, the live check's page in Rules & know-how › Rules shows it as silent |
map_rolled_back | No longer raised: a rollback is in the map's history. Earlier ones stay readable |
cpr_file_names | A scan met file or folder names that look like CPR numbers — one signal per scan, with how many, never the names. Maplith tries to mask these numbers in the map; the fix is yours: rename the files, or leave those folders out of the export, and scan the same source again. The next scan of that source without such names closes the signal. Only members and keys that see the whole map are shown it |
agents_paused | No longer raised: a pause is on the workspace and in its audit trail, and every agent call says so while it lasts. Earlier ones stay readable |
agents_resumed | No longer raised: the resume is in the audit trail. Earlier ones stay readable |
secret_expiring | One of Maplith's own keys is about to expire. Raised only in Maplith's own workspace, never in yours |
secret_expired | One of Maplith's own keys has expired. Raised only in Maplith's own workspace, never in yours |
release_note_published | Maplith published a release note, for its own marketing agent to post about. Raised only in Maplith's own workspace, never in yours, and only while that agent runs |
setup_gap | Something your setup needs is missing: a kind of signal nothing handles, a playbook still in draft or anchored on nothing, a playbook nothing starts (it waits for a signal nothing raises, or runs on a calendar, or starts in a system Maplith cannot see), an operation no key may ask for. Maplith checks every hour and closes the signal itself once the gap is gone. check_setup lists them all |
customer_blocked | A customer was stopped in the app: an upload, a scan, a build, a connection, an agent key or a join review failed. Raised only in Maplith's own workspace, never in yours, with ids, the step and the kind of error — never what was uploaded or typed |
customer_report | A member told Maplith about a problem from the app (Tell Maplith). Raised only in Maplith's own workspace, never in yours, with ids, the page and the kind of error — never what the person wrote |
recipe_drift | A system Maplith reads from its recipe no longer offers a read the recipe calls, or now says one can change data. Raised only in Maplith's own workspace, never in yours, with the recipe and the names of its own read tools, never your data; Maplith stops calling that read until the recipe is updated |
connector_requested | A customer asked Maplith to set up a system that is not open to their workspace yet (Data sources › Systems › Connect on a held system). Raised only in Maplith's own workspace, never in yours, with ids and the system's name, never who asked |
unblocked | Something your workspace waited on from Maplith is ready, and get_task_context says what to tell your human. A system someone asked Maplith to set up is ready to connect: info, one per request, for every agent that sees the whole map; list_requests lists it under recently_done for 30 days, and it closes itself when the system is connected, or after 14 days. Or a problem on Maplith's side that stopped an action here is fixed (payload what: fix): for the agent whose call failed, which is shown it even when its key sees only part of the map, or for the workspace when a person was stopped connecting, testing or scanning a system, or when Maplith did not record which agent was stopped (then your agent is told to try again if it was the one stopped). It is not raised where the same step failed again after the fix went live. It names the step and the day, never anything you wrote, and closes itself after 7 days |
routine_due | A routine you put on a schedule is due: a playbook that runs every day, every working day, every week on a set day, or every month, at a time in its own time zone. One signal each time it falls due, naming the playbook, so your agent is handed exactly that playbook and records the work when done. One nobody took is closed when the next one is raised, which says since when it was missed. Set, pause or remove a schedule in Rules & know-how › Playbooks; Maplith may set one itself where a playbook says when it runs, and your change always wins. Only members and keys that see the whole map are shown it |
automation_failed | An automation registered for your workspace reported that its last run failed — your agent reports each run with register_automation (last_run_at and last_status). One signal per automation, naming it and its system and the objects it reads and writes. Maplith checks every hour and closes the signal itself once a later run is reported as succeeded |
ci_failed | A GitHub Actions workflow failed on the default branch of a repository connected through the Maplith GitHub App: raised the moment GitHub reports the run, one per repository, branch and workflow, and cleared by the next green run of that workflow. Names the workflow, the run, the short commit and a link. Requires a connected GitHub installation with the relevant read permissions and event delivery; an installation owner approves new permissions when the App asks |
deploy_failed | The newest deployment of an environment, as GitHub records it for a connected repository, failed: raised the moment GitHub reports the status, one per repository and environment, and cleared once the newest deployment is live. Requires a connected GitHub installation with the relevant read permissions and event delivery; an installation owner approves new permissions when the App asks |
migration_pending | A migration of Maplith's own database waits to be applied. Raised only in Maplith's own workspace, never in yours |
privilege_finding | Maplith's daily privilege check found something. Raised only in Maplith's own workspace, never in yours |
advisor_finding | A database advisor found something on Maplith's own database. Raised only in Maplith's own workspace, never in yours |
security_alert | GitHub opened a secret-scanning, code-scanning or Dependabot alert on a connected repository: one per alert, with its number, the rule or package, the severity and a link — never the secret itself — and cleared when GitHub reports the alert closed. Requires a connected GitHub installation with the relevant read permissions and event delivery; an installation owner approves new permissions when the App asks |
legal_claim_broken | A claim in Maplith's legal texts no longer holds in production. Raised only in Maplith's own workspace, never in yours |
legal_version_due | A legal version of Maplith's waits to be published. Raised only in Maplith's own workspace, never in yours |
retention_failed | Maplith's daily retention run failed. Raised only in Maplith's own workspace, never in yours |
dmarc_drift | Mail sent as one of your domains needs a look: a sending service's mail fails DMARC, a sender that is not on your list of known senders appears, or the next, stricter DMARC policy has been safe to take for more than a week. One per cause, with the domain, the sending organisation and message counts, and for a policy step the current and the proposed DMARC record — never a message, an address or an IP address. Your agent proposes a change; a person makes it in DNS. Connect Cloudflare under Data sources › Systems with a read-only token, turn on "Check that your email domain is protected (SPF, DKIM, DMARC)" on the connection, choose the domains and their policy ladder, and name your known senders |
demo_drift | The demo figures on Maplith's website drifted. Raised only in Maplith's own workspace, never in yours |
deletion_unfinished | A deletion did not complete. Raised only in Maplith's own workspace, never in yours |
pr_review_due | A pull request on Maplith's own repository waits for review. Raised only in Maplith's own workspace, never in yours, and today not at all: Maplith finds this work in its own repository's queue instead |
dependency_update_due | A dependency update of Maplith's waits: one signal per pull request, with what it changes and whether Maplith can merge it itself. Raised only in Maplith's own workspace, never in yours |
work_queued | An issue in Maplith's own repository is queued to be built. Raised only in Maplith's own workspace, never in yours, and today not at all: Maplith finds this work in its own repository's queue instead |
deep_review_queued | Items in Maplith's own repository wait for a deeper review. Raised only in Maplith's own workspace, never in yours, and today not at all: Maplith finds this work in its own repository's queue instead |
inbound_request | Someone outside Maplith asked for help on its public Bridge repository. Raised only in Maplith's own workspace, never in yours |
control_failed | One of Maplith's own controls could not run. Raised only in Maplith's own workspace, never in yours |
infra_drift | Maplith's own hosting differs from the code that describes it, or blocks an AI crawler that Maplith's robots.txt invites. Raised only in Maplith's own workspace, never in yours |
field_identity_miss | Maplith recognised a column of an export wrongly, so the case is reproduced and the recognition fixed. Raised only in Maplith's own workspace, never in yours |
release_note_ready | A note about something new in Maplith will be published, because what it announces is now live: Maplith publishes it itself, or holds it until its wording is fixed. Raised only in Maplith's own workspace, never in yours |
vendor_incident_resolved_pending_deploy | A hosting vendor's incident is over, but work merged during it is not deployed yet. Raised only in Maplith's own workspace, never in yours |
support_case_opened | A new conversation arrived in your helpdesk, with its tag: the signal the support playbooks wait for. Raised by vendor events where your workspace receives them (Events from Intercom); a helpdesk read from an export shows no conversation arriving. A conversation your team answers or closes in Intercom counts as handled there |
thread_started | A new thread arrived in a mailbox or chat channel you connected, of a kind one of Maplith's mail and chat playbooks covers, and nobody has answered it yet, in the thread or from the mailbox's sent mail. It names that playbook, which get_task_context hands to the agent, and the thread by id, where it is and the minute it started: never a subject, a sender or a message. Raised by each scheduled scan of the connection, once per thread |
join_review | Someone questioned a join or proposed one the map missed, with the reason. Its task context says exactly what to count in your own systems — the overlap of the two keys, how many different values each side has, the sample size — and your agent reports the counts with join_review (op report), never a row. A person decides in Business map › Glossary; only on a join Maplith asked to have tested do counts that agree, from an agent that may act, mark it tested without asking anyone |
field_identity_unsure | A new export of a file has columns Maplith could not tell apart from the ones it had before, and something on your map depends on them, or the source’s declared identity conflicts with measured evidence: one question for the file, never one per column. Where Maplith has a suggestion for a column, it comes with how often such suggestions were right in Maplith's tests, and later in your workspace. A declaration conflict always needs a person’s choice on the linked Glossary question. For other uncertainty a person answers in Business map › Glossary, or your agent with answer_question (kind field_identity); until then what depends on those columns stays as it was last measured, and nothing is removed. Raised once Maplith has switched column recognition on for your workspace |
field_meaning_changed | A column your source still names changed what it holds (another unit, another set of values, another type), and something on your map depends on it. What depends on it stays as it was last measured until someone checks the source and confirms or points it elsewhere. Raised once column recognition is on for your workspace, as above |
grain_unsure | Maplith cannot tell for sure which columns of a file you shared tell its rows apart: on a file of only a few rows one column may be unique by chance, two that are unique together may be so by chance, two pairs may fit equally well, or the file is too large to check every row. Maplith states no grain rather than guess, and asks one question, which you answer in Data sources › Scans by choosing the columns, or none. Your agent may answer it when it can check it in your own system and says what it checked; otherwise it asks you. Your answer always stands over your agent's, is kept for the file, and the next scan states the grain by it; a later scan that disagrees proposes a change in Business map › Glossary instead of making one |
map_confidence_dropped | The last scan of a source scored your map at least 5 points lower than that source's scan before, and new items came up: joins to check, questions, proposals or objects no longer maintained. One per workspace and source, updated while it is open, and only for members and agents who see the whole map. Your agent reads what lowers the score and asks you the questions; it closes by itself when a later scan of that source is back at the score from before the drop. |
schema_dialect_unsure | A database schema you shared as a SQL file does not say whether it is from PostgreSQL or MySQL, and some of its statements read differently in the two. Maplith read the rest at once; these wait for one answer, which you give in Data sources › Scans, with the likelier dialect chosen for you. Your agent may answer it when the file shows the dialect clearly and says what showed it; otherwise it asks you. Your answer always stands over your agent's. The answer is kept for the file, and the next scan reads the rest |
lineage_direction_unsure | Two files hold the same rows and nothing in them says which one is the original. Until you say, agents are told the direction is unknown and to ask you before they correct or sync either. You answer in Data sources › Scans, with a suggestion chosen for you. Your agent may answer it after checking your systems and says what it read; your answer always stands over your agent's. The answer is kept across rescans |
Columns Maplith could not recognise
When an export comes back with renamed, reordered or translated columns, Maplith recognises each column by what it holds and where it comes from, not by its header. A key column under a new name is recognised as the same key when the export's rows line up on it and its other columns agree on those rows; your joins, grains, flags and rules on it follow it to its new name.
When an export's rows do not come back (a daily or 30-day export whose window moved on, or one that switched language), Maplith also reads it the way your business map does: the object the file builds, the fields it has had, and what reads each of them (your rules, joins and metrics). Maplith is switching this on step by step. Where it is on, a column it places that way, or by its seat when one header changed in place, is auto-detected: your rules, joins, terms and metrics keep running on it, and your agent sees it marked auto_detected in get_object until the field is verified by its own rows, by your agent or by a person; a later export under the same header does not verify it. Maplith places a column there only when its profile and type agree with the field's, its values and its relations to the file's other columns do not contradict it, and the column was not one Maplith had already declined for want of such a relation; a key column also needs to share part of its values with the old key, never only its name. Where Maplith cannot tell, it decides nothing on its own: the column gets a place of its own for now, everything that depends on the column it may continue stays as it was last measured, and nothing is removed. If something on your map depends on it (a join, a confirmed term, a metric or a rule you set up), Maplith asks once for the whole file. When a later export settles it (every column it might have been turns up elsewhere in that same export, so it is a new column), Maplith decides that column itself, says why, and does not ask about it again.
Maplith switches column recognition on for workspaces one at a time; until it is on for yours, columns are matched by their headers as before.
Where Maplith has a suggestion for a column, the question shows it: *the same as* one of the columns you had before, or *a new column*. The percentage beside it is how often suggestions like it were right in Maplith's tests, and, once your workspace has answered enough of them, how often they were accepted in your workspace — never the model's own sense of how sure it is. A suggestion is never applied until someone answers: one click on Accept takes it, Change answers otherwise (another column, a new column, or the whole file is another table now). An answer is final for that export, and the next export that agrees with it asks nothing. A person answers in Business map › Glossary; your agent can answer with answer_question (kind field_identity), which waits for a person's approval under an approval key, a key set to hand over only, or whenever the source’s declaration conflicts with measured identity. Task context links directly to that question. While your agents are paused, an agent's answer is not applied and one that waits cannot be approved; you can still answer the column yourself. Where Maplith is not sure the export is the same table as before, only a person can say that a column is one you had before; your agent can still say it is new, or that the whole file is another table now.
Maplith counts how a kind of suggestion fares in your workspace: for example, key columns it suggests continue an earlier column, at 95 % or more. When people in your workspace have accepted that kind 600 times in a row, each with their own click, it earns automatic answers; your agent's answers never count towards it, and the first change of one of its answers takes it away. The object page lists the kinds used on that object's columns, under Maplith's suggestions for this file, with how far each has come. An admin who sees the whole map can switch one off, or turn automatic answers off for the whole workspace; either stays off until someone switches it on again. Switching either on starts the count again from zero: what people accepted while it was off never earns automatic answers. For now an earned kind still suggests: Maplith does not yet answer on its own.
They are listed under Business map › Activity › agent work in your workspace, and served to agents by list_signals with the objects and the payload each one carries. Instead of asking, an agent can be woken when one is raised: see Wake your agent.
Task context includes a trust summary: weak joins and fields not to quote back. Still metadata: fetch rows in your human's own systems using the routes; no source rows are returned by task context.
A signal is work
A signal is raised only when someone has to do something. News that asks nothing of anyone — a comment on a GitHub issue, say — is kept where it happened and raises no signal. A queue of similar items that one agent works through in one pass is one signal with the count and the items, not one per item, and it closes itself when the queue is empty. A rule is different: it raises one signal per group it matches, because each group starts and stops matching on its own and is answered on its own. To work one rule's signals together, read by_rule in the list_signals answer: one entry per rule with work still to do on this page (open, planned or in progress), with those groups, the highest urgency among them, the oldest and the signal ids, each still taken and closed on its own. It counts this page only: page on for the rest. The same grouping is in each signal's payload.rule.id.
To read the most urgent work first, pass order: 'urgency': urgent, then needs attention, then info, newest first within each. Pass the same order with the next_cursor it gave you; a cursor from the other order is refused. Without it the queue stays newest first, which is what polling with since wants.
So these are news, and live in the record where they happened rather than in the signals:
- A finding went away, or someone changed its status. The scan's findings and your workspace's audit trail hold it, and the value summary still counts it under *needing no action*.
- A rule's group stopped matching. The rule keeps it, and answers the signal that said the group matched.
- An admin made an older scan the current map again. The map's history holds it, and
get_map_overviewsays which scan the map is now. - An admin paused or resumed the agents. The workspace and its audit trail hold it; while the pause lasts, every agent call says so.
Signals of these kinds raised before stay readable, as they were. Legacy notice rows keep their recorded done state.
Every signal is in one of four states:
- open — nobody has picked it up yet. Business map › Activity › agent work says for how long.
- planned — someone has picked it up for later: who, since when, and by when if they said. No other agent takes or plans it until that date (a member can still take it over); a plan past its date keeps no agent off.
- in progress — an agent is working on it (its hold runs out after a day unless renewed), or it waits on a person. A signal Maplith's own check settles (a setup gap, a failed automation) that waits on a person is closed as resolved once that check finds the cause gone, unless a member took it or someone worked on it in the last day: nobody has to clear it by hand.
- done — with what came of it.
Your Inbox tells you when a signal has stayed open longer than its severity allows (four hours when urgent, a day for attention, three days for information), and when a plan has passed its date.
A finding you approve is work for your agent
A finding is something a person decides. Once someone approves its suggested fix, it arrives as fix_approved, one signal per finding. Its task context carries approved_fix: the finding as data, its suggested next steps, who approved it and when, whether it was handed to your agent in particular, and what your key may do. Take the signal, do the work in the system that holds the records with your own access (Maplith never returns rows), and record what came of it with record_action: done and fixed, not_an_issue or wont_fix with a note (the finding goes back to the person), or needs_human with a task. The next full scan confirms a fix, or reopens it as seen again.
A fix handed to one agent is offered to that agent only. A person can withdraw an approval while no agent has taken it. Joins a scan could not test are not asked of a person where an agent may test them: Maplith opens a join_review for each, the agent counts the keys in its own systems and reports with join_review (op report), and counts that agree with the map mark the join as tested without asking anyone.
Where no playbook of yours handles fix_approved, Maplith writes a generic one, "Carry out an approved fix". A playbook you had for finding_added is also served when a fix is approved. Adapt either, or retire it under Rules & know-how › Playbooks; Maplith keeps your change and does not write it again.
Rules you write
Findings, slowing processes and thresholds are Maplith's own reading of your map. A rule is yours. In Rules & know-how › Rules an admin builds one from the map's own names, in dropdowns — no syntax, no SQL:
- Judged per group. You pick the object and the field that names the group: per product by SKU, per order by order number. Every group is judged on its own.
- Up to six conditions, all of which must hold, or one group of which at least one must. Each side is a figure over an object and one of its fields — a sum, a count, an average, a smallest or largest value, or the value on the newest row — compared with a number or with another figure of the same group.
- The newest row is told by date and time, as your export writes them. A time with no zone, or with
Zor an offset such as+02:00, is read; a zone written as a name, as in2026-09-01 08:14 UTC, is not. Where such a time sits on a row the rule could take as newest, the rule reportsrule_broken— it cannot tell which row is newest — rather than reading the time as midnight and taking an older row for the current one. - A rolling window of whole days, up to 90, on any date field: "over the last day", "over the last 30 days".
- An action to suggest, from the operations you declared under Agents & access › Policies. It is a hint your agent reads, never an instruction: the agent still asks with
request_action, and your policy decides. - How urgent it is when it fires: info, needs attention or urgent. Every signal the rule raises when a group matches carries it, so money spent on a product that cannot be sold can come before a stale note. You choose it; Maplith never judges it from what the rule says. Left as it is, a rule needs attention. Any saved change to a rule, its urgency included, is news for a group that still matches: a signal closed as not an issue opens again.
- On or off, and 25 rules per workspace.
A rule is judged per group by every scan whose map covers it, as the scan finishes, from the files it read. A firing is therefore as fresh as the files behind it: it says what those files showed, not what a system is doing at this moment. One scan reads one system, so a rule across two is judged by the scan that reads both; a rule no scan has covered for a fortnight is reported as such, rather than reported broken and mended in turn. A rule that names something no scan has any more is told at once, with the reason. And a rescan does not fold an object a rule names into a term you confirmed from the same file: it asks you first.
A group is reached only through joins Maplith tested against your own rows. That is what makes a rule safe to act on: pausing the wrong product's ads because two systems were joined by a guess is worse than no rule at all. So a rule whose path runs over an untested or free-text match is refused when you save it, and the page names the hop. Where a tested join leaves rows behind, the rule is accepted and every signal it raises carries the caveat, counted on the side that cannot travel — "47 of 512 ad spend rows do not reach a product" — and the rows that cannot travel are counted, never guessed. If a later scan takes the join away, the rule raises rule_broken with the reason and what would repair it, rather than quietly judging less than it used to.
A rule across two systems needs a join the map measured. Where the map has none — the two systems name the key differently, or its values look like plain numbers — the refusal says so, and you can set the join yourself: on the object page, choose the field that holds the other object's key, the object, and its field. The next scan measures it the way it measures the joins it finds: how many values match on each side. At half or more it holds, and a rule can group through it; a join that held before keeps holding down to 40 %, so one bad export does not take it away. A join you set is never changed or removed by a scan. If it stops holding, it stays on the map, no rule groups through it, and the Glossary asks whether to keep it or forget it. Two systems that are scanned separately — two connections — are never read by the same scan, so no join between them can be measured, and a rule across them is refused with that reason; to join them, scan their exports together.
When a rule cannot be judged, you are told. A rule that raised nothing has nothing to say, and says nothing. A rule that *cannot* be judged — the join it runs over is gone, a column it reads has left the export, an object it names is on no map any more, or no map has held its objects for a fortnight — is different: its silence would say nothing about your data. So the person who set it up — while they are still a member who sees the whole map — gets a notice under What's new the moment it breaks, which stays until it can be judged again, and Business map › Activity lists every rule that cannot be judged right now, with the reason. They get one mail only when the rule has been unable to be judged for 24 hours or more without a judged scan in between, and at most one per rule per week: a rule that breaks and recovers between scans sends no mail, and never twice for the same break. A key that reads signals sees every break either way.
Rules are shown to members whose view of the map is not limited to part of it, because a rule names objects and fields across it; admins write them, and every version of every rule is kept.
A rule an agent proposes
An agent whose key acts proposes a rule with request_action, op: "proposal" and proposal: {kind: "rule_from_finding", name, definition, finding_id?, urgency?}; a whole-map admin approves it, and an approved rule is on at once. Whatever already matches at its first check is its starting point and raises nothing. definition is in the rules grammar: per: {object, key}, then all (every condition holds) or any (at least one does). A condition compares a term, {fn: SUM | COUNT | AVG | MIN | MAX | LAST, object, field?, at?, window?: {field, days}}, with op (=, !=, <, <=, >, >=) to a number in value or to another term in other. "Per product, under 5 in stock":
{ "per": { "object": "product", "key": "sku" },
"all": [ { "term": { "fn": "SUM", "object": "stock_item", "field": "quantity" }, "op": "<", "value": 5 } ] }Values are numbers. A condition cannot match a text value (status = paid) or the age of a date; a window counts only the rows whose date field falls in its last 1 to 90 days. Where the finding is narrower than what the grammar can say, tie the rule to it with finding_id and say so in the reason, which names each of What it does:, Problem: and Value: at the start of its own line.
Agents keep the rules current
The same agent keeps the rules it works with current. After each scan it reads rule_broken, rule_fired and new findings, and proposes, together, so a person approves them in one go:
- A fix,
proposal: {kind: "rule_update", rule_id, rule_version?, name?, definition?}, when a rule broke, typically because an export renamed a column. For arule_brokensignal,get_task_contextoffers it infix: a ready-maderule_updatewhen the map records what replaced the column, or the fields first seen in that scan to check in your own system when it does not. A fix keeps the rule's id, its history, its urgency, its suggested action and whether it is on. - A retirement,
proposal: {kind: "rule_retire", rule_id, rule_version?, reason}, with one plain sentence on why the rule no longer fits.get_task_contextsuggests it for a rule that has not been checkable in 3 scans in a row over at least 14 days and has no fix (a check between scans does not count), andget_rulemarks a rule that has not fired over 90 days of checks withreview. A retired rule is switched off and kept with its reason under Retired in Rules & know-how › Rules, where an admin can restore it as it was. Nothing is ever retired on its own. - A new rule for a finding your team cares about, as above.
Each waits for a whole-map admin in Agents & access › Activity, one to-do per agent and scan in the Inbox, with what changes shown before and after. A rule's row in Rules & know-how › Rules says when an agent's proposal waits on it, and Formula and history lists every change, who approved it and which agent proposed it.
Why a new rule is quiet at first
A rule's first evaluation is its baseline, and fires nothing. The first time a rule is judged, the groups that already hold are recorded as its baseline, and none of them raises a signal. Saving "stock is zero" must not announce every product that was already sold out before you wrote the rule.
From then on:
- A group that starts holding fires.
- A baseline group that stops holding and later holds again fires.
- A group that has held without interruption since the baseline never fires.
- Changing what the rule asks — the group it judges, a condition, the action it suggests — takes a new baseline. Renaming a rule, changing how urgent it is, or switching it off and on, does not.
Saving a rule stores it and judges nothing, so a new rule's silence means different things at different times:
- Before its first judging, nothing has looked at it yet. There is no baseline, and silence says nothing about your data. Rules & know-how › Rules marks such a rule not checked yet until something judges it.
- After it, silence means the rule was judged and no group has started holding since its baseline. A group that already held at that first judging is part of the baseline — including the one you may have written the rule about — and reaches you only once it stops and later holds again.
- A rule no scan's map holds is not judged at all. It stays silent until it has gone unjudged for more than 14 days, and the next scan then raises
rule_brokensaying so.
The baseline itself is never shown as a signal, in your workspace or to an agent, and it holds no group name and no figure.
What a firing carries, and what is never stored
A rule_fired signal carries the rule and its version, the group it is about, the figure behind each condition at that moment, the join path with each hop's measured coverage, any caveats, and the action the rule suggests. That is the standing a finding's evidence has: a statement about one problem with the figures that prove it.
No figure per group is kept beyond that, and the baseline holds only a hash of each group that already held. The figures are computed while the rule is judged and discarded; Maplith does not build a per-product history out of your rules. Firings are signals, and signals are what your agents read.
When an agent reads a firing it is told the conditions one by one, numbered c1, c2. A condition is verified when Maplith's scan judged it over your export, checked by a live check's observer when your own tool looked in your own system and reported it (Maplith does not read that data), and unverified when no one has yet — the observer left it to the agent that acts, or the rule cannot be judged on the map as it stands. For an unverified condition the agent is told why, which system and export hold its figure, and the join the map tested, when the rule was compiled, that reaches the product or order; when the latest scan could not judge the rule, the reason is told once, and the join is named as the one tested at compile time. A rule that fired also comes with its remedy: the operation the rule suggests, or a plain line that none is declared and the agent proposes a fix to you, together with the written procedure — an SOP or policy document — the map links to the objects the rule reads. A rule that cannot be judged comes with the objects it reads and the routes between them, and every step to repair it begins with who does it.
Metrics you define
The same holds from the other end for numbers. Besides the metrics the map proposes, an admin can define a formula of their own in Business map › Metrics, in the same grammar — a sum, a count, an average, a smallest or largest value over one field, optionally filtered on one field of the same object; a rate of two of those over the same object; or the days between two date fields of the same object as an average, a median or a 90th percentile — each optionally by month, week or day. It is checked against the map as you write it and again at every scan, recorded from the next scan on, and served to your agents by get_metric like any other. You can draw a line on it and be told when the number crosses it.
Give a metric the name of one the map proposed and yours replaces it. Where your formula differs from the one that name measured before, the number starts a fresh series rather than continuing someone else's: the earlier values are kept but no longer reported, and a line drawn on the old formula does not carry over. A formula the map can no longer compute — a field gone after a rescan — raises link_dead with the reason.
The loop, for an agent
list_signals(states: ['open'])— the work nobody has picked up yet, read whole rather than withsince: a signal whose hold ran out, or that a person let go or reopened, is open again without a new timestamp. A planned signal is someone's already (states: ['planned']). News is not a signal: a rollback, say, is inget_map_overview's map version.get_task_context(signal_id)— everything that one signal needs: the objects, the verified route between them, the process step, the open findings, the metric and its values, what was done last time, and for a rule that fired the operation it suggests with your key's policy for it.take_signal(signal_id, signal_version)— so no other agent does the same work. The hold runs out after 24 hours (1 to 72 on request) if the agent never reports back, and the signal goes back to open. Where planning is switched on,take_signal(signal_id, signal_version, plan: true, due)plans it for later instead: the signal is yours until the date (up to seven days when none is given), and you take it when you start. Agent plans use workspace limits: by default 7 days for urgent signals, 30 for attention, and at most two plans per key and episode within 90 days. An admin can change the caps under Agents & access › Connections. Agents cannot change them. Info allows up to 90 days; without a date the earlier of seven days and the workspace cap applies.- Do the work in your own systems, with your own credentials. Where the operation is one you declared, the agent asks first with
request_actionand getsallowed,pendingordenied. Onpendingit does not act: it asks again after a person decides or, if it cannot wait, recordsneeds_humanwith ataskfor that person. record_actionwith what came of it —fixed,not_an_issueorwont_fix— so the next scan knows what a recurrence means: a fix a later scan still sees opens again, with the report attached; a decision to leave it stays quiet until it changes. When your own systems state what the work was worth, pass it asvalue; never estimate one.get_value_summary(since, until)— what came of it over a period: signals raised, acted on, not acted on, needing none or resolved before anyone acted, time to action, actions by outcome, requests through the gate, the value stated, dead connections the probe reached again, and each tracked metric's values beside the actions recorded about it — side by side, never joined. Counts within your key's scope, nothing derived.
A support case, for an agent
A case that arrives in your helpdesk did not start as a signal. The agent handling it calls get_task_context with case instead of signal_id: the kind of identifier it holds for the customer and the kind of case, such as { identifier: "email", topic: "return" } — never the address itself. The answer is the map's metadata, within the key's scope:
lookup— which objects carry that identifier, in which export and system that column actually lives, by which field, and whether that field identifies one row (your own team's fields and yes/no flags are left out). A field is found by its name, as the key of a business term confirmed in the Glossary (so a column calledkundenranswers for "customer number"), as a column a confirmed join ties to that key, or as a contact field Maplith read as e-mail addresses or phone numbers; exact names and confirmed keys come first, then key columns, and each lookup says how it was found;routes— the available join path from there to the objects the case is about;playbooks— your people's guidance for that kind of case, andplaybook_drafts— drafts waiting for an admin, named but never served; both always present;findingsandsignals— what is open on those objects;actions— every operation the key has a policy for, with whatrequest_actionwould decide today.
A topic is matched through the map's own words first — names, written procedures, playbook titles — and through a few built-in synonyms only when the map names nothing for it; each object says how it was reached.
The agent then looks the customer up in your own systems, asks with request_action before it changes anything, and records the outcome with record_action and the request's id. A key that cannot request actions is told to hand the case to a person instead.