Using AWS to Manage AI Spec-Driven Development Artifacts
My Pain Point
Doing spec-driven development with AI agents, I quickly ran into a problem: managing all the artifacts they produce. Brainstorming notes, architecture designs, specifications and contracts, results of architecture and code reviews, troubleshooting notes, etc. Even on a small project you quickly end up with hundreds of markdown files. On a team project, thousands to tens of thousands.
The basic approach is to store them in a folder in the repo (e.g. docs/), with an index.md file listing each file with a short description of its content. This approach has several issues:
- Documentation pollutes your code base
- Publishing an artifact goes through the same PR review as code, which makes code reviews harder and sharing artifacts between team members have to wait the PR review
- It is unrealistic to capture the content of an ADR or a spec in a 1-2 sentence description in an index file, so agents can't rely on it to find the right information
- You can't share information across teams (e.g. the agents of an application developer need access to the architecture designed by the networking or security team in another repo)
How I Used Just 3 AWS Services to Solve it
This is why I came up with the idea of using AWS services behind an MCP server to store, manage, share and search these artifacts. The architecture is pretty simple:
- S3 stores all the markdown artifacts
- S3 Vectors stores an embedding of each H2 section of every artifact. This gives semantic search at a more granular level than the whole document, plus filtering on the vector metadata (e.g. on the artifact type)
- A Bedrock embedding model (Titan Text Embeddings v2) creates these embeddings
- An MCP server exposes tools to store, manage and search artifacts, a few resources, and an MCP App to browse the artifacts visually
- The MCP server uses the developer's own AWS credentials (e.g. an AWS CLI profile)
- The artifacts follow the Open Knowledge Format (OKF) for their frontmatter. The artifact sources and relationships (work in progress), as well as the commit references, are stored in S3 object annotations. This way they can be updated without having to replace the S3 object, since S3 objects and their metadata are immutable.
- In an enterprise, a single S3 bucket and a single vector index can store the artifacts of all teams and projects, each in its own prefix ("subfolder"). A team can then mark some of its artifacts as shared to make them visible to other projects and teams.
Architecture Diagram
!Image description
The Result
Combining just these 3 AWS services gave birth to the Arkeology project with the following features:
- Immediate write-then-read consistency: an artifact written in one agent call is searchable in the next
- Semantic search with metadata filtering: retrieve artifacts by meaning, not just by keywords
- Section-level search precision: a query matches the specific section of an artifact that is relevant, not an average of the whole document
- Cross-team knowledge sharing: canonical artifacts such as ADRs can be made discoverable by the agents of other projects or teams. Nothing is shared by accident: only permanent artifacts explicitly marked as shared are visible outside their project
- Rich, filterable metadata: every S3 object and every vector carries structured metadata, and S3 objects also carry annotations
- Commit-to-artifact traceability: artifacts carry git commit SHA references, stored in S3 object annotations and mirrored into the vector index so you can filter on them
- Arkeology Studio: calling it opens a visual artifact browser directly inside any MCP host that supports MCP Apps (Claude Desktop, claude.ai, VS Code Copilot...)
- Full artifact lifecycle: archive, delete and purge artifacts as projects evolve
- Migration: a skill and server tools to import the documentation of an existing project
- And more
Arkeology Sudio MCP APP Preview
!Image description
Caveats
Team Sharing and IAM Permissions
The sharing of artifacts is enforced by the MCP server, but anyone with IAM read permissions on the whole bucket can bypass the MCP server and, for example, use the AWS CLI to read any data. Least-privilege permissions restricting each team to its own prefix in the S3 bucket must be enforced in IAM. That said, IAM can't do the same inside a shared vector index: any team with query access to the index can read the metadata (titles, descriptions, tags) and embeddings of all teams' artifacts. So only share a vector index with teams you trust.
Arkeology Studio MCP App
It requires an MCP host that supports MCP Apps (Claude Code, for example, doesn't), or using the MCP Inspector, which is a bit of a pain as a developer. I am currently thinking of building an optional frontend on AWS, to have a web interface to access, search and read the artifacts.
Lessons Learned
Vector metadata limits
Vector metadata is limited to 2 KB filterable and 40 KB total per vector. This is why we can't put sources and relationships in the vector metadata and use them to search and filter artifacts. It is also why only the 20 most recent commit references go into the vector index, so you can still find the artifacts linked to recent commits; the full list stays in the S3 object annotations.
Annotations
S3 user-defined object metadata is capped at 2 KB and immutable like the object. Annotations hold up to 1 MiB and can be updated without rewriting the object. But there is a catch: overwriting the object deletes its annotations, so every rewrite must read them first and put them back. And if that read fails, it must raise an error rather than return nothing, or you end up writing empty values over good data, with no other copy to recover from.
Using annotations for sources and relationships comes with some limitations:
- It is not possible to do a search like "give me all artifacts referencing artifact XYZ"
- Search results don't include these fields: an agent has to read or list the artifact to see them
In other words, agents can only navigate these links, not query them.
S3 Vectors
QueryVectors doesn't return the distance unless you ask for it (returnDistance: true). Our code defaulted the missing distance to 0, so every result got a perfect score of 1.0 (score = 1 − distance). The search ranking was silently broken while all unit tests passed.
Conclusion
With just 3 basic AWS services I was able to build an MCP server that basically solves my pain points with managing all the artifacts generated through AI spec-driven development. Improvements to make the tool really functional at an enterprise level would be:
- Host the MCP server on AWS
- Enforce least privilege, so nobody can bypass the MCP server to read the whole bucket content
- Host a web frontend on AWS, to get around the MCP App limitations
The code is open source and available at amanoxsolutions/arkeology.
_(Note: I used AI only to proofread this article, improve readability and draw the architecture diagram)_
@shakhzod1001 I built a second brain skill (with a few a tweeks here and there to make it work better with obsidian) using this standard:
GoogleCloudPlatform/open-knowledge-format
has been a massive game changer.
3 views
Google's Open Knowledge Format (OKF), an implementation that addresses real life problems
Google published the Open Knowledge Format this summer. It is a way to write knowledge down as a folder of Markdown files with a YAML header, so that people and AI models read the same files. Each file is a concept (a rule, a procedure, a metric, a role) and the header says where it comes from, who checked it, when it expires and whether it is still current. No server, no database. If you can cat a file, you can read it.
Great concept, but the spec stops at the format. It does not say how knowledge gets into a bundle, how to know a bundle is right, what to do when two documents disagree, or how a correction found by a user gets back in. In a company, that is the hard part.
This project tries to answer real organizations problems using OKF with skills for coding agents (Claude Code and Pi):
- okf-produce reads the documents, agrees with their owner on what the bundle is for, writes it with a source behind each claim, and keeps it current.
- okf-validate checks a bundle against the spec. Plain Python, runs in CI too.
- okf-answer answers questions from one or several bundles, with a reference after each claim and a warning when a fact is unchecked, expired or not covered.
The same documents can give different bundles for different readers (angle), the skill will ask: an employee, an auditor and a documentation officer do not want the same thing from the same quality manual. The steps that need no judgment are code, not model: hashing, indexes, trust tiers, dates. The model never marks its own work as verified, a person does. Readers that did not write the bundle check it. When a user finds a mistake, the consumer writes a report and the producer picks it up at the next update.
I tested it in a real organization, with several bundles. Token usage and accuracy were satisfactory, good enough that the org wants to keep going.
The repo has three sample bundles made from the same 240 Wikipedia pages on Greek mythology, each from a different angle: the family tree, the stories by theme, and the English words that come from the myths. The family tree bundle includes a calculation (how is Perseus related to Heracles?) with a small checker that proves the approved script ran.
Repo: wmahfoudh/okf
Spec: GoogleCloudPlatform/open-knowledge-format
Are you using OKF, where do you think it breaks, and how you handle trust and sources when you let a model answer from company documents.
176 points · 37 comments
OKF v0.2 frontmatter: what the spec requires, and two ways to check a folder
I make JidoSeal. Free and local; the scan engine is proprietary; JidoSeal also sells optional certificates.
If you keep notes, docs or a knowledge base as markdown with YAML frontmatter, and you point an AI tool at them (Claude Code, Cursor, Copilot, Codex, Gemini CLI, Zed, a local model), Google Cloud's Open Knowledge Format (OKF) is worth ten minutes. It is a small, open spec for exactly that kind of folder: "a directory of markdown files with YAML frontmatter", meant to be "authored by people, generated by agents... and consumed by both". This post covers what v0.2 actually requires, which is less than most people assume, and then two ways to check a folder against it.
What OKF v0.2 requires
The spec is short. You can read all of it at github.com/GoogleCloudPlatform/open-knowledge-for…. Here is the part that matters for frontmatter.
A bundle is just a folder. Every .md file in it is a "concept document", except two reserved filenames: index.md (a directory listing) and log.md (an update history). Subfolders are fine, and git is the recommended way to ship it.
One field is required: type. Section 4.1 lists it as the only required key, and says "a concept carrying just type is fully conformant". Its values are free text that you choose (policy, procedure, Metric, Playbook...). Nobody registers them centrally, and consumers must handle types they don't recognise.
So this file is valid OKF v0.2:
---
type: policy
---
# Leave policy
Employees accrue leave monthly.
Four fields are recommended: title, description (one sentence), resource (a URI for the thing the note describes, if there is one), and tags (a YAML list). If title is missing, a consumer may derive it from the filename.
Optional families, if you want more:
- Trust: generated: { by, at } records who or what wrote the content. verified is a list of { by, at } entries. A consumer derives a trust tier from it: no verified means unverified, a non-human verifier means machine-confirmed, and by: human:<id> means human-reviewed.
- Lifecycle: status is draft, stable or deprecated, and defaults to stable. stale_after is an absolute instant after which the note counts as stale.
- Provenance: sources is a list of entries, each with a required resource.
Conformance (section 11) is three rules. Every non-reserved .md file has a parseable YAML frontmatter block. Every block has a non-empty type. index.md and log.md follow their own sections if present. Just as useful is what consumers must not reject a bundle for: missing optional fields, unknown type values, unknown extra keys, broken cross-links, or missing index.md files. Extra keys of your own are fine.
That gives you a concrete thing to check: does every note open with a frontmatter block, and does that block have a non-empty type? Then, softer: which of the four recommended fields are missing?
Way 1: fifteen lines of Python, no dependencies
This reads only top-level key: value lines. That's enough for type, which is a plain string. It skips the reserved filenames, fails any file without a frontmatter block or a non-empty type, and lists the recommended fields that are missing.
import sys, pathlib
RECOMMENDED = ["title", "description", "resource", "tags"]
bad = 0
for p in sorted(pathlib.Path(sys.argv[1]).rglob("*.md")):
if p.name in ("index.md", "log.md"): continue # reserved filenames, §3.1
lines = p.read_text(encoding="utf-8").splitlines()
if not lines or lines[0] != "---" or "---" not in lines[1:]:
print(f"FAIL {p}: no frontmatter block"); bad += 1; continue
fm = lines[1:lines.index("---", 1)]
keys = {l.split(":", 1)[0].strip(): l.split(":", 1)[1].strip() for l in fm if ":" in l and not l.startswith((" ", "#", "-"))}
if not keys.get("type"):
print(f"FAIL {p}: no non-empty 'type'"); bad += 1; continue
missing = [k for k in RECOMMENDED if k not in keys]
print(f"ok {p}" + (f" (recommended, missing: {', '.join(missing)})" if missing else ""))
sys.exit(1 if bad else 0)
Here it is on a sample folder of six notes, one of which has no frontmatter at all:
$ python3 okf_check.py tiers
ok tiers/bronze/leave-policy.md (recommended, missing: title, description, resource, tags)
ok tiers/gold/leave-policy.md (recommended, missing: resource, tags)
ok tiers/mixed/expenses.md (recommended, missing: description, resource, tags)
ok tiers/mixed/leave-policy.md (recommended, missing: resource, tags)
FAIL tiers/mixed/notes.md: no frontmatter block
ok tiers/silver/leave-policy.md (recommended, missing: resource, tags)
$ echo $?
1
It exits with 1 when anything fails, so it can go straight into CI or a pre-commit hook. It is honest about its limits. It does not parse YAML properly, so it won't notice a frontmatter block that is broken YAML, and it would accept type: "". Section 11 asks for "parseable" YAML, so if you need that guarantee, swap the keys line for yaml.safe_load and accept one dependency.
Way 2: grep or yq, in one line
To list the notes with no type line at all:
$ grep -rL --include='*.md' -E '^type: *[^ ]' tiers
tiers/mixed/notes.md
That is fast, but it also matches a type: line in the body of a note. For a real YAML read of the frontmatter only, mikefarah's yq (v4) has a front-matter mode:
$ for f in $(find tiers -name '*.md' ! -name index.md ! -name log.md | sort); do printf '%s\t%s\n' "$f" "$(yq --front-matter=extract '.type // "MISSING"' "$f")"; done
tiers/bronze/leave-policy.md policy
tiers/gold/leave-policy.md policy
tiers/mixed/expenses.md procedure
tiers/mixed/leave-policy.md policy
tiers/mixed/notes.md MISSING
tiers/silver/leave-policy.md policy
Change .type to .description (or any other field) to find the notes that are missing that one.
If you'd rather have your AI tool do the check, I also maintain a free, local MCP server that runs it and lists each file's missing fields: Jidoseal/jidoseal-mcp
An understanding of my world: a knowledge graph over my Obsidian notes
I have too many notes
In my job I take a lot of notes. I mean a lot of notes. I attend a lot of meetings, and client conversations, catch-ups with colleagues, workshops and the research I pull together for a pursuit all get written down. My vault holds more than 900 markdown files now, organized into a structure of my own.
The problem is what happens as that folder grows. There are too many notes to consume. The structure still makes sense, but it can't tell me what I actually want to know, which is usually something about people, clients and the commitments between them.
Take a question like "who owns the Northwind data contract?" The answer is in there, in a sentence about Dana Whitfield in a quarterly review note. A search will find the note, along with every other note that mentions Northwind. It won't give me the owner.
My vision
So I wanted a way of managing and storing that data that lets me query it in the right way. I want to ask a question in my own words and get the answer, along with the note it came from. That means storing my notes in a way that understands the people, clients and relationships in them.
My solution
Building it turned out to be the manageable part. In my experience the real problem is maintaining the data over time, and that problem shapes every choice in the rest of this post. The system is a consistent folder structure, markdown notes with OKF metadata, a Graphiti graph with my own ontology, a daily Cowork task that keeps it current, and an MCP server that puts it into my daily work.
I picked a structure and stuck to it
I'll show you mine as an example.
My vault has a "Clients" folder, with a folder for each client. Each one holds that client's information and its own people folder, so I can see the people who belong to that client. There's a top-level People folder as well, and folders that describe our go-to-market work and specific teams. Roughly, it looks like this (Northwind is an invented client):
Obsidian Vault/
Clients/
northwind/
Northwind.md
2026-09-14 quarterly review.md
people/
Dana Whitfield/
Dana Whitfield.md
People/
Blog Posts/
Strategy/
You're going to query this knowledge store in your own language. When you talk to Claude, you'll use your own references and the aliases you use for people and clients, so the structure has to make sense to you, and mine won't necessarily.
Whatever you choose, follow it consistently, because that consistency is what the rest of this system depends on.
I built on markdown
Markdown is the foundation of all of this.
People who work with LLMs regularly are comfortable with it already. For anyone who isn't, it's a very simple plain-text format, and it's one LLMs really like to work with. Any tool that reads text can read it, so the notes aren't tied to one app.
I write and read mine in an Obsidian vault. It's a good place to do that, and the wikilinks it uses to connect notes come back later in this story.
OKF FTW: I gave every note metadata
So now I have all these markdown files, which is great. What I don't have is a real sense of the state of each one. Nothing has validated it, and there isn't enough metadata around it to build up a graph from.
That's where the Open Knowledge Format comes in. OKF is published by Google Cloud (spec), and I went with it because it's lightweight. All it asks for is frontmatter: a set of keys and values at the top of the note. It's also extensible, so I can layer on new keys and values over time as I work out what I need, and that has been really helpful. Beyond that, it has a lot going for it because it comes from Google, which has the influence to help something like this gain popularity. And I like the idea of frontmatter as a metadata store. It's open-ended enough that it doesn't get in the way of how I already write.
---
type: Meeting Notes
title: Northwind quarterly review
description: Migration scope, timeline pressure, and who owns the data contract.
tags: [meeting, northwind, migration]
generated: { by: claude/haiku-4-5, at: 2026-09-14T11:10:37Z }
---
Now each note carries metadata about itself: what kind of thing it is, a title, a one-line description, some tags, and a record of what produced it. That metadata could really be anything that makes sense of the document, and you can add whatever properties make sense for your organization.
Say you want to mark when a document should be considered out of date. Add a key like expires_after and put a date there. An LLM can read that key and change how it treats the note, as long as you tell it what the key means, so the metadata isn't arbitrary. A deterministic script can act on the same key, and so can anything else that reads frontmatter.
That's what OKF gives me: a lightweight structure I can tie other actions to. A markdown file can feel very unstructured, and OKF brings structure to it without making me rewrite the document to follow some convention. The document can be whatever it wants to be. The metadata carries the information that programmatic solutions need, whether that's an LLM taking a particular action or a script that runs when it sees a particular value. It opens up a whole range of possibilities for how I organize my data.
OKF's own design goals fit this well. Google's argument for it is that the context agents need should live in an open format of its own, somewhere other than a proprietary service or a pile of unstructured text. It's also deliberately minimally opinionated. type is the only field that's always required, every other field is opt-in, and custom keys are preserved. That last part is what makes the expiry example work. (OKF v0.2 does define a stale_after field for exactly that case, but the same holds for any key you invent.)
Frontmatter also changes how much of a note anything has to read. A script can pull one property, such as the type or a date, without touching the body, and the same holds for an agent. Most of the time an agent only needs to decide whether a note is relevant, and the title, description and tags are enough for that.
An agent won't stop at the frontmatter on its own, though. Left alone, it usually reads the whole file. The simplest fix is an instruction in its standing context: check the frontmatter first, and open the body only for the notes that look relevant. A skill or a tool goes further, because it can parse the frontmatter and hand the agent only those properties, so the body never reaches it unless it asks. In my vault, the sweep also rebuilds an index for each top-level folder, with one line per note built from its title and description, so an agent can scan a folder's index before it opens anything.
How I create the metadata
Metadata only helps if every note has it, so keeping the vault in shape is a job for a script. Mine runs as a sweep, in a fixed order. It waits for the vault to go quiet, checks each note, fills in missing metadata, repairs what it can mechanically, links the names it already knows, rebuilds the indexes, scans for secrets and commits. The one step that needs a model is classification, where a model reads a new note and writes its type, title, description and tags. Everything else is plain code, and I'd keep it that way, because a deterministic job like string comparison belongs somewhere that gives the same answer every time.
Keep the list of types small, too. My approved list has about thirty values, and the classifier only chooses from eight of them. Give a model an open list and it invents a plausible new type for any note that doesn't fit, and no query knows that type exists. A note that doesn't fit gets flagged for me to look at instead.
My notes are about relationships
My notes have semantic structure, but mostly they're about relationships: between the notes themselves, and between the things they describe. Obsidian is built on that idea. It uses wikilinks to link documents together, which is how you build up your own knowledge graph inside Obsidian.
!Obsidian graph view of my vault: a dense cluster of linked notes, with a few unconnected groups at the edges.
The Obsidian graph view does more than look nice. It shows the relationships between ideas and people, and it also shows the ideas that sit on their own. In the screenshot above, the small dots out towards the edges are parts of my knowledge that haven't been connected with the rest of the graph yet.
It also shows where my attention goes. A dot grows as more notes link to it, so the people, entities and themes I talk about most stand out, and I can see who is really at the center of my world. That's useful for spotting gaps. An idea out on the edge may not be getting enough attention. And if there's someone who should be more prominent in my work and I can't see them in the graph, that's probably telling me there's an action I should take.
The graph serves more than one purpose, and that's one of them. What I also needed was to query my data and pull out relationships, because for consultancy work that's where the value is. Everything I deal with has relationships to everything else: companies, clients, people, organizations, protocols, standards. In a graph, the kinds of relationships are commonly described with an ontology.
I use an ontology to bring meaning
An ontology brings semantic understanding to your data. It says what kinds of things exist and how they can relate, so "Dana Whitfield is accountable for the Northwind data contract" means something a program can work with.
So what I'm doing, ever so slowly, is building up a personalized knowledge domain. It comes in layers:
flowchart TB
NOTES["Raw notes<br/>the content"]
OKF["OKF frontmatter<br/>metadata and processing information"]
ONT["Ontology and graph<br/>entities and the relationships between them"]
LLM["An LLM asking questions<br/>of my notes"]
NOTES --> OKF --> ONT --> LLM
The raw notes are the content. The OKF keys are metadata, plus whatever post-processing I want to record. Once I ingest those layers, the things they describe show up as entities, and the relationships between them as typed edges.
This is where it becomes valuable. When the ontology is exposed to an LLM, the LLM can take my question, look at the entities and relationships it knows about, and work out where the relevant information is.
How a note becomes a fact
Here's what that looks like for the Northwind question. From the quarterly review note, extraction pulls out two entities and the relation between them (illustrative):
Dana Whitfield (Person) --ACCOUNTABLE_FOR--> Northwind data contract (Contract)
fact: "Dana Whitfield owns the Northwind data contract and signs off changes to it."
source: Clients/northwind/2026-09-14 quarterly review.md
The relation carries a plain-language fact sentence and the path of the note it came from, so every answer the graph gives can point back to its source.
This is also where the consistency from earlier pays off. An entity's identity comes from its type and its name, with no fuzzy matching. Two notes that both say Northwind merge into one node, with no model call. A note that says Northwind Trading instead creates a second node, and it stays separate until someone maps one name to the other. So the names you use consistently in your notes become the identity of things in your graph. A wikilink like [[Dana Whitfield]] helps here too, because it gives extraction the exact spelling to use.
Why I chose Graphiti
I went with Graphiti for the graph because it's a widely used framework with a lot going for it. It lets me define my own entity and edge types in Python, and it brings the schema, typed edges and hybrid search with it.
The bigger reason was speed. Building out a graph, and the database technology underneath it, can take a long time. Graphiti let me iterate quickly and get to the point where I could focus on the parts I cared about: the ontology, the data structure and the taxonomy I wanted to define.
Graphiti needs a database underneath it, and I run FalkorDB in a single local container. It comes with a browser for looking at the graph, which is handy when you want to see what extraction actually produced. For embeddings, which the hybrid search depends on, I use a local model (nomic-embed-text) through an endpoint Graphiti already speaks. The one model the graph pipeline calls out to is Claude Haiku 4.5, and that's there for cost: it's cheap enough to run over every changed note, every day.
How I keep the graph alive
Once the ontology and taxonomy are in place, and there's somewhere to store them, the work isn't finished. My data keeps changing, so I need a daily process that brings in the new notes and notices when they describe a relationship the ontology doesn't have a name for yet.
That's where Claude Cowork comes in.
A scheduled task runs each day in Cowork and manages my data for me. It writes the OKF metadata for new notes, looks at the data, builds up a sense of the relationships in it, and proposes new relation types where nothing existing fits. It runs at 17:00 and only sends what changed since the last run to a model, so a quiet day sends nothing to a model.
Getting those names right matters. The ontology is what brings sense to the data, and the name of a relationship is how the LLM understands what that relationship means. Say Dana Whitfield reports to the head of data at Northwind, but anything that goes wrong on the contract gets escalated to someone else entirely. Those are different relationships. If the graph gives them the same name, then when I ask who's accountable for the contract, the LLM can't tell them apart, and it may well hand me the wrong person.
So a new relation name has to earn its place. Once the same name turns up in three different notes, it's promoted into the ontology automatically, and Claude Cowork can discover it.
The same task can keep the graph clean as well as growing it, and this is where the OKF metadata from the beginning comes back in. Notes go out of date. Remember the expires_after key? A scheduled task can read that date and treat an expired note differently, retire its facts from the graph, or flag it for me to review. The idea works for any property you define. Your metadata can decide what gets cleaned up, what gets removed from the graph because it's no longer relevant, and how the rest of the data is organized. That interplay between a lightweight metadata format and a task that runs every day is where a lot of the possibilities open up.
I think this applies well beyond ontologies. Any document that tells a model how things currently stand will go out of date, and nothing in the document will tell the model so. That's the kind of problem OKF metadata like an expiry date is there to solve.
Where I use it every day
The last piece is an MCP server that exposes the knowledge graph. Because it's MCP, the graph isn't only available to Cowork. Any agent that can access a local MCP server can use it. I use it with Cowork, and that's where all the pieces, and all the work that came before, start to tie together.
This is where the value actually comes alive. Without this step, it's fancy metadata on my notes. The wikilinks add something too, because they let me see my graph in Obsidian. But the real value, in my mind, is being able to integrate this into my daily work. In Cowork I can ask questions about my own notes and get answers that come from an understanding of my world, and connecting the graph through MCP opens up a whole host of possibilities beyond that.
My MCP server
The server itself is small: one Python process that talks over stdio. It needs no API key, because search runs on the local embeddings and queries go straight to FalkorDB. It gives Claude seven read-only tools, and two of them show the idea well. entity_profile returns a summary of what the graph knows about a named entity. find_relations helps Claude work out which relationship fits a question when it doesn't know the relation name, and that's how the ontology becomes something Claude can discover.
The server also carries instructions, which Claude reads when it connects. Mine tells it to check the graph before a Slack or web search when I ask about my own notes, and to name both sources when Slack and the graph disagree. That matters, because the graph only knows what my notes captured. If Claude filled a gap from the web, the answer would read as though it came from my notes, and I would repeat it with the same confidence.
What leaves my laptop
Much of my work is in regulated environments, so I'd call this out. The graph, the database and the embeddings all stay on my laptop, but every model step in the graph pipeline sends note text to Claude Haiku 4.5, the scheduled task itself runs in Cowork, and answers go to the Claude app that asked for them. Make sure that's acceptable for what's in your notes before you point this at them. The server only runs on my laptop, so a web or phone session can use it only through the Claude desktop app on my laptop, while that app is running.
Here's what Claude gets back when it profiles the contract (illustrative):
Northwind data contract (Contract)
ACCOUNTABLE_FOR <- Dana Whitfield
"Dana Whitfield owns the Northwind data contract and signs off changes to it."
source: obsidian://open?vault=Obsidian%20Vault&file=Clients/northwind/2026-09-14%20quarterly%20review
So, who owns the Northwind data contract? Dana Whitfield, according to the quarterly review on 14 September, and the answer comes back with a link to the note.
What I'm building next
I've also done a test run of Qwen 3 locally on Apple's MLX framework, using the 14B model quantized to 4-bit. The next step is to see whether a local model like that can take over the model steps. Today every model step sends note text to Claude Haiku 4.5. A local model would keep that text on the laptop, which matters for anyone whose notes come from regulated work.
8 reactions · 1 comment
ドキュメントの整理の仕方で参考になる
GoogleCloudPlatform/open-knowledge-format
1 like · 79 views
@Fluyeporlaweb Archivos markdown no es suficiente, tendrías que usar lo que hizo Google con este nuevo formato github.com/GoogleCloudPlatform/open-knowledge-for…
31 views
I built a task board and wiki that live in your Git repo
I’m a Product Manager, and I wanted epics, tasks and a wiki together for my own projects. I tried Backlog md, Beads and a few other approaches, then ended up building my own. Kind of like Jira… but with everything living as Markdown in Git.
It’s called GitDocket. There’s a local web UI for the board and wiki, plus CLI/MCP access so coding agents in tools like Codex or Cursor can work with the same project context.
The useful part is being able to come back to a project and see what was decided, what changed, what was checked and what comes next. Tasks can keep their outcomes and linked commits, and a new agent session can read that history too.
Underneath, the engine is built off of Google’s Open Knowledge Format. The files stay readable and reviewable in Git.
It’s free and open source, and still has rough edges. Would this help you pick a side project back up, or does it feel like too much process? I’d also be interested in how you handle this today.
1 point · 2 comments
Mentions
Using AWS to Manage AI Spec-Driven Development Artifacts My Pain Point Doing spec-driven development with AI agents, I quickly ran into a problem: managing all the artifacts they produce. Brainstorming notes, architecture designs, specifications and contracts, results of architecture and code reviews, troubleshooting notes, etc. Even on a small project you quickly end up with hundreds of markdown files. On a team project, thousands to tens of thousands. The basic approach is to store them in a folder in the repo (e.g. docs/), with an index.md file listing each file with a short description of its content. This approach has several issues: - Documentation pollutes your code base - Publishing an artifact goes through the same PR review as code, which makes code reviews harder and sharing artifacts between team members have to wait the PR review - It is unrealistic to capture the content of an ADR or a spec in a 1-2 sentence description in an index file, so agents can't rely on it to find the right information - You can't share information across teams (e.g. the agents of an application developer need access to the architecture designed by the networking or security team in another repo) How I Used Just 3 AWS Services to Solve it This is why I came up with the idea of using AWS services behind an MCP server to store, manage, share and search these artifacts. The architecture is pretty simple: - S3 stores all the markdown artifacts - S3 Vectors stores an embedding of each H2 section of every artifact. This gives semantic search at a more granular level than the whole document, plus filtering on the vector metadata (e.g. on the artifact type) - A Bedrock embedding model (Titan Text Embeddings v2) creates these embeddings - An MCP server exposes tools to store, manage and search artifacts, a few resources, and an MCP App to browse the artifacts visually - The MCP server uses the developer's own AWS credentials (e.g. an AWS CLI profile) - The artifacts follow the Open Knowledge Format (OKF) for their frontmatter. The artifact sources and relationships (work in progress), as well as the commit references, are stored in S3 object annotations. This way they can be updated without having to replace the S3 object, since S3 objects and their metadata are immutable. - In an enterprise, a single S3 bucket and a single vector index can store the artifacts of all teams and projects, each in its own prefix ("subfolder"). A team can then mark some of its artifacts as shared to make them visible to other projects and teams. Architecture Diagram !Image description The Result Combining just these 3 AWS services gave birth to the Arkeology project with the following features: - Immediate write-then-read consistency: an artifact written in one agent call is searchable in the next - Semantic search with metadata filtering: retrieve artifacts by meaning, not just by keywords - Section-level search precision: a query matches the specific section of an artifact that is relevant, not an average of the whole document - Cross-team knowledge sharing: canonical artifacts such as ADRs can be made discoverable by the agents of other projects or teams. Nothing is shared by accident: only permanent artifacts explicitly marked as shared are visible outside their project - Rich, filterable metadata: every S3 object and every vector carries structured metadata, and S3 objects also carry annotations - Commit-to-artifact traceability: artifacts carry git commit SHA references, stored in S3 object annotations and mirrored into the vector index so you can filter on them - Arkeology Studio: calling it opens a visual artifact browser directly inside any MCP host that supports MCP Apps (Claude Desktop, claude.ai, VS Code Copilot...) - Full artifact lifecycle: archive, delete and purge artifacts as projects evolve - Migration: a skill and server tools to import the documentation of an existing project - And more Arkeology Sudio MCP APP Preview !Image description Caveats Team Sharing and IAM Permissions The sharing of artifacts is enforced by the MCP server, but anyone with IAM read permissions on the whole bucket can bypass the MCP server and, for example, use the AWS CLI to read any data. Least-privilege permissions restricting each team to its own prefix in the S3 bucket must be enforced in IAM. That said, IAM can't do the same inside a shared vector index: any team with query access to the index can read the metadata (titles, descriptions, tags) and embeddings of all teams' artifacts. So only share a vector index with teams you trust. Arkeology Studio MCP App It requires an MCP host that supports MCP Apps (Claude Code, for example, doesn't), or using the MCP Inspector, which is a bit of a pain as a developer. I am currently thinking of building an optional frontend on AWS, to have a web interface to access, search and read the artifacts. Lessons Learned Vector metadata limits Vector metadata is limited to 2 KB filterable and 40 KB total per vector. This is why we can't put sources and relationships in the vector metadata and use them to search and filter artifacts. It is also why only the 20 most recent commit references go into the vector index, so you can still find the artifacts linked to recent commits; the full list stays in the S3 object annotations. Annotations S3 user-defined object metadata is capped at 2 KB and immutable like the object. Annotations hold up to 1 MiB and can be updated without rewriting the object. But there is a catch: overwriting the object deletes its annotations, so every rewrite must read them first and put them back. And if that read fails, it must raise an error rather than return nothing, or you end up writing empty values over good data, with no other copy to recover from. Using annotations for sources and relationships comes with some limitations: - It is not possible to do a search like "give me all artifacts referencing artifact XYZ" - Search results don't include these fields: an agent has to read or list the artifact to see them In other words, agents can only navigate these links, not query them. S3 Vectors QueryVectors doesn't return the distance unless you ask for it (returnDistance: true). Our code defaulted the missing distance to 0, so every result got a perfect score of 1.0 (score = 1 − distance). The search ranking was silently broken while all unit tests passed. Conclusion With just 3 basic AWS services I was able to build an MCP server that basically solves my pain points with managing all the artifacts generated through AI spec-driven development. Improvements to make the tool really functional at an enterprise level would be: - Host the MCP server on AWS - Enforce least privilege, so nobody can bypass the MCP server to read the whole bucket content - Host a web frontend on AWS, to get around the MCP App limitations The code is open source and available at amanoxsolutions/arkeology. _(Note: I used AI only to proofread this article, improve readability and draw the architecture diagram)_
@shakhzod1001 I built a second brain skill (with a few a tweeks here and there to make it work better with obsidian) using this standard: GoogleCloudPlatform/open-knowledge-format has been a massive game changer.
Google's Open Knowledge Format (OKF), an implementation that addresses real life problems Google published the Open Knowledge Format this summer. It is a way to write knowledge down as a folder of Markdown files with a YAML header, so that people and AI models read the same files. Each file is a concept (a rule, a procedure, a metric, a role) and the header says where it comes from, who checked it, when it expires and whether it is still current. No server, no database. If you can cat a file, you can read it. Great concept, but the spec stops at the format. It does not say how knowledge gets into a bundle, how to know a bundle is right, what to do when two documents disagree, or how a correction found by a user gets back in. In a company, that is the hard part. This project tries to answer real organizations problems using OKF with skills for coding agents (Claude Code and Pi): - okf-produce reads the documents, agrees with their owner on what the bundle is for, writes it with a source behind each claim, and keeps it current. - okf-validate checks a bundle against the spec. Plain Python, runs in CI too. - okf-answer answers questions from one or several bundles, with a reference after each claim and a warning when a fact is unchecked, expired or not covered. The same documents can give different bundles for different readers (angle), the skill will ask: an employee, an auditor and a documentation officer do not want the same thing from the same quality manual. The steps that need no judgment are code, not model: hashing, indexes, trust tiers, dates. The model never marks its own work as verified, a person does. Readers that did not write the bundle check it. When a user finds a mistake, the consumer writes a report and the producer picks it up at the next update. I tested it in a real organization, with several bundles. Token usage and accuracy were satisfactory, good enough that the org wants to keep going. The repo has three sample bundles made from the same 240 Wikipedia pages on Greek mythology, each from a different angle: the family tree, the stories by theme, and the English words that come from the myths. The family tree bundle includes a calculation (how is Perseus related to Heracles?) with a small checker that proves the approved script ran. Repo: wmahfoudh/okf Spec: GoogleCloudPlatform/open-knowledge-format Are you using OKF, where do you think it breaks, and how you handle trust and sources when you let a model answer from company documents.
OKF v0.2 frontmatter: what the spec requires, and two ways to check a folder I make JidoSeal. Free and local; the scan engine is proprietary; JidoSeal also sells optional certificates. If you keep notes, docs or a knowledge base as markdown with YAML frontmatter, and you point an AI tool at them (Claude Code, Cursor, Copilot, Codex, Gemini CLI, Zed, a local model), Google Cloud's Open Knowledge Format (OKF) is worth ten minutes. It is a small, open spec for exactly that kind of folder: "a directory of markdown files with YAML frontmatter", meant to be "authored by people, generated by agents... and consumed by both". This post covers what v0.2 actually requires, which is less than most people assume, and then two ways to check a folder against it. What OKF v0.2 requires The spec is short. You can read all of it at github.com/GoogleCloudPlatform/open-knowledge-for…. Here is the part that matters for frontmatter. A bundle is just a folder. Every .md file in it is a "concept document", except two reserved filenames: index.md (a directory listing) and log.md (an update history). Subfolders are fine, and git is the recommended way to ship it. One field is required: type. Section 4.1 lists it as the only required key, and says "a concept carrying just type is fully conformant". Its values are free text that you choose (policy, procedure, Metric, Playbook...). Nobody registers them centrally, and consumers must handle types they don't recognise. So this file is valid OKF v0.2: --- type: policy --- # Leave policy Employees accrue leave monthly. Four fields are recommended: title, description (one sentence), resource (a URI for the thing the note describes, if there is one), and tags (a YAML list). If title is missing, a consumer may derive it from the filename. Optional families, if you want more: - Trust: generated: { by, at } records who or what wrote the content. verified is a list of { by, at } entries. A consumer derives a trust tier from it: no verified means unverified, a non-human verifier means machine-confirmed, and by: human:<id> means human-reviewed. - Lifecycle: status is draft, stable or deprecated, and defaults to stable. stale_after is an absolute instant after which the note counts as stale. - Provenance: sources is a list of entries, each with a required resource. Conformance (section 11) is three rules. Every non-reserved .md file has a parseable YAML frontmatter block. Every block has a non-empty type. index.md and log.md follow their own sections if present. Just as useful is what consumers must not reject a bundle for: missing optional fields, unknown type values, unknown extra keys, broken cross-links, or missing index.md files. Extra keys of your own are fine. That gives you a concrete thing to check: does every note open with a frontmatter block, and does that block have a non-empty type? Then, softer: which of the four recommended fields are missing? Way 1: fifteen lines of Python, no dependencies This reads only top-level key: value lines. That's enough for type, which is a plain string. It skips the reserved filenames, fails any file without a frontmatter block or a non-empty type, and lists the recommended fields that are missing. import sys, pathlib RECOMMENDED = ["title", "description", "resource", "tags"] bad = 0 for p in sorted(pathlib.Path(sys.argv[1]).rglob("*.md")): if p.name in ("index.md", "log.md"): continue # reserved filenames, §3.1 lines = p.read_text(encoding="utf-8").splitlines() if not lines or lines[0] != "---" or "---" not in lines[1:]: print(f"FAIL {p}: no frontmatter block"); bad += 1; continue fm = lines[1:lines.index("---", 1)] keys = {l.split(":", 1)[0].strip(): l.split(":", 1)[1].strip() for l in fm if ":" in l and not l.startswith((" ", "#", "-"))} if not keys.get("type"): print(f"FAIL {p}: no non-empty 'type'"); bad += 1; continue missing = [k for k in RECOMMENDED if k not in keys] print(f"ok {p}" + (f" (recommended, missing: {', '.join(missing)})" if missing else "")) sys.exit(1 if bad else 0) Here it is on a sample folder of six notes, one of which has no frontmatter at all: $ python3 okf_check.py tiers ok tiers/bronze/leave-policy.md (recommended, missing: title, description, resource, tags) ok tiers/gold/leave-policy.md (recommended, missing: resource, tags) ok tiers/mixed/expenses.md (recommended, missing: description, resource, tags) ok tiers/mixed/leave-policy.md (recommended, missing: resource, tags) FAIL tiers/mixed/notes.md: no frontmatter block ok tiers/silver/leave-policy.md (recommended, missing: resource, tags) $ echo $? 1 It exits with 1 when anything fails, so it can go straight into CI or a pre-commit hook. It is honest about its limits. It does not parse YAML properly, so it won't notice a frontmatter block that is broken YAML, and it would accept type: "". Section 11 asks for "parseable" YAML, so if you need that guarantee, swap the keys line for yaml.safe_load and accept one dependency. Way 2: grep or yq, in one line To list the notes with no type line at all: $ grep -rL --include='*.md' -E '^type: *[^ ]' tiers tiers/mixed/notes.md That is fast, but it also matches a type: line in the body of a note. For a real YAML read of the frontmatter only, mikefarah's yq (v4) has a front-matter mode: $ for f in $(find tiers -name '*.md' ! -name index.md ! -name log.md | sort); do printf '%s\t%s\n' "$f" "$(yq --front-matter=extract '.type // "MISSING"' "$f")"; done tiers/bronze/leave-policy.md policy tiers/gold/leave-policy.md policy tiers/mixed/expenses.md procedure tiers/mixed/leave-policy.md policy tiers/mixed/notes.md MISSING tiers/silver/leave-policy.md policy Change .type to .description (or any other field) to find the notes that are missing that one. If you'd rather have your AI tool do the check, I also maintain a free, local MCP server that runs it and lists each file's missing fields: Jidoseal/jidoseal-mcp
An understanding of my world: a knowledge graph over my Obsidian notes I have too many notes In my job I take a lot of notes. I mean a lot of notes. I attend a lot of meetings, and client conversations, catch-ups with colleagues, workshops and the research I pull together for a pursuit all get written down. My vault holds more than 900 markdown files now, organized into a structure of my own. The problem is what happens as that folder grows. There are too many notes to consume. The structure still makes sense, but it can't tell me what I actually want to know, which is usually something about people, clients and the commitments between them. Take a question like "who owns the Northwind data contract?" The answer is in there, in a sentence about Dana Whitfield in a quarterly review note. A search will find the note, along with every other note that mentions Northwind. It won't give me the owner. My vision So I wanted a way of managing and storing that data that lets me query it in the right way. I want to ask a question in my own words and get the answer, along with the note it came from. That means storing my notes in a way that understands the people, clients and relationships in them. My solution Building it turned out to be the manageable part. In my experience the real problem is maintaining the data over time, and that problem shapes every choice in the rest of this post. The system is a consistent folder structure, markdown notes with OKF metadata, a Graphiti graph with my own ontology, a daily Cowork task that keeps it current, and an MCP server that puts it into my daily work. I picked a structure and stuck to it I'll show you mine as an example. My vault has a "Clients" folder, with a folder for each client. Each one holds that client's information and its own people folder, so I can see the people who belong to that client. There's a top-level People folder as well, and folders that describe our go-to-market work and specific teams. Roughly, it looks like this (Northwind is an invented client): Obsidian Vault/ Clients/ northwind/ Northwind.md 2026-09-14 quarterly review.md people/ Dana Whitfield/ Dana Whitfield.md People/ Blog Posts/ Strategy/ You're going to query this knowledge store in your own language. When you talk to Claude, you'll use your own references and the aliases you use for people and clients, so the structure has to make sense to you, and mine won't necessarily. Whatever you choose, follow it consistently, because that consistency is what the rest of this system depends on. I built on markdown Markdown is the foundation of all of this. People who work with LLMs regularly are comfortable with it already. For anyone who isn't, it's a very simple plain-text format, and it's one LLMs really like to work with. Any tool that reads text can read it, so the notes aren't tied to one app. I write and read mine in an Obsidian vault. It's a good place to do that, and the wikilinks it uses to connect notes come back later in this story. OKF FTW: I gave every note metadata So now I have all these markdown files, which is great. What I don't have is a real sense of the state of each one. Nothing has validated it, and there isn't enough metadata around it to build up a graph from. That's where the Open Knowledge Format comes in. OKF is published by Google Cloud (spec), and I went with it because it's lightweight. All it asks for is frontmatter: a set of keys and values at the top of the note. It's also extensible, so I can layer on new keys and values over time as I work out what I need, and that has been really helpful. Beyond that, it has a lot going for it because it comes from Google, which has the influence to help something like this gain popularity. And I like the idea of frontmatter as a metadata store. It's open-ended enough that it doesn't get in the way of how I already write. --- type: Meeting Notes title: Northwind quarterly review description: Migration scope, timeline pressure, and who owns the data contract. tags: [meeting, northwind, migration] generated: { by: claude/haiku-4-5, at: 2026-09-14T11:10:37Z } --- Now each note carries metadata about itself: what kind of thing it is, a title, a one-line description, some tags, and a record of what produced it. That metadata could really be anything that makes sense of the document, and you can add whatever properties make sense for your organization. Say you want to mark when a document should be considered out of date. Add a key like expires_after and put a date there. An LLM can read that key and change how it treats the note, as long as you tell it what the key means, so the metadata isn't arbitrary. A deterministic script can act on the same key, and so can anything else that reads frontmatter. That's what OKF gives me: a lightweight structure I can tie other actions to. A markdown file can feel very unstructured, and OKF brings structure to it without making me rewrite the document to follow some convention. The document can be whatever it wants to be. The metadata carries the information that programmatic solutions need, whether that's an LLM taking a particular action or a script that runs when it sees a particular value. It opens up a whole range of possibilities for how I organize my data. OKF's own design goals fit this well. Google's argument for it is that the context agents need should live in an open format of its own, somewhere other than a proprietary service or a pile of unstructured text. It's also deliberately minimally opinionated. type is the only field that's always required, every other field is opt-in, and custom keys are preserved. That last part is what makes the expiry example work. (OKF v0.2 does define a stale_after field for exactly that case, but the same holds for any key you invent.) Frontmatter also changes how much of a note anything has to read. A script can pull one property, such as the type or a date, without touching the body, and the same holds for an agent. Most of the time an agent only needs to decide whether a note is relevant, and the title, description and tags are enough for that. An agent won't stop at the frontmatter on its own, though. Left alone, it usually reads the whole file. The simplest fix is an instruction in its standing context: check the frontmatter first, and open the body only for the notes that look relevant. A skill or a tool goes further, because it can parse the frontmatter and hand the agent only those properties, so the body never reaches it unless it asks. In my vault, the sweep also rebuilds an index for each top-level folder, with one line per note built from its title and description, so an agent can scan a folder's index before it opens anything. How I create the metadata Metadata only helps if every note has it, so keeping the vault in shape is a job for a script. Mine runs as a sweep, in a fixed order. It waits for the vault to go quiet, checks each note, fills in missing metadata, repairs what it can mechanically, links the names it already knows, rebuilds the indexes, scans for secrets and commits. The one step that needs a model is classification, where a model reads a new note and writes its type, title, description and tags. Everything else is plain code, and I'd keep it that way, because a deterministic job like string comparison belongs somewhere that gives the same answer every time. Keep the list of types small, too. My approved list has about thirty values, and the classifier only chooses from eight of them. Give a model an open list and it invents a plausible new type for any note that doesn't fit, and no query knows that type exists. A note that doesn't fit gets flagged for me to look at instead. My notes are about relationships My notes have semantic structure, but mostly they're about relationships: between the notes themselves, and between the things they describe. Obsidian is built on that idea. It uses wikilinks to link documents together, which is how you build up your own knowledge graph inside Obsidian. !Obsidian graph view of my vault: a dense cluster of linked notes, with a few unconnected groups at the edges. The Obsidian graph view does more than look nice. It shows the relationships between ideas and people, and it also shows the ideas that sit on their own. In the screenshot above, the small dots out towards the edges are parts of my knowledge that haven't been connected with the rest of the graph yet. It also shows where my attention goes. A dot grows as more notes link to it, so the people, entities and themes I talk about most stand out, and I can see who is really at the center of my world. That's useful for spotting gaps. An idea out on the edge may not be getting enough attention. And if there's someone who should be more prominent in my work and I can't see them in the graph, that's probably telling me there's an action I should take. The graph serves more than one purpose, and that's one of them. What I also needed was to query my data and pull out relationships, because for consultancy work that's where the value is. Everything I deal with has relationships to everything else: companies, clients, people, organizations, protocols, standards. In a graph, the kinds of relationships are commonly described with an ontology. I use an ontology to bring meaning An ontology brings semantic understanding to your data. It says what kinds of things exist and how they can relate, so "Dana Whitfield is accountable for the Northwind data contract" means something a program can work with. So what I'm doing, ever so slowly, is building up a personalized knowledge domain. It comes in layers: flowchart TB NOTES["Raw notes<br/>the content"] OKF["OKF frontmatter<br/>metadata and processing information"] ONT["Ontology and graph<br/>entities and the relationships between them"] LLM["An LLM asking questions<br/>of my notes"] NOTES --> OKF --> ONT --> LLM The raw notes are the content. The OKF keys are metadata, plus whatever post-processing I want to record. Once I ingest those layers, the things they describe show up as entities, and the relationships between them as typed edges. This is where it becomes valuable. When the ontology is exposed to an LLM, the LLM can take my question, look at the entities and relationships it knows about, and work out where the relevant information is. How a note becomes a fact Here's what that looks like for the Northwind question. From the quarterly review note, extraction pulls out two entities and the relation between them (illustrative): Dana Whitfield (Person) --ACCOUNTABLE_FOR--> Northwind data contract (Contract) fact: "Dana Whitfield owns the Northwind data contract and signs off changes to it." source: Clients/northwind/2026-09-14 quarterly review.md The relation carries a plain-language fact sentence and the path of the note it came from, so every answer the graph gives can point back to its source. This is also where the consistency from earlier pays off. An entity's identity comes from its type and its name, with no fuzzy matching. Two notes that both say Northwind merge into one node, with no model call. A note that says Northwind Trading instead creates a second node, and it stays separate until someone maps one name to the other. So the names you use consistently in your notes become the identity of things in your graph. A wikilink like [[Dana Whitfield]] helps here too, because it gives extraction the exact spelling to use. Why I chose Graphiti I went with Graphiti for the graph because it's a widely used framework with a lot going for it. It lets me define my own entity and edge types in Python, and it brings the schema, typed edges and hybrid search with it. The bigger reason was speed. Building out a graph, and the database technology underneath it, can take a long time. Graphiti let me iterate quickly and get to the point where I could focus on the parts I cared about: the ontology, the data structure and the taxonomy I wanted to define. Graphiti needs a database underneath it, and I run FalkorDB in a single local container. It comes with a browser for looking at the graph, which is handy when you want to see what extraction actually produced. For embeddings, which the hybrid search depends on, I use a local model (nomic-embed-text) through an endpoint Graphiti already speaks. The one model the graph pipeline calls out to is Claude Haiku 4.5, and that's there for cost: it's cheap enough to run over every changed note, every day. How I keep the graph alive Once the ontology and taxonomy are in place, and there's somewhere to store them, the work isn't finished. My data keeps changing, so I need a daily process that brings in the new notes and notices when they describe a relationship the ontology doesn't have a name for yet. That's where Claude Cowork comes in. A scheduled task runs each day in Cowork and manages my data for me. It writes the OKF metadata for new notes, looks at the data, builds up a sense of the relationships in it, and proposes new relation types where nothing existing fits. It runs at 17:00 and only sends what changed since the last run to a model, so a quiet day sends nothing to a model. Getting those names right matters. The ontology is what brings sense to the data, and the name of a relationship is how the LLM understands what that relationship means. Say Dana Whitfield reports to the head of data at Northwind, but anything that goes wrong on the contract gets escalated to someone else entirely. Those are different relationships. If the graph gives them the same name, then when I ask who's accountable for the contract, the LLM can't tell them apart, and it may well hand me the wrong person. So a new relation name has to earn its place. Once the same name turns up in three different notes, it's promoted into the ontology automatically, and Claude Cowork can discover it. The same task can keep the graph clean as well as growing it, and this is where the OKF metadata from the beginning comes back in. Notes go out of date. Remember the expires_after key? A scheduled task can read that date and treat an expired note differently, retire its facts from the graph, or flag it for me to review. The idea works for any property you define. Your metadata can decide what gets cleaned up, what gets removed from the graph because it's no longer relevant, and how the rest of the data is organized. That interplay between a lightweight metadata format and a task that runs every day is where a lot of the possibilities open up. I think this applies well beyond ontologies. Any document that tells a model how things currently stand will go out of date, and nothing in the document will tell the model so. That's the kind of problem OKF metadata like an expiry date is there to solve. Where I use it every day The last piece is an MCP server that exposes the knowledge graph. Because it's MCP, the graph isn't only available to Cowork. Any agent that can access a local MCP server can use it. I use it with Cowork, and that's where all the pieces, and all the work that came before, start to tie together. This is where the value actually comes alive. Without this step, it's fancy metadata on my notes. The wikilinks add something too, because they let me see my graph in Obsidian. But the real value, in my mind, is being able to integrate this into my daily work. In Cowork I can ask questions about my own notes and get answers that come from an understanding of my world, and connecting the graph through MCP opens up a whole host of possibilities beyond that. My MCP server The server itself is small: one Python process that talks over stdio. It needs no API key, because search runs on the local embeddings and queries go straight to FalkorDB. It gives Claude seven read-only tools, and two of them show the idea well. entity_profile returns a summary of what the graph knows about a named entity. find_relations helps Claude work out which relationship fits a question when it doesn't know the relation name, and that's how the ontology becomes something Claude can discover. The server also carries instructions, which Claude reads when it connects. Mine tells it to check the graph before a Slack or web search when I ask about my own notes, and to name both sources when Slack and the graph disagree. That matters, because the graph only knows what my notes captured. If Claude filled a gap from the web, the answer would read as though it came from my notes, and I would repeat it with the same confidence. What leaves my laptop Much of my work is in regulated environments, so I'd call this out. The graph, the database and the embeddings all stay on my laptop, but every model step in the graph pipeline sends note text to Claude Haiku 4.5, the scheduled task itself runs in Cowork, and answers go to the Claude app that asked for them. Make sure that's acceptable for what's in your notes before you point this at them. The server only runs on my laptop, so a web or phone session can use it only through the Claude desktop app on my laptop, while that app is running. Here's what Claude gets back when it profiles the contract (illustrative): Northwind data contract (Contract) ACCOUNTABLE_FOR <- Dana Whitfield "Dana Whitfield owns the Northwind data contract and signs off changes to it." source: obsidian://open?vault=Obsidian%20Vault&file=Clients/northwind/2026-09-14%20quarterly%20review So, who owns the Northwind data contract? Dana Whitfield, according to the quarterly review on 14 September, and the answer comes back with a link to the note. What I'm building next I've also done a test run of Qwen 3 locally on Apple's MLX framework, using the 14B model quantized to 4-bit. The next step is to see whether a local model like that can take over the model steps. Today every model step sends note text to Claude Haiku 4.5. A local model would keep that text on the laptop, which matters for anyone whose notes come from regulated work.
ドキュメントの整理の仕方で参考になる GoogleCloudPlatform/open-knowledge-format
@Fluyeporlaweb Archivos markdown no es suficiente, tendrías que usar lo que hizo Google con este nuevo formato github.com/GoogleCloudPlatform/open-knowledge-for…
I built a task board and wiki that live in your Git repo I’m a Product Manager, and I wanted epics, tasks and a wiki together for my own projects. I tried Backlog md, Beads and a few other approaches, then ended up building my own. Kind of like Jira… but with everything living as Markdown in Git. It’s called GitDocket. There’s a local web UI for the board and wiki, plus CLI/MCP access so coding agents in tools like Codex or Cursor can work with the same project context. The useful part is being able to come back to a project and see what was decided, what changed, what was checked and what comes next. Tasks can keep their outcomes and linked commits, and a new agent session can read that history too. Underneath, the engine is built off of Google’s Open Knowledge Format. The files stay readable and reviewable in Git. It’s free and open source, and still has rough edges. Would this help you pick a side project back up, or does it feel like too much process? I’d also be interested in how you handle this today.