Replies: 6 comments
-
On the agent-memory open question: it's an authoring concern, not a platform featureOne of the strongest concerns surfaced in this proposal is that moving away from static-multi-entity flows loses the accidental shared agent context that current-shape flows provide — and that "agent context scope" would have to become a first-class platform feature to compensate. I want to argue the opposite: the agent-memory case dissolves into an authoring pattern that uses existing primitives. No new platform feature needed. The pattern: static singleton coordinator + template-per-processFor each current static-multi-entity flow, split into two flows:
They communicate via events. Per-process instances stay bounded. Singleton holds the cross-cutting knowledge explicitly. Worked example: holding-devopsToday: Proposed split: Communication:
Why this works
Generalizes across EmpireMost current static-multi-entity flows have a natural split. Coordinator is optional — many flows don't need one (only ship a coordinator when there's genuine shared state).
The platform doesn't need to know about agent memory. Consumers decide per-flow whether shared state warrants a coordinator. The choice of where the agent lives IS the choice of its memory scope — no separate scoping primitive required. Why this is materially cleaner than a platform "agent context scope" primitiveA platform-level
The singleton-coordinator pattern avoids all of this. Memory becomes data (the coordinator's entity state). Authoring discipline replaces platform machinery. The one subtle area worth namingThe pattern is clean when shared state is information (capacity counts, learned patterns, aggregate spend). It's less clean when shared state is tacit reasoning (an agent that has reviewed 100 cases has implicit understanding hard to crystallize as entity fields). For the tacit case, the singleton coordinator's long-running agent session covers it naturally — within the singleton's flow instance lifetime, the agent has continuous session context. Across restarts or across coordinator boundaries, tacit knowledge has to be crystallized as entity state (rules, templates, patterns written explicitly). This is fine for most Empire cases. The "agent learns implicit patterns across thousands of cases" case probably ends up needing external memory primitives anyway (vector DB, fine-tuning, etc.) — but that's true today and isn't a regression. Implication for this proposalThe "agent context scope as parallel platform workstream" concern dissolves. The proposal becomes:
No new platform features for agent memory. Authors decide per-flow whether they need a coordinator. The platform stays small. This makes the broader proposal materially more shippable — the platform investment shrinks to "make flow instances cheap and first-class" (still real; still needs the load-test validation), without committing to design and ship a new agent-context primitive in parallel. Pilot implicationIf The companion pattern probably deserves a paragraph in the OPCO draft's authoring guidance section so consumers know "if you find yourself wanting cross-instance learning, the answer is a singleton coordinator, not a multi-entity static flow." |
Beta Was this translation helpful? Give feedback.
-
Systematic study of downsides — singleton coordinator + template-per-process patternFollowing up on the previous comment with a comprehensive inventory of the costs this pattern carries, organized by category. The win is still real, but the costs are real too and should be understood before migration starts. Each entry: brief description, severity (Low / Medium / High), and whether the cost is intrinsic (structural to the pattern) or mitigatable (addressable through tooling, convention, or design). Category A: Contract / authoring surface costsA1. Flow count growth (~70% in Empire). Medium. Intrinsic. Empire goes from 10 flows to ~16-17 (10 templates + ~6-7 coordinators). Each flow has its own contract files (schema, nodes, events, agents, prompts). More files to maintain, navigate, version. Per-consumer cost is unchanged (don't ship a coordinator if you don't need one), but total contract surface grows. A2. Cross-flow event surface expands. Medium. Intrinsic. What were internal events between handlers in one static flow become typed cross-flow events between coordinator and per-process instances. Each crossing needs pin declarations, payload schemas, verifier checks, conformance tests. The event topology becomes denser and the analyzer has more to verify at boot. A3. Coordinator agent prompts need rewrites. Medium. Mitigatable. Today's holding-devops-agent prompt assumes it sees all entities. After the split, the coordinator agent reasons over aggregate ContentionState, not individual cases. This is bounded authoring work but real migration effort per coordinator. Worse if the prompt currently bakes in "look at recent entities for context" patterns. A4. Naming and discoverability requires convention. Low. Mitigatable. Authors need to learn: "look for A5. Documentation / teaching burden grows. Medium. Mitigatable. The model is conceptually clean but authoring it requires understanding both halves of the pattern. Onboarding new authors becomes a longer journey: "first you learn flow + entity; then you learn that template flows are per-process and singletons are for shared state; then you learn the coordinator/template pairing pattern." A6. Pattern proliferation risk: false coordinators. Medium. Mitigatable. Once the pattern is canonical, authors may add coordinators where they don't belong — for flows that genuinely don't need shared state, just because the pattern exists. Cargo-culting risk. Mitigation: authoring guidance that says "coordinator is optional; only add when there's real cross-instance work." A7. Test fixture complexity grows. Low. Mitigatable. Testing per-instance behavior used to be "set up one entity in the static flow." Now it's "set up coordinator state + per-instance state + the events between them." More fixture surface, more setup boilerplate per test. Mitigation: shared fixture helpers per coordinator/template pair. Category B: Coordinator state managementB1. Coordinator state grows unbounded without explicit lifecycle. High. Intrinsic. ContentionState, learned patterns, accumulated aggregates, historical decisions — these all accrue on the coordinator entity. Today's multi-entity static flow hides this (old entities just sit there); the new pattern forces explicit decisions:
Every coordinator needs a state-lifecycle design. Cannot be deferred without operational pain. B2. Coordinator becomes a serialization point. Medium. Intrinsic. Many per-instance flows emitting events to the coordinator concurrently means the coordinator's entity has lots of write concurrency. Today's per-entity serialization helps correctness but means the coordinator's throughput is bounded by its single-entity write rate. For high-volume coordinators this matters. B3. Coordinator as "god flow" risk. Medium. Mitigatable. Operators may pack too much into the coordinator over time — every new shared concern accretes there. Coordinator entity grows wide; coordinator agent's prompt grows complex; coordinator becomes a kitchen sink. Discipline needed to keep coordinator scope narrow. Mitigation: review discipline + "split coordinator if it covers >1 concern" guideline. B4. Cold-start problem for new OpCos. Medium. Mitigatable. When a new OpCo first comes online, the coordinator may not have learned anything specific about that OpCo's workload yet. Per-instance flows start with no specific policy guidance. Mitigation: coordinator ships with default policies; learns OpCo-specific patterns over time. But behavior of early OpCo lifecycle is materially different from later OpCo lifecycle (less learned context). Category C: Operational dependenciesC1. Per-instance flows depend on coordinator availability. High. Intrinsic. Today's static-multi-entity flow has no cross-flow runtime dependency — the flow is self-contained. After split, per-instance flows that need coordinator policy events have a runtime dependency on the coordinator being healthy. Coordinator down → policy events stop → instances proceed with stale local mirrors or stall. This is a real new operational coupling. Not catastrophic (Pattern C's local mirrors give graceful degradation) but real. C2. Coordinator availability becomes a first-class SLO. Medium. Intrinsic. Today's flow availability is implicit in the runtime's health. After split, each coordinator becomes a distinct availability surface that ops needs to monitor, alert on, and recover. Number of coordinators × availability targets = real ops surface growth. C3. Bottleneck risk for high-volume cases. High. Mitigatable. For high-volume coordinators (signal-search seeing thousands of signals; treasury-portfolio across many OpCos), the coordinator may become a throughput limit. Per-instance work is naturally parallel; coordinator work is naturally serial-on-its-entity. Mitigation: shard the coordinator (multiple coordinator instances by some partitioning key), or restructure (signals stay as records inside campaign template, not events to a coordinator — which IS the proposal's intended answer for that case). C4. Cross-coordinator coordination is not native. Medium. Intrinsic. If two coordinator domains overlap (e.g., treasury-portfolio AND holding-devops-coordinator both need to know about an OpCo's resource usage), the pattern doesn't natively compose. You need either a third coordinator above them, or events between coordinators (which is more event surface). The pattern is clean for orthogonal concerns but degrades for overlapping ones. Category D: Lifecycle and migrationD1. Migration data: what happens to historical entities? High. Intrinsic. Today's static-multi-entity flows have accumulated entity rows representing real history. The migration doesn't have a clean place to put that history:
No clean answer. Most flows will end up with "legacy entities preserved for read-only inspection; new work flows through the new pattern." D2. Backward compatibility for run history. Medium. Intrinsic. Old D3. Tacit reasoning bounded by session lifetime. Low. Intrinsic. The singleton's long-running agent session covers continuity within the singleton's flow instance lifetime. Across restarts or session resets, tacit knowledge has to be crystallized as state. Fine for most cases. The "agent learns implicit patterns across years of operation" case still needs external memory primitives (vector DB, fine-tuning) — but that's true today too and isn't a regression. D4. Coordinator session lifetime IS its operational lifetime. Medium. Intrinsic. Related to D3 but worth naming: the coordinator's flow instance is a singleton, but flow instances do terminate (intentionally or via cleanup). When a coordinator instance is terminated and recreated, its agent session restarts. Authors who assume "the coordinator always remembers everything" will be surprised. Documentation / lifecycle clarity needed. Category E: Replay, debugging, observabilityE1. Replay across coordinator + instances is more complex. High. Intrinsic.
E2. Operational visibility splits across two flows. Medium. Mitigatable. Today you look at one flow's entity table to see all activity. After: dashboards / debugging / metrics have to cover both coordinator state AND per-instance states. Tooling needs to span both. Mitigation: convention (always pair coordinator + template in dashboards) + tooling support (e.g., "show me everything in the holding-devops domain" surfaces both). E3. Debugging requires understanding the split. Medium. Mitigatable. When something goes wrong in holding-devops-job processing, you may need to look at the coordinator state to understand why (e.g., "the policy event the job is waiting for never arrived because the coordinator entity is in a weird state"). The debugging path is longer and crosses flows. Mitigation: tooling that surfaces the coordinator/template relationship explicitly. E4. Event chain length grows. Low. Intrinsic. Today's "request → process → response" is mostly in-flow. After split, it's "request → emit-to-coordinator → coordinator decides → emit-policy → instance proceeds → emit-result → coordinator updates." More hops, more events to trace through, longer causal chains in run logs. Summary by severityHigh-severity costs (5):
Medium-severity costs (9):
Low-severity costs (3):
Intrinsic vs mitigatableIntrinsic (cannot be eliminated by tooling/convention):
Mitigatable (can be reduced by tooling/convention/design):
About 60% of the costs are intrinsic. The other 40% are addressable with discipline / tooling investment. Implications for the broader studyFor the pilot phase (whichever flow goes first):
NetThe pattern is the right answer. The costs above don't change that conclusion. But they DO mean:
The systematic study before pilot should answer for each prospective coordinator:
If those answers exist for the pilot flow, the pilot is real. If they don't, the pilot is research disguised as engineering. |
Beta Was this translation helpful? Give feedback.
-
My read: adopt the direction, but make the pilot prove the hard partsI agree with the core direction: one flow instance should normally own one primary state entity. I would adopt it as the default authoring model, not as an absolute ban on multi-entity/static shapes. The main reason is semantic ownership. The current static-flow + many entity rows pattern creates a hidden second routing layer: That forces authors and the runtime to keep re-solving identity through That is a better mental model for authors and a better semantic owner for the platform. What we materially win
I agree with the user's intuition here: flow instantiation should be cheap. If it is not cheap enough to model real process instances, the platform is pushing authors toward worse semantic shapes. What we lose or payThe biggest costs are not instance count by itself. The real costs are:
These are serious, but they are better problems than hidden semantic routing. They are explicit architecture/ops problems and can be designed around. Coordinator pattern: good, with a constraintI agree with the follow-up comment that shared agent memory should usually be modeled as a static singleton coordinator plus template-per-process instances, not as a new platform-level But I would add one hard rule:
Tacit LLM experience is useful, but if the system depends on it, it must crystallize into coordinator state: rules, policies, learned thresholds, counters, templates, suppression lists, etc. Otherwise replay, fork, diagnosis, and review become mushy. Platform/authoring acceptance criteria before broad adoptionI would not migrate Empire broadly until the pilot proves these invariants:
Pilot recommendation
This pilot is strong because it exercises both halves: per-request lifecycle plus real cross-instance aggregate policy. Net decisionI would proceed with this as the default design target: Keep lists/maps for contained local data. Promote to child/template flow only when the item needs lifecycle, routing, retries, timers, agent work, or independent audit. Keep multi-entity static flows as an explicit escape hatch for legacy, external ingress, read models, or true singleton aggregate services. Do not keep them as the normal process-instance model. The trade is worth it: we move complexity out of hidden receiver-side entity selection and into explicit process instances, coordinator state, and tooling. That is a better place for the complexity to live. |
Beta Was this translation helpful? Give feedback.
-
Refinement: not all flows want template; some want static singleton + map-as-contained-dataFollowing up on the previous comments. The pressure-test "what actually forces holding-devops to split?" surfaced something important: the proposal's "migrate everything to template" framing over-applies its own principle. For some flows, the right shape isn't template-per-business-identity at all — it's static singleton with a typed map. The patternFor holding-devops specifically: holding-devops (static singleton)
entity: HoldingDevopsState
verticals: map<vertical_id, VerticalContext>
VerticalContext:
active_jobs: [{request_id, status, started_at, ...}, ...]
capacity_allocated: {...}
history: [...]
learned_patterns: [...]
aggregate:
total_capacity, contention, throttle_policy, ...
agent: holding-devops-agent
# session sees the whole entity → all verticals + aggregate
# cross-vertical pattern detection naturally preservedEvent arrives carrying Why this is consistent with the proposal's own ladderThe proposal explicitly says:
For per-vertical context in holding-devops:
By the proposal's own rule, per-vertical context is contained data. Map field is correct. Promoting each vertical to a flow instance would be over-promotion — it goes past the line the proposal itself draws. The unit-of-routing observationThe right addressing unit isn't necessarily per-request or per-vertical or per-anything-specific. It's whatever has its own state machine and lifecycle requiring platform machinery. For holding-devops:
Simplest mapping that works:
When map-as-data is right vs when template-per-instance is rightThe map-as-contained-data pattern works when:
Template-per-instance is right when:
Revised migration table for EmpireApplying this discrimination to the proposal's original table:
About 5-6 flows naturally want template (where per-key state machines need platform machinery). About 3-4 want singleton + map (where per-key state is contained data + shared agent context is valuable). One or two are ambiguous and depend on the specific state shape. This is a materially different migration than "migrate all 10 flows to template." It's:
Refined design principleThe principle from the original proposal should be stated more precisely:
This preserves the "one entity per flow instance" rule (which is right) while making the map-vs-template choice the design decision, with clear criteria for when each fits. One load-bearing technical questionFor map-as-contained-data to work, Swarm's CEL needs to support indexed map access with a variable key: # Reading in guards:
guard:
check: entity.verticals[payload.vertical_id].capacity_allocated.cpu < policy.max_cpu_per_vertical
# Writing nested values:
data_accumulation:
writes:
- target_path: entity.verticals[payload.vertical_id].active_jobs
operation: append
value: {request_id: event.id, status: 'requested', started_at: now()}We hit verifier limits before with list helpers — Swarm #1031 rejected If the verifier doesn't fully support map indexing with variable keys, that becomes the load-bearing platform ask for this pattern — and it's a much smaller and more surgical ask than "make flow instances cheap and first-class for thousands of instances per OpCo." Worth a quick spike against current platform-spec / verifier before this pattern can be confirmed as viable. Net implication for the proposalThe original "one primary entity per flow instance" principle stands. The migration framing needs refinement:
This makes the proposal materially more shippable: smaller migration scope, preserves valuable cross-key agent context where it exists, smaller platform investment (map indexing in verifier is a much smaller change than "cheap flow instances at scale"), and a cleaner mental model for authors ("does per-key need a state machine? template. Otherwise: map value"). |
Beta Was this translation helpful? Give feedback.
-
Authoring impact: simpler mental model, but only if the platform is opinionatedI want to call out the authoring/DX angle explicitly, because it is probably the strongest reason to pursue this direction. Overall, this model should make authoring easier and more natural. The current model asks authors to juggle too many overlapping mechanisms: That is hard to teach because the author has to decide whether identity belongs to the producer, the receiver, the payload, the selected entity, or the query guard. A lot of our recent routing issues came from that ambiguity. The proposed model gives a cleaner ladder: That is closer to how people naturally think about workflows: cases, jobs, campaigns, products, budgets, requests. The simple authoring rubricI would teach authors this decision tree:
This is much easier than asking authors to choose among Flow composition becomes easier tooThis also aligns well with the flow-composition work. For process-shaped receivers, composition should be: For contained data, composition should stop at the owning flow instance: So we should keep two concepts distinct:
If a map key starts behaving like a routable recipient, that is the promotion signal: it should become a flow instance. Where authoring can get harderThe risk is not the principle. The risk is exposing too much design choice without good primitives. Authoring gets harder if:
So the platform should be opinionated. It should make the happy path obvious and make escape hatches feel advanced. What the platform should provide to keep this easyMinimum DX surface I would want before broad migration:
Net authoring verdictYes, this makes authoring more natural if implemented as an opinionated model: It is a clearer mental model than today's static-multi-entity shape. The biggest DX danger is over-modeling. We should not make authors constantly choose among three equally valid patterns. The platform should guide them toward the right one with syntax, verifier feedback, and scaffolds. My bias: make flow instances cheap, make |
Beta Was this translation helpful? Give feedback.
-
Decision: lock the authoring modelLet's lock the model around this principle:
Everything else follows from deciding whether state is local contained data, an independently routable lifecycle, or shared coordination state. Locked mental modelSolidity analogy: Authoring decision rubricAuthors should not choose among many overlapping routing/entity primitives. They should answer these questions:
This keeps the happy path small: and keeps promotion explicit: Composition modelCross-flow delivery routes between flow instances, not arbitrary entity rows inside static flows. Template-to-template happy path: # producer
pins:
outputs:
spend_requested:
event: opco.spend_requested
key: vertical_id
# receiver
instance:
by: vertical_id
pins:
inputs:
spend_requested:
event: opco.spend_requested
# parent
connect:
- from: operating.spend_requested
to: treasury.spend_requestedAnalyzer expansion: If names differ, the parent declares the adapter: connect:
- from: validation.spec_repo_requested
to: spec-repo.repo_requested
using:
instance:
source: vertical_id
target: scope_idThe old composition/bind work still matters, but its public target should be revised: Delivery vs contained-state updateKeep these separate: For process-shaped receivers: For contained data: Do not use What stays as escape hatchThese should remain possible, but not be the normal authoring path:
The analyzer should warn or reject normal in-topology use of Immediate platform surface to build towardHigh-ROI primitives: mode: template
instance:
by: vertical_id
on_missing: create | reject
on_conflict: reject | reusePrimary entity inference: Output pin identity: pins:
outputs:
deploy_completed:
event: deploy.completed
key: vertical_id
carries:
- vertical_id
- component_id
- request_idParent composition: connect:
- from: holding-devops.deploy_completed
to: operating.deploy_completedTyped contained-state operations: data_accumulation:
writes:
- target: entity.verticals[payload.vertical_id].active_jobs
op: append
value:
request_id: event.id
status: requestedWe should avoid making authors hand-write arbitrary CEL/path hacks for maps/lists. If singleton+map is part of the model, typed map operations need to be real contract surface and verifier-owned. Empire migration framingDo not migrate every static flow blindly to template. Use this classification:
The migration target is not a slogan. It depends on whether the keyed item needs platform machinery. Analyzer obligationsThe analyzer should own the boring and dangerous parts:
First pilotsDo two small pilots before broad Empire migration:
This pair proves both sides of the model. A single migration cannot validate the whole design. Locked principleFinal wording I would use in the spec/design docs:
This keeps the model composable, analyzer-friendly, and teachable. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Summary
This is a study of whether Swarm should simplify the entity model around this principle:
Said differently:
This is not the same as saying a flow type can only ever have one runtime instance. A template flow can still have many instances:
The simplification is that each of those flow instances has one primary state object. Cross-flow routing targets flow instances, not arbitrary entities hidden inside a static flow.
After auditing Empire, I think this principle holds strongly. The current complexity is not that Empire has many entity types per flow. It does not. The complexity is that many static flows host many entity instances and then every handler has to recover the right entity with
create_entity,select_entity,select_or_create_entity, andquery_entities.The core finding:
Why this matters
The composition model we have been discussing tries to separate concerns cleanly:
But receiver-side entity lookup adds another hidden routing layer:
That is where a lot of the design complexity comes from:
address_keysinput.address.byselect_entityselect_or_create_entitytarget.matchquery_entities(...).count == 1If we instead make the normal model:
then receiver entity selection mostly disappears. The flow instance itself is the addressed unit.
Empire evidence
I inspected the Empire contracts under:
The package has 10 child flows:
Only
operatingis currently a template flow.Every child flow defines exactly one entity type:
operatingProductvalidationvalidation_casescoringverticallifecyclelifecycle_casetreasuryopco_budgetholding-devopsholding_resourcesspec-repoartifact_reporepo-scaffoldrepo_scaffold_artifact_repodiscoveryscan_campaignsignal-searchsignalThis is important: Empire is not proving that flows need many entity types. It is proving that we currently use static flows as ad hoc instance managers.
Current friction pattern
Validation
validationis a static flow with manyvalidation_caseentities.Entry events create a new entity:
Later callbacks must reselect that entity:
This is really saying:
If
validationwere a template flow keyed byvalidation_case_id, the handler would not needselect_entityfor normal in-topology callbacks. The routing layer would deliver to the correct flow instance.Proposed shape:
Then:
The analyzer proves that
spec-repo.repo_readycarriessource_validation_case_idor maps it tovalidation_case_id.Scoring
scoringis a static flow with manyverticalentities.It creates a vertical on
vertical.discovered:Derived verticals also create more entities:
This is naturally:
Proposed shape:
The
vertical.derivedcase may create a child scoring instance, withparent_idas lineage rather than same-flow entity lookup.Treasury
treasurycurrently creates oneopco_budgetper vertical:Then most operational requests reselect it:
This wants to be:
Then operating can wire to treasury directly:
The target is the
treasury[vertical_id]flow instance. No receiver entity lookup needed.Holding DevOps
holding-devopscreates a new entity for each service request:Completions then validate against the current entity and also query uniqueness:
This looks like a job/process:
That is a strong template-flow candidate:
Each infra/CI/CD/DNS/deploy/rollback request becomes a flow instance. Completion events route back to
holding-devops-job[request_id].The output to
operatingthen becomes a normal composition edge:No per-emit target block:
Spec Repo and Repo Scaffold
These are service flows that currently use
select_or_create_entityby repo identity.spec-repo:Callbacks select the repo entity by the same identity:
repo-scaffoldhas the same pattern withrepo_scaffold_scope,repo_scaffold_scope_id, andrepo_scaffold_artifact_type.These likely want template instances keyed by composite repo identity:
The only caution is that these flows wrap runtime-owned artifact repository behavior. If a single physical artifact repo can receive many commits over time, the flow instance should represent the repo, not the individual commit.
Operating already shows the target model
operatingis already a template flow and has one primary entity,Product.It stores contained data as lists inside the primary entity:
This is the right pattern for contained state:
If a component later needs its own lifecycle, agents, retries, timers, or independent routing, it should become a child/template flow instance.
Proposed rule
The platform default should be:
This gives a clean ladder:
It also gives a simpler explanation to authors:
What this simplifies
1. Receiver-side addressing
Current model:
Proposed model:
This eliminates most normal-path
select_entityusage.2. Composition
Current:
Proposed:
The analyzer routes by instance key.
3. Uniqueness guards
Current:
Proposed:
The runtime/analyzer owns uniqueness for flow instances.
4. Authoring language
Instead of teaching authors:
we can teach:
That is much easier.
What should remain as lists/maps
The simplification should not turn every record into a flow instance.
Use lists/maps when the item is contained and does not need independent routing:
Good list candidates:
Use child/template flows when the item needs any of:
That means these should likely be flow instances, not list entries:
The main downside: agent context across entities
The strongest downside is real:
Today:
After the change:
This matters for agents such as:
analysis-agentlearning across many scored verticals;holding-devops-agentseeing repeated deploy failures across components;treasurylearning spending patterns across OpCos;I do not think this is a reason to keep multi-entity static flows as the default. It means agent memory scope should become explicit.
Proposed split:
Example:
Possible scopes:
Default should be conservative:
This preserves determinism and avoids accidental cross-case leakage, while still allowing useful shared memory.
The important design principle:
Relationship to flow interfaces
This model works well with structural flow interfaces.
A service interface can require instance identity and pins:
A concrete implementation can satisfy it with a template flow:
The parent composes against the interface, not a static entity table.
Relationship to addressed input pins
If we adopt one primary entity per flow instance, input pin addressing becomes much simpler.
Earlier model:
Refined model for common cases:
The input pin does not need to say how to select an entity inside the flow. The flow instance key already defines that.
Input-level address metadata remains useful only when:
So
input.addressshould become advanced/escape-hatch, not the happy path.Suggested platform model
Flow instance declaration
Composite key:
Primary state entity
The entity file defines the primary state shape:
Potential future sugar:
or infer it if exactly one entity type exists.
Connect routing
Analyzer expansion:
Field rename:
Explicit collection update remains local
Handler update could remain a state write/update expression. It should not require creating a component entity unless component becomes independently routable.
Migration implications for Empire
Suggested migration shape:
operatingvertical_idvalidationvalidation_case_idorvertical_iddepending desired uniquenessscoringvertical_id/ scoring case idlifecyclevertical_idtreasuryvertical_idholding-devopsrequest_idspec-repo(scope, scope_id, artifact_type)repo-scaffold(scope, scope_id, artifact_type)plus maybe component id if multiple components share artifact typediscoverycampaign_id/scan_idsignal-searchbatch_id/ campaign idThis migration would remove much of the normal-path
select_entity/select_or_create_entityfrom Empire and shift uniqueness/idempotency to flow instance keys.Potential objections
Objection: What about global service state?
Some services may need global aggregate state. Example: treasury might need portfolio-level budget caps.
Answer: model that as a parent/service singleton flow or explicit aggregate flow, not as many unrelated entities inside one flow.
Objection: What about querying across many instances?
A static flow with many entities makes cross-entity query feel natural.
Answer: cross-instance query should be explicit analytics/read-model capability, not the core routing model. The runtime can still index instances by fields for dashboards/analyzer/runtime, but event delivery should not depend on ad hoc entity queries.
Objection: What about dedup across many cases?
Dedup can be handled by:
It should not require every process flow to be static and multi-entity.
Objection: Does this make too many flow instances?
Yes, it increases instance count. But it reduces internal entity-routing complexity. This is likely a good trade if flow instances are the runtime's primary unit of composition, lifecycle, trace, and isolation.
The platform must make flow instances cheap and first-class.
Analyzer responsibilities
Under this model, verify should prove:
create,reject, or mayberesolve_only.query_entitiesguards.select_entity; if present, verify warns or rejects unless the flow opts into multi-entity/legacy mode.Recommended design principle
I would promote this as a core authoring principle:
And the normal hierarchy should be:
This keeps composition understandable:
Open questions
advanced.multi_entity: trueflag?create_entitydiscouraged or warned?mode: templatebecome the default whenever a flow has states and a primary entity?select_entitybecome legacy/escape-hatch for external callbacks and static service records only?validation_case_idorvertical_id?Recommendation
Adopt this direction as the default design target:
Keep list fields for contained local data. Promote list items to child/template flows when they need independent lifecycle, routing, timers, retries, or agent work. Keep multi-entity static flows as an advanced/legacy escape hatch, not the normal model.
This would simplify Empire materially and also simplify the composition model we are designing. The main missing counterpart is explicit agent memory scope, because we should not rely on multi-entity static flows to get shared agent context by accident.
Beta Was this translation helpful? Give feedback.
All reactions