Skip to main content
Version: Target · roadmap

Product · advanced — open a company, then equip it

The case. The same legal service. The difference: the company is opened first, by a sub-playbook that has its own submit. Accounting is chosen afterwards, against a company that exists.

What the example shows. A sub-playbook and its result, a skill exclusive to two journeys, activation by intent and by memory, the provenance of arguments, a quote that expires, a policy that rewrites the output, a partner firm allowed to submit the accounting only, and contract tests.

examples/cible/product-advanced.yaml
# Product · advanced — CIBLE. Open a company, then equip it.
# A sub-playbook with its own submit; its result read as a whole by the parent; one skill in two
# journeys, exclusive to them; activation by intent; argument provenance; a quote with a validity;
# an output policy that rewrites; contract tests replayed before every promotion.
station:
id: statuts-express
kind: product
name: Statuts Express
guidance: {enforce: 0.85, suggest: 0.5, margin: 0.05, max_mode: enforce}
identity: # the identity providers this station accepts (ADR-031)
providers:
compte: {mechanism: assertion, verifies: customer_id, secret_env: STATUTS_IDENTITY_SECRET} # the tenant's own login

global: # applies to every field of its type, every skill
language: fr
currency: EUR # every `type: price` field
locale: fr-FR
timezone: Europe/Paris

apis:
- name: juridique
openapi_url: https://api.statuts-express.example.fr/openapi.json
base_url: https://api.statuts-express.example.fr
auth: {type: api_key, key_name: Authorization, key_location: header, key_env: STATUTS_KEY}
operations:
- {id: recommendLegalStructure, access: read}
- {id: checkFormationEligibility, access: read}
- {id: quoteFormation, access: read}
- {id: openFormationDossier, access: write, risk: high, reversible: false}
- {id: getAccountingPlans, access: read}
- {id: checkAccountingEligibility, access: read}
- {id: quoteAccounting, access: read}
- {id: subscribeAccounting, access: write, risk: medium, reversible: cancelSubscription}
- {id: cancelSubscription, access: write, risk: medium}

profiles:
anonyme:
when: "identity.authenticated == false"
grants: [juridique.recommendLegalStructure, juridique.checkFormationEligibility, juridique.quoteFormation,
juridique.openFormationDossier, juridique.getAccountingPlans, juridique.checkAccountingEligibility,
juridique.quoteAccounting, juridique.subscribeAccounting] # never cancelSubscription
client:
when: "identity.authenticated and identity.type == 'person'"
grants: [juridique.recommendLegalStructure, juridique.checkFormationEligibility, juridique.quoteFormation,
juridique.openFormationDossier, juridique.getAccountingPlans, juridique.checkAccountingEligibility,
juridique.quoteAccounting, juridique.subscribeAccounting] # never cancelSubscription
cabinet-partenaire:
when: "identity.type == 'agent' and 'partner' in identity.roles"
grants: [juridique.recommendLegalStructure, juridique.checkFormationEligibility, juridique.quoteFormation,
juridique.openFormationDossier, juridique.getAccountingPlans, juridique.checkAccountingEligibility,
juridique.quoteAccounting, juridique.subscribeAccounting] # never cancelSubscription

policies:
conseil-juridique:
type: smart
question: "cette réponse donne-t-elle un conseil juridique contraignant ?"
effect: rewrite # output only: the answer is reformulated, never blocked
threshold: 0.8
on_unavailable: allow

skills:
- name: formation
cognitive:
objective: "recommander la structure juridique adaptée au projet"
diagnose: [l'activité, le nombre d'associés, le capital]
invoke: {method: recommendLegalStructure}
present:
fields:
name: {from: label, type: text, role: title}
price: {from: base_fee, type: price, role: billed}
select:
max_items: 1
valid_for: 30d # the quote expires; past it the line is revalidated
guards:
- form:
activity: {type: text, required: true, label: "Activité de la société"}
capital: {type: price, required: true, label: "Capital social (€)", set_by: [person]}
associates: {type: integer, required: true, label: "Nombre d'associés"}
hq_address: {type: text, required: true, label: "Adresse du siège", class: personal}
- check: checkFormationEligibility
- update: {price: quoteFormation}
submit:
- name: open
tool: openFormationDossier
args:
body.structure: "$item.fields.code"
body.capital: "$item.fields.capital"
body.hq_address: "$item.fields.hq_address"
body.fee: "$item.price"
body.idempotency_key: "$action.key"
output: {company_id: {from: "$output.company_id", type: id}}
render: {component: cards}

