24 Hours of AI Coding ・ /home/ksqsf
Disclaimer: This post was translated into English by an AI model. It may contain mistakes or awkward wording.
Whether from news hype, friends in the AI industry, or online discussions, everyone keeps proclaiming the importance of the AI era. Yet I had never taken it very seriously. Claude Code has been out for more than a year, and I had occasionally used self-paid APIs to write small programs or analyze unfamiliar repositories, but I had never used an AI coding tool for a truly serious project. Yes, LLMs can write code, but did we not already know that? Only recently, after seeing friends use coding agents to build some quite interesting things, did I seriously try AI coding for the first time. The experience was perhaps too successful for me, even shocking. While the memory is still fresh, I want to record my first AI coding project.
A Small Project, Cosmobot, and the Initial Handwritten Stage
I have many ideas I want to try, but often cannot turn them into executable code quickly enough. Recently, because another project chose Haskell, I was considering picking Haskell back up as my "native language of thought" and trying to master it. Since AI is popular, I decided to make an AI agent for practice. Its name is Cosmobot.
My expectations for this AI agent were not high. It was only a practice project, so I deliberately chose a fancier stack I had not used before. The two core components were:
Effectful: a high-performance effect system for structuring the whole system.
Streaming: a streaming data library for modeling message streams from different platforms, streaming LLM output, message chunking, and so on.
Effectful, or effect systems in general, lets us divide a system's side effects into different effects and combine multiple effects. An effect expresses a specific capability, such as a Log effect for logging or an LLM effect for calling an LLM. Functions must explicitly mark the effects they may produce:
mayLog :: (Log :> es) => Eff es ()<br>mayCallLLM :: (LLM :> es) => Eff es ()
Neither mayLog nor mayCallLLM directly invokes the corresponding capability. Instead, loosely speaking, it sends an effect request, which is intercepted by an effect interpreter; only then does the real action occur. In effect-system projects, main often contains a long chain of runSomeEffect calls:
main = runEff $<br>runLog .<br>runLLM .<br>runDatabase .<br>runFile $ do<br>actualCode
Streaming provides stream processing. My reason for using it was simple: a "message stream" should be a stream. Streams from different sources should be mergeable and then processed uniformly:
incomingMessagesTelegram :: (Telegram :> es, HTTP :> es) => Stream (Of TelegramMessage) (Eff es) ()<br>incomingMessagesQQ :: (QQ :> es, HTTP :> es) => Stream (Of QQMessage) (Eff es) ()<br>incomingMessages = incomingMessagesTelegram <> incomingMessagesQQ
With this idea, I implemented a Telegram effect that converted getUpdates into an update stream, and main repeatedly printed updates. Then the project was shelved.
First Six Hours: First Contact with Vibe Coding
This month, coincidentally, ChatGPT gave away a free month of Plus, including some Codex quota. After seeing friends build fun things with vibe coding, I wanted to try it too.
Last Saturday I used the Cosmobot project as the test subject. I wrote a simple AGENTS.md:
You are a super proficient professional Haskell hacker. You value correctness, conciseness, and above all, performance and robustness of a software system. You have superb taste and you hate messy code. You are a fan of algebraic domain design.
cosmobot is a unified chatbot framework. It is an industrial-grade codebase, but yet is simple enough to be read and modified by humans.
You are granted some autonomy to organize the codebase as you wish.
- Use `effectful` for managing the whole application.<br>- Use `streaming` for managing incoming messages.
My first use of AI coding surprised me. Codex performed extremely well on this project. It could always complete the requested tasks smoothly, with high efficiency and apparently good quality.
It one-shotted QQ support, which left a deep impression. It seemed thoroughly familiar with the OneBot API and implemented it in Haskell with ease.
I initially used dotenv for configuration for simplicity. When I asked it to migrate to TOML, it also one-shotted the change.
With only a handful of interactions, the project reached the point where Telegram and QQ were both integrated into a unified message pipeline.
Up to this point, I had barely read the code. I just kept asking Codex to add features, and Codex kept finishing them quickly. What surprised me more was that Codex's implementation had no obvious problems. I did not need to read the code: compile, run, and the behavior was what I wanted. This fast feedback loop was addictive, so much so that I forgot to commit progress. In the end, I only remembered to commit after the full agent tool-calling...