Writing
I Didn’t Want AI to Make Me Faster. I Wanted It to Make Me Better.
AI made me faster, but I started worrying it was making my learning less durable. I could ask ChatGPT or Claude about a terminal command, a bug, a...
AI made me faster, but I started worrying it was making my learning less durable.
I could ask ChatGPT or Claude about a terminal command, a bug, a framework, a compiler concept, or some architecture decision and understand it in the moment. The answer would click. I would use it. The project would move.
Then a few days later, I would need to ask again.
Not because the answer was bad. The answer was usually good. The problem was that it had passed through the chat, not through me.
That became the obsession behind everything I built after that:
How do you use AI to learn faster without slowly outsourcing the parts of learning that make you good?
Stop your deskilling
There is a mode of AI coding that feels productive but is quietly dangerous.
You prompt. The model writes. You accept. You run it. It errors. You paste the error. The model patches. You accept again. The app moves forward.
But did you?
Every great engineer I admire became good through curiosity, projects, hands-on systems work, debugging pain, reading code, writing code, breaking things, fixing things, and slowly building an internal model of how software behaves.
They got better because the work passed through them.
AI can skip that. Not always, not maliciously, but if you let it, AI can remove the exact friction that used to create understanding.
I did not want AI to become a prosthetic for thinking. I wanted it to become a multiplier for thinking.
That distinction became the whole project.
The first shape: Worldview
The first version of this idea had nothing to do with coding. It was called Worldview.
The idea was: if AI chats are where I am forming my technical worldview, those chats should not disappear into a chronological graveyard. They should become a living notebook.
Not a notes app where I manually paste important things. A system that watches what I learn across ChatGPT, Claude, Gemini, and other AI tools, then organizes that knowledge automatically.
If I ask about Wayland today, X11 next week, and Linux compositors later, the system should understand that these are not isolated conversations. They belong under a bigger topic: display systems, rendering, input, protocols, and desktop architecture.
The notebook should reorganize as my understanding improves. A topic may start as its own thing, then later become a subtopic. Two ideas may look unrelated, then later collapse into the same abstraction.
That was the first important primitive:
Learning is not a timeline. Learning is a graph.
Worldview also had a contextual mini-chat idea. If one paragraph in an AI answer confused me, I did not want to pollute the main conversation. I wanted to select that exact part, open a small side-chat, and ask about that slice with surrounding context attached.
But the deeper feature was this: the system should detect what deserves to become actual knowledge.
Some AI usage is disposable. You ask something once, use it, and never need it again. But sometimes AI gives you a command, workflow, debugging move, mental model, or concept you should not keep outsourcing.
Worldview was supposed to notice those moments and say:
You used this today, but this is not disposable. You should actually learn this.
Under the hood, I started building a small retrieval layer called way-engine. The goal was to place concepts under parent concepts, retrieve related ideas, and slowly form a structured map of what I was learning.
Worldview was too broad. It wanted to organize my entire intellectual life: browser extension, notebook, graph, RAG engine, topic hierarchy, concept extraction, dashboard, review system.
Too much.
But the core primitive survived:
AI learning needs memory.
The second shape: Vibe Type
The next version narrowed the problem to code.
AI coding tools were becoming insanely good. Ask for a feature, get a diff. Ask for a refactor, get files changed. Ask for a bug fix, get something that looks correct enough to try.
That is powerful, but it creates a weird failure mode:
You can ship code without becoming the kind of person who could have written it.
The project moves. The app runs. The diff looks real. The dopamine arrives. But your internal model may not have changed much.
That became Vibe Type.
The primitive was simple:
When AI writes code for you, do not just accept the diff. Type it.
Not because typing characters is holy. This was never about becoming a stenographer for Claude. It was about adding friction at the exact moment where learning usually disappears.
When you type code yourself, even if AI originally generated it, your brain does something different. You notice names. You feel the API shape. You see imports. You hit the syntax. You wonder why the function is async. You wonder why the error is handled there. You notice the abstraction boundary.
Vibe Type was a VS Code extension around that loop. It looked at git diffs, found added lines, split them into chunks, and turned them into typing practice. As you typed, it verified each character, showed mistakes, tracked progress, and explained the current line.
It also had a knowledge base so the things you learned from a diff could be saved and revisited later.
Worldview tried to make AI conversations become knowledge.
Vibe Type tried to make AI-generated code become knowledge.
That was sharper, but it still had a flaw: it was downstream of AI. The model had already written the solution. Vibe Type was trying to recover learning after the fact.
If the goal is learning, the loop should not begin after the code already exists.
The system should shape the work before the diff.
Chat was the wrong interface
Before the next version, I kept asking ChatGPT how to learn things. It would answer like a book.
That is not an insult. Books are great. But chat has a specific shape:
ask -> explain -> ask -> explain -> request plan -> get plan -> request code -> get code
Even if you ask it to be a mentor, the interface keeps pulling it back toward being an answer machine.
Coding does not happen in a chat box. Coding happens inside a workspace. You have files open, terminal output, errors, tests, half-written code, previous decisions, local context, project structure, and weird constraints that only make sense inside the repo.
A chat model can talk about that, but it does not naturally live inside it.
That was the next realization:
The mentor had to live inside the work.
The real unit of software learning was not a note or a diff. It was a project.
You do not truly learn React by reading a note about hooks. You do not learn Rust by typing one generated diff. You do not learn compilers by asking ten scattered questions about lexers and parsers.
You learn by building something real enough that the concepts have to touch each other.
That became the first version of Construct.
Construct 1.0: right idea, wrong representation
The first Construct tried to take a project goal and generate a personalized learning workspace.
Something like:
"Build a C compiler in Rust"
"Create a Python todo CLI from scratch"
"Learn module bundlers by building one in TypeScript"
Then the system would generate the project path: lessons, exercises, concept checks, hidden tests, tutor support, file structure, and a persistent knowledge graph.
This was the first version that felt like it was aiming at the correct surface area: not notes, not snippets, not typing practice, but real projects.
The thesis was right.
The implementation was not.
Construct 1.0 asked the model to do too much at once. It had to design the curriculum, generate lessons, decide project structure, create exercises, write hidden tests, maintain learner state, update the knowledge graph, and behave like a tutor.
That sounds exciting until you try to make it reliable.
Then it becomes chaos wearing a nice UI.
The model would forget fields. It would generate structures that did not match the UI. It would make lessons that did not align with exercises. It would imply a concept had been taught when it had not. It would create tests disconnected from the expected implementation.
The failure was not simply “LLMs hallucinate.”
That explanation is too shallow.
The real failure was architectural:
I was asking a probabilistic system to directly generate complex product state without giving it a stable intermediate representation.
That is not engineering. That is begging.
And I begged a lot.
More prompt detail. More schema. More validation. More retries. More repair. More “follow this exactly.”
At some point, adding more instructions becomes a ritual.
The system was telling me something:
The idea was not wrong. The shape was wrong.
The file epiphany
After enough failed attempts, I left the project alone for a while.
Then one day the missing abstraction became obvious:
What if Construct was not an AI that directly generated a learning project?
What if Construct was a player?
And what if the thing it played was a file?
That changed everything.
Instead of asking the model to generate the entire runtime state of a learning experience, it would generate a structured .construct file. The IDE would parse it, validate it, and execute it.
The file became the protocol. The IDE became the runtime. The model became a compiler into that runtime.
This was the first time the system felt engineered instead of hopeful.
And the tape was not just “a list of steps.” It was a small learning DSL.
A tape could start like this:
@construct spec="tape-0.4.1"
@id "daytona-from-scratch"
@title "Daytona from Scratch"
@description "Build a local Daytona-like sandbox runtime in TypeScript while learning agent infrastructure, containers, lifecycle, snapshots, SDK boundaries, and execution safety."
@root "."
@requires "ghost-edit recall-check focus-ranges reference-cards knowledge-cards construct-interact reply-recall tool-using-interact live-steps mastra-verify real-terminal git-milestones"
That header did a lot of work.
It declared the tape spec version, project identity, root, and runtime capabilities the IDE needed to support. This mattered because the file was not just content. It was asking the runtime for features: ghost editing, recall checks, focus ranges, reference cards, knowledge cards, real terminal, git milestones, verification, and live interaction.
Then the tape could scaffold the project:
::files
::file path="package.json"
```json
{
"name": "miniday-runtime",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "tsx src/index.ts",
"test": "vitest run",
"typecheck": "tsc --noEmit"
}
}
::file path="src/domain/types.ts"
export {};
This was important. The tape did not only say “teach sandbox runtimes.” It created the initial workspace shape. Empty files were intentional. The learner would fill them in over time. The structure itself was part of the curriculum.
Then came concept cards:
```construct
::concept id="sandbox.lifecycle" title="Sandbox lifecycle" kind="core-concept" tags="lifecycle state-machines runtime"
::summary
A sandbox lifecycle describes what states a sandbox can be in and which operations are legal in each state.
::end
::why
Without lifecycle state, every operation becomes ambiguous.
Can you execute code after deletion? Can you snapshot a stopped workspace? Can the same sandbox be deleted twice?
::end
::example
```ts
type SandboxStatus = "creating" | "running" | "stopped" | "deleted";
Do not treat lifecycle as a UI label.
Lifecycle is a guardrail for behavior.
title="OCI Runtime Specification" url="https://specs.opencontainers.org/runtime-spec/" why="Use this for the real container-runtime idea that lifecycle is a protocol, not vibes."
This is where the architecture became interesting.
The tape was doing multiple jobs at once:
1. It defined the project scaffold.
2. It declared what runtime features were required.
3. It encoded concepts as first-class objects.
4. It attached summaries, examples, common mistakes, and documentation.
5. It gave the IDE enough structure to render learning UI, not just text.
6. It gave the agent a strict target format.
That is why I started thinking of tapes as **executable pedagogy**.
A markdown tutorial is text.
A `.construct` tape is a learning program.
It tells the runtime what project exists, which files exist, what concepts matter, how those concepts should be explained, what mistakes to watch for, what docs to attach, and which IDE capabilities are needed to teach the project.
The system moved from:
```txt
"Please create a good learning experience."
to:
"Emit a valid program for this learning runtime."
That was a massive upgrade.
Tapes worked, then became the bottleneck
Tapes made Construct work in a way Construct 1.0 never did.
But they also exposed how hard the problem really was.
A good tape was not just a list of steps. It had future context baked into it. If the learner would use an abstraction in step 9, the tape may need to introduce the mental model in step 3, show a small example in step 5, and ask the learner to apply it in step 9 like it was natural the whole time.
That required real teaching structure.
I researched pedagogy and Socratic teaching because a good learning step is not “here is code, type it.” A good step builds the mental model first.
For example, in a TypeScript project, the right path is often not to immediately spray components everywhere. You define the types first. Name the domain. Understand the data shape. Build the functions. Wire the UI. Test behavior. Then refactor once the shape is visible.
Good teaching has sequencing. It introduces an idea, uses it, revisits it, and later asks the learner to transfer it somewhere else.
Tape 0.1 proved the basic path. Tape 0.2 tightened structure. Tape 0.3 became more pedagogical: concept cards, references, recall checks, richer steps. Tape 0.4 moved toward Socratic learning and runtime adaptation.
For a while, this worked really well. I could give a strong model the tape spec and ask it to create a project. It would research the domain, plan the course, attach concepts, write tasks, scaffold files, include docs, and produce something the IDE could play.
Compared to Construct 1.0, it was night and day.
But creating a great tape was itself a hard AI task.
The model had to research enough, scope the project correctly, order concepts, obey the grammar, avoid giving away too much code, and make sure every step connected.
If a user says, “I want to build exFAT from scratch,” what does that mean?
A production filesystem? A toy filesystem? A disk image parser? A learning project about directory entries and allocation tables? C? Rust? A weekend build? A systems course?
The system had to scope ambition without killing curiosity.
That is hard.
Evidence, traces, and adaptation
Static tapes solved representation, but learning is not static.
A learner may understand something instantly. Another learner may get stuck on a concept the tape assumes is obvious. Someone may make a mistake that reveals a deeper misconception. Someone may need a smaller step. Someone may need a question, not another explanation.
So the next primitive was evidence.
Instead of only tracking whether someone clicked next, Construct needed to record what happened during a step:
learner diff
terminal output
files opened
errors hit
tests run
references used
hints requested
concepts involved
submitted answer
Learning products usually track completion.
Completion is cheap.
A checkbox does not mean understanding. Watching a video does not mean you can apply the concept. Reading an explanation does not mean the abstraction entered your head.
Construct needed to know what the learner demonstrated.
If a learner writes a function, the diff is evidence. If they run tests and fix errors, the terminal trace is evidence. If they keep opening previous files to copy a pattern, that is evidence too.
So I built a version where after each step, the system collected a performance trace and gave it to an agent. The agent inspected the trace, judged how the learner did, found gaps, and generated the next step.
This was much better than adapting based on vibes.
But it still felt heavy. Using a model to rewrite the path after every small interaction was expensive and awkward. It felt like one system was playing a tape while another system kept patching the tape because the first system was not alive enough.
That was the clue.
Maybe the answer was not “make tapes more dynamic.”
Maybe the answer was a runtime where guidance could be dynamic, while the underlying primitives stayed structured.
The agent was not the product
The current Construct has agents, but I did not add agents because agents are trendy.
I arrived at agents by trying very hard to make the system less magical.
An unconstrained agent is magic in the worst way. It can do anything, which means it can also do the wrong thing in ten places before you understand what happened.
By the time Construct needed an agent, the hard work was not “make an agent.”
The hard work was building the environment that made the agent useful:
memory
tasks
concept state
workspace tools
file opening
terminal control
diff review
learner evidence
project state
The agent was not the product. The agent was the control loop over those primitives.
An agent without memory is just chat. An agent without tools is just advice. An agent without traces is guessing. An agent without task boundaries becomes a code generator again. An agent without concept state teaches randomly. An agent without a runtime becomes vibes.
The current version worked better because the agent finally had something real to operate on.
It could inspect files, open the right place, highlight lines, run commands, create a practice task, wait for the learner to implement it, compare the compact diff, update memory, and choose the next teaching move.
That is not magic.
That is a loop.
The gritty part: context, terminal, and state
Underneath, this became a context engineering problem.
A mentor is only useful if it remembers what matters without dragging every previous token forever. Long coding sessions are brutal for agents. They forget why the project exists, lose the thread of the architecture, repeat explanations, bloat context, and treat every task like an isolated event.
That is not acceptable for a learning environment.
A learner may work on a project for days. The system has to reopen tomorrow and still know:
what are we building?
what stack are we using?
what commands run it?
what concepts has the learner seen?
what are they weak at?
what tasks were completed?
what mistakes keep appearing?
what should happen next?
That is why Construct uses durable local memory files:
research.md -> compiled domain and stack research
project.md -> architecture, commands, conventions
path.md -> active goals, blockers, next steps
learner.md -> weak spots, mastery, evidence
This is not chat history. Chat history is a transcript. Memory is the distilled state needed to continue the work.
But memory creates its own engineering problems. If a file grows forever, it becomes useless. If the agent forgets to update it, the whole point is lost. If summaries are too aggressive, important evidence disappears. If everything is too fragmented, the agent reads too much.
So memory maintenance has to be part of the loop, not an optional cleanup task.
The workspace also had to be real.
If the goal is learning real engineering, the product cannot use a fake terminal or toy editor. The terminal is where software pushes back. Tests fail there. Builds break there. Logs stream there. Commands become muscle memory there.
Construct needed a real shell process connected to the machine, with output feeding back into the learning loop.
As the app grows, internal systems also need to speak cleanly: agent runs, terminal output, task lifecycle, editor focus, tool calls, traces, logs, protocol events, and eventually LSP-style clients and servers.
That means a sane event/log bus is not architecture astronaut stuff. It is what keeps the system inspectable.
And for a product about making learning inspectable, the internals cannot be chaos.
What Construct is now
The current shape of this work is Construct, available at tryconstruct.cc.
Construct is an AI-native desktop IDE for learning by building real software.
But the important part is not that there is a chat panel beside an editor. The important part is the loop:
research project
create task
learner writes code
capture diff
review evidence
update memory
adapt next step
At project creation, Construct can research the domain, stack, docs, terminology, libraries, and caveats. That becomes durable project memory instead of floating in chat history.
Inside the workspace, the mentor can inspect files, search the project, run terminal commands, scaffold practice tasks, open files, highlight exact lines, and help the learner reason through implementation.
Practice tasks are the core learning unit. The mentor scaffolds a task. Construct captures the baseline. The learner writes code. The learner submits. Construct generates a compact diff. The mentor reviews the diff and updates memory based on evidence.
That is the thread from Vibe Type coming back in a better shape.
Vibe Type said:
Do not let AI diffs bypass the learner.
Construct says:
Make learner-authored diffs the proof of learning.
Same idea. Better body.
What survived every rewrite
Looking back, Worldview, Vibe Type, and Construct were not random pivots. They were the same idea trying to find the right shape.
Worldview contributed memory. Vibe Type contributed authorship. Construct 1.0 contributed project-level learning. Tapes contributed representation. Performance traces contributed evidence. The current system composes them with tools, context, tasks, and adaptation.
That is why the product feels clearer now.
Not because one day I “added an agent.”
Because each failed shape left behind a primitive.
The final product is the composition of everything that survived.
The thing I believe now
AI-assisted learning needs systems, not just models.
The model is important, obviously. But the model alone is not enough.
The learning system needs memory, because chat history is not understanding. It needs project context, because real skills form inside real work. It needs authorship boundaries, because the learner has to do the work that proves learning. It needs tasks, because practice needs shape. It needs traces, because adaptation needs evidence. It needs tools, because teaching inside software requires touching the workspace. It needs constraints, because an unconstrained assistant will always drift back toward doing the work for you.
And yes, at the right layer, it needs an agent.
But the agent is not the magic.
The system around the agent is the product.
AI learning does not become good by making the AI more powerful. It becomes good by designing the loop around what the learner must retain, attempt, prove, and revisit.
That is what I am building.
Not just a better coding assistant.
A better way to learn with AI.