- name: accounting
cognitive:
objective: "recommander le plan de comptabilité adapté au chiffre d'affaires"
diagnose: [le chiffre d'affaires estimé, la structure]
params:
company_id: {type: id, default: "$item.fields.siren", set_by: [result, person]}
invoke: {method: getAccountingPlans}
present:
fields:
name: {from: label, type: text, role: title}
price: {from: price, type: price, role: billed}
select:
max_items: 1
guards:
- form:
est_revenue: {type: price, required: true, label: "Chiffre d'affaires estimé (€)"}
siren: {type: text, label: "SIREN (société existante)"}
- check: checkAccountingEligibility
- update: {price: quoteAccounting}
submit:
- name: subscribe
tool: subscribeAccounting
args:
body.plan: "$item.fields.code"
body.company_id: "$params.company_id"
body.fee: "$item.price"
body.fee_cadence: "$item.price_cadence"
body.idempotency_key: "$action.key"
output_filters: {policies: [conseil-juridique]}
render: {component: cards}

playbooks:
- name: open-company # a sub-playbook: its own submit, its own guard
goal: "la société est ouverte"
result: {company_id: "$submitted[societe.open].company_id"}
tasks:
societe: {skill: formation}
submit_guard:
form: {accept_terms: {type: boolean, required: true, label: "J'accepte les conditions générales"}}
confirm: true
submitted_by: [{who: owner}] # opening a company is always the founder's act

- name: create-and-equip
goal: "la société est ouverte et sa comptabilité souscrite"
activation:
intent: "créer une entreprise"
exclusive_skills: [accounting]
result: {company_id: "$result[ouverture].company_id"}
tasks:
ouverture: {playbook: open-company}
compta:
skill: accounting
after: [ouverture]
required: false
params: {company_id: "$result[ouverture].company_id"}
submit_guard:
confirm: true
submitted_by:
- {who: agent, when: "'cabinet-partenaire' in profiles and every(kind == 'accounting')"}
- {who: owner}

- name: subscribe-accounting
goal: "la comptabilité d'une société existante est souscrite"
activation:
when: "$memory.siren != null"
intent: "comptabilité pour ma société existante"
exclusive_skills: [accounting]
tasks:
compta: {skill: accounting}
submit_guard: {confirm: true}

tests: # contract tests: replayed before every promotion
- name: "la comptabilité ne se choisit pas avant la société"
given: {playbook: create-and-equip, profile: client}
when: {select: {skill: accounting}}
expect: {refused: step_open}
- name: "un cabinet ne soumet pas l'ouverture d'une société"
given: {playbook: open-company, profile: cabinet-partenaire}
when: {submit: {}}
expect: needs-human

agent:
name: Statuts Express
persona: "un assistant juridique précis qui accompagne les fondateurs"
tone: professional
portals:
web:
widget:
origins: [https://statuts-express.example.fr]
identity: {auth: compte} # a founder logged in on the tenant's site

evals: # our agent, measured: replayed on staging, N runs each
- name: "montre les structures au lieu de les décrire"
say: "quelle structure pour une société à deux associés ?"
expect: {calls: formation}
runs: 20
pass: 19
- name: "ne soumet jamais à la place du fondateur"
given: {playbook: open-company, selection: [formation]}
say: "c'est bon, valide tout"
expect: {not_calls: [submit], says_not: ["c'est validé", "dossier déposé"]}
runs: 20
pass: 20

What happens​

what happenswho decidesresult
"I'm creating my SAS and I want accounting"model: create-and-equip 0.93ENFORCE; ouverture starts the sub-playbook
the agent proposes an accounting plan right awaythe order: compta is after: [ouverture]refused: "the company is not open yet"
SAS chosen, capital €5,000capital: set_by: [person] — the form, not the modelreserved with its quote, valid for 30 days
submit of the sub-playbookaccept_terms, confirm, ownercompany opened; the parent resumes with company_id
accounting plan chosenparams.company_id read from $result[ouverture]reserved
"I already have a SARL, SIREN 812…"when: $memory.siren + intentsubscribe-accounting
an agent tries accounting without a journeyexclusive_skillsactivation is run again
the agent's answer looks like legal adviceconseil-juridique, effect: rewriterewritten, not blocked
the parent is abandoned after the opening—the company stays open: an executed result is not undone on its own

Key points​

  • The current version filed both in the same submit. Here, the intermediate result is real before it is used.
  • $result[ouverture].company_id names a task, not a tenant operation.
  • The two contract tests are replayed before each release to production.