Search by

chatflowphp / dialogue

webnarmin

Dialogue kernel for PHP bots: declared lenses and goals, memory derived from an event journal, prompts compiled under a byte budget, hosted on chatflowphp/core.

Package info

github.com/chatflowphp/dialogue

pkg:composer/chatflowphp/dialogue

Statistics

Installs: 8

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.3.0 2026-09-13 20:49 UTC

This package is auto-updated.

Last update: 2026-09-13 20:50:24 UTC


README

Real-time prompt and context management for PHP bots. Every turn the kernel derives the conversation's memory and position from a journal, chooses what the model should see, compiles the window under a budget, asks the model once (with tools), extracts the facts and records the turn. Nothing is stored as state: it is derived again from the journal each time, which is what makes it measurable, replayable and cheap where it can be.

You declare what must become known (goals) and what the model sees while getting there (lenses) in one folder — the declaration, the knowledge, the skills, the tools, the reference dialogues — and host it on chatflowphp/core, so Telegram and friends come from the core adapters. The library holds no provider client, no agent loop and no state machine: the model is behind one interface, the tools behind another, and both are plain data at the seam.

Ten minutes

composer require chatflowphp/dialogue
mkdir -p booking/knowledge booking/evals

booking/process.yaml:

process: booking
slots:
  name:    {type: string, scope: subject, ask: "How should we call you?"}
  phone:   {type: phone, scope: subject, ask: "Which number can we text?"}
  service: {type: enum, scope: run, options: {haircut: "Haircut", beard: "Beard trim"}}
  slot:    {type: string, scope: run, ask: "When would you like to come in?"}
goals:
  - {id: service_chosen, done_when: confirmed(service), lens: welcome}
  - {id: time_chosen, done_when: confirmed(slot), requires: [service_chosen], lens: time}
  - {id: contact_known, done_when: "confirmed(name, phone)", lens: contact}
lenses:
  - id: welcome                     # scripted: no model call
    kind: route
    reply: "Hi! What are you coming for?"
    choices:
      - {label: "Haircut", set: {service: haircut}}
      - {label: "Beard trim", set: {service: beard}}
  - id: time                        # guided: the model asks for what is still open
    kind: collect
    applies: "date, time, when to come"
    goal: "Agree on a date and time."
  - id: contact
    kind: collect
    applies: "name, phone number"
    goal: "Get a name and a phone number for the confirmation."
  - id: faq                         # a detour that returns by itself
    kind: consult
    applies: "address, parking, hours, prices"
    knowledge: {retrieve: all, lookup: all}
farewell: "Done: {service} on {slot} for {name}. See you!"

