Skip to content
Theo Stene
Scroll

Software · AI · Robotics

Theo Stene

MSc student in Cybernetics and Robotics at NTNU, and co-founder & CTO of Pensa, an AI study assistant that lives inside Canvas. I take software from architecture to production.

Projects

Selected projects · 05

01 / 052026 — presentCo-founder & CTO

Pensa

An AI study assistant that lives as a tab inside Canvas and answers students' questions from the instructor's own course material, citing file and page, and says so when an answer goes beyond it. As CTO I architected it and own the full stack.

  • TypeScript
  • RAG (hybrid retrieval)
  • Canvas LTI 1.3
  • AWS
  • Azure Speech
  • Redis
  • Trigger.dev
  • Vercel

Figure 1.1: Product demo: Pensa running as a tab inside a Canvas course.

About this project

Pensa

Role
Co-founder & CTO
Period
May 2026 — present
Team
Three co-founders from NTNU
Status
Pilot project under NTNU Discovery
Stack
TypeScript · RAG (hybrid retrieval) · Canvas LTI 1.3 · AWS · Azure Speech · Redis · Trigger.dev · Vercel

What I built

Pensa is a Canvas-native (LTI 1.3) learning assistant. Students get a free chat over the whole course and three study modes tied to the lecture plan: Prepare gives an overview and a few questions before class, Catch-up gives a structured summary of a missed lecture with follow-up chat, and Reflection asks what stood out and what is still unclear after a lecture, with optional voice. Each of the four has its own retrieval-augmented generation pipeline over hybrid dense and keyword search.

Answers carry inline citations to file and page, and each one is labelled by its basis: Grounded in the course files, Out of files when Pensa goes beyond them, or Off topic when it declines. I own the full stack in TypeScript: the interface, ingestion of PDF, DOCX and PPTX material with Marker and Trigger.dev, LLM inference on AWS, speech on Azure, Redis caching, 2FA login, Docker-based local development, Vercel deployments, and separate dev and prod databases with promotion, backups and a monitoring panel.

Challenges

Grounding versus usefulness: deciding when the retrieved evidence is strong enough to call an answer grounded, and saying so honestly when it isn't. Retrieval and confidence thresholds are calibrated and evaluated against a gold set instead of tuned by feel.

Cost: I optimised every stage of the pipeline and routed queries through a custom prompt classifier, which cut the cost per answer by 93% at unchanged answer quality. An application-layer proxy enforces hard billing limits across AWS and Azure.

Real course material: courses with 50+ files and 800+ page textbooks have to be ingested reliably, and Pensa may only ever see what students can already see in Canvas. GDPR compliance was worked out together with external legal counsel.

What I learned

Most of the quality and cost of a RAG product is decided in the unglamorous stages — chunking, thresholds, routing and caching — and those need an evaluation set, not intuition. In education, trust is part of the product: citations, data handling and instructor control matter as much as the model's output.

Results

Pensa raised NOK 80,000 from Aneo and NTNU Discovery, was named NTNU Startup of the Month and started an NTNU pilot with about 500 students in autumn 2026.

pensa.no(opens in a new tab)

02 / 05Autumn 2026Web & analytics · team of two

Plantegreia

A fake-door market test for a device that would water houseplants with moisture from the air. For an NTNU Technology Management course we ran Facebook ads that led to a realistic web shop and measured how far visitors got toward buying, at four different prices.

  • Next.js
  • TypeScript
  • three.js
  • Blender (Python)
  • Prisma + SQLite
  • Docker
  • Ansible

Figure 2.1: The shop's home page opens with a scroll-driven 3D product story built in three.js.

About this project

Plantegreia

Role
Website, phone 3D story, analytics fixes
Team
Theo Stene and Emil Djupvik
Course
Technology Management, NTNU
Period
September — October 2026
Stack
Next.js · TypeScript · three.js · Blender (Python) · Prisma + SQLite · Docker · Ansible

The experiment

