Skip to content

feat(application): an app can declare the FLOWS and AGENTS it is made of - #205

Merged
rubenvdlinde merged 2 commits into
developmentfrom
feat/app-binds-flows-and-agents
Aug 15, 2026
Merged

feat(application): an app can declare the FLOWS and AGENTS it is made of#205
rubenvdlinde merged 2 commits into
developmentfrom
feat/app-binds-flows-and-agents

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

feat(application): an app can declare the FLOWS and AGENTS it is made of

Exporting an OpenBuild app leaves behind everything that makes it do
anything, and the reason is upstream of the exporter: the application
schema has nowhere to say what those things are.

It declared fifteen properties, two of which bind the app to other objects —
dataRegisters and connectors. Neither is a flow and neither is an agent.
Measured on the dev instance: hydra-console binds one register with three
schemas, while the instance holds 86 Flow entities and 14 agent
definitions, none of them reachable from the application. Adding bundling
code first would have had nothing to read.

This adds flows and agents as optional slug-addressed bindings, shaped
exactly like dataRegisters so the builder pickers, the export payload and
the reviewer all recognise them. Slug rather than id because ids are
per-instance: an exported app carrying one resolves to a different object,
or to nothing, wherever it lands — which is the failure this whole line of
work exists to prevent.

🔑 THERE IS EXACTLY ONE FLOW SYSTEM (ADR-065). The binding is flows, it
points at the OpenRegister Flow ENTITY, and there is no such thing as a
"hermiq flow". A flow that calls an agent is an ordinary OpenRegister flow
whose nodes are agentic node types hermiq contributes to the engine's
registry. A second binding for hermiq would rebuild exactly the ambiguity
ADR-065 was written to end — it exists because "flow" meant five things
across the fleet and that overlap "already produced one dead feature and one
silent data-loss bug".

⚠️ The property description names the ENTITY on purpose. An agentflow
OBJECT store in the hermiq register mirrors some definitions and drifts: a
definition written to the object left the engine executing the previous
graph, run log showing the old node set, no error anywhere. Consumers must
resolve the entity, and now the schema says so rather than leaving each one
to get it right alone.

kind: config — one register.d fragment, no PHP, no route, no listener.
Nothing reads these bindings yet; the follower spec
(openbuild-exports-flows-and-agents, kind: code) adds the bundler, the
install/update-time seeding and the e2e coverage. Split per ADR-032, which
rejects mixed after two such specs burned a full builder budget without
producing a PR.

Verified against a live instance rather than the file: the schema reads back
17 properties with both bindings present, Application.required unchanged at
["slug","name"], dataRegisters and connectors untouched. Constraints were
each observed refusing AND a paired control accepting — an unknown key beside
a valid one is refused by additionalProperties, an uppercase slug by the
pattern, a numeric id by the type, while no-bindings and a dangling slug are
both accepted. The first unknown-key test omitted flow and was refused for
a missing required property, proving nothing; it is recorded and re-run.

One acceptance criterion could not be verified as written and says so in
tasks.md: re-saving any of the 30 existing applications is refused by
unrelated OpenBuild rules (appType immutability, hybrid slug read-only), so
"existing objects stay valid" rests on both properties being optional plus an
observed save with no bindings, not on a direct observation of those 30.

Instance left as found: 30 applications, no probe leftovers, no back-filling.

Exporting an OpenBuild app leaves behind everything that makes it do
anything, and the reason is upstream of the exporter: the `application`
schema has nowhere to say what those things are.

It declared fifteen properties, two of which bind the app to other objects —
`dataRegisters` and `connectors`. Neither is a flow and neither is an agent.
Measured on the dev instance: `hydra-console` binds one register with three
schemas, while the instance holds 86 `Flow` entities and 14 agent
definitions, none of them reachable from the application. Adding bundling
code first would have had nothing to read.

This adds `flows` and `agents` as optional slug-addressed bindings, shaped
exactly like `dataRegisters` so the builder pickers, the export payload and
the reviewer all recognise them. Slug rather than id because ids are
per-instance: an exported app carrying one resolves to a different object,
or to nothing, wherever it lands — which is the failure this whole line of
work exists to prevent.

🔑 THERE IS EXACTLY ONE FLOW SYSTEM (ADR-065). The binding is `flows`, it
points at the OpenRegister `Flow` ENTITY, and there is no such thing as a
"hermiq flow". A flow that calls an agent is an ordinary OpenRegister flow
whose nodes are agentic node types hermiq contributes to the engine's
registry. A second binding for hermiq would rebuild exactly the ambiguity
ADR-065 was written to end — it exists because "flow" meant five things
across the fleet and that overlap "already produced one dead feature and one
silent data-loss bug".