Put what the bot should know into booking/knowledge/*.md (headings are addresses), check the folder, then talk to it:

vendor/bin/dialogue lint booking          # the declaration against its knowledge; what each lens costs
vendor/bin/dialogue evals booking         # the reference dialogues in booking/evals, scripted
$environment = Environment::load('booking');
$kernel = new Kernel($environment->process, $environment->corpus, $model, $tools);   // your ModelInterface, your ToolRegistry

Dialogue::install($application, $kernel, $streams);          // a chatflowphp/core Application
$application->onCommand('start', fn(Context $ctx) => $ctx->enter(DialogueScene::ID));
$model = new ScriptedModel();
$bot = new DialogueTester(new Kernel($environment->process, $environment->corpus, $model, $tools));
$bot->start()->assertReplied('Hi! What are you coming for?')->assertModelCalls(0);

$model->reply('Tuesday 15:00 or Friday 18:00 are free. Which suits you?');
$bot->press('Haircut')->assertGoal('time_chosen')->assertLens('time');

$model->extract(['slots' => ['slot' => ['value' => 'Friday 18:00', 'confirmed' => true]]]);
$bot->say('Friday at six')->assertGoal('contact_known');

The complete example — an environment folder with tools, a corpus, a skill, a console chat and its eval tests — is examples/Barbershop; the walk-through is getting started.

Where it sits

chatflowphp/dialogue an agent framework (Symfony AI Agent, LangGraph) a flow builder (typebot)
where the state is nowhere: derived from the journal every turn in the graph/agent runtime, per node or thread in the flow engine, a pointer to the current block
who holds the loop the host, one turn at a time; the kernel yields steps (a model request, a round of tools) and folds the answers the agent, until it decides to stop the engine, block by block
what moves the conversation goals reached (done_when over memory); a goal that stops holding reopens by itself the model's choices edges drawn by the author
what the model sees a compiled window: task, knowledge slice, retrieved sections, what is known and open, hints, the transcript tail — under a budget, in two tiers for the cache whatever the loop accumulates a prompt per block
what is measurable the turn record: ladder of determinism, cost per lens, cache share, lookup rate, slots by source and corrections, drop-off; evals, replay, shadow traces completion funnels
the model and the tools interfaces with data at the seam; any provider, any MCP or PHP tool, wired by the application the framework's own the builder's own

What you get

  • Goals, not steps. done_when predicates over memory decide where the conversation is. Order, requires, applies_if, review and strict shape the funnel; nothing else moves it.
  • Lenses, not prompts. Each lens is a context: task, knowledge slice, slots, tools, buttons, voice. scripted lenses never call the model; guided ones collect; free ones consult.
  • Memory with provenance. Every value knows who set it, when, and what it replaced. Buttons and tools confirm for free; the model's extractions confirm by rule.
  • A journal as the truth. Events on a core stream; memory, position and the chosen lens are folded from it. Replay old dialogues under a new declaration, run in shadow next to a live bot.
  • Prompts as structure. Baseline, task, knowledge, what is known, what is open, where the conversation is, hints — packed under a byte budget with the transcript tail last.
  • Tools that fit the tick. Read tools run during generation; write tools run after commit, at least once, with an idempotency key. bind hides parameters from the model, save reads results back into slots.
  • Cheap where it can be. Buttons, typed button labels, scripted replies, expression flags and silence rules cost nothing; a model is asked only for what needs one, and the window's stable tier hits the provider's cache.
  • Replies checked before they are sent. Guards — a pattern the reply must or must not match, a length — are declared next to the lens; a failing reply is asked for again with what was wrong, what still fails is sent or replaced by the fallback, and every hit is in the turn record and the analytics.
  • One folder per process. The declaration, knowledge/, skills/ in the Claude Code format, tools.yaml, .mcp.json, evals/; other folders join through uses:.
  • The model measured, not trusted. Reference dialogues run scripted for free and live on your model; live, a judge model rates every reply against criteria you declare next to the evals — one question, no jargon, leads to the body — binary, with a reason, per criterion and per run, so a change of prompt or of model is compared, not guessed. A simulated client plays the user from a case description where a script would run out; the judge is calibrated against replies a person rated.
  • A console. dialogue lint, evals (scripted or live, judged and saved, or only what a change affects), judge (a saved run judged again, no actor call; recorded journals judged run by run; calibration against a person's ratings), compare (two runs of one eval, reply by reply), replay, analytics (the ladder, the cost, and how runs end), journal, case (a regression from a real conversation, without its text), coverage (what no run has touched of the declaration), digest (the week on one page: health, invariants, the judge already written, the runs to open — no model called, no one's text carried), prune (the words out of old journals, the numbers kept), explain (one turn read back: position, ranking, assessment, the window block by block and its proportion, the reply), findings (the register of what went wrong in real conversations, cross-checked against the suite).

Installation

composer require chatflowphp/dialogue

Requires PHP 8.2+, chatflowphp/core 2.x, symfony/yaml, psr/clock and ext-mbstring; nothing else. You bring the model: implement ModelInterface for your provider (the example carries a reference client to copy for any OpenAI-compatible chat/completions endpoint) — or wrap a provider library such as Symfony AI Platform in fifty lines. Tools are ToolInterface objects the application registers, from PHP classes or an MCP client. vendor/bin/dialogue is the console.

Documentation

  • Getting started — a folder, a declaration, a model, a host, in ten minutes
  • Playbook — the order of work and the design rules learned on a reference project, with the numbers
  • Architecture — what the library is and is not, the entities, the seam, the turn as a fold
  • The environment folder — knowledge, skills, tools, MCP servers, evals, uses:
  • Declaration — the schema: process, lenses, goals, slots, tools, buttons
  • A turn — assess, fold, position, score, reply; the weights and why
  • Memory and the journal — events, folding, scopes, snapshots
  • Knowledge — corpus addressing, lookup, examples, budgets
  • Tools and models — read and write tools, binding, the model contract
  • Hosting on core — the scene, two-tick turns, timers, buttons
  • TestingDialogueTester, ScriptedModel, host tests
  • Texts — the kinds of text in a declaration: source, proof, the checklist for a change
  • Evals — reference dialogues as YAML, run scripted in the suite and live by hand
  • Replay — old journals through a new declaration, differences turn by turn
  • Analytics — the journal reduced to numbers: ladder, cost, slots, routing, drop-off, how runs end

Contributing

Run composer check before a pull request; see CONTRIBUTING.md for the rules that keep the kernel pure. Releases are listed in CHANGELOG.md.

License

MIT. See LICENSE.