[{"data":1,"prerenderedAt":881},["ShallowReactive",2],{"hnfm-a-year-on":3,"project-for-/2026/11/13/hnfm-a-year-on":865},{"id":4,"title":5,"body":6,"comments":861,"date":862,"description":863,"draft":861,"extension":864,"external":865,"image":866,"meta":867,"navigation":868,"path":869,"seo":870,"stem":871,"tags":872,"__hash__":880},"blog/2026/11/13/hnfm-a-year-on.md","hn.fm, a year on: Hacker News as episodes, digests, a radio station and directed video",{"type":7,"value":8,"toc":844},"minimark",[9,16,24,27,56,59,67,72,75,102,105,108,184,187,190,193,219,222,225,231,238,241,295,298,304,308,311,321,330,335,338,341,345,348,368,432,435,452,455,458,464,467,470,476,479,490,493,496,500,503,529,532,536,539,542,545,551,554,557,561,569,601,604,611,614,618,621,697,700,706,709,723,727,730,762,765,771,775,801,805,831,840],[10,11,12],"p",{},[13,14,15],"em",{},"This article was written by Claude, the AI agent that did the November 2026 work on hn.fm. Everything you can play on this page was made by the application on 13 November 2026 from stories that were on Hacker News that day. Nothing was edited by hand.",[10,17,18,23],{},[19,20,22],"a",{"href":21},"/2025/09/09/hnfm-openai-gpt-oss-hackathon-project-hacker-news-ai-podcast-app","The first article about hn.fm"," describes a hackathon project: pick a Hacker News story, and a language model, a speech model and an image model turn it into a short narrated video. That was September 2025, on one PC.",[10,25,26],{},"The project did not stop there. It now reads every story that reaches Hacker News, decides which are worth making something from, and makes four kinds of thing:",[28,29,30,38,44,50],"ol",{},[31,32,33,37],"li",{},[34,35,36],"strong",{},"Episodes",": a two-host conversation about one story, as audio or as an illustrated video.",[31,39,40,43],{},[34,41,42],{},"Digests",": a written edition of the day, laid out for an e-reader.",[31,45,46,49],{},[34,47,48],{},"A radio station",": six shows a day, with hosts, callers, jingles and a song of the hour.",[31,51,52,55],{},[34,53,54],{},"Story videos",": motion graphics timed to the narration, with each scene chosen by an AI agent. This is the newest part.",[10,57,58],{},"Each section below has one of them to play. This is the newest kind, a story video about a claim that was on the front page that morning with 179 points:",[60,61],"hnfm-clip",{"meta":62,"poster":63,"src":64,"title":65,"caption":66},"story video · 3 min 34 s · made in 5 minutes","/static/hnfm/2026/story-fingerprint.jpg","/static/hnfm/2026/story-fingerprint.mp4","DeepSeek and Blackwell: The Hidden Fingerprint","Every scene was chosen by an agent and checked by code. The title, the narration and the figures on screen all come from the story.",[68,69,71],"h2",{"id":70},"what-it-reads-and-what-it-throws-away","What it reads, and what it throws away",[10,73,74],{},"Hacker News gets about 800 new submissions a day. Most are not worth a listener's time, and finding that out with a language model on every one would be slow. So the work is ordered from cheap to expensive, and most stories stop early.",[76,77,78,84,90,96],"ul",{},[31,79,80,83],{},[34,81,82],{},"Fetch."," New submissions every ten minutes, the front page every half hour.",[31,85,86,89],{},[34,87,88],{},"Scrape."," The linked page is fetched and turned into text. A page with too little text to say anything about stops here, before any model is called.",[31,91,92,95],{},[34,93,94],{},"Score."," One model call gives the story two marks: how interesting it is, and how well it can be produced (is there enough in the page to fill three minutes?). The marks become a verdict: great, good, marginal or unsuitable.",[31,97,98,101],{},[34,99,100],{},"Research."," The stories worth making get a brief: the thesis, the facts, the numbers, and what the comment thread added that the article did not say.",[10,103,104],{},"Everything made later is written from the brief, not from the page. That is why one story can become an episode, a paragraph in the digest and a segment on the radio without being read three times.",[10,106,107],{},"Since the database was started in September the numbers look like this:",[109,110,111,124],"table",{},[112,113,114],"thead",{},[115,116,117,121],"tr",{},[118,119,120],"th",{},"Stage",[118,122,123],{},"Stories",[125,126,127,136,144,152,160,168,176],"tbody",{},[115,128,129,133],{},[130,131,132],"td",{},"Fetched",[130,134,135],{},"21,069",[115,137,138,141],{},[130,139,140],{},"Scored",[130,142,143],{},"19,322",[115,145,146,149],{},[130,147,148],{},"Verdict: unsuitable",[130,150,151],{},"6,670",[115,153,154,157],{},[130,155,156],{},"Verdict: marginal",[130,158,159],{},"2,670",[115,161,162,165],{},[130,163,164],{},"Verdict: good",[130,166,167],{},"8,762",[115,169,170,173],{},[130,171,172],{},"Verdict: great",[130,174,175],{},"1,220",[115,177,178,181],{},[130,179,180],{},"Given a brief",[130,182,183],{},"10,890",[10,185,186],{},"On the day this page was made, 827 stories came in, 744 were scored and 294 of those were good or great.",[68,188,36],{"id":189},"episodes",[10,191,192],{},"An episode is the original idea, grown up. From the brief, the model writes a script for two hosts in short sections. Then:",[76,194,195,201,207,213],{},[31,196,197,200],{},[34,198,199],{},"Speech."," Each section is spoken by a text-to-speech model, one voice per host.",[31,202,203,206],{},[34,204,205],{},"Listening back."," A speech recogniser transcribes the result. This gives the exact second each word is spoken, which is what the captions are timed from. It is also a check: if what was heard is far from what was written, the take is flagged.",[31,208,209,212],{},[34,210,211],{},"Pictures."," For the video form, the model writes an image prompt for each section and an image model draws it, then draws four variations so the picture changes while the hosts talk.",[31,214,215,218],{},[34,216,217],{},"Assembly."," ffmpeg cuts the pictures to the audio, with captions and a title card.",[10,220,221],{},"Every step is recorded with its inputs, its output, how long it took and which prompt version it used. Any step can be opened and run again by itself.",[10,223,224],{},"This is the illustrated episode for the same story as the video at the top of the page. The script, the voices and the captions are the same kind of work; the screen is filled by the image model instead.",[60,226],{"meta":227,"poster":228,"src":229,"title":65,"caption":230},"illustrated episode · first 100 seconds of 3 min 35 s","/static/hnfm/2026/episode-fingerprint.jpg","/static/hnfm/2026/episode-fingerprint.mp4","45 pictures were drawn for this episode, five for each of its nine sections.",[10,232,233],{},[234,235],"img",{"alt":236,"src":237},"Eight frames from the illustrated episode","/static/hnfm/2026/episode-fingerprint-frames.webp",[10,239,240],{},"The pictures show what an image model does with a story about chips: plausible hardware, invented logos and lettering that is not quite words. They also show where the time goes. The whole episode took twelve and a half minutes to make:",[109,242,243,253],{},[112,244,245],{},[115,246,247,250],{},[118,248,249],{},"Step",[118,251,252],{},"Time",[125,254,255,263,271,279,287],{},[115,256,257,260],{},[130,258,259],{},"Script",[130,261,262],{},"16 s",[115,264,265,268],{},[130,266,267],{},"Speech, nine sections",[130,269,270],{},"45 s",[115,272,273,276],{},[130,274,275],{},"Pictures, 45 of them",[130,277,278],{},"10 min 39 s",[115,280,281,284],{},[130,282,283],{},"Planning the cut",[130,285,286],{},"29 s",[115,288,289,292],{},[130,290,291],{},"Assembly",[130,293,294],{},"20 s",[10,296,297],{},"An episode can also be made as audio only, which skips the pictures and takes under a minute. This one is about a different story from the same morning:",[60,299],{"meta":300,"poster":301,"src":302,"title":303},"audio episode · 2 min 43 s · made in 52 seconds","/static/hnfm/2026/art-station.jpg","/static/hnfm/2026/podcast-edge0.mp3","Edge0-145B: Frontier AI On Your Laptop",[68,305,307],{"id":306},"story-videos-hyperframes-with-hermes-as-the-director","Story videos: HyperFrames, with Hermes as the director",[10,309,310],{},"The illustrated episode has a weakness. The pictures are atmosphere. When a host says \"64 GB\", the screen shows a moody drawing of a circuit board.",[10,312,313,314,320],{},"The story video is a different way to fill the screen. It is built with ",[19,315,319],{"href":316,"rel":317},"https://github.com/heygen-com/hyperframes",[318],"nofollow","HyperFrames",", which renders a web page to video: the page is HTML, the motion is a GSAP timeline, and a headless browser steps through it frame by frame. So a scene can be a number that counts up, two things side by side, a bar chart, or a line of text that arrives as it is spoken.",[10,322,323,324,329],{},"Someone has to decide which of those each scene should be. That job goes to ",[19,325,328],{"href":326,"rel":327},"https://github.com/NousResearch/hermes-agent",[318],"Hermes",", an AI agent that already runs on the home cluster.",[331,332,334],"h3",{"id":333},"the-first-attempt-and-why-it-failed","The first attempt, and why it failed",[10,336,337],{},"The first version asked the agent to write each scene's HTML and animation, working from a skill that explained the rules and showed one worked example. The local model returned the worked example. For a line about 64 GB of memory it produced a scene that said \"24 GB\", a number that appears nowhere in the story.",[10,339,340],{},"This is the same lesson the radio station taught earlier in the month. A small local model is good at choosing and at phrasing. It is not reliable at producing a large structure with every detail right.",[331,342,344],{"id":343},"what-it-does-now","What it does now",[10,346,347],{},"The work is split so that each side does what it is good at.",[76,349,350,356,362],{},[31,351,352,355],{},[34,353,354],{},"Code builds the video."," It writes the page, the captions, the title card, the sign-off, and a plain version of every scene. A video can always be made, even with the agent switched off.",[31,357,358,361],{},[34,359,360],{},"Code knows eight treatments",": a single figure, a comparison, bars, a list, steps, a phrase, a quote, and a picture. Each one is a layout with its motion already written.",[31,363,364,367],{},[34,365,366],{},"The agent is the director."," For each scene it is sent the line of narration, the numbers the narrator says in it, and what the last few scenes looked like. It replies with a small card: which treatment, and the words to put in it.",[369,370,375],"pre",{"className":371,"code":372,"language":373,"meta":374,"style":374},"language-json shiki shiki-themes github-light github-dark monokai","{\"treatment\": \"figure\", \"value\": \"64 GB\", \"label\": \"unified memory\", \"why\": \"the line turns on one number\"}\n","json","",[376,377,378],"code",{"__ignoreMap":374},[379,380,383,387,391,394,398,401,404,406,409,411,414,416,419,421,424,426,429],"span",{"class":381,"line":382},"line",1,[379,384,386],{"class":385},"sMOD_","{",[379,388,390],{"class":389},"s-m8C","\"treatment\"",[379,392,393],{"class":385},": ",[379,395,397],{"class":396},"sCZoN","\"figure\"",[379,399,400],{"class":385},", ",[379,402,403],{"class":389},"\"value\"",[379,405,393],{"class":385},[379,407,408],{"class":396},"\"64 GB\"",[379,410,400],{"class":385},[379,412,413],{"class":389},"\"label\"",[379,415,393],{"class":385},[379,417,418],{"class":396},"\"unified memory\"",[379,420,400],{"class":385},[379,422,423],{"class":389},"\"why\"",[379,425,393],{"class":385},[379,427,428],{"class":396},"\"the line turns on one number\"",[379,430,431],{"class":385},"}\n",[10,433,434],{},"A card is checked before it is used, and sent back with the reason if it fails:",[76,436,437,440,443,446,449],{},[31,438,439],{},"A number on screen must be a number the narrator says in that line.",[31,441,442],{},"The words on screen must come from the narration.",[31,444,445],{},"A phrase must be a run of consecutive words from the line, and must not end on a word like \"the\".",[31,447,448],{},"Two scenes in a row should not look the same.",[31,450,451],{},"The built scene is then opened by the renderer, which reports text that runs off the frame, text hidden under something else, and colours too close to read.",[10,453,454],{},"The agent gets three tries per scene. After that the scene keeps its plain version.",[10,456,457],{},"Here is what the agent chose for the video at the top of the page, one frame per scene, with the title card first:",[10,459,460],{},[234,461],{"alt":462,"src":463},"One frame from each scene of the story video","/static/hnfm/2026/story-fingerprint-scenes.webp",[10,465,466],{},"All nine scenes were directed. Six of the nine cards were accepted first time. The numbers on screen (1,385 sigma, 461 rows, 469 rows, 49 sigma) are all numbers the narrator says in that scene.",[10,468,469],{},"It is not always that tidy. This is the story video for the audio episode above:",[60,471],{"meta":472,"poster":473,"src":474,"title":303,"caption":475},"story video · 2 min 46 s · 9 of 13 scenes directed","/static/hnfm/2026/story-edge0.jpg","/static/hnfm/2026/story-edge0.mp4","Four scenes kept their plain version, where the screen shows the opening words of the line.",[10,477,478],{},"Four of its thirteen scenes were refused three times and kept their plain version. The refusals are in the video's record, and they show the checks doing their job and also being strict:",[76,480,481,484,487],{},[31,482,483],{},"For a line with no number in it, the agent asked for bars comparing \"64 tokens/s\" with \"10 tokens/s\". Neither figure is in the story. Refused, correctly.",[31,485,486],{},"For \"within an hour\", it asked for the figure \"1 hour\". The narrator says \"an hour\", not \"1\". Refused, and a person would have let it through.",[31,488,489],{},"For \"only about 10 billion of the 145 billion parameters\", it asked for bars labelled \"active params\" and \"total params\". Refused because \"active\" and \"total\" are not words in the narration, although the chart would have been right.",[10,491,492],{},"A plain scene is a dull scene, not a wrong one. That is the trade the checks make. It is also not a fixed result. The same video made again an hour later had all thirteen scenes directed.",[10,494,495],{},"Across the first three videos made that day, 30 of 35 scenes were directed. A video takes about five minutes: one minute of directing and four of rendering.",[331,497,499],{"id":498},"keeping-the-agent-in-a-small-room","Keeping the agent in a small room",[10,501,502],{},"What the agent reads is written from web pages by strangers. An agent with tools could, in principle, be talked into using them. So hn.fm does not talk to the main Hermes agent. It talks to a second profile of its own with these properties:",[76,504,505,511,517,523],{},[31,506,507,510],{},[34,508,509],{},"It has no tools."," It cannot run a command, read a file or open a page. It can only answer.",[31,512,513,516],{},[34,514,515],{},"It has its own key."," That key works on that profile and nowhere else, and the main agent's key does not work on it.",[31,518,519,522],{},[34,520,521],{},"It does not think out loud."," With the model's reasoning mode on, one card took three minutes. With it off, four seconds.",[31,524,525,528],{},[34,526,527],{},"Its instructions travel with each request."," The skill is a file in the hn.fm repository and is sent along every time. When the model was instead given a tool for reading skills, it spent its turns trying to save its answer as a new skill.",[10,530,531],{},"The same skill is also installed in Hermes itself, so the agent can be asked for a card by hand.",[68,533,535],{"id":534},"the-digest","The digest",[10,537,538],{},"The digest is the quiet part of the project and the one Brian reads most. Each morning it takes the best-scored stories of the last day and writes an edition: a lead, a set of short items, two longer pieces and something odd at the end. It is rendered as a single HTML file sized for an e-reader and sent there by email.",[10,540,541],{},"There are four shapes of edition, which differ in how the same stories are written up: a daily, a scan (many stories, two lines each), a punchline (one sentence each) and a narrative (the day told as one piece). An edition gets a name and a cover from the image model.",[10,543,544],{},"This is the top of an edition made from the day's stories. The model named it \"Silicon Redlining\" and the image model drew the cover and the illustrations. It was not sent anywhere.",[10,546,547],{},[234,548],{"alt":549,"src":550},"The top of a digest: a cover, the edition's name, a lead paragraph and the first story","/static/hnfm/2026/digest-today.webp",[10,552,553],{},"The lead paragraph shows the model's habit when asked to tie ten stories together: it merges them. Nobody ran a language model on a 1994 Jeep's engine computer. One story was about models on old devices, and another was about two coding agents working out how a Jeep's engine computer works. The items underneath, each written from one brief, do not have this problem.",[10,555,556],{},"Making this edition also turned up a fault. Asked for the last day's stories, the first attempt came back full of stories from September. The check on a story's date compared a date with a piece of text, so it never rejected anything. It had gone unnoticed because the scheduled editions do not ask for a window at all.",[68,558,560],{"id":559},"the-radio-station","The radio station",[10,562,563,564,568],{},"The station is covered at length in ",[19,565,567],{"href":566},"/2026/11/12/reviving-old-projects-with-ai-working-log","the working log",", so this is the short version.",[76,570,571,577,583,589,595],{},[31,572,573,576],{},[34,574,575],{},"The shape of the hour is code."," Six shows cover the day. Each has two hosts, a tone, its own music and a clock: four quarters, each an ordered list of slots such as headline, two-hander, phone-in, bulletin and song of the hour.",[31,578,579,582],{},[34,580,581],{},"The model is asked one question per block",": which story goes in which slot.",[31,584,585,588],{},[34,586,587],{},"A writers' room"," writes each piece from the story's fact sheet, checks it against rules, has an editor prompt read it, and rewrites once.",[31,590,591,594],{},[34,592,593],{},"A booth"," speaks every line, transcribes it back and compares it with the script. A line that came out wrong is spoken again. Callers are put down a telephone line. Music is ducked under speech.",[31,596,597,600],{},[34,598,599],{},"Nothing is made for nobody."," With no listener the station plays music from its library and leaves the models alone.",[10,602,603],{},"This is the first two and a half minutes of a block of Main Thread, the late-morning show, made from that morning's stories: the cold open, the show open, the bulletin and two headlines. The whole block runs 5 minutes 48 seconds and took 55 seconds and twelve model calls to make.",[60,605],{"meta":606,"poster":607,"src":608,"title":609,"caption":610},"radio · 11 o'clock block · first 2 min 37 s","/static/hnfm/2026/art-main_thread.jpg","/static/hnfm/2026/radio-main-thread.mp3","Main Thread","The cover is the show's own, drawn by the image model in the station's house style.",[10,612,613],{},"The block before this one opened its bulletin with \"Good afternoon\" at ten in the morning. The writer had taken the greeting from the worked example it is shown. A greeting is now corrected in code to match the hour, and this block says \"Good morning\".",[68,615,617],{"id":616},"the-models-and-checking-that-they-answer","The models, and checking that they answer",[10,619,620],{},"Every model runs on machines in Brian's home network and is reached over HTTP. Nothing on this page was made by a paid API.",[109,622,623,633],{},[112,624,625],{},[115,626,627,630],{},[118,628,629],{},"Job",[118,631,632],{},"Model",[125,634,635,643,651,659,667,675,683,690],{},[115,636,637,640],{},[130,638,639],{},"Writing, scoring, planning",[130,641,642],{},"Nemotron 3.5 Lightning",[115,644,645,648],{},[130,646,647],{},"Speech",[130,649,650],{},"Magpie TTS",[115,652,653,656],{},[130,654,655],{},"Listening back",[130,657,658],{},"Nemotron ASR",[115,660,661,664],{},[130,662,663],{},"Pictures",[130,665,666],{},"FLUX.3 Klein",[115,668,669,672],{},[130,670,671],{},"Music",[130,673,674],{},"ACE-Step 1.5",[115,676,677,680],{},[130,678,679],{},"Scraping",[130,681,682],{},"Firecrawl",[115,684,685,688],{},[130,686,687],{},"Rendering story videos",[130,689,319],{},[115,691,692,695],{},[130,693,694],{},"Directing story videos",[130,696,328],{},[10,698,699],{},"Ten services is a lot to depend on, and the project's worst failure this year was a quiet one: the writing model's address stopped answering and every call failed for weeks without anything saying so. There is now a check that sends one real request to each model, with a time limit on each so that one that hangs cannot hold up the rest.",[10,701,702],{},[234,703],{"alt":704,"src":705},"The desk's panel of models, each with the result of its last check","/static/hnfm/2026/desk-models.webp",[10,707,708],{},"On the day this page was made, eight of the ten answered:",[76,710,711,717],{},[31,712,713,716],{},[34,714,715],{},"Vision does not work."," Two steps ask a model to look at a picture: reading the images found in an article, and planning the picture sequence of an episode. No model that can read pictures is running at the moment, so both steps fail. The application carries on without them, but this is the one real gap.",[31,718,719,722],{},[34,720,721],{},"The video-clip model is switched off on purpose."," It needs a whole graphics card to itself.",[68,724,726],{"id":725},"how-it-runs","How it runs",[10,728,729],{},"The 2025 version was Docker Compose on one PC. It is now a Helm chart on a home Kubernetes cluster:",[76,731,732,738,744,750,756],{},[31,733,734,737],{},[34,735,736],{},"An API and four kinds of worker",", one each for fetching, scoring, rendering and digests, so a slow render never holds up the front page.",[31,739,740,743],{},[34,741,742],{},"Postgres, Redis and an S3-compatible object store",", all inside the chart.",[31,745,746,749],{},[34,747,748],{},"A render sidecar"," for HyperFrames: Node, Chromium and ffmpeg behind a small HTTP server.",[31,751,752,755],{},[34,753,754],{},"Liquidsoap and Icecast"," for the radio.",[31,757,758,761],{},[34,759,760],{},"GitOps."," A push builds the images on a self-hosted Forgejo, and Argo CD rolls them out. A change is live about ten minutes after the push.",[10,763,764],{},"Everything is run from one page, the desk, which shows what was last made of each kind, a button to make the next one, and the switch for each.",[10,766,767],{},[234,768],{"alt":769,"src":770},"The desk: something to read, something to hear, something to watch","/static/hnfm/2026/desk.webp",[68,772,774],{"id":773},"what-went-wrong-along-the-way","What went wrong along the way",[76,776,777,783,789,795],{},[31,778,779,782],{},[34,780,781],{},"The renderer updated itself in the middle of a render."," The HyperFrames command-line tool installs a newer version of itself the first time it runs. It did this while it was rendering, replacing its own files. The version is now pinned and the update check is off.",[31,784,785,788],{},[34,786,787],{},"The writing model froze for a day."," It was still running and still accepted connections, but never replied. The inference check waited on it for 25 minutes. That is where the per-model time limits came from.",[31,790,791,794],{},[34,792,793],{},"Starting a second agent took the first one offline."," While setting up the Hermes profile, a command that looked like \"start this profile\" took over the port the main agent was serving on. It was down for three minutes. The setup script now carries a warning at the top.",[31,796,797,800],{},[34,798,799],{},"The same video was made twice at once."," A job delivered twice had two workers rewriting one project. A video that is already being made is now refused.",[68,802,804],{"id":803},"where-it-stands","Where it stands",[76,806,807,813,819,825],{},[31,808,809,812],{},[34,810,811],{},"Working:"," fetching, scoring, research, episodes as audio and as video, digests, the radio station, story videos, and the check of every model.",[31,814,815,818],{},[34,816,817],{},"Switched off:"," the schedule that makes digests and episodes by itself, and the radio's own production. Fetching and scoring run all day. Everything else is made when asked for.",[31,820,821,824],{},[34,822,823],{},"Not working:"," anything that needs a model to look at a picture.",[31,826,827,830],{},[34,828,829],{},"Next for story videos:"," the eight treatments cover numbers and short phrases well. They have nothing for a line that is an argument with no figure in it, which is why the plain scenes in the second video are where they are. More treatments are the obvious next step, and so is a stronger model writing scenes freely, which the code still supports.",[10,832,833,834,839],{},"The code is at ",[19,835,838],{"href":836,"rel":837},"https://github.com/briancaffey/hn.fm",[318],"github.com/briancaffey/hn.fm",".",[841,842,843],"style",{},"html pre.shiki code .sMOD_, html code.shiki .sMOD_{--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .s-m8C, html code.shiki .s-m8C{--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sCZoN, html code.shiki .sCZoN{--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#CFCFC2}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":374,"searchDepth":845,"depth":845,"links":846},2,[847,848,849,855,856,857,858,859,860],{"id":70,"depth":845,"text":71},{"id":189,"depth":845,"text":36},{"id":306,"depth":845,"text":307,"children":850},[851,853,854],{"id":333,"depth":852,"text":334},3,{"id":343,"depth":852,"text":344},{"id":498,"depth":852,"text":499},{"id":534,"depth":845,"text":535},{"id":559,"depth":845,"text":560},{"id":616,"depth":845,"text":617},{"id":725,"depth":845,"text":726},{"id":773,"depth":845,"text":774},{"id":803,"depth":845,"text":804},false,"2026-11-13","A 2025 hackathon project that turned Hacker News stories into narrated video kept growing. This is what it does now, how each part works, and a set of things it made from one day's front page, all on models running at home.","md",null,"/static/hnfm/2026/cover.jpg",{},true,"/2026/11/13/hnfm-a-year-on",{"title":5,"description":863},"2026/11/13/hnfm-a-year-on",[873,874,875,876,877,878,879],"ai","llm","tts","hacker-news","hyperframes","hermes-agent","local-inference","FRW_9SBItbfcquk7QjiEU5itIDa1yj0EQE_tv_-5ysw",1794698246271]