Plantegreia was a concept: a graphite stake for the plant pot that would capture moisture from the air in the room and release it as water near the roots. Instead of building it first, we tested whether anyone wanted it.

Facebook ads led visitors to a Norwegian-language shop that looked real. Each visitor saw one of four prices (349, 499, 699 or 990 kr), and every step toward a purchase was counted. Clicking pay led to a page explaining that the product isn't available yet. No order was placed, no money was ever taken, and the terms page disclosed that the shop was a market study.

What I built

My teammate Emil Djupvik built the first version of the shop, the price test, the analytics and the deploy pipeline. I redesigned the site and rewrote the home and product pages in Norwegian; built the phone layout of the scroll-driven three.js product story; wrote a Blender Python script that adds the water outlets to the 3D model; and added the quantity selector, the one-year water-guarantee badge, and tracking for quantity changes, FAQ opens, the guarantee terms and the gift link.

On the data side I fixed double counting in the all-visitor funnel with a schema change and a one-time SQL migration, added database backups before every migration in the Ansible deploy, and moved the site to plantegreia.no with permanent redirects that keep the ad tracking parameters.

Challenges

Counting a funnel when cookies are opt-in. Most visitors never accept cookies, so the site measures in two layers: everyone is counted anonymously (a salted hash of the IP for visits, and hourly totals per step and price group), while visitors who consent also get full session events. Merging the two without counting anyone twice took a schema change and a data migration.

On phones, some iOS browsers misreported the small viewport height and collapsed the 3D stage, so it is sized from the real window height instead.

Results

As of 16 October 2026: 1,004 visits, 145 clicks on the product call to action (14.4%), 20 buy clicks (2.0%) and 5 pay clicks (0.5%) across the four price groups. The funnel showed clearly where people dropped off — but with about 250 visits per group and zero to two pay clicks in each, it was far too little data to say anything about price. Knowing what an experiment can't tell you is a result too.

03 / 052026Solo project

Prompt Queue

A prompt engineering tool: a Windows desktop app for drafting and organising prompts that mix formatted text and screenshots, and copying them to Claude Code or a browser chat in exactly the format each one accepts. I use it every day.

  • Python
  • Markdown

Figure 3.1: Prompts queued in tabs like Features and Bugs. The editor mixes formatting and inline screenshots and saves everything as plain Markdown.

About this project

Prompt Queue

Role
Solo project
Period
September 2026
Platform
Windows desktop
Size
≈ 4,300 lines of Python
Stack
Python · Markdown

What I built

A small, native-feeling Windows app that works as a queue for the prompts I send to Claude Code. Prompts live in coloured tabs and pages, can be checked and copied in batches, and are archived instead of deleted when they're done. The editor supports bold, italic, headings and lists, screenshots are pasted inline, and everything autosaves as plain Markdown files with the images next to them.

The core feature is copying. In VS Code mode each image becomes an @-mention that Claude Code attaches automatically, and large images are shrunk to fit first. Browser and Terminal modes handle the different ways chat sites and the CLI treat the clipboard.

Challenges

The hard parts were all in the details of Windows and the tools. Tk's built-in undo drops embedded images, so undo and redo are snapshot-based on top of a Markdown model. Claude Code was observed to skip @-mentioned images over 256 KB, so oversized images go through an automatic downscaling ladder — palette PNG first, then JPEG — until they fit.

Browsers and the VS Code prompt box drop the text when an image is on the clipboard at the same time, which led to separate copy modes and a two-step paste for browsers. The clipboard itself needed a custom layer on pywin32, and saving reliably meant atomic writes that cope with files locked by other processes.

What I learned

How the Windows clipboard really works, how to keep a rich-text editor and a plain Markdown file in sync, and how much time a small tool built around your own workflow can save.

Results

In daily use since September 2026 as part of my Claude Code workflow — the screenshots for this website went through it. About 4,300 lines of Python in eleven modules, with no build step and two third-party dependencies.

04 / 052025 — 2026Solo developer

