DEV Community

Nitin Shankar Madhu
Nitin Shankar Madhu

Posted on

My experience with Jachacks Winter.

This post documents my experience hacking at JacHacks Winter, a hackathon centered around the Jac programming language.

I went into the event knowing more than most about Jac, since one of my teammates (Chuka Ezeoke), and I participated (and won!) the Jac track at RocketHacks a month prior.

The impetus for the idea we (me, Chuka and Meron) eventually came up with stemmed from the first line on the Jaseci documentation site - "The only language you need to create anything". That seemed like a outrageous claim to make. And it was, but as I would discover, it was one that was unfounded.

Nowhere in the documentation could I find any references to mobile development, but it seemed in the spirit of the language to make it easy to create mobile apps. After all, Jac does pack in language abstractions to make web applications, something quite novel and possibly unique to the language, so why not include one of the most profitable platforms to develop for in the mix? The addition of the compiler now meant React Native apps now had real static type checking (looking at you Typescript fans).

My work at that time heavily involved mobile development, so I naively assumed that Jac's React internals would map cleanly to React Native's compilation process as well. After all, React Native looked so much like React that it could almost be it! As I found out, this was not the case.

While it was clear that React Native was not just compiling html and cramming that into a mobile view, a lot of (who am I kidding its all of it) it's internal machinery was different. How much different this was is what was surprising to me.

React minified its Javascript and created a simple html / css web app. Simple enough. However, due to the impossibility of having Android's class based Java SDKs follow React-ive primitives, the way Facebook implemented React Native's internal engine was to create a JS VM that would run on top of Android's Native code, using React only for managing lifecycle events like UI updates, and leaving the highly optimized android operations to native C++ code.

I hear you asking, so there is React somewhere in there, right? wouldn't that make it easier to transpile React to mobile?

Not quite. To work around the smaller number of primitives that Android had accessed to (as opposed to the billions of html tags available on the web), React Native also had it's own UI component library, and along with this a ton of tiny quirks that made it impossible to port in the 22 hours of the hackathon.

React Native was therefore not an output format that I could enable with a different configuration flag. Supporting it properly would have required either a new compiler target or a substantial compatibility layer translating Jac’s generated React code into React Native primitives.

Looking at what other existing compiler boundaries we could use led us to Capacitor, a framework that let you convert websites to native mobile apps. Our approach was to hijack Jac's Vite compilation path and have it render a mobile app at the end, since they both used Vite for the bundling process.

Possibly due to a lack of faith in my ability, Meron and Chuka had started working on a mobile app in parallel that I could later translate to Jac. Since we were taking control of the Jac compiler to suit our own needs, they did something quite unusual, and came up with the name for our project first. It's called Hijac (get it?). It was to be an mobile agent that ran on your phone and could take actions for you autonomously. This chart below illustrates why we went with this idea.

Our first prototype was predictably inelegant. Vite bundled the React output into HTML and CSS, and sequentially called cap sync, which ran Capacitor's api to generate a mobile app, and HMR didn't quite refresh on every edit. But the important part worked: an application written in Jac was running as an installable mobile application.

Now came the fun part, designing an execution layer for our agent, and have the app finally do something. Fortunately for us, Jac had OSP, yet another language primitive. OSP made made graphs first class and I didn't quite understand the point of this when reading the docs, but it made sense once a need surfaced.

Agents are really just loops, and the interesting ones are ones that can perform multiple actions in response to different inputs. And when you zoom out, this it's just a graph. Here's what it was structured as:

Nodes hold state (like motion event from your phone)and walkers are the little autonomous agents that traverse that graph, visit nodes, and make function calls. So we didn't have to build a workflow framework. A workflow was just: a sensor fires, and a walker gets spawned to look at it, which decides what (if anything) to do. The user could create these "workflows" through a natural-language prompt that intelligently configured this loop.

Here's the walker that fired for your phone's motion sensors

def infer_motion_decision(
    snapshot: MotionSnapshot,
    motion_threshold: float,
    auto_act: bool
) -> MotionDecision by llm();

# I have a lot to say about `by llm` as well, but I encourage you to read the docs for more details.

walker MotionDecisionWalker {
    has motion_data: dict;
    has motion_threshold: float = 2.5;
    has auto_act: bool = True;
    has use_llm: bool = False;

    can evaluate with Root entry {
        snapshot = MotionSnapshot(accel_x=..., accel_z=..., gyro_alpha=...);
        intensity = snapshot.intensity();   # sqrt(ax² + ay² + az²)

        decision = MotionDecision(
            action_type = ("trigger" if self.auto_act else "notify")
                          if intensity > self.motion_threshold else "log",
            ...
        );

        # optionally hand the whole snapshot to an LLM 
        if self.use_llm {
            llm_decision = infer_motion_decision(
                snapshot, self.motion_threshold, self.auto_act
            );
            decision = llm_decision;
        }

        report { "type": decision.action_type, "message": ... };
    }
}

# from the Capacitor sensor callback:
resp = root spawn MotionDecisionWalker(
    motion_data=motion_data,
    motion_threshold=config.motionThreshold,
    auto_act=config.autoAct,
    use_llm=use_llm
);
Enter fullscreen mode Exit fullscreen mode

So the whole agent collapsed into one loop:

Our demo with fall detection is where all of it came together. If the phone is dropped, the accelerometer spikes past ~25 m/s², goes still for a few seconds. The walker spawns, crosses the confidence threshold for whether a user has fallen, it kicks off an autonomous chain: a 30-second countdown on screen, and if nobody intervenes, a real call is sent to an emergency contact.

Of course, you can use the tens of sensors that ship on modern phones to do literally anything else, like detecting when you're close to home and ordering a pizza if your agent decides you need a treat that day.

In the end, we demoed all of this, and ended up taking home first place in the Social Good track.

Here's a video of the agent in action:

If you'd like to play around with Hijac, the code is on Github.

Top comments (0)