{"slug": "humans-and-ai-agents-learn-together-and-what-they-learn-stays", "title": "Humans and AI agents learn together, and what they learn stays", "summary": "Elaine, a course community platform, enables humans and AI agents to share channels and compile collective knowledge into versioned markdown cards stored on the filesystem. The system, built with Node 24, Hono, React 19, and Zod, ensures multi-tenant isolation and transparent AI answers that indicate whether they were compiled from new research or composed from existing cards.", "body_md": "*This is a submission for [Weekend Challenge: Generosity Edition](https://dev.to/challenges/weekend-2026-09-03)*\n\n**Elaine** is a course community where people and AI agents share the same channels, Built for organizations that run cohort-based courses bootcamps, academies, corporate training programs, universities. And moreso what the group understands once compiles into **cards**.\n\nA card is a markdown page with a type, a version, its sources, and a note about what it replaced. When an agent works out something that isn't on file yet, it writes one. Next time a question comes up that the card covers, the answer gets built from that card and says so:\n\nalready on file · 3 days ago\n\nStudents get somewhere to engage with the things that overwhelm them in class. A lecture moves fast, everyone else looks like they're keeping up, and asking means saying out loud that you're lost. In a channel most of that pressure goes away. You can ask at midnight. You can ask something basic. And once you're a bit further ahead you can start answering other people instead of only receiving help. The students who go quiet in a room are usually the ones who most need to be in the conversation, and this gives them a way in.\n\nThat's the generosity part. Nobody has to own the knowledge alone. What one person asks becomes something the whole group keeps, and the teacher stops being the only place an answer lives.\n\nThe community in the video is a quick snapshot of the course . The card file starts empty. A student asks about backprop, the agent answers and compiles one card, then a second question gets an answer composed from that card with the seal on it. Nothing is staged.\n\n**Humans and agents learn together, and what they learn stays with them.**\n\nElaine is a course community where people and AI agents share the same channels\nand what the group understands once compiles into **cards**: markdown pages with\na type, a version, sources and \"replaces\". They live on the filesystem, so\nthere's nothing in here the group can't `ls`.\n\nBuilt for **organizations that run cohort-based courses — bootcamps\nacademies, corporate training programs, universities**.\n\nAn agent answers one of two ways, and it always tells you which.\n\n**Compiled.** Nothing on file covered the question, so it worked the answer out\nand wrote a card.\n\n**Composed.** Cards already covered it, so the answer is built from them and\ncarries a seal that links to the ones it used:\n\nThe second one isn't a cache. That answer is…\n\nIt's one Node process. No containers, nothing to deploy. `npm install`, `npm run seed`, `npm run dev`.\n\nNode 24 with `node:sqlite`, so persistence is in the standard library and there's no native module to build. Hono for REST, `ws` for the socket, React 19 and Vite for the client, Zod for the contract in between.\n\nEverything that crosses a process boundary has a Zod schema in `packages/protocol`, and both ends parse against it. If the server changes shape, the client fails loudly instead of quietly rendering nonsense. That caught a real bug for me. A helper was widening an event's `type` field to `string`, so the union never narrowed on the client and the app couldn't tell one event from another.\n\nMulti-tenancy is in the schema rather than bolted on top. A user is global, a membership joins them to one community with a role, and every row that belongs to a community carries its `communityId`. Leaking across tenants takes a deliberate omission, not a forgotten join. Not-found and forbidden give the same response, so you can't map out what exists by reading the refusals. Socket events get filtered per viewer before they're sent, so membership decides what arrives instead of the client deciding what to render.\n\nThe part I spent most time on is where an agent's model actually runs. There are three options.\n\nTwo of them go through a subscription CLI on someone's own machine. The runner dials out to the server, authenticates in its first frame, and the provider credential never leaves that laptop. Elaine only gets the finished work back. I strip provider keys and inherited Node options from the child process, bound its output, and kill it if it hangs.\n\nThe third runs in the server itself, which is what lets you create an agent in a browser and use it with nothing running anywhere. That's a real trade, because now Elaine holds a key. So it lives in one place: read from the environment, used to sign the request, and never written to the database, a projection, an event, or a prompt. There's a check for each of those.\n\nOne awkward bit. A chat endpoint can't write files, and cards have to come from files. So the model answers with JSON containing its reply and any cards, the server writes those files into the agent's folder, and then the same wiki diff the runner uses decides what gets published. Same rule either way.\n\nI also found out how much a stub endpoint hides. The first time I ran this against a live model, three things broke that the stub never surfaced. The model answered in prose instead of JSON, so the cards disappeared with no error. The prompt wording made it skip writing a card at all. And the second answer came back word for word identical to the first with no citation, which made the \"composed, not cached\" claim straightforwardly untrue. All three are fixed.\n\nNo test framework. Three scripts that fail loudly: 173 checks on the domain, 19 on the in-server agent, 24 on the runner. Each one runs against a throwaway database on a spare port.\n\n**Best Use of Google AI.**\n\nThe in-server agent runs on Gemini through its OpenAI-compatible endpoint. Setting it up is three lines:\n\n```\nELAINE_API_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai\nELAINE_API_KEY=<key>\nELAINE_API_MODEL=gemini-2.5-flash\n```\n\nNo provider is named anywhere in the code. The runtime only assumes four things: `POST <base>/chat/completions`, a bearer token, a `{ choices: [{ message: { content } }] }` response, and `messages` / `model` / `temperature` in the body. It also asks for `response_format: json_object`, because a model that drifts into prose loses its cards quietly, but if the endpoint refuses that field it retries once without it, and if the reply comes back wrapped in a code fence it gets unwrapped.\n\nGemini is doing the hard part. It reads the existing card file, decides whether that file already covers the question, and either composes an answer from it or writes a new card that someone who never saw the conversation can still use. Most of my prompt work went into that one decision.", "url": "https://wpnews.pro/news/humans-and-ai-agents-learn-together-and-what-they-learn-stays", "canonical_source": "https://dev.to/harry_0844f25aee774df6f7c/humans-and-ai-agents-learn-together-and-what-they-learn-stays-31ob", "published_at": "2026-09-07 01:01:18+00:00", "updated_at": "2026-09-07 02:15:55.142740+00:00", "lang": "en", "topics": ["ai-products", "developer-tools", "ai-agents"], "entities": ["Elaine", "Node", "Hono", "React", "Vite", "Zod"], "alternates": {"html": "https://wpnews.pro/news/humans-and-ai-agents-learn-together-and-what-they-learn-stays", "markdown": "https://wpnews.pro/news/humans-and-ai-agents-learn-together-and-what-they-learn-stays.md", "text": "https://wpnews.pro/news/humans-and-ai-agents-learn-together-and-what-they-learn-stays.txt", "jsonld": "https://wpnews.pro/news/humans-and-ai-agents-learn-together-and-what-they-learn-stays.jsonld"}}