A hackathon pitch builder that brainstorms ideas, structures them into a typed pitch, researches them with real tools, and routes them to the right expert mentors — each stage one small Jac construct. Read the four snippets, then run the whole thing as a full-stack app.
curl -fsSL https://raw.githubusercontent.com/jaseci-labs/jaseci/main/scripts/install.sh | bash -s -- --standalone
export PATH="$HOME/.local/bin:$PATH"
jac install # resolves the LLM capability into .jac/venv
export OPENAI_API_KEY="your-key"
jac start app.jac # -> http://localhost:8000Each one is a single language construct. Together they cover most of what an agent needs to do.
Every file below runs on its own with jac run — no server, no UI, just the primitive. This is the exact source in the repo.
Write a signature, skip the body. The function name, parameter names, return type and its sem description become the prompt.
"""Step 1: Generate — the `by llm()` pattern.
The function signature (name + params + return type + docstring) IS the prompt.
No function body needed — the LLM fills it in at runtime.
Run: jac run step1_generate.jac
"""
def:pub brainstorm_ideas(interests: str, skills: str) -> str by llm();
sem brainstorm_ideas = "Brainstorm 3 creative hackathon project ideas based on the person's interests and skills. For each idea give it a catchy name and a one-sentence description. Make them fun, feasible in 24 hours, and genuinely useful.";
with entry {
ideas = brainstorm_ideas(
interests="music, coffee, sustainability",
skills="Python, React"
);
print("=== Step 1: Generate ===\n");
print(ideas);
}1. **EcoTune**: An interactive platform that curates personalized music playlists based on your coffee brewing process and sustainability goals. 2. **BrewCycle**: A web app connecting local coffee shops with customers looking to trade surplus coffee grounds for plants or compost. 3. **Melody Mug**: A coffee mug that plays a soothing soundscape when filled, encouraging mindful breaks while reducing waste.
Same as Generate, but the return type is a typed obj. The compiler enforces the schema, so the model cannot hand back malformed data.
"""Step 2: Extract — typed return from `by llm()`.
Same as Generate, but returns a typed obj instead of str.
The compiler enforces the schema — no JSON parsing, no "parse and pray."
Run: jac run step2_extract.jac
"""
enum Difficulty { BEGINNER, INTERMEDIATE, ADVANCED }
enum Track { WEB, MOBILE, AI_ML, GAME, OTHER }
obj HackathonPitch {
has title: str;
has problem: str;
has solution: str;
has tech_stack: list[str];
has wow_factor: str;
has difficulty: Difficulty;
has track: Track;
}
def:pub structure_pitch(raw_idea: str) -> HackathonPitch by llm();
sem structure_pitch = "Turn a raw hackathon idea into a structured, compelling pitch with a title, problem statement, solution, tech stack, wow factor, difficulty, and track.";
with entry {
pitch = structure_pitch(
"An app that matches leftover restaurant food with nearby shelters in real time"
);
print("=== Step 2: Extract ===\n");
print(f"Title: {pitch.title}");
print(f"Problem: {pitch.problem}");
print(f"Solution: {pitch.solution}");
print(f"Tech stack: {pitch.tech_stack}");
print(f"Wow factor: {pitch.wow_factor}");
print(f"Difficulty: {pitch.difficulty}");
print(f"Track: {pitch.track}");
}Title: Food Rescue Connect
Problem: Every day, restaurants throw away tons of edible food while
nearby shelters struggle to feed the hungry.
Solution: A mobile app that lets restaurants list leftover food in real
time, which nearby shelters can claim.
Tech stack: ['React Native', 'Node.js', 'Express', 'MongoDB', 'Google Maps API']
Difficulty: Difficulty.INTERMEDIATE
Track: Track.MOBILEenum fields make invalid values unrepresentable — difficulty can only ever be one of three values.Hand the model callable Jac functions. It runs a ReAct loop on its own: reason, call a tool, observe the result, repeat until it has enough.
"""Step 3: Invoke — `by llm(tools=[...])`.
Give the LLM callable functions. It runs a ReAct loop:
reason → call tool → observe result → repeat until done.
Run: jac run step3_invoke.jac
"""
import from tools { search_github, describe_tech_stack, estimate_build_time }
def:pub research_idea(idea: str) -> str by llm(
tools=[search_github, describe_tech_stack, estimate_build_time]
);
sem research_idea = "Research a hackathon project idea thoroughly before answering. Use the tools to find real data.";
with entry {
result = research_idea(
"AI-powered accessibility tool that generates live captions for deaf users at events"
);
print("=== Step 3: Invoke ===\n");
print(result);
}### Similar Open-Source Projects on GitHub - live-caption/live-caption — 2.1k stars - openai/whisper — 68k stars ### Recommended Tech Stack - Frontend: React + WebSockets for live caption streaming - Backend: Python (FastAPI), Whisper for speech-to-text ### Estimated Build Time - Duration: 24-36 hours with a team size of 4.
sem tells the model when to reach for each one; it decides the order and when to stop.The graph is the routing table. The model reads each node's description and visits the ones that fit. Every selected node spawns a worker that runs in parallel.
"""Step 4: Route + Spawn — `visit [-->] by llm()` + `flow spawn` + `wait`.
The LLM reads node descriptions and picks the best expert(s).
Each selected node flow-spawns a parallel AdviceWorker.
Walker collects all results by awaiting each with `wait`.
No if/else chains. The graph topology IS the routing table.
Run: jac run step4_route.jac
"""
# Worker walker — one per selected mentor, runs concurrently
walker AdviceWorker {
has pitch: str;
has mentor: str;
has advice: str = "";
can work with Root entry {
self.advice = get_advice(self.pitch, self.mentor);
}
}
def:pub get_advice(pitch: str, mentor: str) -> str by llm();
sem get_advice = "Give practical next-step advice for building this hackathon project. Suggest 3-5 concrete first tasks.";
# Expert nodes — LLM reads `description` to decide which ones to visit
node WebDevMentor {
has description: str = "Expert in web apps: React, APIs, databases, authentication, and full-stack deployment";
can respond with HackathonAdvisor entry {
t = flow root spawn AdviceWorker(pitch=visitor.pitch, mentor="Web Dev");
visitor.tasks = visitor.tasks + [t];
visitor.mentors = visitor.mentors + ["Web Dev"];
}
}
node MobileMentor {
has description: str = "Expert in mobile apps: iOS, Android, React Native, Expo, and mobile UX";
can respond with HackathonAdvisor entry {
t = flow root spawn AdviceWorker(pitch=visitor.pitch, mentor="Mobile");
visitor.tasks = visitor.tasks + [t];
visitor.mentors = visitor.mentors + ["Mobile"];
}
}
node AIMLMentor {
has description: str = "Expert in AI/ML: LLMs, embeddings, computer vision, and model APIs like OpenAI";
can respond with HackathonAdvisor entry {
t = flow root spawn AdviceWorker(pitch=visitor.pitch, mentor="AI/ML");
visitor.tasks = visitor.tasks + [t];
visitor.mentors = visitor.mentors + ["AI/ML"];
}
}
node GameDevMentor {
has description: str = "Expert in game dev: Unity, Godot, Pygame, game design, and interactive experiences";
can respond with HackathonAdvisor entry {
t = flow root spawn AdviceWorker(pitch=visitor.pitch, mentor="Game Dev");
visitor.tasks = visitor.tasks + [t];
visitor.mentors = visitor.mentors + ["Game Dev"];
}
}
node CollectNode {} # walker walks here after all mentor visits to await workers
walker HackathonAdvisor {
has pitch: str;
has tasks: list = []; # flow spawn handles (awaited with `wait`)
has mentors: list = []; # parallel list: mentor name per task
has results: list = [];
can route with Root entry {
collect = CollectNode();
here ++> WebDevMentor() ++> collect;
here ++> MobileMentor() ++> collect;
here ++> AIMLMentor() ++> collect;
here ++> GameDevMentor() ++> collect;
# Route: LLM reads each node's description, visits the best match(es).
# Each visited node flow-spawns an AdviceWorker — all run in parallel.
visit [-->] by llm(incl_info={"Hackathon pitch": self.pitch});
# `visit` is deferred, so results can't be collected here — the walker
# arrives at CollectNode only after every selected mentor has run.
visit collect;
}
can collect with CollectNode entry {
# Await each parallel worker and collect results
for i in range(len(self.tasks)) {
w = (wait self.tasks[i]) as AdviceWorker;
self.results = self.results + [{"mentor": self.mentors[i], "advice": w.advice}];
}
}
}
with entry {
advisor = root spawn HackathonAdvisor(
pitch="A real-time multiplayer trivia game with AI-generated questions using WebSockets and React"
);
print("=== Step 4: Route + Spawn ===\n");
for r in advisor.results {
print(f"--- {r['mentor']} ---");
print(r["advice"]);
print("");
}
}=== Step 4: Route + Spawn === --- Web Dev --- 1. Set up the project repository and initialize a React application. 2. Research and integrate a WebSocket library (e.g. Socket.IO). ... --- AI/ML --- 1. Implement a basic AI model or API for generating trivia questions. ... --- Game Dev --- 1. Design the basic UI layout: questions, answer options, player scores.
The same four primitives wired into a full-stack app — server logic in app.jac, React-style client components in frontend/. One language, one command.
jac start app.jac
# -> http://localhost:8000
# walkers are exposed as HTTP endpoints automatically:
# POST /walker/run_brainstorm {interests, skills}
# POST /walker/run_structure {raw_idea, interests, skills}
# POST /walker/run_research {idea, title, solution}
# POST /walker/run_route {title, problem, solution, ...}A client component imports server walkers directly and spawns them — the HTTP call, serialization and typing are generated for you.
sv import from ...app { run_brainstorm }
async def do_brainstorm() -> None {
result = root spawn run_brainstorm(
interests=interests, skills=skills
);
ideas = result.reports[0];
}