Creative Combat

A real-time, two-player mobile action game played on one shared phone: each round both players get a random attack and a random tactical ability and have to combine them to win. I built it alone — about 12,000 lines of C# in Unity, plus a Node.js and MongoDB backend.

  • C#
  • Unity
  • Node.js
  • Express
  • MongoDB
  • JWT

Figure 4.1: Gameplay: inviting a friend's account to my phone, then a round with two players on one screen.

About this project

Creative Combat

Role
Solo developer
Period
June 2025 — February 2026
Platforms
iOS and Android
Size
≈ 12,000 lines of C#, 1,800 of JavaScript
Stack
C# · Unity · Node.js · Express · MongoDB · JWT

What I built

The Unity client has nine abilities — from a lasso and teleport to a flamethrower and a radiation zone — a status-effect system with spreading burn, a lava arena with obstacles and power-ups, two-player multitouch controls on one screen, and shop, collection, ranking and feedback pages.

The backend is a Node.js and Express REST API on MongoDB. It provides passwordless, device-linked accounts with JWT auth, a friend-invite and lobby system, heartbeat-based online presence, Elo-style rankings, an ability shop paid for with in-game “brains” and priced on the server, rate limiting and version gating. A friend can join the host's phone with their own account, so one match updates both players' profiles.

Challenges

The game runs on one phone, but both players keep their own accounts — so the host's phone has to report the result for both. To limit what the host's phone can write, the server issues three kinds of tokens: a 7-day device-bound session token, a 2-hour lobby token for both players, and a 15-minute single-use match token that lets the host write only that one guest's result.

Keeping lobby state consistent between two phones without WebSockets meant polling and a client-side state machine that detects joins, finished games and half-completed joins, and repairs them. Later I added tiered rate limiting and migrated live usernames to case-insensitive lookups without collisions.

What I learned

I learned C# and JavaScript from scratch on this project. Beyond the languages: how to design authentication around a real usage pattern, how to keep distributed state consistent with simple polling, how to tune physics-heavy gameplay for mobile, and what shipping really involves — version gating, a privacy policy, store assets and App Store review.

Results

A complete iOS and Android game with online accounts, friend invites, rankings and an in-game shop, built alone over about nine months and submitted for App Store review.

05 / 052021School project

Music Marketplace

A two-sided marketplace for ghost production: producers upload exclusive, unreleased tracks, and buyers purchase them to release under their own name. A school project I took well beyond the brief, built with PHP and MySQL.

  • PHP
  • MySQL
  • HTML
  • CSS
  • Stack Overflow

Figure 5.1: Walkthrough: the catalogue, producer uploads, the admin review panel and the database.

About this project

Music Marketplace

Role
Full-stack developer
Period
2021
Context
School project
Status
Prototype on a local server
Stack
PHP · MySQL · HTML · CSS · Stack Overflow

What I built

Buy Ghost Production is a full-stack marketplace for ghost production, where producers sell finished, unreleased tracks that the buyer releases under their own name. Buyers browse a catalogue with an audio player on every row, search by song name, and filter or sort by genre, price and date added.

Producers log in, submit tracks through an upload form and manage their listings. An admin account reviews submissions, reads messages from the contact form and sees a genre breakdown of the catalogue. Server-side PHP with a MySQL database, and HTML and CSS on the front end.

Challenges

Combining free-text search with several filters and sort orders in one catalogue view, keeping three roles — visitors, producers and the admin — separate behind one login system, and making sure uploaded files and track details end up consistently in the database and in the admin's review queue.

What I learned

This was my first complete full-stack application: relational data modelling in MySQL, server-rendered pages in PHP, authentication with role-based access, file uploads, and how a moderation workflow ties the two sides of a marketplace together.

Results

A working prototype, shown end to end in the walkthrough video. It ran on a local server and was never launched publicly.

Contact

Let's talk.

I'm open to conversations about AI systems, robotics, research and internships. Email is the fastest way to reach me.