← The Workshop
Guide30–45 min to buildBasic Python and terminal familiarityLast tested

A Tiny Knowledge Base for Your Personal Agent

Give your agent a little context that matters. Start with two YAML files, assemble a focused prompt, and keep old instructions out of the way.

You ask an agent to plan a day in a new city. It returns twelve attractions, three shopping centres and an evening across town. You wanted a slow morning, a gallery and time to wander.

The agent knows plenty about the destination. It knows very little about how you like to travel.

So you explain your preferences again.

A small knowledge base gives those repeated explanations a home. Your conventions, useful examples and working methods become files you can review, change and bring into the next task.

In The Knowledge Base as Instrument, I explored why that arrangement interests me. Here, we'll build a small version you can actually use.

What you'll leave with: two reusable entries and a script that assembles them into context for a travel-planning task. It includes your chosen defaults, rejects archived entries and produces text you can paste into an agent or chat.

Before you start

You'll need Python 3.10 or newer, Git, a text editor and an AI assistant you already use. The terminal commands below use a macOS or Linux shell. On Windows, use WSL or adapt the activation command for your shell.

There is no model API to configure. The script prepares text; you decide where to send it.

The route through this guide: create the folder, write two entries, assemble the context, then try one real task. The six-folder structure comes afterwards, when you have something worth organising.

1. Give your context a home

Create a small repository and an isolated Python environment:

mkdir personal-kb
cd personal-kb
git init
python3 -m venv .venv
source .venv/bin/activate
python -m pip install PyYAML==6.0.3
mkdir -p kb/domain kb/templates

Create requirements.txt:

PyYAML==6.0.3

And .gitignore:

.venv/
__pycache__/
context.txt

The repository will hold the entries you maintain. context.txt will be a disposable bundle for a particular task.

YAML is useful here because it keeps small fields and longer prose together in a readable file. Markdown or JSON could work too. The important choice is to make each entry explicit enough that both you and the model can understand it.

2. Write the two things you keep repeating

Start with one set of preferences and one template. We'll use a fictional traveller who enjoys quiet, unhurried city days. Change the details to reflect your own preferences.

A preference: what makes a good day out

Save this as kb/domain/travel-preferences.yaml:

id: travel-preferences
class: domain
status: current
summary: Preferences for unhurried city trips
body: |
  Plan no more than two main activities per day.
  Prefer galleries, gardens and neighbourhood walks.
  Leave time for a quiet cafe and an unplanned stop.
  Group activities in one area to avoid crossing the city repeatedly.
  Offer an indoor alternative if the weather changes.

This is information the model cannot reliably infer. “Plan a good day out” leaves a lot open. “No more than two main activities” gives it a concrete constraint.

The | keeps the following indented lines together as a block of text. Use spaces for YAML indentation, not tabs.

A template: what the plan should contain

Save this as kb/templates/day-plan.yaml:

id: day-plan
class: templates
status: current
summary: Structure for a practical one-day itinerary
body: |
  Start with the neighbourhood and a sentence explaining the choice.
  Outline the morning, lunch break and afternoon in that order.
  For each main stop, explain why it fits the traveller's preferences.
  Include travel time between stops, labelled as an estimate.
  Add one indoor alternative and list any details still to verify.
  Check current opening hours and booking requirements when browsing
  is available. Otherwise mark them as unverified; do not invent them.

The preferences shape the choices. The template shapes the plan. Keeping them separate lets you reuse the same preferences for a weekend itinerary or a shortlist of places to stay.

Checkpoint: you should have two .yaml files, each with a unique id, a short summary and status: current. You do not need to catalogue everything you know before trying this.

3. Assemble only what the task needs

Create context.yaml beside the kb folder:

always:
  - travel-preferences

These are the defaults for this small travel-planning workspace. “Always” means every time you run this script, not every conversation in every tool. Keep the list short and relevant to the workspace.

Now save the following as context.py, also beside kb:

"""Print a small, validated context bundle. No model call is made."""
from pathlib import Path
import sys

import yaml

ROOT = Path(__file__).resolve().parent


def read_mapping(path):
    data = yaml.safe_load(path.read_text(encoding="utf-8"))
    if not isinstance(data, dict):
        raise ValueError(f"{path.name}: expected a YAML mapping")
    return data


def assemble(requested):
    entries = {}
    for path in sorted((ROOT / "kb").rglob("*.yaml")):
        entry = read_mapping(path)
        for field in ("id", "class", "status", "summary", "body"):
            value = entry.get(field)
            if not isinstance(value, str) or not value.strip():
                raise ValueError(f"{path.name}: missing or invalid {field}")
        if entry["status"] not in ("current", "archived"):
            raise ValueError(f"{path.name}: invalid status")
        entry_id = entry["id"]
        if entry_id in entries:
            raise ValueError(f"Duplicate id: {entry_id}")
        entries[entry_id] = entry

    defaults = read_mapping(ROOT / "context.yaml").get("always", [])
    if not isinstance(defaults, list) or not all(
        isinstance(item, str) for item in defaults
    ):
        raise ValueError("context.yaml: always must be a list of ids")

    sections = []
    for entry_id in dict.fromkeys(defaults + requested):
        if entry_id not in entries:
            raise ValueError(f"Unknown entry: {entry_id}")
        entry = entries[entry_id]
        if entry["status"] == "archived":
            if entry_id in requested:
                raise ValueError(f"Requested entry is archived: {entry_id}")
            print(f"Skipping archived default: {entry_id}", file=sys.stderr)
            continue
        sections.append(
            f"## {entry_id}\n{entry['summary']}\n\n{entry['body'].strip()}"
        )
    return "\n\n".join(sections)


