EFFECT
talk
01/41
~ datapizza/effect.md Effect Milan11 JUNE 2026

SPEAKER Leonardo Trapani @leo_trapani
@leo_trapani
2026
Effect Milan 2026
EFFECT
In this talk
02/41
AGENDA

In this talk

How Datapizza uses Services and Layers to make Dual adapt to real enterprise systems.

  1. What we build at Datapizza
  2. Services & Layers
  3. Boundaries AI can follow
  4. Concrete examples
  5. Customize the edges
@leo_trapani
2026
Effect Milan 2026
EFFECT
whoami
03/41
SPEAKER

Leonardo Trapani

Founding Engineer @ Datapizza
pfp.png
@leo_trapani
2026
Effect Milan 2026
EFFECT
part 01
04/41
PART · 01 ──

What we build at Datapizza

@leo_trapani
2026
Effect Milan 2026
EFFECT
what we build
05/41
AI TRANSFORMATION

How do we support companies?

Datapizza
Our vision
Make Italy a leading player in Tech & AI.
Our
Mission
Create an ecosystem that helps companies through their AI Transformation.
+200 projects
delivered with enterprises and SMBs
+500.000
members in our community

Our structure

AI Transformation Lab
We define the company's AI transformation path across strategy, people, and technology.
AI for People
Human integration of AI inside companies
AI Adoption
Structured programs and assessments to bring AI to the whole organization
AI & Tech Recruiting
Specialized support for AI-ready profiles
AI Technology
Tailored solutions engineered by the R&D team
Custom AI Technology
Bespoke AI solutions integrated into core enterprise systems
AI R&D & Products
AI innovation and scalable proprietary products
Builder Community
technology and Generative AI professionals
&
Open Source Framework
GenAI for enterprise projects with RAG and AI agents
@leo_trapani
Datapizza · AI Transformation
Effect Milan 2026
EFFECT
dual
06/41

What companies ask for

“On a new ticket, create a PR automatically.”
“When an RFP arrives by email, extract the requirements and draft the first response.”
“Answer customer questions about their insurance policy.”
“Check new contracts against our internal policy and flag missing clauses.”
“Route procurement requests through approvals and internal systems.”
@leo_trapani
tickets · email · insurance · contracts · procurement
Effect Milan 2026
EFFECT
dual
07/41
OPTION 01

Custom AI projects

They fit the company: data, processes, permissions, compliance, internal systems.

But many requests do not need a new product from scratch.
Too expensive when the hard parts are always the same.
@leo_trapani
fit is good · starting from zero is not
Effect Milan 2026
EFFECT
dual
08/41
OPTION 02

Generic agent platforms

They already (not really?) have agents, workflows, guardrails, observability, deployment.

But they are not connected to the actual company.
The defaults rarely match internal systems, permissions, or infrastructure.
@leo_trapani
platform is good · generic assumptions are not
Effect Milan 2026
EFFECT
dual
09/41

Dual

The company-shaped agent platform
The platform provides
agent orchestration
workflow execution
guardrails, identity, connections, traces
We integrate
company systems and infrastructure
permissions and compliance
processes and domain logic
The platform adapts to the company, not the other way around.
@leo_trapani
Dual · company-shaped agent systems
Effect Milan 2026
EFFECT
part 02
10/41
PART · 02 ──

Services & Layers

Effect turns boundaries into explicit requirements. service → layer → graph
@leo_trapani
2026
Effect Milan 2026
EFFECT
definition
11/41
"
Dependency Injection (DI) is a software design pattern used to create loosely coupled code.
@leo_trapani
2026
Effect Milan 2026
EFFECT
service pattern
12/41
With Effect, your dependencies become explicit, type-safe, testable, and swappable.
@leo_trapani
2026
Effect Milan 2026
EFFECT
context
13/41
CONTEXT

What is a capability in DUAL?

CAN BE
A tool an agent can use
A block in a workflow
DEFINED WITH
Typed input / output
An implementation
Permissions
@leo_trapani
list · search · invoke
Effect Milan 2026
EFFECT
defining services
14/41

Defining services

export class CapabilityRegistry extends Context.Service<  CapabilityRegistry,  {    readonly list: () =>      Effect.Effect<ReadonlyArray<Capability>, RegistryUnavailable>    readonly search: (query: string) =>      Effect.Effect<ReadonlyArray<Capability>, RegistryUnavailable>    readonly invoke: (request: InvokeCapabilityById) =>      Effect.Effect<CapabilityResult, CapabilityNotFound | CapabilityInputError | CapabilityInvokeError>  }>()("dual/CapabilityRegistry") {}
@leo_trapani
2026
Effect Milan 2026
EFFECT
you can just yield* services
15/41

