Skip to main content
Version: Target · roadmap

From the current version to the target

What changes in the file​

currenttargetwhy
tenant_id, store_name, domainstation: {id, kind, name}an organisation owns several stations
global: {language, currency, locale}global.timezone as wella sub-identity's hours, the inboxes' deadlines
agent: {name, persona, tone, rules, emoji, model}agent.mode as well; the rules that hold for every agent become policiesagent.rules still reach both doors; only how our agent speaks should stay there
agent.portals.web.widget: {origins, theme}the same shape; identity.auth as wellthe tenant's site is a portal of the agent, not the station
—agent.portals: {web, teams, slack, whatsapp} with identity.authnew portals, and the proof of who is talking
—station.identity.providers (otp, oidc, assertion, each with verifies:) and identifies: true on a form fieldan identified room (declared), then authenticated (verified). identifies: true already exists in the current version
apis[].operations: [{id, access, reversible, exposed}]risk and audience addedthe risk, what goes out. access, reversible and exposed already exist in the current version
skills[].invoke: {method, args} or {native}, params: {x: {default}} (default optional)invoke.guards; set_by and type on a parameterwho can set a parameter, what type its value is, and a read has its guards. The single declaration (invoke, kinds, present, select, submit, render), invoke.native, invoke.args, $params read by invoke.args and by the submit calls, a parameter whose value is its default or a task's binding already exist in the current version
catalog: {from_skill, mode, query_arg} at the station level—: the config has no catalogue; an item is resolved by what an invoke showed in the room, and read through the present of the skill that showed itthe invoke already says what the station offers; a second declaration would contradict it, and an indexed copy would go stale
a skill's present.fields: {from, type, role, template}the same block; the headline role and the class addedtyped fields, their roles (title, billed, link) and the table of types, shared with forms, already exist in the current version; an item is read through the present of the skill that showed it
kinds: {x: {when, select, submit}}kinds.<x>.present added (a permission on what a profile sees); kinds.<x>.fields, the line fields the person fills line by linea kind is recognised by a condition on a presented field; this already exists in the current version. A choice that belongs to one line (a dish's side dish) had nowhere to be written
select: {max_items, guards: [form, require, check, update]}, each guard with whenpermission, policy, approval added to guards; name, effect, mode, reason on every guard; unique, valid_for; quantity: {min, max, step}, most often per kindan ordered list, open to other guards. The list, require, when and the condition language already exist in the current version
submit: [{name, mode, tool, args, output, line_fields, line_shape}], one call per skill or per kindseveral calls, run in order; each output typed: output: {x: {from, type}}; line: replaces line_fields and line_shape ($line.ref, $line.quantity, $line.price, $line.fields.<field>); extra_lineschain writes inside one skill; one type table for everything that carries a value. Named calls, their outputs and $action.key already exist in the current version
$submitted[task.call].output: a playbook's task$result[tache], $read[tache] addedwhat a task read or returned. $submitted[task.call] already exists in the current version
playbooks[]: {name, goal, tasks: {name: {skill, kind, one_of, choose, all_of, when, required, label, after, params}}}one_of: {choose, fallback, methods: {name: {skill, kind, when, label, params}}}: choose inside the one_of, a label per method; no more all_of, after: [a, b] waits for both; when on $read (not on $memory or $submitted); choose: clm; params bound to $read or $result; depends_on, on_open, guards on a taskthe condition on data, the data, a method's name as the person reads it. Named tasks, goal, label, after, the choice (one_of, choose: customer|rule, written next to one_of), when on $selection.kinds, a task's (and each method's) params bound to $submitted[task.call] with the after they imply, and the order enforced on choice already exist in the current version
— (the agent opens the playbook: auto_trigger, start_playbook)activation: {when, intent, exclusive_skills}a playbook that opens and is law
the station's submit_guardsubmit_guard per playbook, with submitted_bywho submits, and under which rule
an order declared on each skill whose lines it sends, grouped by operation at submitsubmit on the playbook: the call that sends the lines of several tasks, declared once, its lines taken by $selection[task=…]; cited $submitted[<call>]two copies of one call could diverge, and the body then depended on the order of the lines
—profiles with grantsrequired as soon as there is a skill: the migration writes an anonyme profile granted the operations the existing skills call, one by one
agent.portals.web.widget.labels, which does not reach refusalstexts: {fr: {<key>: "…"}} at the station level, for refusal and hold sentencesthe model reads these sentences too: they hold on every door
—policies, inboxes, events, automations, tests, evalsnew, all optional

The previous form of playbooks is not read: the tool rewrites it (python -m convoy_core.config.migrate). Each item of steps becomes a task named after its skill, with no after: the order of a list does not state a dependency, the author writes it if they want it. description becomes goal; trigger is dropped, and its intent becomes the goal of a playbook that had no description. $submitted[<skill>.<call>] becomes $submitted[<task>.<call>] when the skill is the action — or a one_of method's — of tasks of a single name. An all_of goes away: each task whose after names it waits for its members directly. choose and fallback move inside the one_of, and its methods go under methods.

The catalogue. The tool removes catalog. Nothing replaces it: search goes through a skill whose invoke is the tenant's search operation (the question under a parameter, instead of query_arg), and a reference is resolved by what the room has already seen. mode: indexed has no equivalent: Convoy does not index a tenant's catalogue (the case of a tenant with no search operation stays open).

The submit too. line_fields: {ref: a, quantity: b} becomes line: {a: "$line.ref", b: "$line.quantity"} (a missing key keeps slug or qty); line_shape: ref becomes line: "$line.ref"; with neither, nothing is written: the default shape is the same. Calls of one operation carried by the skills of several tasks of a playbook become a single call in that playbook's submit, when their args and line shape are the same: $selection[kind=k] there becomes $selection[task=…], and the references $submitted[<task>.<call>] to it become $submitted[<call>]. Different args, or a skill also used outside any playbook, are left to the author, with the reason. A migrated config must send the tenant the same requests, byte for byte.

The build order​

A step ships only when its examples from this version pass in the current version.

  1. Action states (pending, read) and read traces, in the room and the receipt.
  2. The order enforced on choice; tasks (after, one_of, when); the planner (named tasks, goal, label, after, one_of with choose: customer or rule, when on the chosen kinds and the order enforced on choice, step_open, are built; choose written inside the one_of, a method's label, the removal of all_of, when on $read, choose: clm and steering the model toward the open tasks follow).
  3. The new form of skills, the old one read for one version; the six configs migrated (the ordered list of select guards, require, when and the condition language are built; the skill's single declaration is built, with params and their default, invoke.args, invoke.native and kinds.<k> (when, select, submit), and a task's params bound to $submitted[…]).
  4. The submit guard per playbook, with the playbook's order call (submit, line:, extra_lines) and line fields; then each playbook's state graph, compiled at load, before the guidance; sub-playbooks; then result and $result, which read a sub-playbook.
  5. The journey attached to the room; approvals tied to a version of the action; valid_for.
  6. Profiles, per_profile, permissions; per-person identity (the identity plane, identifies and the otp provider via the tenant are built; via: convoy, oidc, assertion, the choice per portal, authentication requests and submitted_by: owner follow).
  7. approval, when a room hosts several people with roles.
  8. submitted_by, when actors are authenticated; then delegated sub-identities.
  9. set_by, data classes, audiences; read guards.
  10. Deterministic activation, seek_guidance without a model, the first-write rule.
  11. Idempotency per action, unique, reversible, budget, inboxes ($action.key is built; reversible is declared and makes the MCP submit destructive; the approval screen and the submit order follow).
  12. Automations, with their profile and loop checking; the agent section and its portals (the agent section and agent.portals.web.widget: {origins, theme} are built; Teams, Slack, WhatsApp next to the widget and the PWA follow).
  13. Station versions, staging, contract tests, replay; the agent's evals.
  14. The ranking model in shadow, then in SUGGEST, then in ENFORCE, station by station.

Where these decisions come from​

  • ADR-028 §6 — a playbook as a set of tasks and four relations.
  • ADR-029 — everything is a station; the action; the selection as an order of actions; sub-playbooks; who submits.
  • ADR-030 — the decision point, the planner, the ranking model; profiles; seek_guidance; provenance, data classes, time, execution, the Human In The Loop, automations.
Not all names are settled

Some keys in this guide are proposals the ADRs have not yet settled: the station block, global.timezone, auth.on_behalf_of, station.delegation, agent.portals and identity.auth, the syntax of tests and evals. They may change at implementation; the meaning will not.