Labs / Lab 12

Build a persistent skill gateway

Move from a one-shot tool to a tiny platform that keeps state, remembers prior exchanges, and writes a trace of what happened.

What you'll build

A gateway that remembers what happened between calls.

By the end of this lab you will have a small persistent gateway that loads its skills from skills.json, keeps durable state in state.json, and appends both messages and events to JSONL logs. That is the big shift: the process is no longer the whole system. The files are part of the system too.

Compared with the stateless tool from Lab 01, this gateway can remember notes, accumulate searches, queue work, and recover that history on the next run. It is still tiny on purpose, but the shape is much closer to a real assistant host.

Run it

cd ai_ecosystem_labs
python3 12-persistent-platform/gateway.py status
python3 12-persistent-platform/gateway.py message cli "hello"
Starting here? Quick setup
git clone https://github.com/BanditF/ai_ecosystem_labs
cd ai_ecosystem_labs
python3 12-persistent-platform/gateway.py status
python3 12-persistent-platform/gateway.py message cli "hello"

Those two commands show the registry first, then a real message flowing through the gateway.

Time guide. Setup: ~2 min. Working through it: 30–50 min because persistence, routing, and logs make this one noticeably denser.

Why this piece exists

A useful assistant usually has to outlive one prompt.

A stateless tool is great when all you need is "take this input, return one result." But the moment you want memory, queued work, approvals, or even a reliable audit trail, one function call is not enough. You need a place where skills are defined, state is saved, and every interaction can be looked up later.

That is what this gateway is teaching. It is not trying to be a full production framework. It is showing the minimum shape of a persistent platform: registry, state, message history, event history, and a small command surface that ties them together.

Real-world analog: think of a help desk system that keeps the conversation transcript in one place and workflow updates in another. The transcript is the user-facing memory. The workflow log is the operational trace.

The code

gateway.py

Walk through it

Four things worth noticing.

state.json is durable memory

The gateway loads state at the start of each call and saves it again before returning. That means state.json survives process restarts. If you stored a note, queued a digest, or recorded a search last run, the next run can still see it.

Skills.json is the registry, but handlers are still in Python

The JSON file defines what skills exist, their prefixes, and their metadata. But gateway.py dispatches to hardcoded Python functions by skill name. Adding new behavior still requires editing the Python. The registry is useful for matching and documentation — it's the first step toward data-driven dispatch, not the whole solution.

messages.jsonl becomes conversational memory

Every user message and assistant reply is appended to messages.jsonl. That file is the running transcript. The gateway can inspect it later through the history command, and a more advanced version could use it as retrieval context.

events.jsonl tracks operations, not dialogue

Events record what changed: a message was processed, approval was required, a schedule ran, or a worker accepted delegated work. Messages and events sound similar, but they are different concerns. One is conversation. The other is system activity.

Expected output

What a successful run looks like.

Before you send anything, the state is basically empty:

{
  "assistant_name": "toy-platform",
  "memory": [],
  "searches": [],
  "delegations": [],
  "schedules": []
}

After a run like python3 12-persistent-platform/gateway.py message cli "remember persistent systems keep state", you should see the saved state grow:

{
  "memory": [
    {
      "id": "memory-1",
      "text": "persistent systems keep state",
      "channel": "cli"
    }
  ],
  "next_ids": {
    "memory": 2
  }
}

The conversation log gets a new line in messages.jsonl:

{"time":"2025-05-06T12:00:00Z","channel":"cli","role":"user","content":"remember persistent systems keep state","meta":{}}
{"time":"2025-05-06T12:00:00Z","channel":"cli","role":"assistant","content":"Stored note memory-1.","meta":{"skill":"remember_note"}}

And the operational log gets a separate line in events.jsonl:

{"time":"2025-05-06T12:00:00Z","event":"message_processed","channel":"cli","text":"remember persistent systems keep state","skill":"remember_note","ok":true,"approval_required":false}

If you see those three ideas — state before and after, message history, and event history — the lab is doing the right kind of work.

Try this

Three things to try before moving on.

  1. Run the gateway twice with the same input. Send the same remember message two times, then open state.json. You should see the memory list accumulate new entries instead of resetting on each run.
  2. Add a new skill in skills.json. Copy an existing skill entry in skills.json and give it a new name. Run gateway.py status — your new skill appears in the registry. Then try invoking it — it falls through to help behavior, because there's no matching handler yet. That's the gap between registry and implementation.
  3. Delete state.json and re-run. Right now the included gateway does not recreate missing state yet, so this will fail with a file-not-found error. If you want the reset flow described here, add bootstrap logic that seeds a fresh default state when state.json is missing.

Concepts behind this

For the bigger idea of durable assistant memory, read the agents page. The persistence here is toy sized, but the platform shape is the same one larger systems use.

For the registry side, go back to skills, hooks, and wrappers. skills.json is the cleanest example in the labs of skills being declared as data instead of buried in code.