⚠️ The property description names the ENTITY on purpose. An `agentflow`
OBJECT store in the hermiq register mirrors some definitions and drifts: a
definition written to the object left the engine executing the previous
graph, run log showing the old node set, no error anywhere. Consumers must
resolve the entity, and now the schema says so rather than leaving each one
to get it right alone.

kind: config — one register.d fragment, no PHP, no route, no listener.
Nothing reads these bindings yet; the follower spec
(openbuild-exports-flows-and-agents, kind: code) adds the bundler, the
install/update-time seeding and the e2e coverage. Split per ADR-032, which
rejects `mixed` after two such specs burned a full builder budget without
producing a PR.

Verified against a live instance rather than the file: the schema reads back
17 properties with both bindings present, `Application.required` unchanged at
["slug","name"], `dataRegisters` and `connectors` untouched. Constraints were
each observed refusing AND a paired control accepting — an unknown key beside
a valid one is refused by `additionalProperties`, an uppercase slug by the
pattern, a numeric id by the type, while no-bindings and a dangling slug are
both accepted. The first unknown-key test omitted `flow` and was refused for
a missing required property, proving nothing; it is recorded and re-run.

One acceptance criterion could not be verified as written and says so in
tasks.md: re-saving any of the 30 existing applications is refused by
unrelated OpenBuild rules (appType immutability, hybrid slug read-only), so
"existing objects stay valid" rests on both properties being optional plus an
observed save with no bindings, not on a direct observation of those 30.

Instance left as found: 30 applications, no probe leftovers, no back-filling.
…irely

Implementing the follower's bundler falsified two things this spec asserted,
and both would have shipped into a merged schema before anyone noticed.

1. THE `Flow` ENTITY HAS NO SLUG. It carries uuid, name, app, enabled,
   trigger, nodes, edges — and `FlowMapper` offers `findByUuid()` with no
   slug lookup beside it. The binding said slug purely by analogy with
   `dataRegisters`, and the analogy does not hold. Now UUID-addressed.

   The reasoning that produced "slug" was really an argument against the
   numeric `id` — an auto-increment column, per-instance, resolving to a
   different flow or to nothing after an export — and that argument is
   exactly why a UUID works. Conditional on one thing, now an explicit
   requirement on the follower: the importing side must seed the flow with
   the SAME UUID rather than minting a new one, or every binding in the
   imported app points at nothing while every file looks correct.

2. THERE MUST BE NO `agents` BINDING. The `agent` schema already carries
   `applicationSlug`, and `AgentsController` already resolves an
   application's agents through it. An `agents` array on the application
   would be a second edge for the same relationship, pointing the other way
   — two facts that can disagree with nothing to arbitrate between them.
   The exporter queries agents by `applicationSlug`; the schema gains
   nothing. Same instinct ADR-065 applies to flows, one domain down.

Re-verified against the live instance after the correction, not against the
file: the schema reads back 16 properties (was 15), `flows` present with the
UUID pattern, `agents` ABSENT — checked as absent rather than merely never
added. Constraints observed accepting AND refusing:

  real sequencer UUID 6b14a1fd-…            ACCEPTED, round-tripped
  the SLUG this spec first specified        REFUSED — pattern
  numeric id 5020                           REFUSED — type
  valid UUID + an unknown key               REFUSED — additionalProperties
  no `flows` key at all                     ACCEPTED
  dangling but well-formed UUID             ACCEPTED

The slug case stays as a permanent regression guard: it is what this spec
originally asked for, and a schema accepting it would resolve to nothing at
export time.

Seed Data now carries real UUIDs read off the instance, so the follower's
fixtures resolve rather than merely parse — including the sequencer, whose
76-node graph contains `hermiq.workload-step` and therefore exercises "an
agentic flow binds like any other" instead of only the happy path.

Follower artefacts updated to inherit both corrections.
Instance left as found: 30 applications, no probe leftovers.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openbuild @ cf5a5f7

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
test-l10n
check-gitignore
check-nc-floor
format
composer ✅ 106/106
npm ✅ 638/638
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-15 07:14 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openbuild @ 7c85037

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
test-l10n
check-gitignore
check-nc-floor
format
composer ✅ 106/106
npm ✅ 638/638
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-15 07:50 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit 76b513e into development Aug 15, 2026
74 of 80 checks passed
rubenvdlinde pushed a commit that referenced this pull request Aug 15, 2026
# Conflicts:
#	openspec/changes/openbuild-exports-flows-and-agents/tasks.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants