sci

bhauman 2025-09-12T18:03:12.260519Z

I'm may be a crazy man but I'd like make an sci repl-eval loaded with a library as a MCP tool... for an online SAS service. Ie create todos, edit todos, etc. Question: is this insane? Or amazing? Or both? So basically instead of delineating all the tools for a service provide one todos_sci_repl tool with a narrow well defined composable clojure library with access to clojure.core as well. The biggest question is what is the basic baseline eval safety of SCI? How much would I have to do lock it down?

borkdude 2026-04-18T09:13:36.911499Z

it's a similar PR to @whilo's one but whilo's one is more flexible since you can plug in your own interrupt-fn which would be preferable to me, as it signals more "it's up to you"... :) @bhauman what would you do in the other 50% non-working cases?

➕ 1
bhauman 2026-04-18T20:52:35.447939Z

Well my calculus would be that for my use case that doesn't encourage the LLM to create unbounded sequences or loops that could not terminate that this would be a very rare occurrence So a 50% improvement on very rare/never? Is a pretty big improvement

bhauman 2026-04-18T20:59:44.482959Z

But also my use case is in cljs land so these patches don't apply. I might have to patch in limits for unbounded seqs and loops, and I don't know if that's even possible yet… and any hit on performance is absolutely nonmaterial for this use case. But from what I've seen SCI is an absolutely killer lightweight scripting engine for SOTA LLMs. And CLJS products can just drop it in. WebMCP is coming and front end apps will be able to present a scripting engine as a tool rather than a set of MCP tools that don't compose.

borkdude 2026-04-18T21:02:28.929569Z

you can override everything in core with your own thing by adding it in your config as {:namespaces {'clojure.core {'repeat <your-repeat-override-here>}}}

borkdude 2026-04-18T21:03:46.776919Z

> But also my use case is in cljs land so these patches don't apply. How would :interrupt-fn not apply here? It would work in CLJS?

borkdude 2026-04-18T21:04:57.183169Z

My question still wasn't answered though. What would you do with the other 50% of unhandled cases (even though they might be rare). Kill the browser I guess?

borkdude 2026-04-18T21:05:47.538769Z

@bhauman you might be interested in https://github.com/PEZ/epupp which is a browser plugin based on #scittle to tamper with any webpage from a REPL that your AI can connect to as well

bhauman 2026-04-18T21:06:56.578149Z

Yeah the browser tab wouldn't probably get shut down with an out of control script message.

borkdude 2026-04-18T21:08:20.015399Z

(epupp works on any page, even on pages that don't let you use JS eval since SCI does not use eval)

bhauman 2026-04-18T21:09:38.955119Z

So I just looked at interrupt-fn pr yes that would meet my needs.

borkdude 2026-04-18T21:11:12.498609Z

I agree that not having to kill your browser in 50% of cases is better

bhauman 2026-04-18T21:18:06.663579Z

But this has never happened to me ever. But it did happen in Clojure repl back in the Claude 3.5-7 days. Especially on reading infinite seqs

mkvlr 2025-09-14T13:09:50.669129Z

in https://github.com/babashka/sci/commit/7145c3a8ef0a4329e23964f8b9eac34d0eebe93d I implemented handling interruptions for recursive function calls a while back. Don’t remember anymore if I found cases this didn’t handle back then.

borkdude 2025-09-14T13:16:27.243119Z

It works for recursion but not in general (other cases mentioned above)

mkvlr 2025-09-14T13:16:49.920729Z

what’s an example? (repeat :x) seems to work

borkdude 2025-09-14T13:17:13.709159Z

Realize it

mkvlr 2025-09-14T13:17:47.331729Z

ah right, that doesn’t work, thanks

borkdude 2025-09-12T18:04:31.533349Z

makes sense. SCI should be safe by default, no access to file system, network, etc. the only unsafe bit is how long things can take to execute. but if you can control that in the host system, it should be fine.

bhauman 2025-09-12T18:23:29.476559Z

@borkdude OK very good to know.

phronmophobic 2025-09-12T18:28:23.713009Z

I've always been curious about this. Have there been any serious attempts of people trying to jailbreak sci? What about fuzzing? Is there a security model that says what behaviors would be considered security violations?

borkdude 2025-09-12T18:29:02.072799Z

try to break it and report :)

borkdude 2025-09-12T18:29:25.201569Z

of course I give no guarantees legal wise ;)

😂 1
phronmophobic 2025-09-12T18:29:52.622299Z

Is there a security model that defines what a security violation is?

borkdude 2025-09-12T18:30:53.071929Z

good question, I guess there is not a formal definition, but I would consider these things: • mutate things in host, including Clojure vars in the host system • touch files on the file system, or write, delete etc

bhauman 2025-09-12T18:42:36.160669Z

@borkdude is there a mutable object to shutdown the eval iteration

borkdude 2025-09-12T18:44:50.063649Z

@bhauman it was there in the beginning, but this was very hard to give any guarantees about. this is why I removed it and said: solve it in the host. e.g. you can implement some recursive function which takes a long time. or you can just do something like (apply + (repeat 1)) and wait forever. there's no interpreter in the middle of that anymore after it's running

borkdude 2025-09-12T18:45:51.742319Z

there used to be Thread/stop but JVM removed it. there's ways to get it back though, CIDER has something like this using a java agent

borkdude 2025-09-12T18:46:59.464189Z

what I could do is inject Thread/isInterrupted calls everywhere, but then I'd have to re-implement almost all of core where the above problem applies

borkdude 2025-09-12T18:47:51.682139Z

Perhaps using some JVM bytecode magic you can still do this in the host or perhaps that's what the CIDER java agent is doing kind of, haven't really checked

borkdude 2025-09-12T18:48:41.663269Z

ah here is some info on that: https://docs.cider.mx/cider/basics/up_and_running.html#enabling-nrepl-jvmti-agent

borkdude 2025-09-12T18:49:50.956889Z

ah they wrote some C to bring back Thread/stop basically: https://github.com/nrepl/nrepl/blob/master/libnrepl/src/nrepl_agent.c

borkdude 2025-09-12T18:53:36.196219Z

One interesting approach I've seen in the clojure IRC channel. They had a clojure evaluation bot, but it no longer worked since java security sandboxing was deprecated. What they did: use a new babashka process for every call. bb comes with SCI as well. So they evaluated their code in SCI in a babashka process and killed the process if it ran longer than n seconds.

👍 1
borkdude 2025-09-12T18:58:56.131659Z

I guess could build a similar dedicated binary if you want to have fast startup and less memory usage and do the "kill the process if it takes too long" thing or start a separate JVM/clojure process if startup time doesn't matter that much

mkvlr 2025-09-13T11:53:17.747249Z

> what I could do is inject Thread/isInterrupted calls everywhere, but then I’d have to re-implement almost all of core where the above problem applies @borkdude are you sure that this is going to be that bad? why not add this to a place at a time, and when someone reports another case, also add it there? Are you concerned about the performance implications?

borkdude 2025-09-13T11:58:20.201739Z

it's just a half baked solution since functions of clojure core, etc and functions that users expose to SCI won't handle this

mkvlr 2025-09-13T12:24:12.317129Z

and you consider it only worthwhile if it works in all cases?

borkdude 2025-09-13T12:24:28.674389Z

yes

mkvlr 2025-09-13T12:26:15.955829Z

I see. Like a performance improvement, I’d consider it worthwhile if sci would handle interruptions better for some cases.

borkdude 2025-09-13T12:26:22.289559Z

e.g. for the above use case or the clojure IRC use case putting some (Thread/interrupted) here and there isn't sufficient. Creating this expectation that it will sometimes work will often be misunderstood for "it supports this". It's one of the reasons I removed the "eval counter" solution way in the beginning.

borkdude 2025-09-13T12:27:06.539909Z

how would it be a performance improvement to put (Thread/isInterrupted) calls in code?

mkvlr 2025-09-13T12:27:53.247439Z

I didn’t say it is an performance improvement, but that I see it similar to a performance improvement

mkvlr 2025-09-13T12:28:08.809559Z

sci being more responsive to an interruption I mean

mkvlr 2025-09-13T12:28:38.262239Z

so I’d consider it worthwhile, even it it only improves responsiveness in some areas

mkvlr 2025-09-13T12:29:23.057039Z

and I see it similar because it’s about how long you need to wait in case you hit ^C

mkvlr 2025-09-13T12:31:07.207659Z

but I wouldn’t want to give guarantees that it would work like this in all cases

2025-09-13T19:20:59.393779Z

I would not introduce a semi-working interrupt feature either, @borkdude

➕ 2
🙏 1
2025-09-13T19:57:36.224979Z

I did quite some tests and thinking around Sci as a sandbox, but to say this was exhaustive would be naive of course. I believe the implementation is solid. The main weakness is the possibility of recursion and occupying threads this way/Dos attacks. E.g. the fn and defn macros can be redefined to be safe, but then people can still call the underlying fn* . Not sure what can be done about this. The lazy infinite sequences such as repeat and range can be limited by reimplementing an old feature (:realize-max)

borkdude 2025-09-13T19:59:27.783709Z

E.g. the fn and defn macros can be redefined to be safe, but then people can still call the underlying fn*
Not sure what you mean by this

2025-09-13T20:01:50.874429Z

So you can rewrite the fn macros with an invocation counter and put a limit on it to limit the recursion possibility. But the underlying fn* you cannot rewrite, so as long as you can call that infinite recursion is still possible. I have some examples somewhere if that helps

borkdude 2025-09-13T20:02:22.447319Z

I remember your examples from around 2020 or so. This is why I removed the feature ;)

borkdude 2025-09-13T20:04:33.471559Z

I mean, I remember that we talked about it, not the concrete examples

2025-09-13T20:05:45.749019Z

Yeah exactly. I've been still thinking about it and I hope this can be solved somehow, some day as it would give opportunities for multitenancy. I will share the examples later. Not near a computer now

2025-09-13T20:06:35.257229Z

But for @bhauman purpose I think sci is safe enough. If you want to prevent access to disk and other dangerous things I believe sci is safe. As long as you hide dangerous things behind a controlled API I think clojure MCP would be ok. I don't infinite recursion would do much harm in that setting

borkdude 2025-09-13T20:07:21.667799Z

multitenancy should be supported with SCI as in, if you fork a context for each customer, should be ok

2025-09-13T20:07:25.296439Z

I've actually thought of using sci and clojure MCP for that reason, but unfortunately just too many other things to do

2025-09-13T20:09:29.387789Z

I believe there are some potential problems with the forking. E.g redefinitions to vars will not propagate, but changes to values such as atoms will propagate. And I believe some other scenarios I found a long the way of experimentong with this

2025-09-13T20:10:05.222329Z

But this could be solved with more work I believe

borkdude 2025-09-13T20:10:38.559959Z

re-definitions of vars do not propagate. I consider that a feature

2025-09-13T20:11:49.844209Z

Yeah I agree. Maybe better if I come up with some examples soon

❤️ 1
2025-09-13T20:15:13.206479Z

So I meant the re-definition of vars do not propagate and that is what you want. But if we have a var that maps to an atom, forking will not stop changes to that atom to be propagated and that can be problem sometimes. I ran into this myself at least when I didn't expect it

borkdude 2025-09-13T20:16:08.274859Z

If you give users access to that atom, yes they can change it

✔️ 1
2025-09-13T20:16:10.023769Z

But again I think this is not relevant for clojure-mcp using sci as a sandbox

borkdude 2025-09-13T20:39:58.077059Z

to clarify somewhat: • changes to vars are propagated if you expose users to the same vars • but if fork a context and let users create new vars, they won't be seen in the old context. that's just how clojure hashmaps work

➕ 1
borkdude 2025-09-13T20:40:25.748599Z

and • you can create immutable vars for users so they can't modify them (but you can also map a function directly to a name without a var, even safer)

➕ 1
whilo 2026-05-26T18:19:58.674689Z

I am also happy to map out all pieces that need to be intercepted for full safety (e.g. under adversarial conditions), @borkdude already helped in pointing out all the edge cases. In general I think this is an important design space for Clojure right now.

borkdude 2026-04-17T09:45:41.735699Z

Here's another issue about the same topic: https://github.com/babashka/sci/issues/1038 I'm still on the fence about it and leaning towards what @jackrusher was saying: > I would not introduce a semi-working interrupt feature either, @borkdude

mkvlr 2026-04-17T10:39:14.866129Z

I still would love to see bb responding to being interrupted 🙃

borkdude 2026-04-17T10:41:33.655449Z

oh bb isn't going to have this, it's a disputable SCI feature which isn't going to be enabled in bb

borkdude 2026-04-17T10:42:06.411149Z

mainly not for performance reasons

mkvlr 2026-04-17T10:42:37.153639Z

I couldn’t measure a performance impact when I implemented it…

borkdude 2026-04-17T10:43:46.462379Z

ok if it isn't measurable then I may change my mind, but the other objection is that it's just "semi-working", I haven't changed my mind on that, but I remain open to be convinced otherwise.

mkvlr 2026-04-17T10:43:48.527889Z

but it could start like you said in sci, not in bb

mkvlr 2026-04-17T10:44:18.932949Z

java also has a semi working interrupt feature…

2
mkvlr 2026-04-17T10:44:37.805599Z

you can only interrupt threads if they’re doing work that can be interrupted

borkdude 2026-04-17T10:44:53.594049Z

and that works in bb already :)

borkdude 2026-04-17T10:45:23.103759Z

cc @whilo

mkvlr 2026-04-17T10:46:48.343649Z

but being an interpreter you have more control about what can be interrupted

mkvlr 2026-04-17T10:47:38.242799Z

and you can also do it for some cpu-bound things

borkdude 2026-04-17T10:48:33.402029Z

there's more info in the issue about what the gaps are. https://github.com/babashka/sci/issues/1038

mkvlr 2026-04-17T10:49:57.106049Z

what are the downsides you can think of? • implementation complexity • only partial support like in the issue, which could result in user confusion and wrong expectations • performance penalty

mkvlr 2026-04-17T10:50:00.313199Z

anything else?

borkdude 2026-04-17T10:51:52.724679Z

• implementation complexity: this isn't that complex and performance penalty is ok if you don't provide an :interrupt-fn. so that's ok. • partial support: this is the main objection. I think you will end up re-creating almost all of clojure to basically be aware of your :interrupt-fn • see point 1

mkvlr 2026-04-17T10:55:22.532369Z

I wonder if that can also be solved by documenting the limitations

mkvlr 2026-04-17T10:55:49.472859Z

like these More examples of long running programs that bypass interrupt-fn: aren’t solvable I think?

borkdude 2026-04-17T10:56:49.518789Z

they are solvable if you provide overrides for the interop (something not fully supported but could be) which is made aware of your interrupt-fn ;)

mkvlr 2026-04-17T11:01:06.527149Z

jvm code that doesn’t check for interrupts doesn’t respond to them, isn’t that a known/expected limitation for this feature?

borkdude 2026-04-17T11:03:12.839749Z

yeah well, I guess :interrupt-fn could be sold as: SCI-level interruption in loops/SCI-interpreted fns and the rest is up to you to override. But the question is: what are the useful use cases for this that are 100% valid without getting into the "rewrite all of core and interop" problem.

mkvlr 2026-04-17T11:03:20.452449Z

> My impression is that you basically have to rewrite almost all of clojure.core to make programs fully interruptible it would not be my understanding that adding some checks for interrupts in sensible places would make sci programs “fully interruptible”. Can you explain why you think this would have to be an all or nothing approach?

mkvlr 2026-04-17T11:04:15.859779Z

I guess one reason is you’ve been burnt by trying to add the safe eval counter stuff?

borkdude 2026-04-17T11:05:15.266899Z

yeah that and that I haven't seen actual use cases where this is really helpful without getting into the "rewrite all of core" to make it work all the way

borkdude 2026-04-17T11:05:35.848969Z

"semi-working" being the key term

mkvlr 2026-04-17T11:05:42.279389Z

why do you think it’s only useful without going all the way?

borkdude 2026-04-17T11:06:07.938079Z

because something half-baked doesn't feel satisfying?

mkvlr 2026-04-17T11:06:23.660269Z

I think responding instantly to ctrl+c when I enter an infinite loop in bb in the repl would make it better

borkdude 2026-04-17T11:06:39.429929Z

that already works, it exits the process?

borkdude 2026-04-17T11:06:58.052889Z

oh, the REPL

borkdude 2026-04-17T11:07:35.681209Z

that would be one use case, but it's still half-baked compared to how Thread/stop worked

borkdude 2026-04-17T11:08:40.058249Z

I'll wait for what whilo has to say and why he needs his feature. there could be use cases that are more compelling

mkvlr 2026-04-17T11:24:21.604969Z

I’d be curious to learn if you’d like the feature more if you tried it in your local dev bb for a week

2026-04-17T15:57:43.510859Z

I think the main issue with this feature is a lifetime of bug reports from users who are confused about why “interrupt” didn't work. (And documenting the limitation won't prevent this.)

borkdude 2026-04-17T15:57:57.659989Z

exactly

mkvlr 2026-04-18T00:53:50.386989Z

since interrupts also don’t always work on the host platform, I’m not sure it would be such a big deal. A drop in the bucket of bug reports in the borkverse perhaps? 🙃 Maybe not advertising this as a feature could also help.

mkvlr 2026-04-18T01:13:51.933339Z

but I can see how not advertising the feature doesn’t make sense for :interrupt-fn .

bhauman 2026-04-18T01:14:55.344659Z

So, now that I'm about to put something like this in production I'm very interested in this feature. Even if it doesn't work all the time…

bhauman 2026-04-18T01:16:07.681379Z

For my use case the LLM hasn't yet created an infinite loop but it would be nice to have a time out just in case….

bhauman 2026-04-18T01:16:46.836379Z

And even if it worked 50% of the time that’s better than nothing.

mkvlr 2026-04-18T01:21:24.216529Z

https://github.com/mk/sci/commit/7145c3a8ef0a4329e23964f8b9eac34d0eebe93d was my patch for handling interrupts for function calls

🙏 1
borkdude 2026-06-16T13:29:43.307239Z

follow up here: https://clojurians.slack.com/archives/C015LCR9MHD/p1781616545093549