Palantir charges six to seven figures a year for Foundry. Their core innovation is not the software -- it is the idea: put every business noun, every relationship between them, and every verb that acts on them into one queryable graph. Then let humans and agents ask questions in natural language instead of writing SQL.
We needed exactly this. But we are a 10-person Arabic ed-tech company, not a Fortune 500 oil company. So we built it ourselves.
This post documents the full reasoning, the architecture, what we rejected and why, and the falsifiable kill gates that will tell us if this was a good idea or a waste of time.
The Problem
Knowledge about our business is scattered across 5+ systems:
"Which users are stuck?"
└── lives in MySQL. Only eng can query.
"What does endpoint X touch?"
└── lives in code + tribal memory.
"Why is this Thurayya chapter failing?"
└── lives in Athena cold storage + Amplitude + realtime_answers.
"What do we know about user Layla?"
└── 5 different tables, 3 different systems.
"If I change this Lambda, what breaks?"
└── nobody knows. We find out in prod.
Every question requires an engineer to write SQL. Each answer takes 15-45 minutes. We measured: ~8 hours per week of engineering time disappears into ad-hoc queries for support, content, and growth teams.
That is $40k/year of engineering labor spent as a human database adapter.
What Palantir Actually Sells
Strip away the marketing and Foundry is four things:
┌─────────────────────────────────────────────────┐
│ Palantir Foundry │
│ │
│ 1. Graph Store ── nodes + edges + types │
│ 2. Extractor Layer ── pulls from your systems │
│ 3. Query Layer ── Cypher / search / NL │
│ 4. Action Layer ── verbs that mutate state │
│ │
│ Price: $$$,$$$/year + US region data gravity │
└─────────────────────────────────────────────────┘
These four components are not rocket science. They are a graph database, some ETL scripts, a query interface, and an API gateway. What makes Foundry valuable is that Palantir forces you to actually model your business in a single graph. The discipline is the product, not the software.
We can enforce that discipline ourselves.
Why We Rejected Foundry
Four concrete reasons, not vibes:
- Cost. Six to seven figures per year. Our entire engineering budget does not justify a Foundry license.
- Data gravity. Foundry pulls data into US regions. Our prod DB is in me-south-1 (Bahrain) and eu-west-1 (Ireland). Cross-region egress for PII data creates a compliance problem we do not have today.
- Lock-in. Foundry Ontology objects use a proprietary schema. Leaving means rebuilding everything.
- Overkill. Foundry is built for organizations with thousands of tables. We have ~60 domain modules.
The DIY Architecture
Four layers. Same conceptual structure as Foundry. ~10x cheaper.
┌────────────────┐ nightly ┌──────────────────┐
│ RDS (prod) │─ read-rep ──▶│ rds_extractor │──┐
└────────────────┘ └──────────────────┘ │
│
┌────────────────┐ on push ┌──────────────────┐ │
│ src/models/* │────────────▶│ model_extractor │──┤
│ src/resources │ │ resource_extr. │ │ ┌─────────────┐
│ docs/plans/* │ │ plan_extractor │ ├────▶│ GraphStore │
│ serverless.yml│ │ infra_extractor │ │ │ (JSONL) │
└────────────────┘ └──────────────────┘ │ └──────┬──────┘
│ │
┌────────────────┐ real-time ┌──────────────────┐ │ │
│ SNS: outcome │────────────▶│ outcome_listener │──┘ │ query
│ events │ └──────────────────┘ │
└────────────────┘ ▼
┌──────────────┐
│ Claude agent │
│ + tool-calls │
└──────┬───────┘
│ actions
▼
┌──────────────────────┐
│ existing API endpts │
│ grant, reorder, etc. │
└──────────────────────┘
Layer 1: Graph Store
For Stage 1, we chose JSONL -- append-only, line-delimited JSON files in S3, loaded into an in-memory index at Lambda cold-start. No database server. No infrastructure.
Why not Neo4j or Postgres+AGE from day one? Because we are running a 2-week spike. Spinning up Neo4j for a prototype is premature optimization. JSONL handles <100k nodes fine. The real store decision happens at Phase 3, when we have actual query patterns to optimize for. (I wrote a separate deep-dive on Neo4j vs Postgres+AGE vs JSONL if you want the full comparison.)
Layer 2: Extractors
One Python module per source of truth. Each extractor reads one system and emits typed Node and Edge objects:
src/ontology/extractors/
├── model_extractor.py ── walks SQLAlchemy models
├── resource_extractor.py ── walks Flask resources
├── plan_extractor.py ── reads docs/plans/ YAML front-matter
├── infra_extractor.py ── reads serverless.yml
└── rds_extractor.py ── queries prod read-replica
The critical design constraint: extractors are read-only projections. If the graph and the source disagree, the source wins. The graph gets rebuilt nightly from scratch. This is cheaper than bidirectional sync and eliminates an entire class of consistency bugs.
Layer 3: Query
A Claude agent with the full ontology schema in its cached system prompt. Users type natural language. The agent translates to graph queries, runs them, and returns structured answers.
User: "Which Amal users in UAE finished onboarding but
never opened day 2?"
Agent: [calls graph_query tool]
[filters User nodes: app=Amal, country=UAE,
onboarding_complete=true, day_2_opened=false]
[returns 340 users in a table]
Layer 4: Actions
The agent can trigger existing API endpoints. It never has its own mutations -- it calls back into the existing domain layer with full audit logging.
User: "Send those 340 users a 50% off winback email."
Agent: [calls execute_action tool]
[POST /boss/v1/campaigns with user_ids + template]
[logged in activity_log with source=ontology]
The Schema
Declared in a single YAML file. "Terraform for ontology":
version: 1
id_format: "{type}:{natural_key}"
object_types:
App: { natural_key: name }
User: { natural_key: id } # PII redacted at boundary
Subject: { natural_key: id }
Chapter: { natural_key: id }
ContentByte: { natural_key: id }
ContentBit: { natural_key: id }
Membership: { natural_key: id }
Plan: { natural_key: path }
Endpoint: { natural_key: path_method }
LambdaFn: { natural_key: name }
link_types:
has: { from: [App, Subject, Chapter],
to: [Subject, Chapter, ContentByte] }
contains: { from: ContentByte, to: ContentBit }
answered_by: { from: ContentBit, to: User }
pays_for: { from: User, to: Membership }
unlocks: { from: Membership, to: ContentByte }
touches: { from: Plan,
to: [Endpoint, ContentByte, LambdaFn] }
deployed_to: { from: Endpoint, to: LambdaFn }
action_types:
grant_membership: { target: User, endpoint: POST /boss/v1/memberships }
regenerate_bit: { target: ContentBit, endpoint: POST /boss/v1/content-bits/{id}/regenerate }
Every change to this file is a PR with CI validation. The schema is the single source of truth for what the business graph looks like.
What This Unlocks -- Three Outcomes
Outcome 1 (Months 1-3): Stop Wasting Eng Time on SQL
Support, content, and growth teams self-serve answers through the agent UI. An 8h/week time sink becomes a 90-second interaction.
Before:
Support lead → Slack message to eng → 30 min wait →
eng writes SQL → pastes results → support lead acts →
total: 45 min per question
After:
Support lead → types question in ontology UI →
agent returns answer + action button → done →
total: 90 seconds
Outcome 2 (Months 6-12): Content That Improves Itself
Every answered question generates a signal: user X got question Y wrong/right, took Z milliseconds. This signal flows through the graph.
A weekly batch job finds content with low correct-rates per age group. It regenerates those questions -- new phrasing, new audio, new images -- and A/B tests the new version against the old. The winner replaces the loser automatically.
Week 1: "Letter jim for age 5-6" -- 43% correct rate
│
▼
Regeneration: 3 new variants created overnight
│
▼
Week 2: A/B test on 1,000 kids each
│
▼
Week 3: Winner deployed. Correct rate: 61%.
Nobody noticed. Nobody intervened.
A competitor who copies our content today gets a static snapshot. We get the loop. That gap widens every week.
Outcome 3 (Year 2-3): Personalization + B2B Schools
The same graph that answers support questions and improves content can also drive a per-child daily lesson picker. If Layla struggled with short vowels yesterday, today's lesson starts with a warm-up on short vowels.
Package the same graph as a teacher dashboard: per-student mastery, class-wide weak topics, exportable reports. Sell to schools at $8/student/month.
The Cost
┌─────────────────────────────┬──────────┬──────────┬──────────┐
│ Item │ Yr 1 │ Yr 2 │ Yr 3 │
├─────────────────────────────┼──────────┼──────────┼──────────┤
│ Graph store (JSONL → Neo4j) │ $0 │ $600 │ $1,200 │
│ Claude API (cached, agents) │ $1,200 │ $2,400 │ $4,800 │
│ Lambda compute (extractors) │ $50 │ $100 │ $200 │
│ Eng maintenance (0.5wk/qtr) │ $15,000 │ $15,000 │ $15,000 │
├─────────────────────────────┼──────────┼──────────┼──────────┤
│ TOTAL │ ~$16k │ ~$18k │ ~$21k │
└─────────────────────────────┴──────────┴──────────┴──────────┘
Palantir Foundry: six figures/year minimum.
DIY: ~10x cheaper.
The Kill Gates
Every phase has a falsifiable exit condition. Fail means kill, not extend.
P0 ─── pass ──▶ P1 ─── pass ──▶ P2 ─── pass ──▶ P3 ──┐
│ │ │ │
fail fail fail ▼ pass
▼ ▼ ▼ P6/P7/P8
ABORT fallback Outcome 1 only (personalization,
to wiki (no loop) planner, schools)
| Phase | Gate | Deadline | Kills if fails |
|---|---|---|---|
| P0 | 3 recent plan docs have YAML falsifiable_claims | 2026-04-25 | Everything |
| P1 | Agent answers 20 support Qs in <50% baseline SQL time | 2026-06-01 | Graph effort |
| P2 | OutcomeEvent delivery >=99% for 7 days | 2026-06-20 | Moat loop thesis |
| P3 | Regenerated bits show >=5pp correct_rate lift | 2026-08-31 | Outcome 2 |
Committed eng before the first real kill gate: 1.6 eng-weeks. That is the actual bet. Everything beyond that is earned at a gate.
The Falsification Test
Claims I am least certain about, stated so they can be disproven:
-
"The ontology will save 8h/week of eng time." This assumes support/content teams will actually use the agent UI instead of defaulting to Slack. If adoption is <30% after 3 months, the savings are fictional.
-
"Content self-improvement via A/B is a moat." This assumes correct_rate is the right proxy for content quality, that regenerated variants are meaningfully different, and that the sample sizes are achievable. If A/B shows no lift, the loop does not close and we revert to Outcome 1 only.
-
"$16k/year is the real cost." This assumes 0.5 eng-wk/quarter of maintenance is sufficient. If the graph drifts from the source schema and requires constant manual fixing, the real cost is much higher.
-
"JSONL is fine for Stage 1." If the graph exceeds 100k nodes before Phase 3, or if query latency exceeds 2 seconds on real questions, the JSONL store becomes a bottleneck earlier than planned.
The One Insight
Palantir's real product is not software. It is the discipline of modeling your business as a graph. The software is just the enforcement mechanism.
You can enforce that discipline yourself with a YAML schema, some Python extractors, and an LLM that reads the graph. The hard part is not the technology. The hard part is maintaining the schema as the business evolves -- which is why every phase has a kill gate that tests whether humans are actually keeping it up.
If they are not, the ontology is dead regardless of the technology underneath. That is the most falsifiable claim of all.
Director of Agentic AI for the Enterprise at Writer. Building at the intersection of language, intelligence, and design.