if __name__ == "__main__":
    try:
        result = assemble(sys.argv[1:])
    except (OSError, ValueError, yaml.YAMLError) as error:
        raise SystemExit(f"Context error: {error}")
    print(result)

Run it from the repository root:

python context.py day-plan > context.txt

Open context.txt. It should contain travel-preferences, followed by day-plan, each once. The first came from your defaults; the second came from the task you selected.

The script reads the files locally to check them, but only prints the selected entries. Reading a file on your computer is different from sending its contents to a model.

A misspelled ID produces an error. Duplicate IDs do too. An archived default is skipped with a message in the terminal; explicitly requesting an archived entry stops the run. That last distinction makes an outdated task selection visible.

4. Try it on something real

Paste the contents of context.txt into a new conversation, followed by a task such as:

Using the context above, suggest a relaxed day in Kyoto for a first-time
visitor. Start and finish near Kyoto Station, between 9 am and 6 pm.
Use public transport where practical. Treat this as a provisional plan;
flag anything that depends on the travel date or needs a current check.

The context tells the assistant how to approach the work. The task tells it what to do now. You can change either without rewriting the other.

Check the result: does it keep to two main activities? Leave room for a cafe break? Avoid unnecessary trips across the city? Include an indoor alternative and clearly flag unverified details?

A well-formatted itinerary can still be impractical. Check the route and current venue information before relying on it; the knowledge base supplies preferences, not live destination data.

Then try the same task in another fresh conversation without the bundle. Keep the model and settings the same. Compare the amount of editing each answer needs. Repeat on a few real tasks before deciding the arrangement helps; one pleasing answer is a useful signal, not proof.

If the result is still wrong, revise the entry that owns the problem. A missing transport estimate belongs in the template. A day packed with attractions points to a preference that needs to be clearer.

When you're happy with the first version, save a checkpoint:

git add .gitignore requirements.txt context.yaml context.py kb
git commit -m "Add a small travel knowledge base"

Git may ask you to configure your author name and email before your first commit. A local repository is enough for this exercise; a remote is optional.

5. Let the folders grow with the work

The original arrangement has six classes of knowledge. They are useful distinctions, not six folders you must fill on day one.

Folder What belongs here Example
domain Conventions and constraints Preferred pace and interests
reviews Lessons distilled from feedback Previous itineraries left too little time for lunch
snippets Small examples worth reusing A useful example of a transport comparison
templates Shapes of recurring deliverables A day plan or packing list
workflows Procedures used for particular tasks The checks to make before booking a trip
artifacts Reference facts and supporting material Dated notes on transport passes

For this script, use the same five fields in every entry and put its content in body. A snippet can contain code; a workflow can contain numbered steps. The class field describes the entry. It does not automatically decide when to load it.

That decision needs its own policy:

Context layer How to use it
Small defaults Put essential conventions in context.yaml. Include review lessons only when they apply throughout this workspace.
Task-specific material Pass the IDs of relevant templates, snippets or workflows to the script.
Pointers Tell a file-capable agent which workflow or reference exists, with a path and short description.
On-demand references Let that agent read the full file when the task needs it.

The first two layers work now. The last two require an assistant that can actually read the repository. A chat window cannot open a local path just because you mention it. In that case, select the entry yourself and include it in the bundle.

For example, once you have created an transport-notes entry, you could run python context.py day-plan transport-notes. An agent with file access could instead receive a pointer: “For transport reference material, read kb/artifacts/transport-notes.yaml; use only entries marked current and verify time-sensitive details.”

The useful question is: what does this task need that the model would not otherwise know?

Keep yesterday's instructions out of today's work

The quiet failure mode is an entry that used to be true.

You used to enjoy packed itineraries. Now you prefer two main stops and a slower pace. The old preference stays in the repository, and next week it reaches the model alongside the new one. Both look equally authoritative.

Git preserves the history. You still have to decide what is current.

Every entry is either current or archived. Never both.

To retire an entry, change its status:

status: archived

Try that on day-plan, then rerun the command from step three. It should report Requested entry is archived: day-plan. Restore status: current afterwards.

Treat any output file from a failed run as unusable: shell redirection may already have emptied it. Fix the error and run the command again before pasting the bundle.

When two entries disagree, resolve the conflict or clarify their scope. An archived entry can retain a note in its body pointing to its replacement. Periodically skim the current entries and ask whether each still describes how you work.

Keep credentials out of these files, and review private material before sending a bundle to a hosted model. This example assumes the entries are your own trusted material; copied web pages should not silently become instructions for the agent.

Why bother keeping it small?

Chroma's Context Rot research found that performance became less reliable as input length grew in its evaluated tasks. The extent varied with the model and conditions. That is a reason to test focused context, not a guarantee that shorter prompts always win.

Anthropic's context-engineering guidance describes a hybrid approach: keep useful core context up front and retrieve other material when needed. This tutorial applies that idea at a scale you can inspect by opening a few files.

Neither source proves that this exact folder structure is optimal. The test is whether it helps with your work.

Your next small experiment

Choose one task you repeat each week. Add one constraint you usually have to explain and one example or template you wish the agent would follow. Use them for a few runs, and note which corrections stop recurring.

As the collection grows, selecting entries may start taking longer than doing the work. That is the point to improve summaries, introduce search or give the agent more responsibility for retrieval.

For now, two good entries are enough to begin. The next time you catch yourself explaining the same preference, you'll have somewhere to put it.

End of articleReturn to the Workshop →