[{"slug":"ai-tycoon","title":"AI Tycoon","stack":["Astro","React","TypeScript","Tailwind CSS","Three.js","@react-three/fiber","@react-three/cannon"],"status":"live","demoId":"ai-tycoon","description":"# AI Tycoon\n\n# AI Tycoon\n\nAn interactive tycoon-style simulation rendered with React 19,\nThree.js, and `@react-three/cannon` for physics. The player starts\nas **Victorin Elsoñador** — sole engineer, sole dreamer — and\ngrows a small AI model company in the country of **Notchance\nParsimony**. Each simulated tick, revenue, salaries, hardware\nmaintenance, and rent are deducted from cash; staff productivity\ngradually raises the model's quality; and random events fire from\na fully deterministic, seeded event loop.\n\n**[Try the demo →](/demos/ai-tycoon)**\n· [Project page](/proyectos/ai-tycoon)\n\n## Demo\n\n![Live simulation overview](/projects/ai-tycoon/overview.webp)\n\nThe demo at `/demos/ai-tycoon` mounts the simulation as a\nsingle React 19 island inside an Astro 7 page. The screenshot\nabove shows one tick of the engine: cash `$24,590.25`, two racks\non the compute floor, a `DECISION REQUIRED` prompt from the\nNotchance Parsimony regulator (Defy or Comply, 10-rep penalty for\ncompliance), Victorin's motivational speech bubble, and the last\nten events scrolling in the event log at the bottom.\n\nThe HUD on the left tracks eight meters — **Cash**, **Day /\nmonth**, **GPU** (compute tier), **Workers** (office + remote),\n**Subs**, **Revenue**, **Morale**, **Reputation**, and\n**Quality** — and updates every tick. The right rail is the\n**Operations** panel: it lists hire-office (`+$800 / tick`),\nhire-remote (`+$400 / tick`), and the next **Compute Floor**\nupgrade with its live price. Below it sits the simulation speed\nselector (Pause / 0.25× / 0.5× / 1× / 2× / 4×).\n\n## What's inside\n\n### Three endings, deterministic from one seed\n\nEvery run starts from a Mulberry32-seeded `GameState`. The same\nseed reproduces the same company name, the same event sequence,\nand the same outcome. There are three terminal endings:\n\n| Ending              | Trigger                                                                                          |\n| ------------------- | ------------------------------------------------------------------------------------------------ |\n| `BANKRUPT`          | `cash <= 0` for **8** consecutive ticks                                                           |\n| `INDUSTRY FAILED`   | `industryViability <= 0.1` **and** `reputation < 40` for **16** consecutive ticks                 |\n| `OPEN-WEIGHTS CHAMPION` | `modelQuality >= 95`, `reputation >= 80`, and `tick >= 120` all true at once                   |\n\nOnce an ending fires, the engine locks in `state.ending` and the\n`EndScreen` component takes over.\n\n### Eight event kinds\n\nEvery tick, the engine rolls one event from the seeded RNG. The\nkind affects different meters and sometimes spawns a sub-system\n(like the regulator NPC).\n\n| Kind                  | Effect                                                                                          |\n| --------------------- | ----------------------------------------------------------------------------------------------- |\n| `motivationalSpeech`  | `morale += 10`; `+5 %` model quality growth for **4** ticks                                       |\n| `badReview`           | `morale -= 8`; random internet critic                                                            |\n| `govBlock`            | Triggers the regulator NPC; player chooses Defy or Comply (`-10 rep` for comply)                  |\n| `hardwareSpike`       | Next upgrade costs `2×` for **4** ticks (vendor gouging)                                          |\n| `communityLove`       | `reputation += 5`                                                                                |\n| `investor`            | One-shot cash injection from a named investor                                                     |\n| `competitorRelease`   | Quarterly (`tick % 720`) competitor drop; `qualityLossPct` + `subscriberLossPct` dip              |\n| `founderDecision`     | Victorin takes action on his own (5 % per tick; hire or upgrade if affordable)                     |\n\nThe event log at the bottom of the simulation shell keeps the last\nten events visible at any speed, and auto-scrolls to the newest\nentry.\n\n### The regulator (NPC)\n\nGovernment blocking events spawn a regulator character that walks\ninto the office over **3 ticks** (`arriving`), stands at the door\n(`present`), and walks back out after the player resolves the\nprompt (`leaving`). The `DECISION REQUIRED` panel sits above the\noffice with a 3-second countdown; if the player ignores it,\n**Victorin decides randomly** and the outcome is logged as a\n`founderDecision`.\n\nDefying the regulator has no immediate reputation cost — Victorin's\nattitude toward Notchance Parsimony is canonical defiance, so\ncomplying is the only way to lose rep.\n\n### Six hardware tiers\n\n`hardwareTiers` lists the full ladder from a solo laptop to a\n32-GPU datacenter cluster. Each upgrade raises\n`modelQualityCeiling` by `compute * 2`, and every tier adds\nmaintenance costs (`$150 × (tier + 1) × 24` per simulated day).\n\n| Tier | Name                              | Base price    | Compute |\n| ---- | --------------------------------- | ------------- | ------- |\n| 0    | Solo Laptop                       | $200          | 1       |\n| 1    | Single Workstation GPU            | $2,500        | 4       |\n| 2    | 4-GPU Rig                         | $12,000       | 14      |\n| 3    | 8-GPU Cluster                     | $40,000       | 35      |\n| 4    | 16-GPU Pod                        | $110,000      | 80      |\n| 5    | 32-GPU Datacenter Cluster         | $300,000      | 180     |\n\nThe \"GPU\" meter in the HUD shows the current tier (0-5). Final\nupgrade cost is `basePrice * hardwarePriceIndex * spikeMultiplier`,\nso a hardware spike temporarily doubles the price for the next\nfour ticks.\n\n### Staff: founder + up to 11 hires\n\nThe founder (`Victorin Elsoñador`, `tier: talent`, `productivity:\n0.7`, `salaryPerTick: 0`) is always present. Hires come in two\nflavors — **office** (a 3D character on the floor, `+$800 / tick`)\nor **remote** (floats off-screen connected by dashed lines to a\ncloud, `+$400 / tick`). The cap is **12 total staff** (founder +\n11). Each hire is generated from a random `officeRoles` /\n`remoteRoles` entry and tagged with a `normal`, `pro`, or\n`talent` tier; the tier feeds into `TIER_QUALITY_BONUS`, which\nmultiplies the per-tick model-quality growth.\n\n### Victorin's autonomy\n\nEvery tick there's a **5 %** chance Victorin acts on his own. He\nwill hire a new teammate (office or remote, 50/50) if there's a\nfree slot, or buy the next hardware tier if the company can\nafford it. Founder-driven events are logged as `founderDecision`\nwith a separate message (\"Victorin Elsoñador hired a new office\nteammate.\"), so the event log always tells you whether a hire\ncame from the player or from Victorin.\n\n### The office scene\n\nA Three.js orthographic isometric camera renders the office\nfloor with `assets/isometricUrls`, `assets/furnitureUrls`, and\n`assets/characterUrls` (Kenney Blocky Characters furniture kit\nassets). Each tick, characters walk to a new desk via\n`officeMovement.ts`, the server rack grows when hardware is\nupgraded, and remote workers stay connected to the cloud with\ndashed lines.\n\n## Architecture\n\n- **Astro 7** routes `/demos/ai-tycoon` and renders a single\n  React 19 island (`client:load`) for the simulation engine,\n  HUD, and Three.js canvas.\n- **Pure simulation core.** `demo/engine/` exposes\n  `stepTick`, `applyAction`, and the event atoms from `events.ts`\n  as pure functions over `GameState` — no mutation, no\n  side-effects. Every action returns a brand-new state object.\n- **Deterministic RNG.** `engine/rng.ts` ships a Mulberry32 PRNG\n  (`mulberry32`) plus `pickInt` and `pickOne` helpers. Every\n  atom that needs randomness takes an explicit `rng` parameter,\n  so vitest specs can replay any tick from a fixed seed.\n- **State is `useState`-held, never lifted.** `index.tsx` owns\n  the single `GameState` via `useState` and mirrors it into a\n  `stateRef` so the `setInterval` tick loop always reads the\n  latest state without re-creating timers. The interval is\n  recreated only when `paused`, `ending`, or `weekSpeedMs`\n  change.\n- **Three.js scene graph** is built with `@react-three/fiber`,\n  physics comes from `@react-three/cannon` (gravity + mass on\n  box characters), and all sprite assets are loaded from the\n  Kenney furniture kit URLs in `demo/assets/`.\n- **Tailwind 4** (Vite plugin) styles the HUD, decision panel,\n  end screen, and event log with CSS custom-property tokens\n  defined in `src/styles/global.css`.\n- **TypeScript strict** across the engine, components, and\n  tests. `applyAction` uses an exhaustive `_exhaustive: never`\n  check on the `Action` discriminated union, so any new action\n  kind is a compile-time error until its reducer arm exists.\n\n## Trade-offs\n\n- **No save / no resume.** The seed is regenerated on every page\n  load (`nextSimulationSeed(current.seed)` on mount). The sim is\n  meant to be replayed, not persisted.\n- **Three.js + cannon add weight.** The full demo bundle\n  (Three.js, cannon-es, Kenney sprites) is heavier than the\n  resume-generator demo. It only loads on `/demos/ai-tycoon`,\n  not on the rest of the site.\n- **Worker salaries are paid even when paused** is *not* true —\n  pausing halts `stepTick`, so salaries and rent stop accruing.\n  Victorin's autonomy RNG is also paused.\n- **Determinism costs replayability.** Same seed → same game.\n  Players who want to branch from a particular moment have to\n  fork the seed manually.\n- **`@react-three/cannon` box physics** is intentionally simple\n  (gravity + mass, no joints). Characters look like they're\n  standing on the floor; they don't collide with each other.\n\n## Try the demo\n\n**[sersofts.org/demos/ai-tycoon](https://sersofts.org/demos/ai-tycoon)**\non the live site. The demo works on desktop and tablet; phones\nget the simulation shell but the embedded overlay is tuned for ≥\n760 px viewports. Click **+** in the HUD to expand any meter, hit\n**Pause** to freeze the simulation, and try **Defy** on the first\nregulator prompt to see Victorin's preferred path.\n\n## Files of note\n\n- `demo/engine/sim.ts` — `stepTick`, `applyAction`,\n  `stepCashFlow`, `stepModelQuality`, and all the per-tick\n  sub-reducers. The `Action` discriminated union lives here.\n- `demo/engine/endings.ts` — `checkBankrupt`,\n  `checkIndustryFailed`, `checkChampion`, and the streak\n  thresholds (`BANKRUPT_STREAK_THRESHOLD = 8`,\n  `INDUSTRY_FAILED_STREAK_THRESHOLD = 16`,\n  `CHAMPION_MIN_TICK = 120`).\n- `demo/engine/events.ts` — `sampleNextEvent` and per-kind\n  reducers (`applyMotivationalSpeech`, `applyBadReview`,\n  `applyHardwareSpike`, `applyCommunityLove`, `applyInvestor`,\n  `applyCompetitorRelease`, `scheduleGovBlock`).\n- `demo/engine/rng.ts` — `mulberry32`, `pickInt`, `pickOne`.\n- `demo/engine/initialState.ts` — `createInitialState(seed)`;\n  the canonical starting `GameState`.\n- `demo/index.tsx` — top-level island, owns the `useState` /\n  `stateRef` pair and the `setInterval` tick loop.\n- `demo/components/Scene.tsx` — Three.js orthographic isometric\n  scene + physics + the HUD overlay shell.\n- `demo/components/HUD.tsx` — left-rail meters (cash, day,\n  GPU, workers, subs, revenue, morale, reputation, quality).\n- `demo/components/DecisionPanel.tsx` — right-rail operations\n  panel (hire-office, hire-remote, compute floor upgrade).\n- `demo/components/DecisionPrompt.tsx` — regulator decision\n  panel with the Defy / Comply buttons.\n- `demo/components/EndScreen.tsx` — bankrupt / industry-failed /\n  champion overlays with a **Restart** button that re-seeds.\n- `demo/components/EventFeed.tsx` — last ten events with\n  auto-scroll.\n- `demo/data/hardwareTiers.ts` — six-tier ladder, base prices,\n  compute multipliers.\n- `demo/data/companyNames.ts` — WoW-flavored name pool\n  (`Sylvanas`, `Ironforge`, `Frostmourne`, ...) with category\n  badges.\n- `demo/data/founderAutonomy.ts` — 5 % per-tick probability\n  Victorin acts on his own.\n- `tests/unit/` — vitest specs for `stepTick`, endings, and\n  event reducers.\n- `tests/e2e/` — Playwright specs for demo hydration, the\n  operations panel, and the GPU upgrade adding a rack."},{"slug":"dashboard","title":"Dashboard","stack":["Astro","React","TypeScript","Tailwind CSS"],"status":"live","demoId":"dashboard","description":"# Dashboard\n\n## Overview\n\nAn interactive product analytics dashboard for monitoring a SaaS platform. It\nsurfaces the metrics that matter during an operational check: active users,\nrevenue, error rate, p95 latency, traffic sources, and recent events.\n\n## Demo behavior\n\nThe local demo is intentionally self-contained. Visitors can switch between\none-hour, daily, and weekly snapshots, and pause or resume the visual live\nupdate state. Each range updates the metric cards, revenue chart, traffic\nbreakdown, and operational feed together.\n\n![Revenue overview dashboard](/projects/dashboard/overview.png)\n\nThe dashboard keeps the operational read compact: headline metrics sit above\nthe revenue trend, traffic-source breakdown, and recent activity feed. The\nselected time range updates the whole surface together, while the live-update\nindicator makes the current state visible at a glance.\n\n## Implementation notes\n\n- The React island is registered explicitly for `/demos/dashboard` and keeps\n  its fixture data, components, and browser test alongside this document.\n- SVG renders the chart directly, avoiding a chart dependency for a small,\n  portable demo.\n- All UI colors use the portfolio tokens, so the dashboard automatically\n  supports both Rony Classic and Modern visual variants."},{"slug":"dynshop","title":"dynshop","stack":["React","JavaScript","Material UI","Stripe","Google Maps","WebSockets"],"status":"live","projectUrl":"https://dynshop.online","description":"# dynshop\n\n## Overview\n\ndynshop is the production platform behind DyN's rental and service marketplace. It brings customer bookings and partner operations into one workflow, from browsing available properties, vehicles, yachts, and services to confirming a reservation and completing the service.\n\n**[Visit dynshop.online ↗](https://dynshop.online)**\n\n## Customer experience\n\nCustomers can explore catalog items, review availability, choose a date and service, provide the required information, and follow the reservation through payment and fulfillment. The platform supports notifications, customer documents, signatures, ratings, and status updates throughout the flow.\n\n## Partner operations\n\nPartners get tools for managing properties, vehicles, yachts, service templates, staff profiles, catalogs, availability schedules, and supporting documentation. The operations surface also keeps reservations, balances, commissions, customer communication, and service execution in one place.\n\n## Payments and service fulfillment\n\nThe reservation flow supports deposits, final payments, refunds, and platform commissions through Stripe. Service providers can confirm the start of a booking with a check-in code, record the condition or completion of the service, and close the experience with customer feedback.\n\n## Production architecture\n\nThe web client connects to dedicated API and WebSocket services for live updates. Stripe handles payment workflows, Google Maps supports location-based experiences, and the installable PWA can send push notifications so partners can react to reservations even when the application is closed."},{"slug":"kimi-k3-quantization","title":"Kimi K3 Quantization Lab","stack":["Kimi K3","Mixture of Experts","Quantization","MXFP4","GPU Inference","Local AI"],"status":"wip","description":"# Kimi K3 Quantization Lab\n\n## Overview\n\nThe Kimi K3 Quantization Lab is a new technical research project focused on\nmaking a frontier-scale open-weight model more practical to run locally. Kimi\nK3 is a 2.8-trillion-parameter native multimodal model from Moonshot AI, with\na sparse Mixture-of-Experts design and published open weights.\n\nThis is a laboratory project, not a finished runtime or a production release.\nThe goal is to measure how much of Kimi K3's capability can survive increasingly\naggressive memory and compute constraints, and to document the trade-offs along\nthe way. The [official Kimi K3 overview](https://www.kimi.ai/ai-models/kimi-k3)\nand [technical announcement](https://www.kimi.ai/blog/kimi-k3) are the reference\npoints for the model's published architecture and capabilities.\n\n## Research goals\n\n- Fit useful quantized variants on hardware that is significantly less powerful\n  than the infrastructure normally associated with a model of this scale.\n- Minimize quality loss across coding, reasoning, knowledge work, and multimodal\n  tasks while reducing the memory footprint.\n- Explore whether a complete usable variant can be loaded on a single consumer\n  graphics card, even if that requires careful context, batch, and throughput\n  constraints.\n- Investigate expert-aware loading, routing, and execution strategies to push\n  Mixture-of-Experts inference speed as far as possible on domestic hardware.\n\n## Experiment tracks\n\n### Quantization and quality retention\n\nThe first stage compares precision formats, calibration strategies, and mixed-\nprecision layouts. Each reduction is evaluated against a reference model so a\nsmaller file is only considered useful when its behavior remains functional for\nreal tasks.\n\n### Consumer hardware targets\n\nThe hardware ladder moves from relatively capable local workstations toward\nordinary domestic PCs. Memory usage, load time, tokens per second, latency,\nthermal behavior, and power draw matter alongside benchmark scores.\n\n### Single-GPU feasibility\n\nOne milestone is to determine whether a carefully selected quantized and\nexpert-aware configuration can fit on one graphics card. This is an experiment\nto map the boundary, not a promise that the full model will reach that target.\n\n### Expert execution and throughput\n\nBecause Kimi K3 uses sparse experts, the lab will test how routing, expert\nplacement, and selective loading affect speed on local hardware. The intention\nis to improve practical throughput without turning the model into a narrow or\nunreliable specialist.\n\n## Evaluation criteria\n\nEvery candidate should be measured across several dimensions:\n\n- task quality and regression against an uncompressed reference;\n- coding, reasoning, knowledge-work, and multimodal usefulness;\n- VRAM/RAM consumption, model load time, and context limits;\n- prompt processing speed, generation speed, latency, and stability;\n- thermal and power behavior on domestic hardware; and\n- reproducibility of the conversion, calibration, and evaluation process.\n\n## Current status\n\nThe project has just started. There are no final quantization results or\nhardware claims yet. The work is intentionally exploratory: technical tests will\nshow how far Kimi K3 can be carried toward local execution while keeping it\ngenuinely functional, and unsuccessful approaches are part of the laboratory\nrecord too."},{"slug":"portfolio-ai-chat","title":"Portfolio AI Chat","stack":["Astro","React","TypeScript","SSE","Tailwind CSS","MDX"],"status":"live","repoUrl":"https://src.sersofts.org/victorvargas/portfolio","description":"# Portfolio AI Chat\n\n## Overview\n\nPortfolio AI Chat gives the portfolio a conversational entry point. Visitors can ask what Victor built, which technologies he used, or why a project was designed a certain way. The answer is grounded in the same project and expertise content that powers the site, so the chat can lead directly to the relevant project page instead of becoming a disconnected FAQ.\n\n![Rony chat in its initial state](/projects/portfolio-ai-chat/chat-overview.webp)\n\n## Live conversation\n\nRony streams the answer into the conversation as it arrives. When the bot identifies useful documents, the project grid switches to a matched view and surfaces the related work. A question such as “What can I ask Rony about?” can bring the chat, the retrieval sources, and the project cards into the same flow.\n\n![Rony answering a visitor question with matched projects](/projects/portfolio-ai-chat/chat-response.webp)\n\n## Architecture\n\nThe feature is split into small boundaries so the browser only owns presentation and conversation state:\n\n| Layer | Responsibility |\n| --- | --- |\n| React chat island | Collects messages, renders Markdown-like responses, shows streaming status, and reports matched sources. |\n| Astro `/api/chat` | Validates the request and forwards the SSE stream without exposing the bot's internal port to the browser. |\n| `rony-chat-bot` | Runs the Rony agent, retrieves relevant project context, and emits response chunks, sources, and lifecycle events. |\n| Project index | Publishes the MDX collection through `/projects.json`, which the bot consumes when it reindexes. |\n\nThe portfolio remains the source of truth. A content update becomes searchable after the bot reindexes the JSON feed, so the rendered site and the conversational index do not need separate copies of project files.\n\n## Interaction design\n\n- **Progressive response** — server-sent events show the first useful chunk without waiting for a complete answer.\n- **Stable layout** — the chat log has a bounded scroll area, keeping the hero and project grid from jumping while tokens arrive.\n- **Source-aware navigation** — returned source slugs switch the grid into “Matched projects” mode and keep the answer connected to the work it references.\n- **Two visual Rony modes** — the dog control can switch between the classic sprite and the alternate animated treatment without changing the conversation.\n- **Resettable context** — resetting the chat clears the visible conversation, matched cards, and server conversation id together.\n\n## Try the chat\n\n1. Open the [home page](/) and choose **Chat with Rony**.\n2. Ask about a project, its stack, or a design decision.\n3. Watch the answer stream in, then open a matched project from the cards above the chat.\n\nThe current experience is designed for questions about the portfolio rather than general-purpose browsing. Focused prompts produce the most useful project matches.\n\n## Files of note\n\n- `src/components/Chat.tsx` — conversation state, SSE parsing, response rendering, source callbacks, and Rony mode switching.\n- `src/pages/api/chat.ts` — request validation and streaming proxy boundary.\n- `src/pages/projects.json.ts` — project catalogue consumed by the bot during reindexing.\n- `src/content/projects/**/*.mdx` — the searchable project source material."},{"slug":"resume-generator","title":"Resume on Demand","stack":["Astro","React","TypeScript","@react-pdf/renderer","Tailwind CSS"],"status":"live","demoId":"resume-generator","description":"# Resume on Demand\n\n# Resume on Demand\n\nA live demo where the visitor curates what goes into a PDF resume\nbefore downloading it. The data source is the same `expertise`\ncontent collection that backs `/resume`, so there is no duplication\nand no backend call to generate the PDF.\n\n**[Try the demo →](https://sersofts.org/demos/resume-generator)**\n· [Project page](https://sersofts.org/proyectos/resume-generator)\n· [Resume](https://sersofts.org/resume)\n\n## Demo\n\n![Selection panel](/projects/resume-generator/controls.webp)\n\nThe selection panel groups items by kind. **The profile is always\nincluded** — it carries the visitor's identity (name, location,\ncontact links), and an anonymous PDF isn't a useful resume. The\ndefault selection covers the profile, the latest experience entry,\nand every skill group; everything else is opt-in.\n\n![Live PDF preview](/projects/resume-generator/preview.webp)\n\nThe right pane streams a live PDF preview rendered by\n`@react-pdf/renderer` that updates as toggles change. The\n**Download PDF** button exports the same document with a filename\nderived from the profile name plus today's date:\n\n```text\nVictor Hugo Vargas Servin -- Resume -- 2026-08-20.pdf\n```\n\n## What's inside\n\n### Profile (mandatory)\n\nName, location, email, LinkedIn, GitHub, portfolio URL, photo,\nsummary line, and the bio paragraphs.\n\n### 8 experience entries\n\nMetrimex, Didcom, **DoneHQ** (WordPress Developer — ACF Pro,\nContentKing, SEO), Welo, eCloud, SofiaXT, Teknol, Lanix.\n\n### 8 AI skill groups (top of the Skills section)\n\n| Title                       | Focus                                                    |\n| --------------------------- | ------------------------------------------------------- |\n| AI Tooling                  | Tools I use day to day                                  |\n| AI Prompt Engineering       | System prompts, templates, few-shot, instruction tuning |\n| MCP & Tool Calling          | MCP servers / clients, tool registry, JSON schemas      |\n| AI Skills Authoring         | Project + global Skills, skill schemas, AGENTS.md      |\n| Harness & Agent Engineering | Agent loops, tool integration, sandbox, streaming       |\n| RAG Architecture            | ChromaDB, vector search, chunking, reranking            |\n| Local LLM Operations        | llama.cpp, GGUF, CPU + GPU inference                    |\n| Open-Weight Model Optimization | Quantization (GGUF, GPTQ, AWQ), LoRA, QLoRA, distillation |\n| AI Cost & Privacy Strategy  | Hybrid routing, on-prem, data sovereignty              |\n\n### 11 general skill groups\n\nProgramming languages, databases, web frameworks, mobile, maps &\ngeolocation, desktop, containers, DevOps & cloud, testing, top\nstrengths, and the original \"AI Tooling\" anchor.\n\n### 2 education entries\n\nITESCA (Ingeniería en Sistemas, 2014) and UTH (TSU en Redes y\nTelecomunicaciones, 2010).\n\n### 2 spoken languages\n\nSpanish (native) and English (conversational).\n\n### Placeholders for new content\n\nEmpty directories for `side-projects/`, `talks/`, and `courses/`\n— content can be dropped in without schema changes.\n\n## Architecture\n\n- **Single content source.** `src/content/expertise/*.mdx`\n  defines a discriminated union over the eight kinds. `/resume`,\n  `/api/expertise.json`, `/docs.json` (chat-bot source), and the\n  demo all read from this one collection.\n- **Pure selection logic** lives in `demo/lib/selection.ts`\n  (`groupByKind`, `defaultSelection`, `toggleSelection`,\n  `selectedItemsForPdf`) and is covered by vitest unit specs.\n- **Browser-side PDF** via `@react-pdf/renderer`. There is no\n  backend round-trip for document generation.\n- **Per-section anti-orphan.** Each section `View` sets\n  `wrap={false}`, so the heading and its body always travel\n  together. If a section can't fit at the bottom of a page, the\n  whole section moves to the next page.\n- **Mandatory profile** in `selectedItemsForPdf` — the profile is\n  always prepended to the payload, regardless of selection state.\n\n## Trade-offs\n\n- `@react-pdf/renderer` ships its own font + layout engine,\n  around 150 KB gzipped. It only loads on\n  `/demos/resume-generator`, not on the rest of the site.\n- Filename is computed from the visitor's profile name and the\n  local date. If they uncheck the profile and leave everything\n  else blank, the download button disables — a profile without\n  jobs / skills isn't a useful resume.\n\n## Try the demo\n\nThe live version is at\n**[sersofts.org/demos/resume-generator](https://sersofts.org/demos/resume-generator)**.\nNo signup, no deploy, no email — open it, pick what you want, hit\ndownload.\n\n## Run locally\n\n```bash\npnpm install\npnpm dev\n```\n\nThen open the demo at `http://localhost:4321/demos/resume-generator`.\n\n## Files of note\n\n- `demo/PdfDocument.tsx` — react-pdf document. Monochrome style,\n  chip-wrap layout, anti-orphan sections.\n- `demo/components/SelectionPanel.tsx` — grouped checklist.\n  Profile is disabled with a `Required` badge.\n- `demo/components/DownloadButton.tsx` — `PDFDownloadLink` with\n  a dynamic `<name> -- Resume -- YYYY-MM-DD.pdf` filename.\n- `demo/lib/selection.ts` — pure selection logic + payload\n  builder. Profile is always prepended in the payload.\n- `demo/hooks/useExpertise.ts` — fetch + selection state.\n- `tests/unit/` — vitest specs for selection logic.\n- `tests/e2e/` — Playwright specs for default selection,\n  mandatory profile, and PDF blob validity."},{"slug":"rony-chat-bot","title":"rony-chat-bot","stack":["Go","ChromaDB","OpenAI API","Anthropic API","SSE","MCP"],"status":"live","repoUrl":"https://src.sersofts.org/victorvargas/rony-chat-bot","description":"# rony-chat-bot\n\n## Overview\n\nRony Chat Bot is the reusable backend behind chat experiences such as Portfolio AI Chat. It gives a web client a small HTTP contract while keeping provider selection, retrieval, persona, persistence, and context management on the service side.\n\nThe local stack runs as three Podman services: the Go bot on port `7331`, a local llama.cpp chat model, and a llama.cpp embedder for semantic retrieval.\n\n![Rony Chat Widget running against the local bot](/projects/rony-chat-bot/widget-overview.webp)\n\n## Runtime\n\nThe service accepts a conversation as JSON and can answer either as a complete response or as an SSE stream. The streaming path emits lifecycle events such as `start`, `chunk`, `sources`, `compaction`, `done`, and `error`, allowing a client to render progress without waiting for the full model output.\n\n![Rony Chat Widget showing a streamed answer and retrieved sources](/projects/rony-chat-bot/widget-response.webp)\n\nThe default local provider is `qwen2.5-3b-instruct` behind llama.cpp. The provider interface also supports OpenAI-compatible endpoints, Anthropic, Ollama, and other local models without changing the HTTP client contract.\n\n## Architecture\n\n| Boundary | Responsibility |\n| --- | --- |\n| HTTP server | Validates requests, exposes chat/health/reindex endpoints, and keeps the SSE stream flushable. |\n| Agent runner | Builds the system prompt, selects a provider, invokes tools, and manages the response lifecycle. |\n| Hybrid RAG | Combines SQLite FTS5 keyword search with multilingual embeddings and returns the source slugs used for an answer. |\n| SQLite store | Persists conversations, messages, indexed chunks, and retrieval metadata. |\n| Web widget | Provides a dependency-free drop-in client with bilingual controls, conversation restore, Markdown-lite rendering, and responsive layout. |\n\nThe service is intentionally independent from the portfolio frontend. Projects and reference documents are fetched from JSON sources during reindexing, so the bot does not need a mirrored Markdown corpus.\n\n## Widget integration\n\nThe `web/` directory contains a dependency-free widget that can be added to Astro, Next.js, or a plain HTML page with one stylesheet and one script tag. Configuration stays in `data-*` attributes:\n\n```html\n<script\n  src=\"/chat-widget.js\"\n  data-api-url=\"https://chat.example.com\"\n  data-title=\"Ask me anything\"\n  data-position=\"bottom-right\"\n  data-theme=\"auto\"\n  defer\n></script>\n```\n\nThe widget supports English and Spanish interface controls, stores the active conversation id in `localStorage`, restores known conversations after a reload, and switches to a full-screen panel on narrow screens.\n\n## Operations\n\n- **Health** — `GET /api/health?deep=true` verifies the LLM provider, SQLite store, and indexed chunk count. The local Podman stack reports a healthy bot, model, and store.\n- **Reindex** — `POST /api/reindex` refreshes project and document sources, including embeddings when the embedder is available.\n- **Local runtime** — Podman publishes the bot at `http://localhost:7331`; the `rony-llm` and `rony-embedder` services stay private to the compose network.\n- **Configuration** — provider, CORS origins, source URLs, RAG settings, and context limits live in `configs/portfolio-bot.yaml` and environment variables.\n\nThe service is self-hosted by default, but the same binary can point at a cloud provider when privacy, latency, or model quality requirements change.\n\n## Files of note\n\n- `internal/server/` — HTTP handlers, SSE transport, health checks, conversations, and middleware.\n- `internal/agent/` — provider-neutral agent loop, tool calls, and context compaction.\n- `internal/portfolio/` — source fetching, chunking, hybrid retrieval, embeddings, and SQLite persistence.\n- `web/chat-widget.js` — dependency-free client with SSE parsing, language switching, and history restore.\n- `web/chat-widget.css` — scoped responsive styles and light/dark theme tokens.\n- `configs/portfolio-bot.yaml` — local provider, source, RAG, and runtime configuration."},{"slug":"rony-harness","title":"Rony Harness","stack":["Go","Bubble Tea","Cobra","SQLite","LLMs","llama.cpp"],"status":"live","repoUrl":"https://src.sersofts.org/victorvargas/rony-harness","description":"# Rony Harness\n\n# Rony Harness\n\nRony Harness is a terminal-first workspace for turning an LLM into a useful\nsoftware agent. It can connect to hosted providers such as Claude and GPT,\nor to local runtimes such as Ollama and llama.cpp, then give the selected model\nreal tools for reading, editing, searching, and running work inside a project.\n\nThe important part is the loop around the model: Rony shows what it is doing,\nasks before consequential actions, records the files it touched, and keeps the\nconversation available as a resumable session.\n\n**[Read the public source repository ↗](https://src.sersofts.org/victorvargas/rony-harness)**\n\n## Demo\n\nThe product is a terminal UI rather than a hosted web demo, so these captures\nshow the workflow that matters: choose a model, frame the task, review the\nagent's intent, approve tools, and inspect the resulting file without leaving\nthe same interface.\n\n### 01 — Select models and scripts\n\n![Model and script selection in the LLM panel](/projects/rony-harness/select-models.png)\n\nThe `/llm` panel can expose several configured scripts and model profiles in\none place. Local models can be started and monitored from the sidebar while a\ndifferent provider remains available for the conversation. The model info\npanel makes the selected script, model path, endpoint, and running status\nvisible before work begins.\n\n### 02 — Keep commands close to the conversation\n\n![Contextual command menu and learning sidebar](/projects/rony-harness/contextual-menu.png)\n\nSlash commands open focused panels for context, status, tools, shortcuts,\nfiles, sessions, models, and the local LLM server. The learning sidebar also\nkeeps spellcheck and translation feedback nearby, so utility actions do not\nneed to compete with the main chat transcript.\n\n### 03 — Show the reasoning and ask for permission\n\n![Rony thoughts and a tool approval request](/projects/rony-harness/thoughts-and-permissions.png)\n\nRony's thoughts can be expanded when a task needs more context, while the\napproval card makes a tool call explicit. In the example, the proposed `bash`\ncommand is shown before execution with clear **Allow**, **Always allow**,\n**Deny**, and **Cancel** choices. This keeps human intent in the loop for\nwrites, edits, shell commands, and network access.\n\n### 04 — Make tools and edited files visible\n\n![Tool calls and created files in the chat interface](/projects/rony-harness/tools-and-files.png)\n\nThe final answer records which tools were used, and the Files sidebar lists\nthe path and change count for files created or edited during the session. The\nresult is easier to audit than a single opaque answer: the conversation shows\nthe work, while the sidebar shows its concrete footprint.\n\n### 05 — Read the created document in place\n\n![Created document opened directly in the interface](/projects/rony-harness/view-documents.png)\n\nSelecting a file opens its content directly in the harness. That small detail\ncloses the loop between an agent's write action and the human review: you can\ninspect the generated document, compare it with the request, and continue the\nconversation without switching applications.\n\n## What's inside\n\n### One interface for local and cloud models\n\nProvider and model selection can come from CLI flags, environment variables,\nproject configuration, user configuration, or the shared provider catalog.\nThe supported client layer covers OpenAI-compatible endpoints, Anthropic,\nOllama, and llama.cpp, so the same workflow can move between private local\ninference and hosted models.\n\n### A real software-tool registry\n\nThe harness registers file, shell, web, workflow, and memory capabilities:\n\n- `read`, `write`, `edit`, `glob`, and `grep` for project files.\n- `bash` and `webfetch` for execution and external context.\n- `delegate` for builder and planner sub-agents.\n- `save_plan` and `update_plan` for durable work plans.\n- Memory and dictionary tools for reusable processes, facts, and language\n  learning.\n\n### Modes that change the level of autonomy\n\nNormal mode asks before tool calls, Build mode can auto-approve its configured\nworkflow, Plan mode stays read-only, and Chat mode is a lightweight read-only\nassistant. They are modes for the same Rony persona, not separate characters,\nand `Tab` cycles between them inside the TUI.\n\n### Sessions that survive the terminal\n\nConversations are stored in project-local SQLite databases. The CLI and the\n`/sessions` panel support listing, searching, resuming, inspecting, exporting,\nand deleting sessions, which makes a long software task a recoverable workflow\ninstead of a one-shot prompt.\n\n## Architecture\n\nRony Harness is the CLI/TUI layer around the sibling `rony-llm-agent` library.\nThe library owns the reusable agent loop, LLM client interfaces, persona,\nmemory primitives, and base sandbox components. This repository adds the\nsoftware-development surface:\n\n- A single `rony` binary with Cobra commands for chat, sessions, configuration,\n  diagnostics, and version information.\n- A Bubble Tea interface with a chat transcript, model and LLM panels, a\n  learning sidebar, an edited-files sidebar, and interactive approval cards.\n- A tool registry assembled from the harness's file, shell, delegation, plan,\n  memory, and dictionary tools.\n- Agent-mode configuration, `AGENTS.md` discovery, file attachments, and\n  project-local session storage.\n\nThe result is a deliberate boundary: the library makes agent behavior\nreusable, while the harness makes that behavior comfortable and inspectable\nfor day-to-day software work.\n\n## Design decisions\n\n### Approval is part of the product surface\n\nTool calls are rendered as visible events instead of being hidden behind the\nfinal response. Destructive operations default to an approval gate, and the\nbash sandbox applies allow and deny patterns before execution. The project\ndocumentation is explicit that filesystem confinement is still a follow-up;\nthe current safety model is built around permissions, command filtering, and\ncredential redaction.\n\n### Local-first without locking out hosted providers\n\nLocal llama.cpp servers can be started, stopped, monitored, and switched from\nthe `/llm` panel, while cloud providers remain available through the same\nprovider abstraction. This makes privacy, cost, latency, and capability a\nruntime choice rather than a rewrite of the agent workflow.\n\n### A visible trail beats a magical answer\n\nThoughts, tool calls, approvals, tool results, and edited files all stay near\nthe conversation. The interface is designed for a human who wants to know\nwhat happened and why, not only whether the model produced a plausible final\nparagraph.\n\n## Try the project\n\nRony Harness is a terminal application, so there is no browser demo to embed\nhere. Explore the implementation, configuration reference, and as-built\narchitecture in the **[public source repository ↗](https://src.sersofts.org/victorvargas/rony-harness)**.\n\n## Files of note\n\n- `cmd/rony` — the executable entry point and CLI command wiring.\n- `internal/cli` — the Bubble Tea chat, slash commands, panels, approvals,\n  agent modes, sessions, and tool registry assembly.\n- `internal/tools` — file, shell, and web handlers used by the harness.\n- `internal/storage` — SQLite session persistence and export support.\n- `internal/sandbox` — bash allow/deny patterns and execution limits.\n- `configs/ai_providers.yaml` and `configs/agents.yaml` — provider and mode\n  defaults that can be extended without changing the workflow code.\n- `docs/architecture.md` — the repository's detailed as-built technical\n  reference."},{"slug":"rony-llm-agent","title":"rony-llm-agent","stack":["Go","OpenAI API","llama.cpp","ChromaDB","MCP","Agent loop"],"status":"live","repoUrl":"https://src.sersofts.org/victorvargas/rony-llm-agent","description":"# rony-llm-agent\n\n## Overview\n\n`rony-llm-agent` is the reusable core behind products such as `rony-chat-bot` and `rony-harness`. It owns the generic agent behavior — provider abstraction, orchestration, memory, personas, tools, and sandboxing — while each product chooses its UI, deployment, and configuration.\n\nIt is intentionally not a CLI, HTTP server, or opinionated product. Consumers use the public Go interfaces and compose only the adapters they need.\n\n![Rony describing the reusable agent runtime](/projects/rony-llm-agent/agent-response.webp)\n\n## Execution model\n\nThe agent loop is streaming-first and explicit about its limits. A run adds the user input to history, asks the configured LLM for a response, executes any requested tools, appends their results, and continues until the model finishes or a guardrail stops the run.\n\n- `iter.Seq2` exposes generated chunks without callback plumbing.\n- `MaxIters`, token budgets, timeouts, and stop conditions are configuration rather than hidden constants.\n- A mock LLM client makes every iteration deterministic in unit tests without spending real tokens.\n- The response reports content, tool calls, iteration count, duration, and token usage for downstream observability.\n\n![Rony explaining the filesystem sandbox used by the agent](/projects/rony-llm-agent/agent-safety.webp)\n\n## Architecture\n\nThe library follows ports-and-adapters boundaries so products do not couple their domain logic to a provider SDK or storage implementation:\n\n| Package | Responsibility |\n| --- | --- |\n| `pkg/agent` | Iterative loop, streaming, termination, approvals, and sub-agent orchestration. |\n| `pkg/llm` | Provider-neutral client interface, message types, capabilities, and streaming chunks. |\n| `pkg/rag` | Retrieval-augmented memory, embeddings, semantic search, and vector-store boundaries. |\n| `pkg/persona` | System prompt assembly, YAML personas, and `AGENTS.md` discovery. |\n| `pkg/tools` | JSON Schema tools, permissions, registry, results, and execution contracts. |\n| `pkg/config` | YAML loading and hierarchical configuration precedence. |\n\nPorts define the behavior the core needs; adapters implement OpenAI, Anthropic, Ollama, llama.cpp, ChromaDB, and embedding backends around those contracts. Swapping an adapter does not change the agent loop.\n\n## Safety boundaries\n\n- **Filesystem sandbox** — tool paths are validated against the consumer's project root with `os.Root`, keeping reads and writes inside the allowed workspace.\n- **Permission-aware tools** — tools carry `Allow`, `Ask`, or `Deny` policies so a product can require approval before consequential operations.\n- **Bounded execution** — iteration, token, output-size, timeout, and cancellation limits prevent one model response from consuming an unbounded run.\n- **Deterministic tests** — mock providers and embedding clients let the security and termination rules be tested without external services.\n- **Persona isolation** — the consuming project's `AGENTS.md` contributes its own instructions, so the same runtime can serve different products without sharing product-specific behavior.\n\nThe library provides the safety primitives; the consuming application still owns its approval UX and deployment permissions.\n\n## Product adapters\n\nThe same runtime is used in different contexts:\n\n| Consumer | What it adds |\n| --- | --- |\n| [rony-chat-bot](/proyectos/rony-chat-bot) | HTTP/SSE transport, persistent conversations, portfolio retrieval, and a drop-in web widget. |\n| Rony Harness | Terminal UI, local/cloud model selection, real software tools, approvals, and persistent sessions. |\n\nRony Harness is a concrete consumer of the runtime: the same core handles model selection, tool execution, approvals, and the resulting files while the TUI owns the interaction surface.\n\n![Rony Harness selecting model scripts for an agent session](/projects/rony-harness/select-models.png)\n\n![Rony Harness showing tool calls and generated files](/projects/rony-harness/tools-and-files.png)\n\nThis separation keeps the core small and reusable while allowing each product to expose the right interaction model for its audience.\n\n## Files of note\n\n- `pkg/agent/` — loop orchestration, streaming, iteration limits, and sub-agents.\n- `pkg/llm/` — public provider interface and model-neutral request/response types.\n- `pkg/rag/` — memory and retrieval abstractions.\n- `pkg/persona/` — persona loading and `AGENTS.md` discovery.\n- `pkg/tools/` — registry, permissions, tool results, and sandbox integration.\n- `docs/architecture.md` — ports, adapters, core interfaces, and the execution algorithm."},{"slug":"tetris","title":"Tetris","stack":["Astro","React","TypeScript","Tailwind CSS","Web Audio API","HTML5 Canvas"],"status":"live","demoId":"tetris","description":"# Tetris\n\n## Overview\n\nA complete Tetris clone that runs as an Astro server-rendered page with a\nclient-side React island for the game board. It uses HTML5 Canvas for rendering\nthe grid, Web Audio API for sound effects, and Tailwind 4 design tokens so it\nstays consistent across both Rony Classic and Modern visual variants.\n\n## Demo behavior\n\nThe local demo renders a full game grid with piece preview, hold queue, next-pool\ndisplay, ghost piece, scoring, levels, and line-clearing animation frames. All\nkeyboard controls are wired to the native game loop — left, right, down, rotate,\nspawn, hard-drop, pause — with no external dependencies.\n\nOn smaller screens, the board scales to the available width and exposes touch\ncontrols for movement, rotation, hard drop, pause, and restart.\n\nOn mobile, tap the board to rotate the piece, swipe down to hard-drop it, and\nuse the side arrows to move left or right.\n\n![Playable Tetris board and controls](/projects/tetris/overview.png)\n\nThe screenshot shows the compact game surface: a 10-column board, a falling\npiece and ghost piece, the next-piece preview, score/level/line counters, and\nthe pause, rotate, and drop controls. The same surface adapts to both the\nClassic and Modern portfolio variants.\n\n## Technologies\n\n- Astro 7 for server-side routing and prerendered pages.\n- React 19 island via `@astrojs/react` for the game board and state management.\n- TypeScript strict mode across all components and types.\n- Tailwind 4 (Vite plugin) for layout, spacing, responsive breakpoints, and\n  design tokens shared with the rest of the portfolio.\n- HTML5 Canvas for rendering the grid, active piece, ghost piece, and line-clear\n  animation frames.\n- Web Audio API for synthesized sound effects — line clears, soft drop, hard drop,\n  tetris bonus, game over, and piece lock. No audio libraries or files required.\n- Generated with the local Qwen3.6 35B MoE model (`Qwen3.6-35B-A3B-IQ3_S`) as a\n  demonstration of running capable large language models on consumer hardware without\n  network dependencies.\n\n## Implementation notes\n\n- The React island is registered explicitly for `/demos/tetris` and keeps its own\n  game engine, rendering loop, audio synthesis, and fixtures alongside this document.\n- All UI colors reference the portfolio design tokens so the canvas overlay UI\n  (buttons, score readouts, level indicators) automatically supports both Rony\n  Classic and Modern visual variants."}]