Compare commits
1
Commits
main
..
gitea-pages
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0d5ffcdbe |
@@ -1,7 +1,3 @@
|
|||||||
# homelab
|
# homelab
|
||||||
|
|
||||||
Personal AI platform — architecture and capabilities overview
|
Personal AI platform — architecture and capabilities overview
|
||||||
|
|
||||||
## Docs
|
|
||||||
|
|
||||||
- [What a Client PoC Taught Us About Our Own AI Delivery Pipeline](docs/articles/what-a-client-poc-taught-us-about-cad.md) — cross-repo review of Continuous Agentic Development (CAD) against an external Requirements-Engineering PoC, plus what it surfaced across this stack.
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
# What a Client PoC Taught Us About Our Own AI Delivery Pipeline
|
|
||||||
|
|
||||||
*A cross-repo review of Continuous Agentic Development (CAD), and the fifteen-repo stack that runs it — prompted by comparing notes with a from-scratch AI Requirements-Engineering PoC.*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## The setup
|
|
||||||
|
|
||||||
A few weeks ago I was pulled into a proof-of-concept for a client project: an AI-driven Requirements-Engineering platform, built from scratch by two humans and a swarm of coding-agent sessions. Call it **the RE-PoC**. It ingests source documents, runs an elicitation bot against a human author, gates every output through a structural quality check and an LLM-judged governance review, and commits the result as a pull request — never merging autonomously.
|
|
||||||
|
|
||||||
I wasn't looking for a case study. But halfway through reading its process docs, I recognized the shape of a problem I'd already spent months solving in my own homelab: **how do you let coding agents build software continuously, without turning "the agent said it's done" into the only signal anyone checks?**
|
|
||||||
|
|
||||||
That's also the plain-English definition of **Continuous Agentic Development (CAD)** — the pipeline my own stack runs, visualized end to end in a tool I call the Atlas. So I did the obvious thing: a full structural comparison, then pointed the same lens at my *own* fifteen-repo stack to see if it held up as well as I assumed.
|
|
||||||
|
|
||||||
It didn't, not entirely. Here's what came out of it.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What CAD actually is
|
|
||||||
|
|
||||||
Nine stages, one feedback loop:
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart LR
|
|
||||||
A[00 Signals] --> B[01 TELOS]
|
|
||||||
B --> C[02 Strategic session]
|
|
||||||
C --> D[03 Spec to Gitea issue]
|
|
||||||
D --> E{04 Human dispatch gate}
|
|
||||||
E --> F[05 Execute — agent swarm]
|
|
||||||
F --> G[06 PR to CI]
|
|
||||||
G --> H[07 CD to pod]
|
|
||||||
H --> I[08 Loop back — scored vs TELOS]
|
|
||||||
I -. feedback .-> B
|
|
||||||
|
|
||||||
style E fill:#f9d67a,stroke:#333
|
|
||||||
```
|
|
||||||
|
|
||||||
Three gates sit across that pipeline, each guarding a different failure mode:
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TB
|
|
||||||
subgraph Gates["Three orthogonal gates"]
|
|
||||||
direction LR
|
|
||||||
G1["Spec integrity<br/><i>is the issue untampered</i>"]
|
|
||||||
G2["Repo eligibility<br/><i>may agents run here at all</i>"]
|
|
||||||
G3["Output correctness<br/><i>does the PR actually satisfy the spec</i>"]
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
The whole thesis of the stack's audit layer is that **the trace *is* the audit package** — if you build the pipeline right, you don't write a compliance report after the fact, you render one from the event stream.
|
|
||||||
|
|
||||||
That's the pitch. The RE-PoC was a chance to test it against an independently-built system solving the same problem from a different angle.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## The single most useful thing the comparison surfaced
|
|
||||||
|
|
||||||
Both systems, built independently, hit the exact same failure mode — and fixed it in two different ways worth knowing about.
|
|
||||||
|
|
||||||
**The failure:** an agent (or a human standing in for one) reviews another agent's work, says "approved," and it turns out the work wasn't actually done. A rubber stamp with a signature on it.
|
|
||||||
|
|
||||||
**Fix #1 — the process answer (the RE-PoC).** After a real incident where a reviewer wrote "ACCEPTED" over a known-unmet checklist item, they hardened their review protocol: every review comment must open with exactly one of three words (`APPROVED` / `REWORK` / `ESCALATE`), and the reviewer must transcribe the acceptance checklist verbatim, item by item, *before* writing that word. "Approved" written above an unchecked box is now defined as self-contradictory — automatically treated as rework, no judgment call needed.
|
|
||||||
|
|
||||||
That's a good fix. It's also a fix that depends on the reviewer following the convention correctly, every single time.
|
|
||||||
|
|
||||||
**Fix #2 — the mechanical answer (my own stack, independently).** Across four separate repos in my pipeline, I found the same problem solved a structurally different way: **the thing being judged never gets to supply its own verdict.**
|
|
||||||
|
|
||||||
- The executor's review gate hard-overrides an LLM "approve" to "revise" if the actual test suite didn't pass — approval is documented as "a signal, not a guarantee."
|
|
||||||
- A separate spec-compiler's PR gate injects the pass/fail logic into a sandboxed process that runs *against* the candidate, rather than trusting a verdict the candidate hands back. The internal shorthand for this, after a debate about it: *"a self-claim has no authority."*
|
|
||||||
- A third component never trusts an agent's self-reported "done" — it polls the actual CI status on the actual commit, independently, every time.
|
|
||||||
|
|
||||||
The difference matters: the RE-PoC's fix makes a false "approved" *visually self-contradictory*. Mine makes it *impossible to act on*, because nothing downstream ever reads the agent's own opinion of its work as the verdict. One is a better habit. The other doesn't care whether you're having a good day.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart LR
|
|
||||||
subgraph P["Process fix"]
|
|
||||||
R1[Reviewer writes verdict] --> C1{Checklist transcribed first?}
|
|
||||||
C1 -->|yes, consistent| OK1[Verdict stands]
|
|
||||||
C1 -->|contradicts checklist| RW1[Auto rework]
|
|
||||||
end
|
|
||||||
subgraph M["Mechanical fix"]
|
|
||||||
R2[Agent proposes verdict] -.ignored.-> X((discarded))
|
|
||||||
T[Independent check: tests / CI / gate] --> V2[Real verdict]
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Four more things worth stealing — in both directions
|
|
||||||
|
|
||||||
**1. Nobody in my stack has a claim protocol, and the RE-PoC does — because they got burned first.**
|
|
||||||
Two independent agent sessions on their side once read the same "unblocked" signal and both built the same task concurrently — landed on the identical branch name, wasted real compute. Their fix: a lightweight claim-comment protocol (post a claim before touching anything, cap work-in-progress, re-check freshness immediately before pushing, not just before claiming). My own stack has *nothing* like this anywhere — one component gets close (a distributed lock for a single watcher process) but nothing coordinates multiple agents against a shared backlog. It's a gap I only found because someone else hit the failure first.
|
|
||||||
|
|
||||||
**2. My stack independently reinvented a "sovereignty gate" three times — theirs has none.**
|
|
||||||
Three unrelated components in my pipeline all converged on the same idea without coordinating: before any content reaches a cloud LLM call, check whether it's allowed to leave the local network at all, and fail closed if unsure. The RE-PoC has no equivalent — not a flaw exactly, more a sign it's never had to operate under a "don't leak client data" constraint. Worth them at least deciding that on purpose rather than by omission, especially given their own stated premise is demonstrating *governed* agent-driven delivery.
|
|
||||||
|
|
||||||
**3. Whether an LLM-judged gate gets to *block* anything is a decision I haven't actually made yet.**
|
|
||||||
The RE-PoC's governance review can outright block a merge. A knowledge-quality service in my own stack does the opposite on purpose: its LLM judge is confirmation-only, never allowed to execute or block anything, routed to a human above a severity threshold. Two live precedents, opposite philosophies, and I have two more components in my own stack about to need this exact decision with no answer recorded anywhere. That's not a maturity gap, it's an undecided fork — worth closing before either one gets built on autopilot.
|
|
||||||
|
|
||||||
**4. Glossaries rot the moment nobody enforces them — theirs is CI-gated, three of mine aren't.**
|
|
||||||
Cheap, mechanical, easy to add: flag any new domain term that shows up in code or docs but isn't in the glossary. Advisory only, doesn't block anything, just stops the thing from going stale. They do it. I have three separate glossaries and zero enforcement on any of them.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## The consolidation side-quest
|
|
||||||
|
|
||||||
Doing this review meant reading fifteen-plus of my own repos back to back for the first time in a while, which is its own kind of audit. Two clusters of genuine redundancy fell out:
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart LR
|
|
||||||
subgraph "Already done (precedent)"
|
|
||||||
ISV["ingestion-svc<br/>(archived 2026-05-14)"] -.absorbed into.-> HGI[hyperguild/ingestion]
|
|
||||||
end
|
|
||||||
subgraph "Same move, not yet done"
|
|
||||||
BIN[brain-ingest-ntfy<br/>CI red 2 months] -.propose absorb.-> HGI
|
|
||||||
BWI[brain-weekly-ingest<br/>never actually built] -.propose absorb.-> HGI
|
|
||||||
end
|
|
||||||
subgraph "Separate lifecycle question"
|
|
||||||
J[journal<br/>self-flagged provisional] -.propose fold into.-> B[brain]
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
One repo (`journal`) had literally flagged itself for a consolidation review in its own README. Another pair of repos were doing the same job as a component that had *already* absorbed a third repo doing that exact job a year earlier — the precedent was sitting right there and nobody had followed it since. Filed as tracked issues, cross-linked both directions, not silently merged — consolidation is a decision, not a cleanup script.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Nine takeaways, if you're building something similar
|
|
||||||
|
|
||||||
1. **A rubber-stamp failure will happen to you too.** It's not a sign of a bad review process — it's a sign you have a review process with a human or an LLM in the loop. Plan for it.
|
|
||||||
2. **Prefer a mechanical verdict over a well-written convention, when you can.** Conventions degrade under drift. A gate that computes its own answer independently of the thing it's judging doesn't.
|
|
||||||
3. **If more than one agent might touch the same backlog, you need a claim protocol before you need it.** Waiting for the collision to design the fix is expensive and avoidable.
|
|
||||||
4. **Decide your LLM-gate's authority on purpose.** Blocking vs. advisory-only is a philosophy choice with real consequences, not a default you back into.
|
|
||||||
5. **If you operate under a data-sovereignty constraint, build the gate before you need it,** not after the first accidental cloud call.
|
|
||||||
6. **A glossary without enforcement is a glossary that's already stale.** Advisory-only CI checks are cheap insurance.
|
|
||||||
7. **The best comparison you'll get is an independently-built system solving your exact problem differently.** Convergent design (three repos reinventing the same gate without coordinating) is a stronger signal than any one of them alone.
|
|
||||||
8. **When you find a repo that's already flagged itself for cleanup, believe it and act — don't wait for the calendar date it names.**
|
|
||||||
9. **Write the comparison down somewhere durable, cross-link it to the actual work items it implies, and send fixes back to whoever you learned from.** A comparison that lives only in a chat transcript is a comparison that didn't happen.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What we sent back
|
|
||||||
|
|
||||||
Two of these findings went back to the RE-PoC's own tracker: a suggestion to pair their process-based rubber-stamp fix with a mechanical fallback, and a question about whether their governance-review LLM call needed a sovereignty check of its own. Owed, since half the value here came from reading their incident log in the first place.
|
|
||||||
|
|
||||||
*Everything referenced from my own stack above is tracked and cross-linked in the relevant repos' issue trackers as of this writing.*
|
|
||||||
+585
@@ -0,0 +1,585 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Mathias — Personal AI Platform</title>
|
||||||
|
<style>
|
||||||
|
|
||||||
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bg: #07090f;
|
||||||
|
--surface: #0d1120;
|
||||||
|
--surface-2: #121929;
|
||||||
|
--border: rgba(255,255,255,0.06);
|
||||||
|
--border-2: rgba(255,255,255,0.10);
|
||||||
|
--text: #dde3ee;
|
||||||
|
--text-muted: #526080;
|
||||||
|
--text-dim: #253248;
|
||||||
|
--accent: #0ea5e9;
|
||||||
|
--accent-soft: rgba(14,165,233,0.10);
|
||||||
|
--accent-glow: rgba(14,165,233,0.06);
|
||||||
|
--green: #22c55e;
|
||||||
|
--green-soft: rgba(34,197,94,0.10);
|
||||||
|
--radius: 14px;
|
||||||
|
--radius-sm: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html { scroll-behavior: smooth; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', system-ui, sans-serif;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.65;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body::before {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background-image:
|
||||||
|
radial-gradient(circle at 50% 0%, rgba(14,165,233,0.05) 0%, transparent 55%),
|
||||||
|
linear-gradient(rgba(255,255,255,0.015) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(255,255,255,0.015) 1px, transparent 1px);
|
||||||
|
background-size: 100% 100%, 44px 44px, 44px 44px;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
max-width: 1080px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 28px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reveal {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(22px);
|
||||||
|
transition: opacity 0.6s cubic-bezier(0.16,1,0.3,1), transform 0.6s cubic-bezier(0.16,1,0.3,1);
|
||||||
|
}
|
||||||
|
.reveal.visible { opacity: 1; transform: none; }
|
||||||
|
.reveal-delay-1 { transition-delay: 0.08s; }
|
||||||
|
.reveal-delay-2 { transition-delay: 0.16s; }
|
||||||
|
.reveal-delay-3 { transition-delay: 0.24s; }
|
||||||
|
.reveal-delay-4 { transition-delay: 0.32s; }
|
||||||
|
.reveal-delay-5 { transition-delay: 0.40s; }
|
||||||
|
.reveal-delay-6 { transition-delay: 0.48s; }
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
padding: 130px 0 110px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-eyebrow {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--accent);
|
||||||
|
margin-bottom: 36px;
|
||||||
|
opacity: 0;
|
||||||
|
animation: fadeUp 0.7s cubic-bezier(0.16,1,0.3,1) 0.1s forwards;
|
||||||
|
}
|
||||||
|
.hero-eyebrow::before {
|
||||||
|
content: '';
|
||||||
|
width: 24px;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--accent);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeUp {
|
||||||
|
from { opacity: 0; transform: translateY(16px); }
|
||||||
|
to { opacity: 1; transform: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-name {
|
||||||
|
font-size: clamp(64px, 9vw, 108px);
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.04em;
|
||||||
|
line-height: 0.95;
|
||||||
|
background: linear-gradient(145deg, #e8edf5 20%, #3d5a80 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
opacity: 0;
|
||||||
|
animation: fadeUp 0.7s cubic-bezier(0.16,1,0.3,1) 0.2s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-role {
|
||||||
|
font-size: 15px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: 32px;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
opacity: 0;
|
||||||
|
animation: fadeUp 0.7s cubic-bezier(0.16,1,0.3,1) 0.3s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-desc {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.75;
|
||||||
|
color: var(--text);
|
||||||
|
max-width: 540px;
|
||||||
|
margin-bottom: 44px;
|
||||||
|
opacity: 0;
|
||||||
|
animation: fadeUp 0.7s cubic-bezier(0.16,1,0.3,1) 0.4s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 0;
|
||||||
|
animation: fadeUp 0.7s cubic-bezier(0.16,1,0.3,1) 0.5s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 7px 16px;
|
||||||
|
border: 1px solid rgba(34,197,94,0.22);
|
||||||
|
border-radius: 100px;
|
||||||
|
font-size: 12.5px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--green);
|
||||||
|
background: var(--green-soft);
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--green);
|
||||||
|
flex-shrink: 0;
|
||||||
|
animation: statusPulse 2.8s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes statusPulse {
|
||||||
|
0%,100% { opacity: 1; box-shadow: 0 0 0 0 rgba(34,197,94,0.5); }
|
||||||
|
50% { opacity: 0.7; box-shadow: 0 0 0 5px rgba(34,197,94,0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip {
|
||||||
|
padding: 7px 16px;
|
||||||
|
border: 1px solid var(--border-2);
|
||||||
|
border-radius: 100px;
|
||||||
|
font-size: 12.5px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
section { padding: 96px 0; }
|
||||||
|
section + section { border-top: 1px solid var(--border); }
|
||||||
|
.section-header { margin-bottom: 52px; }
|
||||||
|
|
||||||
|
.section-label {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--accent);
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-heading {
|
||||||
|
font-size: clamp(26px, 4vw, 38px);
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
line-height: 1.15;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.use-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--border);
|
||||||
|
gap: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.use-card {
|
||||||
|
background: var(--surface);
|
||||||
|
padding: 36px 32px;
|
||||||
|
transition: background 0.18s ease;
|
||||||
|
}
|
||||||
|
.use-card:hover { background: var(--surface-2); }
|
||||||
|
|
||||||
|
.use-num {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
color: var(--text-dim);
|
||||||
|
margin-bottom: 22px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.use-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
color: var(--text);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.use-desc {
|
||||||
|
font-size: 13.5px;
|
||||||
|
line-height: 1.65;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arch-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arch-col {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arch-col-head {
|
||||||
|
padding: 18px 24px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
background: var(--surface);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--text-muted);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arch-col-head::before {
|
||||||
|
content: '';
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--text-dim);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arch-col.accent-col .arch-col-head::before { background: var(--accent); }
|
||||||
|
.arch-col.accent-col { border-color: rgba(14,165,233,0.15); }
|
||||||
|
.arch-body { background: var(--surface); }
|
||||||
|
|
||||||
|
.arch-row {
|
||||||
|
padding: 18px 24px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
.arch-row:last-child { border-bottom: none; }
|
||||||
|
.arch-row:hover { background: var(--surface-2); }
|
||||||
|
|
||||||
|
.arch-row-name {
|
||||||
|
font-size: 13.5px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text);
|
||||||
|
margin-bottom: 3px;
|
||||||
|
letter-spacing: -0.005em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arch-row-spec {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(6, 1fr);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--border);
|
||||||
|
gap: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat {
|
||||||
|
background: var(--surface);
|
||||||
|
padding: 36px 24px;
|
||||||
|
transition: background 0.18s;
|
||||||
|
}
|
||||||
|
.stat:hover { background: var(--surface-2); }
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 38px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.04em;
|
||||||
|
color: var(--text);
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value sup {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--accent);
|
||||||
|
vertical-align: super;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value .unit {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-muted);
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 12.5px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
padding: 36px 0;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-left {
|
||||||
|
font-size: 13.5px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-right {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.use-grid { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
.arch-grid { grid-template-columns: 1fr; gap: 14px; }
|
||||||
|
.stats-grid { grid-template-columns: repeat(3, 1fr); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.use-grid { grid-template-columns: 1fr; }
|
||||||
|
.stats-grid { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
.hero { padding: 90px 0 80px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="page">
|
||||||
|
|
||||||
|
<section class="hero">
|
||||||
|
<div class="hero-eyebrow">Personal AI Platform</div>
|
||||||
|
<h1 class="hero-name">Mathias</h1>
|
||||||
|
<p class="hero-role">Digital Product Manager & Technology Consultant · Sweden</p>
|
||||||
|
<p class="hero-desc">A self-hosted AI development environment — from bare metal to running agents. Built for privacy, production quality, and speed of iteration.</p>
|
||||||
|
<div class="hero-meta">
|
||||||
|
<span class="status-pill"><span class="status-dot"></span>All systems operational</span>
|
||||||
|
<span class="chip">Open source tooling</span>
|
||||||
|
<span class="chip">Gitea-native</span>
|
||||||
|
<span class="chip">No cloud lock-in</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="section-header reveal">
|
||||||
|
<div class="section-label">What it enables</div>
|
||||||
|
<h2 class="section-heading">Six capabilities.<br>One platform.</h2>
|
||||||
|
</div>
|
||||||
|
<div class="use-grid">
|
||||||
|
<div class="use-card reveal reveal-delay-1">
|
||||||
|
<div class="use-num">01</div>
|
||||||
|
<div class="use-title">Private AI Inference</div>
|
||||||
|
<div class="use-desc">GPU-accelerated local LLM serving with 27+ model routes. Data never leaves the network. Cloud APIs available as optional tier.</div>
|
||||||
|
</div>
|
||||||
|
<div class="use-card reveal reveal-delay-2">
|
||||||
|
<div class="use-num">02</div>
|
||||||
|
<div class="use-title">Agent Orchestration</div>
|
||||||
|
<div class="use-desc">Executor + reviewer loops for autonomous coding tasks. Risk-tiered tool middleware. Flat peer-to-peer architecture, not hierarchical.</div>
|
||||||
|
</div>
|
||||||
|
<div class="use-card reveal reveal-delay-3">
|
||||||
|
<div class="use-num">03</div>
|
||||||
|
<div class="use-title">Financial Automation</div>
|
||||||
|
<div class="use-desc">Invoice extraction, validation, and ISO 20022 payment generation. Fortnox integration with full audit trail and multi-tenant isolation.</div>
|
||||||
|
</div>
|
||||||
|
<div class="use-card reveal reveal-delay-4">
|
||||||
|
<div class="use-num">04</div>
|
||||||
|
<div class="use-title">Knowledge Management</div>
|
||||||
|
<div class="use-desc">Self-hosted brain with BM25 + vector hybrid retrieval. Persistent memory across sessions, machines, and AI harnesses.</div>
|
||||||
|
</div>
|
||||||
|
<div class="use-card reveal reveal-delay-5">
|
||||||
|
<div class="use-num">05</div>
|
||||||
|
<div class="use-title">Private Git & CI/CD</div>
|
||||||
|
<div class="use-desc">Gitea as canonical remote. Flux GitOps, Gitea Actions, buildah OCI builds. Full code workflow without GitHub dependency.</div>
|
||||||
|
</div>
|
||||||
|
<div class="use-card reveal reveal-delay-6">
|
||||||
|
<div class="use-num">06</div>
|
||||||
|
<div class="use-title">Full-Stack Development</div>
|
||||||
|
<div class="use-desc">Go + HTMX services from local dev to production Kubernetes pods — same day. Templates, scaffolding, and one-command deploys.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="section-header reveal">
|
||||||
|
<div class="section-label">How it's built</div>
|
||||||
|
<h2 class="section-heading">Hardware to protocol.</h2>
|
||||||
|
</div>
|
||||||
|
<div class="arch-grid">
|
||||||
|
|
||||||
|
<div class="arch-col reveal reveal-delay-1">
|
||||||
|
<div class="arch-col-head">Hardware</div>
|
||||||
|
<div class="arch-body">
|
||||||
|
<div class="arch-row">
|
||||||
|
<div class="arch-row-name">koala</div>
|
||||||
|
<div class="arch-row-spec">RTX 5070 · k3s cluster node · GPU inference · Tailscale hub</div>
|
||||||
|
</div>
|
||||||
|
<div class="arch-row">
|
||||||
|
<div class="arch-row-name">iguana</div>
|
||||||
|
<div class="arch-row-spec">M2 Ultra Mac · Services · Builds · Ollama models</div>
|
||||||
|
</div>
|
||||||
|
<div class="arch-row">
|
||||||
|
<div class="arch-row-name">flamingo</div>
|
||||||
|
<div class="arch-row-spec">Mac mini · Daily driver · Edge node · Claude Code</div>
|
||||||
|
</div>
|
||||||
|
<div class="arch-row">
|
||||||
|
<div class="arch-row-name">piguard</div>
|
||||||
|
<div class="arch-row-spec">Raspberry Pi · Network gateway · LiteLLM proxy</div>
|
||||||
|
</div>
|
||||||
|
<div class="arch-row">
|
||||||
|
<div class="arch-row-name">piblock</div>
|
||||||
|
<div class="arch-row-spec">Raspberry Pi · NAS · Restic off-host backup target</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="arch-col reveal reveal-delay-2">
|
||||||
|
<div class="arch-col-head">Platform</div>
|
||||||
|
<div class="arch-body">
|
||||||
|
<div class="arch-row">
|
||||||
|
<div class="arch-row-name">k3s + Flux</div>
|
||||||
|
<div class="arch-row-spec">Kubernetes on koala · GitOps reconciliation · all manifests in git</div>
|
||||||
|
</div>
|
||||||
|
<div class="arch-row">
|
||||||
|
<div class="arch-row-name">Tailscale mesh</div>
|
||||||
|
<div class="arch-row-spec">Zero-trust networking across all five machines · no VPN config</div>
|
||||||
|
</div>
|
||||||
|
<div class="arch-row">
|
||||||
|
<div class="arch-row-name">Observability</div>
|
||||||
|
<div class="arch-row-spec">Prometheus · Grafana · Jaeger distributed tracing · Loki</div>
|
||||||
|
</div>
|
||||||
|
<div class="arch-row">
|
||||||
|
<div class="arch-row-name">Identity & Secrets</div>
|
||||||
|
<div class="arch-row-spec">Dex OIDC · SOPS-encrypted manifests · k8s secretKeyRef</div>
|
||||||
|
</div>
|
||||||
|
<div class="arch-row">
|
||||||
|
<div class="arch-row-name">CI/CD</div>
|
||||||
|
<div class="arch-row-spec">Gitea Actions · buildah OCI builds · local registry · Flux apply</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="arch-col accent-col reveal reveal-delay-3">
|
||||||
|
<div class="arch-col-head">AI Stack</div>
|
||||||
|
<div class="arch-body">
|
||||||
|
<div class="arch-row">
|
||||||
|
<div class="arch-row-name">LiteLLM proxy</div>
|
||||||
|
<div class="arch-row-spec">27+ model routes · local + cloud · single unified API</div>
|
||||||
|
</div>
|
||||||
|
<div class="arch-row">
|
||||||
|
<div class="arch-row-name">llama-swap</div>
|
||||||
|
<div class="arch-row-spec">GPU model manager · hot-swap between slots · RTX 5070 offload</div>
|
||||||
|
</div>
|
||||||
|
<div class="arch-row">
|
||||||
|
<div class="arch-row-name">MCP protocol</div>
|
||||||
|
<div class="arch-row-spec">brain · gitea · infra · routing — Dex-authenticated servers</div>
|
||||||
|
</div>
|
||||||
|
<div class="arch-row">
|
||||||
|
<div class="arch-row-name">Brain KB</div>
|
||||||
|
<div class="arch-row-spec">BM25 + pgvector hybrid retrieval · LLM synthesis · 100+ entries</div>
|
||||||
|
</div>
|
||||||
|
<div class="arch-row">
|
||||||
|
<div class="arch-row-name">ADK agents</div>
|
||||||
|
<div class="arch-row-spec">Go ADK · kimi-k2.6 executor · gemma4-31b reviewer · OTLP traces</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="section-header reveal">
|
||||||
|
<div class="section-label">By the numbers</div>
|
||||||
|
<h2 class="section-heading">What's running.</h2>
|
||||||
|
</div>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat reveal reveal-delay-1">
|
||||||
|
<div class="stat-value">5</div>
|
||||||
|
<div class="stat-label">Machines on<br>Tailscale mesh</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat reveal reveal-delay-2">
|
||||||
|
<div class="stat-value">27<sup>+</sup></div>
|
||||||
|
<div class="stat-label">LLM routes<br>local + cloud</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat reveal reveal-delay-3">
|
||||||
|
<div class="stat-value">4</div>
|
||||||
|
<div class="stat-label">MCP servers<br>in production</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat reveal reveal-delay-4">
|
||||||
|
<div class="stat-value">12<span class="unit">GB</span></div>
|
||||||
|
<div class="stat-label">GPU VRAM for<br>local inference</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat reveal reveal-delay-5">
|
||||||
|
<div class="stat-value">100<sup>+</sup></div>
|
||||||
|
<div class="stat-label">Brain knowledge<br>entries</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat reveal reveal-delay-6">
|
||||||
|
<div class="stat-value">1<span class="unit">day</span></div>
|
||||||
|
<div class="stat-label">Idea to<br>production deploy</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="footer reveal">
|
||||||
|
<div class="footer-left">Mathias · Sweden</div>
|
||||||
|
<div class="footer-right">All source on Gitea · Built with Go, Kubernetes & curiosity</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var els = document.querySelectorAll('.reveal');
|
||||||
|
var io = new IntersectionObserver(function(entries) {
|
||||||
|
entries.forEach(function(e) {
|
||||||
|
if (e.isIntersecting) { e.target.classList.add('visible'); io.unobserve(e.target); }
|
||||||
|
});
|
||||||
|
}, { threshold: 0.08, rootMargin: '0px 0px -40px 0px' });
|
||||||
|
els.forEach(function(el) { io.observe(el); });
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user