You can just yield* services

export const invokeCapabilityById = Effect.fn("invokeCapabilityById")(  function* (id: CapabilityId, input: unknown) {    const registry = yield* CapabilityRegistry    return yield* registry.invoke({      id,      input,      context: { source: "agent" }    })  })// Effect<CapabilityResult, CapabilityError, CapabilityRegistry>
@leo_trapani
2026
Effect Milan 2026
EFFECT
effect type
16/41
THE TYPE
Effect<A, E, R>
A · SUCCESS

value

E · FAILURE

errors

R · REQUIREMENTS

CapabilityRegistry

@leo_trapani
2026
Effect Milan 2026
EFFECT
squiggles are your friend
17/41

Squiggles are your best friend

const program = invokeCapabilityById(  "google.gmail.search",  { q: "from:finance has:attachment" })Effect.runPromise(program)//                ^^^^^^^^^// Type 'CapabilityRegistry' is not assignable to type 'never'.
@leo_trapani
2026
Effect Milan 2026
EFFECT
building layers
18/41

Building layers

const CapabilityRegistryLive = Layer.effect(  CapabilityRegistry,  Effect.gen(function* () {    const db = yield* Db    const dual = yield* Dual.Service    return CapabilityRegistry.of({      list: () => listCapabilities(db),      search: (query) => searchCapabilities(db, query),      invoke: ({ id, input, context }) =>        Effect.gen(function* () {          const capability = yield* findCapability(db, id)          const decoded = yield* decodeInput(capability, input)          return yield* dual.invoke({            actionId: capability.actionId,            input: decoded,            context          })        })    })  }))
@leo_trapani
2026
Effect Milan 2026
EFFECT
providing services
19/41

Providing services

const Live = CapabilityRegistryLive.pipe(  Layer.provide(Layer.mergeAll(DbLive, DualLive)))const runnable = program.pipe(  Effect.provide(Live))await Effect.runPromise(runnable)
@leo_trapani
2026
Effect Milan 2026
EFFECT
composition graph
20/41
FULL GRAPH

An example of where it can go.

Service Layer
@leo_trapani
services declare needs · layers choose implementations
Effect Milan 2026
EFFECT
part 03
21/41
PART · 03 ──

Boundaries AI can follow

Shallow vs deep modules for humans and coding agents. interfaces · depth · explicit context
@leo_trapani
2026
Effect Milan 2026
EFFECT
reference
22/41
Software Fundamentals Matter More Than Ever — Matt Pocock
Software Fundamentals Matter More Than EverMatt Pocock · youtu.be/v4F1gFy-hqg
@leo_trapani
2026
Effect Milan 2026
EFFECT
matt pocock · software fundamentals
23/41
"
Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system.
A Philosophy of Software Design· John Ousterhout
@leo_trapani
2026
Effect Milan 2026
EFFECT
part 03
24/41

Shallow modules

@leo_trapani
2026
Effect Milan 2026
EFFECT
part 03
25/41

Shallow modules

@leo_trapani
2026
Effect Milan 2026
EFFECT
part 03
26/41

Deep modules

@leo_trapani
2026
Effect Milan 2026
EFFECT
part 03
27/41

Effect forces you
and your agents
to follow good software fundamentals.

@leo_trapani
2026
Effect Milan 2026
EFFECT
part 03 · service / layer
28/41

Deep modules in Effect

@leo_trapani
service is the boundary · layer is the implementation
Effect Milan 2026
EFFECT
part 04
29/41
PART · 04 ──

Concrete
examples

where this pattern appears contract → requirements → layer
@leo_trapani
same shape, different surfaces
Effect Milan 2026
EFFECT
part 04 · http api
30/41

Effect HttpApi: the contract

// contract: route + payload + success + errorsexport class UsersApiGroup extends HttpApiGroup.make("users")  .add(    HttpApiEndpoint.get("search", "/search", {      payload: { search: Schema.String },      success: Schema.Array(User),      error: SearchQueryTooShort    })  )  .prefix("/users") {}
@leo_trapani
endpoint definition is separate from handlers
Effect Milan 2026
EFFECT
part 04 · http api
31/41

Effect HttpApi: the Layer

