Injecting runtime variables

I'm building a high-load chatbot with Effect and need to pass userId (a runtime variable) down to functions without prop drilling.
The main blocker is that I'm using the experimental @effect/ai library with toolkit support, and I need userId inside toolkit handlers. I can't find a way to prop drill userId to these handlers, and I don't want to move the tool logic outside of the handlers just to access userId.
How can I pass runtime context like userId to deeply nested Effect services, particularly toolkit handlers that I can't modify?


What I am doing currently to tackle this:
I have created a service MessageState, where I store userId

export class MessageState extends Context.Reference<MessageState>()("MessageState", {defaultValue: () => ({userId: ""})}) {}


And I pass it to all the layers needed it in a runtime

export const app = () => Effect.gen(function* () {
    /// *** some code ***/
    

    yield* aggregator.messagesStream.pipe(
        Stream.groupByKey((message) => message.userId, {
            bufferSize: 256
        }),
        GroupBy.evaluate((userId, userStream) => userStream.pipe(
            Stream.mapEffect((message) =>
                MessageProcessor.pipe(
                    Effect.provide(SalonToolkitHandlers),
                    Effect.provideService(MessageState, {userId}),
                    Effect.flatMap((processor) => processor.processMessage(message))
                ), {concurrency: 1}
            )
        )),
        Stream.runDrain
    )

    // some more code
})
) 


However, I really don't like what I am doing. Firstly because app knows too much about implementation. Ideally I would like to provide all the layers for the app at the initialization point, not in the runtime. But if I do so, MessageState does not get overwritten when I provide it

Is there any cleaner way of approaching the problem?
Was this page helpful?