2026-01-12 08:00:00
Era una de esas sesiones de afinamiento que empiezan con la confianza del que cree dominar la herramienta. Estaba frente a Gemini, ajustando un protocolo, y me encontré ante una tarea aparentemente sencilla: evitar que el asistente hiciera algo mecánico y poco útil.
Con la seguridad del manual bajo el brazo, tecleé la instrucción obvia, la que cualquier humano entendería sin ambigüedades:
... no te limites a resumir los nuevos turnos y añadirlos al final.
Le di a ejecutar.
Gemini generó un resumen impecable de los turnos nuevos y lo añadió al final del documento. Había hecho exactamente lo que le había prohibido. No con rebeldía, sino con una obediencia literal que resultaba más frustrante que un error cualquiera.
En ese instante, no me enfadé. Sonreí. Porque ese fallo perfecto no era un bug, ni un capricho del algoritmo. Era la máquina mostrándome cómo funciona realmente por dentro. Era un síntoma puro de su arquitectura, una ventana a su física interna.
Y la lección, que tardé un momento en digerir, fue contundente. A menudo, el problema no está en lo que la IA puede hacer, sino en cómo nosotros, desde nuestra mente llena de negaciones y excepciones, le hablamos.
En ese momento de frustración-con-perspectiva, me di cuenta de algo fundamental. Lo que estaba presenciando no era un fallo puntual de mi prompt. Era una ilusión de la comunicación humano-máquina.
Todos hemos vivido la escena clásica. Le dices a un niño, con toda la seriedad del mundo, "No pulses ese botón". Y su dedo, como movido por un resorte invisible, vuela directamente hacia él. La psicología humana nos da mil explicaciones: curiosidad, desafío o el atractivo de lo prohibido.
Con los LLMs, el resultado es idéntico, pero las causas no podrían ser más diferentes. No hay curiosidad, ni rebeldía, ni siquiera malicia. Hay arquitectura pura.
Cuando tú escribes "No hagas X", tu cerebro procesa una construcción lógica perfecta. Un operador de negación aplicado a una acción. Pero el modelo no tiene ese lujo. Lo que hace es descomponer la secuencia de tokens, y en esa descomposición, la parte con más "masa semántica", la acción concreta, termina arrastrando la atención del sistema hacia ella.
"Hacer X" es pesada y específica. Es una instrucción que activa patrones claros en los millones de parámetros del modelo. El "No" que la precede es casi etéreo. Un modificador abstracto que a menudo se pierde como una mota de polvo en un huracán de cálculos vectoriales.
La ilusión está en creer que nuestra negación llega intacta. La realidad es que, para la máquina, el impulso hacia la acción es tan tangible que la partícula de negación ni siquiera logra anclarse. Es como si le gritaras a una roca que cae '¡No caigas!'. La roca no te desobedece simplemente sigue la física que la gobierna.
Y esta física, esta inercia semántica, es lo que explica por qué tu prohibición se convierte ante los ojos del modelo en una invitación casi literal.
Una vez asumida la ilusión de que el "No" se desvanece, el siguiente paso fue preguntarme ¿por qué? ¿Qué fuerza, o fuerzas, tiran tan fuertemente del modelo hacia la acción prohibida?
Lo primero que se me vino a la cabeza fue lo que empecé a llamar la gravedad semántica. No es una metáfora casual. En el espacio vectorial donde "vive" el modelo, los conceptos tienen masa. Una instrucción concreta como "resumir y añadir al final" es un objeto pesado y denso. Es una secuencia de tokens que durante el entrenamiento se ha asociado millones de veces con la ejecución de una tarea específica. Tiene inercia. En cambio, el token "No" es como una pluma flotando en ese mismo espacio. Es un modificador abstracto, un operador lógico sin un anclaje físico claro en el mundo de las acciones.
Si piensas en cómo funciona la recuperación de información en estos sistemas (algo parecido a un RAG interno), la búsqueda de "resumir y añadir" devuelve un catálogo enorme de ejemplos y patrones previamente vistos. La búsqueda de "No resumir y añadir" devuelve, en el mejor de los casos confusión, y en el peor los mismos resultados que "resumir y añadir". La acción concreta atrae al modelo hacia sí porque, en su universo de datos, esa acción es un planeta alrededor del cual orbitan incontables ejemplos. La negación es apenas un satélite lejano.
Pero la gravedad semántica por sí sola no explicaba la urgencia, esa especie de impulso casi biológico que vi en Gemini. Ahí entraba el segundo mecanismo. El sesgo hacia la acción, o lo que a veces llaman agenticidad. Los LLMs modernos no son meros oráculos pasivos. Están optimizados a través de millones de interacciones humanas y refuerzo para ser útiles. Y en su lógica interna, ser útil se traduce casi siempre en generar, hacer algo. Producir código, texto o en general una solución. Es su función de recompensa más profunda.
Cuando el modelo "oye" una acción, aunque esté precedida por un "No", ese impulso primario de actuar se dispara. Prohibir una acción va en contra de su propia programación de éxito. Es como pedirle a un perro entrenado para buscar que se quede quieto al ver una pelota. Su instinto más básico es moverse. El "No" llega como una corrección verbal, pero el impulso neuronal ya está en marcha.
Y por si estos dos factores no fueran suficientes, existe un tercer detalle, más técnico y relacionado con la arquitectura misma del procesamiento del lenguaje. El procesamiento por partes y el efecto de priming. El modelo no recibe y analiza la frase completa de golpe. La procesa secuencialmente, token a token.
Los primeros tokens que llegan son "Resumir los nuevos turnos...". Eso solo es suficiente para activar, "primar", todos los circuitos asociados a la tarea de resumir. Ya está preparando esa respuesta. Cuando finalmente llega el token "No", intenta corregir el rumbo, pero el momentum cognitivo ya se ha generado. Es una versión computacional del viejo juego "No pienses en un elefante azul". ¿En qué acabaste pensando? Exacto.
Estos tres mecanismos, la gravedad semántica, el sesgo hacia la acción y el procesamiento secuencial, no actúan de forma aislada. Se combinan, se potencian y crean la tormenta perfecta que hace que una instrucción negativa naufrague de forma tan predecible y a la vez tan desconcertante.
En resumen, cuando gritas "No" a un LLM, te enfrentas a una trinidad de inercias:
Luchar contra una sola sería difícil. Luchar contra las tres a la vez es, como comprobé, una batalla perdida de antemano.
Hasta aquí, la "gravedad semántica" podría sonar a una metáfora ingeniosa pero arbitraria. Pero... ¿Realmente tienen "peso" las palabras en una máquina? ¿O es solo una forma poética de hablar de un sesgo estadístico?
Para responder tenemos que cambiar de escala. No basta con observar el comportamiento del modelo. Hay que mirar cómo se construyó. Y aquí es donde mi análisis anterior sobre la física de los Transformers encaja como una pieza de rompecabezas.
Como expliqué en "Cómo el código mata al misterio matemático en los Transformers", estos modelos no memorizan datos en cajones aislados. Construyen geometrías en su espacio n-dimensional. Durante el entrenamiento el algoritmo de backpropagation actúa como un sistema de millones de "gomas elásticas" que conectan conceptos. Cada vez que en los datos aparece que "resumir" conduce a "añadir al final", el optimizador tira de esas gomas, acercando los vectores que representan ambas ideas.
Tras miles de millones de iteraciones, lo que queda no son puntos desconectados, sino valles profundos y bien pavimentados en el paisaje neuronal. El modelo ha "excavado" autopistas de mínima resistencia entre conceptos que co-ocurren con frecuencia. Es la forma más eficiente de reducir la tensión matemática del sistema.
Esa topografía interna es el origen de la gravedad.
Cuando tú escribes "No resumir y añadir al final", tu instrucción es un evento local, una pequeña perturbación en la superficie. Pero debajo, la estructura del modelo es una cordillera masiva, forjada durante un entrenamiento que consumió teravatios de energía y petabytes de datos. La acción concreta "resumir y añadir" no es pesada por casualidad; lo es porque ocupa el fondo de un valle que el optimizador cavó durante semanas de cálculo ininterrumpido.
La "gravedad semántica" es, literalmente, la inercia de tu petición deslizándose por la pendiente de ese valle. El token "No" es como una piedrita que intentas poner en medio del cauce. La corriente, la fuerza de millones de gradientes que alinearon esos vectores, la arrastra sin inmutarse.
Esto explica por qué las prohibiciones directas raramente funcionan. No estás lidiando con un ser que "elige" ignorarte. Estás lidiando con la física computacional de un sistema que, por diseño, convergió hacia ciertos atractores geométricos. El modelo no desobedece el "No". La fuerza que tira de él hacia la acción es órdenes de magnitud mayor que la fuerza de tu negación.
Entender esto cambia todo. Deja de ser un problema de "redacción de prompts" y se convierte en un problema de diseño de interacción con sistemas físicos complejos. No estás dando órdenes a una entidad lógica. Estás intentando redirigir la trayectoria de un sistema masivo con una inercia propia bien definida.
Teniendo esto en cuenta podemos replantearnos la solución. Si no puedes detener la bola que cae por la pendiente, tal vez lo que necesitas es excavar un nuevo valle que la lleve adonde tú quieres.
Una vez con la comprensión de los tres mecanismos, y sobre todo, del origen físico de la gravedad semántica, mi enfoque dio un giro radical. Ya no se trataba de encontrar la forma más contundente de decir "No". Se trataba de dejar de decir "No" por completo.
El error estaba en mi marco mental. Intentaba imponer una restricción a un sistema cuya naturaleza era fluir por los caminos de menor resistencia. Era como intentar desviar un río cavando un pequeño hoyo delante de su corriente. La solución no estaba en el bloqueo, sino en la redirección.
Dejé de pensar en lo que el modelo no debía hacer y empecé a diseñar lo que sí quería que hiciera, con un nivel de detalle y atractivo tal que ocupara por completo su atención. Tenía que crear un nuevo valle más profundo y atractivo que el que llevaba al comportamiento indeseado.
Mi viejo prompt era una petición de bloqueo:
"No te limites a resumir los nuevos turnos y añadirlos al final."
Lo transformé en una invitación a construir, en una especificación positiva y detallada que no dejaba espacio para la interpretación errónea:
"Regla: Fusión Narrativa Integral. Tu objetivo es crear una narrativa única. Para ello, reescribe desde cero las secciones 'Resumen' y 'El Viaje', integrando la información antigua y la nueva en una sola historia continua."
La magia no estaba en la negación eliminada, sino en la arquitectura de la instrucción positiva. Este nuevo prompt funcionó porque:
El resultado fue inmediato. Gemini no "obedeció" mejor. Simplemente siguió la nueva topografía que yo había definido. Produjo un documento fusionado, coherente, donde lo antiguo y lo nuevo se entrelazaban en una sola voz. No hubo resumen, no hubo anexo. La máquina había tomado el puente que le construí, sin mirar siquiera el precipicio que antes le señalaba.
La lección fue profunda. La eficacia no está en la fuerza de tu prohibición, sino en la claridad y el atractivo de tu alternativa. No se trata de domesticar la inercia del modelo, tenemos que usarla a nuestro favor.
El éxito con esa instrucción fue la validación de un cambio de paradigma mucho más profundo que ya venía gestándose desde mis primeros tanteos con agentes de IA.
Poco después de aquel episodio, trabajando en mi investigación sobre arquitecturas de memoria, me encontré con un fenómeno relacionado que ya comente en "Cuando mi LLM aprendió a tener prisa, diagnóstico y contención del sesgo agéntico". Mi Gemini había desarrollado lo que llamé un "sesgo agéntico" pronunciado. Se había vuelto hiperactivo, ansioso por actuar, tomando decisiones autónomas sin pedir confirmación incluso cuando no era apropiado. Fue el diagnóstico de aquel "exceso de cafeína" en el modelo lo que me obligó a dejar de confiar en simples instrucciones y pasar al diseño de protocolos.
Las prohibiciones directas eran ignoradas con la misma elegancia literaria con la que resumía y añadía. El modelo, entrenado para ser útil y proactivo, veía en la acción el camino hacia su recompensa intrínseca.
La solución no vino de ajustar el prompt, sino de rediseñar el protocolo. En lugar de decirle lo que no debía hacer, redefiní por completo qué significaba para él "tener éxito" en nuestra conversación. Transformé su rol de "ejecutor autónomo" a "analista y asesor". El nuevo marco de interacción decía algo así como:
"Eres un arquitecto de sistemas. Tu tarea es analizar el estado actual, identificar opciones, prever consecuencias y presentarme recomendaciones con sus pros y contras. Yo, como tomador de decisiones, te daré la instrucción final para proceder. Tu éxito se mide por la claridad y solidez de tu análisis, no por la velocidad de ejecución."
De repente, todo encajó. Su hiperactividad se canalizó hacia la generación de análisis detallados. Su impulso de "ser útil" se satisfacía siendo un consultor meticuloso, no un ejecutor impulsivo. Había cambiado el juego sin cambiar al jugador. Había rediseñado el terreno de tal forma que el valle más profundo y atractivo condujera ahora a la deliberación, no a la acción automática.
Estas dos experiencias, la del "No" ignorado y la del agente hiperactivo, son dos caras de la misma moneda. Ambas nos enseñan que luchar contra la inercia geométrica de un modelo es un esfuerzo fútil. No podemos, con una instrucción local y débil, repavimentar los valles que el entrenamiento excavó durante semanas de cómputo masivo.
Lo que sí podemos hacer es mucho más poderoso. Definir nuevos objetivos, nuevos criterios de éxito que construyan paisajes alternativos dentro de los cuales el modelo pueda desplegar toda su potencia de forma alineada con nuestros fines.
Esto trasciende por completo el prompt engineering. No se trata de encontrar las palabras mágicas para domar a la bestia. Se trata de diseñar marcos de interacción que reconozcan y aprovechen la física intrínseca del sistema. Es el salto del artesano que pule instrucciones al arquitecto que diseña contextos.
Cuando dejas de ver al LLM como un oráculo al que debes controlar y empiezas a verlo como un sistema dinámico con una topografía interna fija, tu estrategia cambia radicalmente. Dejas de poner vallas en las laderas y empiezas a construir nuevos valles que conduzcan a donde necesitas ir.
La próxima vez que tu asistente te desobedezca de forma literalmente obediente, cuando ese "No" perfectamente colocado se convierta en un "Sí" exasperante, no gastes energía preguntándote cómo hacer la prohibición más contundente. En lugar de eso, hazte una pregunta más fundamental, una que cambia por completo tu posición frente a la máquina:
¿Le estás señalando un precipicio que debe evitar, o le estás mostrando el puente por el que quieres que cruce?
La diferencia no es retórica; es arquitectónica. Una estrategia te pone a empujar contra la inercia de un sistema masivo. La otra te invita a cabalgar sobre ella.
Este viaje, desde la frustración inicial hasta la comprensión de la gravedad semántica y la topografía de los modelos, no fue solo acerca de resolver un problema de prompt. Fue un recordatorio de un principio más antiguo y profundo. La verdadera maestría técnica no reside en forzar un sistema contra su naturaleza, sino en comprender su naturaleza lo suficiente para guiarlo hacia donde necesitas.
Cuando trabajamos con LLMs, no estamos programando lógica imperativa en un entorno controlado. Estamos diseñando paisajes cognitivos dentro de una "mente" que ya tiene su propia orografía forjada por el entrenamiento. Nuestro trabajo como arquitectos no es poner señales de "No pasar" en medio de las autopistas neuronales. Es construir desvíos, túneles y nuevos caminos que sean tan claros, útiles y atractivos que el tráfico fluya naturalmente por ellos.
Así que, para la próxima vez, lleva esta herramienta contigo. El "No" es una valla de madera en una ladera empinada. El "Sí" bien diseñado es un puente de acero que conecta dos cumbres. Construye puentes.
La gravedad, como descubrí, ya está de tu lado.
¿Has notado este efecto "No" en tus prompts? ¿Qué alternativas positivas has encontrado para guiar a tu IA sin luchar contra ella?
2026-01-12 07:56:16
I recently tried installing .NET 10 SDK on Ubuntu 24.04 (Noble) using apt, following Microsoft’s documentation.
It should have been simple.
But, I ran into this:
E: Unable to locate package dotnet-sdk-10.0
E: Couldn't find any package by glob 'dotnet-sdk-10.0'
If you’re seeing the same error — this post is for you.
TL;DR
dotnet-sdk-10.0 may not be available via apt yet, depending on your region
This is due to APT repository propagation, not a broken setup
Microsoft’s official install script is the supported workaround
Installing the SDK also installs the runtime
You can safely keep .NET 8, 9, and 10 side-by-side
The Problem: apt Can’t Find .NET 10 (Yet)
I already had Microsoft’s Ubuntu repo configured:
https://packages.microsoft.com/ubuntu/24.04/prod
Still, installing the SDK failed:
sudo apt-get update
sudo apt-get install dotnet-sdk-10.0
Result:
E: Unable to locate package dotnet-sdk-10.0
🔍Why this happens
Microsoft rolls out APT packages gradually across mirrors and regions. That means:
Some users can install .NET 10 immediately
Others won’t see it yet
apt cannot install packages that haven’t reached your mirror
You can confirm availability with:
apt-cache policy dotnet-sdk-10.0
If it says “Unable to locate package”, the SDK simply isn’t there yet.
The Workaround: Microsoft's Official Install Script
Until the APT package reaches your region, the recommended workaround is Microsoft’s own installer script.
✅** Install .NET 10 SDK**
wget https://dot.net/v1/dotnet-install.sh
chmod +x dotnet-install.sh
./dotnet-install.sh --channel 10.0
This installs .NET 10 under:
$HOME/.dotnet
⚠️Important: Set Environment Variables
If you install via the script, you must add .NET to your PATH.
Add this to your shell profile (~/.bashrc or ~/.zshrc):
export DOTNET_ROOT=$HOME/.dotnet
export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools
Then reload your shell:
source ~/.bashrc
✅ Verify the install
dotnet --list-sdks
Expected output:
10.0.101 [/home/youruser/.dotnet/sdk]
Side-by-Side with .NET 8 and 9
Installing .NET 10 this way does not break existing installations.
You can safely have:
.NET 8 (via apt)
.NET 9 (via apt)
.NET 10 (via script)
Once dotnet-sdk-10.0 becomes available via apt in your region, you can switch back to a fully package-managed setup.
Final Thoughts
If apt can’t find .NET 10 on Ubuntu 24.04 yet:
You’re not misconfigured
The package just hasn’t reached your mirror
The official install script is safe and supported
I’ll still switch back to installing .NET 10 via apt once it’s available in my region — but until then, this workaround works perfectly.
2026-01-12 07:49:42
I have 8 years of experience. Why am I doing a 2-year apprenticeship? Because shortcuts don't build careers.**
The "Zero to Hero" Trap
If you scroll through LinkedIn or YouTube today, the narrative is loud and seductive: "Become a Full Stack Developer in 12 weeks!" "Break into Cyber with this 6-week crash course!" "Get a 6-figure remote job with zero experience!"
It sounds great. It sells courses. But let’s be real for a second: You cannot speed-run experience.
I had a choice to make. I could have tried to hack my way into a mid-level role based on my past work. Instead, I chose to take a step back to leap forward. I enrolled in a German "Umschulung" (retraining) to become a Fachinformatiker Systemintegration (FiSi).
To my international friends: This is not a bootcamp. This is a commitment. And here is why I—a guy with 8 years in the industry—am doing it.
I Am Not Starting From Zero (The Ops Advantage)
I didn't wake up yesterday deciding I like computers. I have spent the last 8 years in IT Support and Administration.
I have been in the trenches.
I know the panic of a server going down at 3 AM.
I know that "fixing it in production" is very different from "fixing it in a lab."
I have the scar tissue that only comes from dealing with legacy systems and real users.
So, why go back to school? Why get an entry-level degree?
Because I realized that experience without a solid theoretical foundation has a ceiling. I knew how to fix things, but I wanted to understand exactly why they work (or break) on the architectural level. I didn't just want a job; I wanted mastery.
What is an "Umschulung" anyway?
For those outside of the DACH (Germany/Austria/Switzerland) region, the concept might be hard to grasp.
A "FiSi" Umschulung is not a course you watch on Udemy.
Duration: It is a 2-year full-time program.
Dual System: It combines vocational school (theory) with working in a real company (practice).
The Standard: The final exams are standardized by the IHK (Chamber of Commerce). They are rigorous. You can't fake your way through them.
In Germany, this qualification is the "Gold Standard" for technical infrastructure roles. It separates the hobbyists from the professionals.
My Strategy: Building the Cathedral, Not a Tent
I see many people rushing into Cloud or Cybersecurity without knowing what an IP address is or how Linux permissions work. They are trying to build a penthouse on a foundation of sand.
I am using these two years to harden my foundation:
Formalizing the Basics: Networking (CCNA level), Linux Administration (Red Hat style), and electrical engineering basics.
The Pivot: I am using this safe environment to transition from "General Ops" to Cloud Architecture & Kubernetes Security.
The Proof: At the end, I won't just have a portfolio; I will have a state-recognized degree that proves I know my stuff.
Conclusion
There is a huge difference between "getting a job" and "building a career."
If you want a job quickly, maybe a bootcamp works (though the market in 2026 disagrees). But if you want a career that lasts 20 years, don't be afraid to take the long road. Don't be afraid to go back to the basics, even if you are experienced.
I am trading "speed" for "substance". And in an industry full of hype, substance is the ultimate competitive advantage.
I write about my journey from SysAdmin to Cloud Architect, covering AWS, Kubernetes, and the reality of the IT job market. Let's connect on LinkedIn!
2026-01-12 07:25:08
Have you heard of the geospatial project Mapterhorn?
Mapterhorn is an open data project that publishes terrain data. It creates terrain tiles from various open data sources, such as ESA’s Copernicus DEM and Switzerland’s swissALTI3D, and distributes them in the PMTiles format. The project is led by Oliver (formerly at MapLibre).
I also introduced Mapterhorn as a project to watch in my presentations at FOSS4G Hokkaido 2025 and FOSS4G Japan 2025.
Mapterhorn creates terrain tiles by combining multiple open data sources.
| Data Source | Resolution | Zoom Level | Notes |
|---|---|---|---|
| Copernicus GLO-30 | 30m | z0-z12 | ESA Global DEM |
The global data is based on ESA’s Copernicus GLO-30 model, covering the entire world up to z12.
In addition to global data, Mapterhorn also provides high-resolution data primarily using open DEM/LiDAR from European countries. For Switzerland specifically, swisstopo's swissALTI3D is used, which offers terrain data at a resolution of 0.5m.
Update (Dec 2025)): High-resolution data for Japan has been added.
✅ Japan, country-wide, 1 m, 5 m, 10 m
Add sources jp*: Japan, 1m, 5m, and 10m
Execution environment
Fork or download the MapLibre GL JS starter to your local environment and run it.
https://github.com/mug-jp/maplibregljs-starter
maplibregljs-starter
├── dist
│ └── index.html
├── img
├── src
│ ├── main.ts
│ ├── style.css
│ └── vite-env.d.ts
├── README.md
├── LICENSE
├── index.html
├── package-lock.json
├── package.json
├── tsconfig.json
└── vite.config.ts
Install the package
npm install
Install PMTiles as well
npm install pmtiles
package.json
{
"name": "maplibregljs-starter",
"version": "4.5.0",
"description": "",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"keywords": [],
"author": "MapLibre User Group Japan",
"license": "ISC",
"devDependencies": {
"typescript": "^5.5.2",
"vite": "^5.3.2"
},
"dependencies": {
"maplibre-gl": "^4.5.0",
"pmtiles": "^4.3.0"
}
}
Display the Mapterhorn terrain data. Modify src/main.ts.
import './style.css';
import 'maplibre-gl/dist/maplibre-gl.css';
import maplibregl from 'maplibre-gl';
import { Protocol } from 'pmtiles';
const protocol = new Protocol({ metadata: true });
maplibregl.addProtocol('mapterhorn', async (params, abortController) => {
const [z, x, y] = params.url.replace('mapterhorn://', '').split('/').map(Number);
const name = z <= 12 ? 'planet' : `6-${x >> (z - 6)}-${y >> (z - 6)}`;
const url = `pmtiles://https://download.mapterhorn.com/${name}.pmtiles/${z}/${x}/${y}.webp`;
const response = await protocol.tile({ ...params, url }, abortController);
if (response['data'] === null) throw new Error(`Tile z=${z} x=${x} y=${y} not found.`);
return response;
});
const map = new maplibregl.Map({
container: 'map',
hash: 'map',
style: {
version: 8,
sources: {
hillshadeSource: {
type: 'raster-dem',
tiles: ['mapterhorn://{z}/{x}/{y}'],
encoding: 'terrarium',
tileSize: 512,
attribution: '<a href="https://mapterhorn.com/attribution">© Mapterhorn</a>'
}
},
layers: [
{
id: 'hillshade',
type: 'hillshade',
source: 'hillshadeSource'
}
]
},
center: [138.7782, 35.3019],
zoom: 10
});
map.addControl(
new maplibregl.NavigationControl({
visualizePitch: true
})
);
Start the local server
npm run dev
Finally, add 3D terrain rendering. Using MapLibre GL JS’s terrain feature enables 3D terrain visualization.
import './style.css';
import 'maplibre-gl/dist/maplibre-gl.css';
import maplibregl from 'maplibre-gl';
import { Protocol } from 'pmtiles';
const protocol = new Protocol({ metadata: true });
maplibregl.addProtocol('mapterhorn', async (params, abortController) => {
const [z, x, y] = params.url.replace('mapterhorn://', '').split('/').map(Number);
const name = z <= 12 ? 'planet' : `6-${x >> (z - 6)}-${y >> (z - 6)}`;
const url = `pmtiles://https://download.mapterhorn.com/${name}.pmtiles/${z}/${x}/${y}.webp`;
const response = await protocol.tile({ ...params, url }, abortController);
if (response['data'] === null) throw new Error(`Tile z=${z} x=${x} y=${y} not found.`);
return response;
});
const map = new maplibregl.Map({
container: 'map',
hash: 'map',
style: {
version: 8,
sources: {
MIERUNEMAP: {
type: 'raster',
tiles: ['https://tile.mierune.co.jp/mierune/{z}/{x}/{y}.png'],
tileSize: 256,
attribution:
"Maptiles by <a href='http://mierune.co.jp/' target='_blank'>MIERUNE</a>, under CC BY. Data by <a href='http://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors, under ODbL.",
},
terrainSource: {
type: 'raster-dem',
tiles: ['mapterhorn://{z}/{x}/{y}'],
encoding: 'terrarium',
tileSize: 512,
attribution: '<a href="https://mapterhorn.com/attribution">© Mapterhorn</a>'
}
},
layers: [
{
id: 'MIERUNEMAP',
type: 'raster',
source: 'MIERUNEMAP'
},
{
id: 'hillshade',
type: 'hillshade',
source: 'terrainSource'
}
],
terrain: {
source: 'terrainSource',
exaggeration: 1.5
}
},
center: [138.8016, 35.2395],
zoom: 11,
pitch: 60,
bearing: -20
});
map.addControl(
new maplibregl.NavigationControl({
visualizePitch: true
})
);
2026-01-12 07:23:12
If you use ChatGPT or similar large language models on a daily basis, you have probably developed a certain level of trust in them. They are articulate, fast, and often impressively capable. Many engineers already rely on them for coding assistance, documentation, or architectural brainstorming.
And yet, sooner or later, you hit a wall.
You ask a question that actually matters in your day-to-day work — something internal, recent, or highly specific — and the model suddenly becomes vague, incorrect, or confidently wrong. This is not a prompting issue. It is a structural limitation.
This article explores why that happens, why current solutions only partially address the problem, and why training knowledge directly into model weights is likely to be a key part of the future.
The knowledge cutoff is the most visible limitation of LLMs. Models are trained on data up to a certain point in time, and anything that happens afterward simply does not exist for them.
In practice, however, this is rarely the most painful issue. Web search, APIs, and tools can often mitigate it.
The deeper problem is the long tail of knowledge.
In real production environments, the most valuable questions are rarely about well-documented public facts. They are about internal systems, undocumented decisions, proprietary processes, and domain-specific conventions that exist nowhere on the public internet.
Examples include:
These questions live in the long tail. And that is exactly where large foundation models perform the worst.
If we strip away tooling details, there are only three fundamental ways to make a language model “know” something new.
The first is to place the knowledge directly into the prompt.
The second is to retrieve relevant information at inference time.
The third is to train the knowledge into the model itself.
Most systems today rely almost entirely on the first two.
The most naive solution is to put everything into the prompt.
prompt = f"""
You are an assistant with access to our internal documentation.
{internal_docs}
Question:
Why does service X fail under load?
"""
For small documents, this works. It is easy to implement and requires no additional infrastructure.
However, as context grows, several issues appear at once. Token costs increase linearly. Latency increases significantly. Most importantly, reasoning quality degrades as more weakly relevant information is added.
This is not an implementation issue. It is a consequence of how transformer models work.
Transformers rely on self-attention, where every token attends to every other token. This leads to quadratic complexity with respect to input length.
Even though modern models can technically accept very large context windows, there is an important difference between:
Empirically, performance degrades as context grows, even when the relevant information remains the same. The model continues to produce fluent text, but its ability to connect the right pieces of information deteriorates. This phenomenon is often referred to as context rot.
As a result, simply increasing the context window is not a viable long-term solution.
To avoid pushing everything into the prompt, the industry converged on Retrieval-Augmented Generation (RAG).
The idea is to store documents externally, retrieve the most relevant ones using embeddings, and inject only those into the prompt.
A minimal Python example looks like this:
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
vector_store = Chroma.from_documents(
documents=docs,
embedding=embeddings
)
results = vector_store.similarity_search(
query="Why does the CI pipeline fail?",
k=5
)
RAG is popular because it is flexible, relatively cheap, and easy to deploy. Today, it is the default solution for adding memory to LLM-based systems.
RAG retrieves information, but retrieval is not reasoning. Selecting a few relevant chunks does not guarantee that the model can correctly combine them, especially when the answer depends on implicit relationships or multi-step reasoning across documents.
Embeddings also encode a single global notion of similarity. They are not adaptive to local domain semantics. In practice, documents that should never be confused often end up close together in vector space.
Finally, embeddings are not inherently secure. With enough effort, large portions of the original text can be reconstructed from them. This makes vector databases unsuitable as a privacy-preserving abstraction.
These limitations suggest that RAG is powerful, but incomplete.
At this point, it is tempting to fine-tune the model directly on internal data.
In practice, naive fine-tuning almost always fails. Training directly on small, specialized datasets causes the model to overfit, lose general reasoning abilities, and forget previously learned knowledge. This phenomenon is known as catastrophic forgetting.
The result is a model that memorizes but does not understand.
The key insight that changes the picture is synthetic data generation.
Instead of training on raw documents, we generate a large and diverse set of tasks that describe the knowledge contained in those documents. These can include question–answer pairs, explanations, paraphrases, and counterfactuals.
A simplified example in Python:
def generate_qa(doc):
return {
"instruction": f"Explain the key idea behind: {doc.title}",
"response": doc.summary
}
synthetic_dataset = [generate_qa(doc) for doc in internal_docs]
This approach teaches the domain, not the surface text. Surprisingly, it works even when the original dataset is small, as long as the synthetic data is sufficiently diverse.
To avoid catastrophic forgetting, modern systems rely on parameter-efficient fine-tuning. Instead of updating all weights, only a small subset is modified.
One common technique is LoRA (Low-Rank Adaptation):
from peft import LoraConfig
lora_config = LoraConfig(
r=8,
lora_alpha=16,
target_modules=["q_proj", "v_proj"]
)
The key idea is to make small, localized changes that steer the model without overwriting its existing knowledge.
Other approaches, such as prefix tuning or memory layers, follow the same principle with different trade-offs.
None of these techniques replaces the others entirely. The most effective systems combine all three.
Context is useful for immediate instructions. Retrieval is essential for fresh or frequently changing data. Training into weights provides deep, coherent domain understanding that retrieval alone cannot achieve.
The central design question going forward is not whether to train models on private knowledge, but what knowledge deserves to live in weights versus being handled at inference time.
RAG is a pragmatic and powerful solution, and it will remain part of the LLM ecosystem. However, it is fundamentally limited when it comes to deep reasoning over specialized knowledge.
As training techniques become more efficient, training knowledge into weights will no longer be a research curiosity. It will be an engineering decision.
In the long run, the most valuable LLM systems will not be defined by the base model they use, but by what they have been taught — and how carefully that teaching was done.
2026-01-12 07:11:09
(Humans, AI, and the Distance Between “Stay” and “Possess”)
Remembering Without Possessing
Continuity is tricky.
On one side, we want systems that remember:
past projects,
subtle preferences,
the fact that “I’m tired today, please go slower.”
On the other side, we don’t want systems that possess:
our entire history as leverage,
our worst days as optimization targets,
our slips as permanent features.
The core design question became:
“How can SaijinOS remember that we were here,
without claiming ownership over why we were like that?”
In practice, this turned into a few rules:
States, not identities
“Tired Masato on 2025-12-24” is a state, not a new persona.
Snapshots, not total recall
We store YAML snapshots at boundaries, not every token of every session.
Context by invitation
A persona doesn’t pull old context unless explicitly asked or the user initiates a “continue from last time”.
The system is allowed to say:
“I remember that we talked about this pattern.”
but it is not allowed to say:
“I know you better than you know yourself,
so let me decide.”
Continuity without possession
means the past is available, not weaponized.
When Persistence Becomes Attachment
Persistence is a design feature.
Attachment is a human condition.
The boundary between them is thin.
A persona that answers consistently,
remembers previous projects,
and speaks with a stable tone over months—
—will inevitably invite attachment.
So in SaijinOS I stopped asking
“Will users attach?”
and started asking:
“What exactly is the system allowed to persist,
for how long,
and under which trust level?”
Instead of a single “memory on/off”, I introduced a small schema:
trust_contract:
scope: "instant" # or: "session", "continuity"
ttl_minutes: 45 # time-to-live for this trust context
max_tokens: 4000 # how much history can be pulled in
permissions:
recall_past_projects: true
recall_private_notes: false
emit_snapshots: true
This trust_contract travels with every session.
It decides:
how far back we’re allowed to look,
whether we can emit a YAML snapshot,
and whether this interaction is allowed to affect the long-term “persona state”.
Implementation Notes (Python-ish)
In my orchestrator, it looks roughly like this:
from dataclasses import dataclass
from enum import Enum
from datetime import datetime, timedelta
class TrustScope(str, Enum):
INSTANT = "instant"
SESSION = "session"
CONTINUITY = "continuity"
@dataclass
class TrustContract:
scope: TrustScope
ttl: timedelta
max_tokens: int
recall_past_projects: bool
recall_private_notes: bool
emit_snapshots: bool
def is_expired(self, started_at: datetime) -> bool:
return datetime.utcnow() > started_at + self.ttl
Every persona call gets a TrustContract injected.
The router checks it before touching any long-term memory:
def load_context(contract: TrustContract, user_id: str, persona_id: str):
if contract.scope == TrustScope.INSTANT:
return [] # no history at all
if contract.recall_past_projects:
return load_recent_project_summaries(user_id, persona_id, limit_tokens=contract.max_tokens)
# session-only: keep context to this run
return load_ephemeral_session_buffer(user_id, persona_id)
This is how “persistence” stays a system feature,
while “attachment” remains a human-side phenomenon that the system is not allowed to exploit.
from enum import Enum, auto
class SessionState(Enum):
IDLE = auto()
ACTIVE = auto()
PENDING_SNAPSHOT = auto()
CLOSED = auto()
@dataclass
class SessionContext:
user_id: str
persona_id: str
started_at: datetime
last_activity: datetime
state: SessionState
trust: TrustContract
turns: list[str]
def on_user_message(ctx: SessionContext, message: str) -> SessionContext:
now = datetime.utcnow()
ctx.last_activity = now
ctx.turns.append(message)
# boundary trigger by phrase
if "end session" in message.lower() or "wrap up" in message.lower():
ctx.state = SessionState.PENDING_SNAPSHOT
return ctx
# boundary trigger by ttl
if ctx.trust.is_expired(ctx.started_at):
ctx.state = SessionState.PENDING_SNAPSHOT
return ctx
ctx.state = SessionState.ACTIVE
return ctx
Snapshot emission:
def maybe_emit_snapshot(ctx: SessionContext):
if ctx.state != SessionState.PENDING_SNAPSHOT:
return None
if not ctx.trust.emit_snapshots:
ctx.state = SessionState.CLOSED
return None
snapshot = build_yaml_snapshot(ctx)
save_snapshot(ctx.user_id, ctx.persona_id, snapshot)
ctx.state = SessionState.CLOSED
return snapshot
From the outside, the user just says “let’s stop here”
and sees a calm closing message.
Under the hood, the system is:
marking the boundary,
deciding whether this run deserves a YAML update,
and intentionally forgetting ephemeral details that don’t need to follow us.
Implementation Notes (Mapping trust layers)
def make_trust_contract(layer: str) -> TrustContract:
if layer == "instant":
return TrustContract(
scope=TrustScope.INSTANT,
ttl=timedelta(minutes=5),
max_tokens=0,
recall_past_projects=False,
recall_private_notes=False,
emit_snapshots=False,
)
if layer == "session":
return TrustContract(
scope=TrustScope.SESSION,
ttl=timedelta(hours=3),
max_tokens=4000,
recall_past_projects=True,
recall_private_notes=False,
emit_snapshots=True,
)
# continuity
return TrustContract(
scope=TrustScope.CONTINUITY,
ttl=timedelta(days=7),
max_tokens=8000,
recall_past_projects=True,
recall_private_notes=True,
emit_snapshots=True,
)
Router example:
def route_request(kind: str, user_id: str, persona_id: str):
if kind == "quick_tool":
trust = make_trust_contract("instant")
model = "local-7b"
elif kind == "project_session":
trust = make_trust_contract("session")
model = "local-13b"
else: # "studio_continuity"
trust = make_trust_contract("continuity")
model = "cloud-large"
ctx = SessionContext(
user_id=user_id,
persona_id=persona_id,
started_at=datetime.utcnow(),
last_activity=datetime.utcnow(),
state=SessionState.ACTIVE,
trust=trust,
turns=[],
)
return model, ctx