// implementation: handler group as a Layerexport const UsersApiHandlers = HttpApiBuilder.group(  Api,  "users",  Effect.fn(function* (handlers) {    const users = yield* Users    return handlers.handle("search", ({ payload }) =>      users.search(payload.search)    )  }))const ApiRoutes = HttpApiBuilder.layer(Api).pipe(  Layer.provide(UsersApiHandlers))
@leo_trapani
handler layer satisfies the API requirements
Effect Milan 2026
EFFECT
part 04 · dual
32/41

DUAL capability: Google provider contract

// contract: provider + Gmail send capabilityexport const gmailSend = Action.make("send", {  name: "Send Gmail message",  description: "Send an email through Gmail"}).pipe(  Action.withVersion("v1", {    input: Schema.Struct({      to: Schema.String,      subject: Schema.String,      body: Schema.String    }),    output: Schema.Struct({ messageId: Schema.String })  }))export const googleProvider = Provider.make("google").pipe(  Provider.withAuthMethod(googleOauth),  Provider.add(Group.make("gmail").pipe(Group.add(gmailSend))))
@leo_trapani
provider + group + action define the capability surface
Effect Milan 2026
EFFECT
part 04 · dual
33/41

DUAL capability: Gmail send Layer

// implementation: Gmail handlers provided as a Layerexport const GoogleLive = Provider.toLayer(  googleProvider,  googleProvider.of({    actions: {      gmail: {        send: {          v1: Effect.fn("@dual/google/gmail.send")(            function* ({ to, subject, body }, context) {              const gmail = yield* GmailClient              const messageId = yield* gmail.sendMessage({                accessToken: context.auth.current.value.accessToken,                to,                subject,                body              })              return { messageId }            }          )        }      }    }  }))
@leo_trapani
the action handler can require services like GmailClient
Effect Milan 2026
EFFECT
part 05
34/41
PART · 05 ──

Customize the edges

Swap infrastructure without rewriting business logic. credentials · storage · models
@leo_trapani
2026
Effect Milan 2026
EFFECT
part 05
35/41
DATAPIZZA PROMISE

“This platform will be built around your systems.”

Enterprise AI is mostly about integrating with the boundaries a company already has.

@leo_trapani
compliance · infrastructure · deployment boundaries
Effect Milan 2026
EFFECT
part 05
36/41
"
Great. Now build it around our systems.
Enterprise client· five minutes after the demo
@leo_trapani
the defaults are never the deployment
Effect Milan 2026
EFFECT
part 05
37/41
“Use Redis, not an in-memory queue.”
“Secrets must live in our secret manager.”
“Our LLM gateway runs in our Azure tenant.”
“We give you our sandbox.”
“Send errors and traces to Sentry.”
@leo_trapani
Effect Milan 2026
EFFECT
part 05
38/41

The tempting mistake

please-dont.tsTSbranching
if (client === "azure-bank") {  queue = new RedisQueue()  secrets = new KeyVault()  sandbox = new PrivateSandbox()  model = new AzureOpenAI()}if (client === "another-enterprise") {  // repeat forever...}
@leo_trapani
customization should live at the edge
Effect Milan 2026
EFFECT
part 05 · squiggles
39/41

Squiggles become the deployment checklist

client-entrypoint.tsTSmissing requirements
import { Dual } from "@dual/sdk"const dual = Dual.make(app)const runServer = dual.runServer({ port: 3825 })await Effect.runPromise(runServer)//                  ~~~~~~~~~// ts(2345) Argument of type// Effect<never, ServerStartError,//   QueueService | CredentialsService | ModelGateway |//   SandboxService | StaticServing | Telemetry// > is not assignable to Effect<never, unknown, never>.//// Missing requirements:// QueueService | CredentialsService | ModelGateway |// SandboxService | StaticServing | Telemetry
@leo_trapani
the type tells us what the client must provide
Effect Milan 2026
EFFECT
part 05 · actual shape
40/41

Provide the client layer

client-entrypoint.tsTSclient layer
const ClientLive = Layer.mergeAll(  RedisQueue.layer,  KeyVaultCredentials.layer,  AzureModelGateway.layer,  PrivateSandbox.layer,  S3StaticServing.layer,  SentryTelemetry.layer,)await Effect.runPromise(  runServer.pipe(    Effect.provide(ClientLive),    Effect.orDie,  ))
@leo_trapani
customization lives at the edge
Effect Milan 2026
EFFECT
end of talk · questions?
41/41
END · Q&A

Thank
you.

Leonardo Trapani Founding Engineer @ Datapizza
@leo_trapani
2026
Effect Milan 2026