T3 ships more than one product, so the mark had to hold as t3.chat, as t3.code, and on its own in a favicon or an app grid. Nobody commissioned it. I drew it between March and May 2026 and posted the versions as they came, first in the T3 Discord and then as a thread on X. The community, @r_marked, and @theo told me which ones were working.
Hundreds of versions, in public
There are hundreds of drawings behind this mark. Most went up the day I drew them. Most are wrong. Drawing the wrong shape is how I find the right one, and doing that in a channel full of people means someone tells me within the hour.
The T and the 3 want to be one figure instead of two characters standing next to each other. The crossbar of the T carries into the 3, so the pair shares a spine. Finding that meant testing how far the 3 could open, how much the terminals could shear, and how much of the T could go missing before the mark stopped reading as T3.
Feedback in the open
The first thing I posted was the system board. A mark that ships across a family has to be drawn where it gets used, so I built the cases out: the two logotypes, the iOS icon on its grid, the favicon ladder down to sixteen pixels, and the lockup on its clear-space frame. The angular version below is the one the server started calling the meat grinder.
On 19 March 2026 I posted the application board in the T3 Discord and asked for thoughts.
markr came back with the note that set up the next two months: "It still feels like there is a bit of excess space between the glyphs, I can't quite put my finger on why though." I asked whether he meant the mark or the type. The mark, he said, the t and the 3.
A week later I brought the mark back heavier, on a brighter palette. markr: "Feels very heavy now, which compared to the rest of the products, feels out of place. I do like the idea with the sloping 3 though to get rid of that bug dead space." Someone else said it reminded them of a racing car logo.
I posted a thinner version the same evening and asked whether that killed the racing car. It moved the problem instead of solving it: pushing the 3 away reintroduced the gap, and not connecting the two made the mark look heavily unproportional. markr relayed what Theo had said about the iteration: not sold on the weights, or on the shape of the 3.
By 31 March I was posting frames nine at a time. One reply read the fourth and the last as Ws, which is what happens when the crossbar shears too far. markr thought the direction was closer to right, since Theo had not liked the angles in the old logo, so the fix for one problem kept reopening another. "Mixed feedback on lots of them makes me go into all kinds of directions," I wrote. markr: "design by committee".
On 5 May the angled 3 got its verdict, secondhand: "I also like the angled 3, but Theo didn't 😭"
Some passes went angular and mechanical, with the bowl of the 3 squared off into facets.
Color came last
Pink belongs to T3, but the mark had to survive without it. Every candidate was judged in white on black first and only then given the palette.
Refining the drawing
The last pass is invisible at a glance. I thinned the joint between the crossbar and the bowl, pulled the bowl tighter, and squared the terminals so the mark keeps its rhythm at favicon size.
On 13 May I posted the two curves side by side. markr: "I feel insane but those changes made a difference to my perception...."
T3 has not adopted any of this. Every version is still public.
A local document agent that answers from your files and shows its work
Heph is a document agent that runs on your machine. Point it at a folder and ask a question. It places the passages used for the answer beside the response. The application has an interactive terminal and a one-shot command; other tools can use its JSONL service.
I built Heph because general assistants were unreliable when I used them to study. A plausible mistake could take longer to find than the original answer saved. I wanted a tool that treated my files as the source and exposed enough evidence for me to check each answer quickly.
The demo shows the actual interaction model. The active armory, selected model, reasoning level, answer, and retrieved evidence remain visible during the conversation. Other commands are available through the command palette and slash routes.
Heph requires Python 3.13 and is managed as a five-package uv workspace. Textual and Rich provide the terminal foundation. There is one install and no optional extras: retrieval is lexical, document conversion is written against the file formats directly, and credentials can be stored through the operating system keyring. The full install is 43 packages and about 46 MB, with no machine-learning runtime, no CUDA, and no model downloads. A local llama.cpp model runs through a managed binary rather than a Python ML stack, so choosing local inference does not change the dependency graph.
Armories
An armory is a normal folder for one subject. It contains the source material for that subject and its own index, memory, chat history, and diagnostics. A biology armory, for example, can be opened with heph Biology after its files are placed in the materials directory.
The layout stays inspectable on disk:
docs/index.md: Armory layout
~/.armories/[name]/
├── materials/ # PDFs, Office docs, notes, code to cite
│ ├── [file].pdf
│ └── [file].md
├── .harness/ # Local Heph state
│ ├── armory.toml # Armory marker
│ ├── rag_index.json # Retrieval index
│ ├── memory.json # Armory memory
│ ├── chats/ # Saved sessions
│ ├── traces/ # JSONL traces when enabled
│ ├── usage/ # Token and cost snapshots
│ └── ignore # Indexing ignore rules
└── README.md # Armory notes
Each armory has a separate local index. When a hosted model is selected, Heph sends that provider the active question, its instructions, and the retrieved passages needed for the answer. A local llama.cpp model keeps those prompts and passages on the machine.
Retrieval
The pipeline around BM25 is mine: document conversion, chunking, indexing, query changes, ranking, fallback behavior, and the source mapping used by citations.
Markdown is split by heading so its section structure survives indexing. Other text uses fixed-window chunking. Office and OpenDocument files are ZIP containers of XML, so .docx, .pptx, .xlsx, .odt, and .ods are read directly with the standard library and a hardened XML parser, under limits on member count, declared size, and output size, and with archive member paths rejected if they escape the container. PDF extraction uses pdftotext when it is installed and a bundled PDFium build otherwise. Formats that cannot be read faithfully are reported with the conversion target instead of being indexed as empty. A failed document is isolated instead of stopping the full index.
Each chunk records its source path and character offsets. Markdown chunks also retain the nearest heading. Indexing hashes the source files and only processes files that changed. The index is stored as JSON. Indexing also rejects path traversal and refuses to follow symlinks out of the armory.
A query passes through the available retrieval stages before any context reaches the model:
Retrieval pipeline
query
→ normalize + expand
→ BM25
→ TF-IDF
→ rank fusion
→ feedback (optional)
→ source + quote + negation checks
→ top-k chunks
Retrieval is lexical. BM25 and TF-IDF are both implemented against the standard library, and weighted reciprocal-rank fusion combines them. Earlier versions reached dense retrieval and cross-encoder reranking through sentence-transformers, which pulled Torch, CUDA, and a set of native document dependencies into every install. That cost was not proportionate for a tool meant to sit on a personal machine, so those stages were removed rather than made optional. A request for a stage that no longer exists fails with that reason instead of silently returning lexical results.
Post-processing can expand a query with a small synonym set, apply pseudo-relevance feedback, favor quoted phrases, use source-path hints, and penalize results that conflict with negated terms.
Evidence
Evidence is a typed object with an ID that lasts for one turn. After retrieval, Heph favors distinct sources and applies the context budget. It then assigns IDs in prompt order such as E1 and E2. The model cites those IDs. A verification pass checks the reply against the exact evidence objects supplied for that turn.
The verifier distinguishes valid citations from invented IDs. It also detects a grounded answer that omitted its citations and an answer produced without evidence. Opening a citation maps the stored character offsets back to line spans in the original file and shows the matching excerpt. Absolute paths and paths outside the armory are rejected during that lookup.
Package boundaries
The workspace contains ai, extensions, heph, harness, and interfaces. Import-linter contracts enforce the dependency direction:
pyproject.toml: [tool.importlinter]
[tool.importlinter]
root_packages = ["ai", "extensions", "heph", "harness", "interfaces"]
exclude_type_checking_imports = true
include_external_packages = true
[[tool.importlinter.contracts]]
name = "AI must stay below Heph, the harness, extensions, and interfaces"
type = "forbidden"
source_modules = ["ai"]
forbidden_modules = [
"extensions",
"heph",
"harness",
"interfaces",
]
The ai runtime cannot import the application, retrieval harness, extension layer, or interface. Retrieval cannot import chat, agent, or document adapter code. Material handling cannot import retrieval. The interface cannot import application composition, and command modules cannot reach into TUI internals. CI runs the contracts.
Hosted APIs and local models
Heph supports Pollinations, OpenRouter, OpenAI, DeepSeek, Z.AI, local llama.cpp, and custom OpenAI-compatible endpoints. Each is a provider configuration you choose between. Hosted credentials are resolved when needed from provider references, environment variables, or keyring storage.
The runtime normalizes streaming output and tool calls, records usage, shapes prompt-cache fields, retries eligible failures with exponential backoff, and opens a circuit breaker after repeated provider errors. If a stream fails before any output appears, it can be retried. If the connection fails after text is already visible, StreamRecoveryError carries that partial response so the interface can preserve it.
heph local searches curated GGUF releases, installs a chosen model, manages a loopback-only llama.cpp server, and runs a tool-call probe. A downloaded model appears in the model picker only after it returns a valid tool call with valid JSON arguments. Failed models remain available for later revalidation.
Terminal details
The interface is built on Textual, with a small runtime patch for modified key sequences sent by tmux and xterm. I replaced the standard input behavior to support multiline editing and shell-style word deletion.
I also added text selection across transcript and input widgets. Transparent rendering subclasses prevent the compositor from painting opaque backgrounds behind panels that should remain clear. A semantic color palette supplies the terminal's dark and light themes.
Safety boundaries
Armory files are untrusted input. Memory entries have size and confidence limits, and filters check them for invisible Unicode, prompt-injection patterns, and secret-exfiltration patterns. Document workers are bounded so a hostile file cannot consume unlimited memory or keep the process alive indefinitely. Source mapping and session identifiers reject unsafe paths.
A separate attempts policy reviews each answer. It can accept the result, abstain when evidence or safety requirements are not met, or retry under a stricter grounding rule. It can also request another source when one document dominates the evidence.
Repository checks
The repository uses Ruff, the ty type checker, import-linter, Bandit, Vulture, dependency checks, Pylint duplicate-code checks, and Radon complexity checks. Pytest runs with a configured coverage floor. CI also verifies generated documentation and release state, and the repository includes a runbook for failed checks.
Command surfaces
Running heph <armory> opens the terminal application. The CLI can create an armory, list or index materials, inspect index health, and manage local models. heph chat ask handles one-shot questions, including JSONL output, while heph sdk serve exposes the stdio service.
Inside the TUI, slash routes open evidence, models, armories, memory, sessions, settings, materials, and the editable keymap. These routes belong to the terminal interface; they are not separate shell commands. The repository's CLI reference lists the complete current command set.
Identity work
After the agent was working, I began drawing a custom typeface for a graphical interface I was considering. The terminal would keep its monospaced face, while the typeface, logo, and wordmark would share the same drawing decisions.
I worked through the character set slowly, checking individual curves and the spacing between letters.
Those drawings produced the current Heph lockup.
Next work
Heph is in beta. My next task is better retrieval on messy collections. I also want faster indexing and a shorter path from a citation to its source passage. The provider layer can change without altering the armory format or evidence IDs.
I use Heph for my own document work every day.
GitHub repository
A self-initiated brand system for Filen
I have used Filen for years. The product worked well for me, but its logo never did. I often redesign things I use, so I gave myself a full identity brief: a mark, wordmark, app icon, pattern language, and campaign copy. Filen did not commission the work.
Trying ideas
The first rounds were broad. I tried a disappearing block that could also read as an F, but the letter depended too much on explanation.
Next came a folder drawn in perspective, its open edge doing the work of an F.
I also drew a heavier, architectural symbol based on stairs. Closing the staircase made it feel stable, but added too much detail for a small app icon. Another version used stacked blocks with the upper layer hidden. Neither survived the scale tests.
I then tried a pangolin mascot. Its overlapping scales made sense as a metaphor for protection, and all eight pangolin species are threatened with extinction. The animal also offered a pattern language. The problem was the face: every attempt to make it distinctive made the mark harder to read.
The patterns stayed
The mascot went, but the repeated scales were worth developing.
I stopped drawing the scales literally and kept the repetition. Vertical panels could hide or reveal parts of the mark as light crossed them. That gave the identity a visual link to privacy without forcing a lock or shield into the logo.
For the mark itself, the angled folder remained the clearest option. It reads as storage first and reveals the F on a second look.
Making it work
After choosing the mark, I reduced it at several sizes and built an app icon in Icon Composer.
The light studies turned the repeated panels into a usable image system.
I also tested a red colorway. In the campaign layouts, red shifted the message from protection toward warning and danger. The monochrome version kept the emphasis on privacy and control, so I dropped the red.
The final lockup pairs the folder mark with a plain wordmark. The campaign copy states the privacy benefit directly, while the panel imagery carries the ideas of concealment and controlled access.
I posted the concept and shared it with the Filen community in February 2026. Forms only surface when the light crosses them.
The site you are on is the case study
This repository is both the portfolio and its proof. Authored Markdown, shared HTML partials, route CSS and JavaScript, media, generated pages, machine-readable mirrors, and verification scripts live together so a source change can be rebuilt and checked from one checkout. You can read the source code.
Built from source
The package exposes one build command: npm run build. It runs node scripts/build-page.mjs, verifies the public agent-discovery contracts, and prepares the Cloudflare Pages output.
scripts/site-config.mjs is the route registry. It defines the homepage bundle, eight case studies - Filen, Heph, Ben Davis, T3, mL7, n0thing, CURVES, and gildrb.com - and the /all bundle. scripts/build-page.mjs resolves HTML includes, renders the Markdown, bundles route-specific CSS and JavaScript, inlines the profile JSON-LD, and writes the homepage, /all, all eight case pages, profile.json, and llms-full.txt.
scripts/prepare-cloudflare-output.mjs copies the publishable tree to public/, enforces the homepage byte budget, and rejects render gates, unused font preloads, or superseded entry behavior. Cloudflare Pages Functions provide Markdown negotiation and the public API and MCP routes.
The page system
The homepage is one sortable Date / Project / Scope / All table. Case rows are complete links, and the default order is newest first. The /all route derives its articles from those homepage rows, pins the homepage's own site entry last in its default order, and keeps explicit sort requests available through query parameters. Case pages receive a generated View next table from the same row data, so project metadata is not maintained in a second registry.
The eight case routes are /filen, /heph, /ben-davis, /t3, /ml7, /n0thing, /curves, and /site. Each route has a structural template, authored Markdown, shared or route-specific bundles, responsive media partials, and generated HTML. The case-study Markdown sources are also published at /content/<project>.md.
The design rules
The palette has one background token and three text colors. A separate pair controls selected text. Light and dark themes change those token values without introducing another palette.
Layout values are named in CSS. The main article column is 760px wide on desktop. It sits beside a 240px sidebar with a 48px gap. A 6px token handles compact link stacks; larger separations use 24px, 32px, 48px, or 80px according to the relationship between elements. Case titles are 28/36 on desktop and 24/32 on mobile. Body copy is 16/24, while captions and code labels use 14/20. Inter Variable is self-hosted for interface text and Ioskeley Mono is used for code.
Links and controls use the gray text tokens at rest and move to the primary text color on direct hover. Keyboard focus uses a visible ring. The Heph demo has a reduced-motion mode; routine controls change state without decorative animation.
src/styles/10-base.css
:root {
--bg: #000000;
--text-primary: #ffffff;
--text-secondary: #b3b3b3;
--text-tertiary: #767676;
--highlight-bg: #b3b3b3;
--highlight-text: #ffffff;
--section-gap: 24px;
--section-content-gap: 6px;
--text-media-gap: 32px;
--link-line-height: 24px;
--theme-toggle-size: 32px;
--theme-toggle-optical-offset: 2px;
--footer-stack-bottom-gap: 4px;
--footer-title-optical-offset: 4px;
--sidebar-column: 240px;
--content-column: 760px;
--layout-gap: 48px;
--media-radius: 22px;
}
Verification
node scripts/verify-page.mjs builds every route in memory with write: false, then compares the result with the committed homepage, case pages, profile.json, and llms-full.txt. It also checks generated View next tables, route order, metadata, JSON-LD, link roles, missing asset references, unreferenced image files, design tokens, homepage entry behavior, the Heph demo contract, and discovery metadata.
node scripts/check-public.mjs checks sensitive path names and privacy-pattern matches in the working tree and reachable history, then runs gitleaks against both the directory and Git history.
node scripts/verify-crawlability.mjs checks 27 public routes with five crawler user agents. It verifies status, canonical URLs, content types, minimum body sizes, Content-Signal headers, cache behavior, indexability, discovery links, robots policy, and sitemap coverage. The checked set includes the homepage, /all, the eight case routes, eight Markdown source routes, and the discovery files.
The Heph demo
The terminal on the Heph case study is a simulation written in vanilla JavaScript. It plays one retrieval sequence and opens cited evidence. The keyboard controls shown in the interface also work. Reduced-motion users receive the completed state without the timed playback.
Machine-readable routes
The site publishes a Schema.org identity graph, WebFinger records, host metadata, an llms.txt reference, the generated llms-full.txt export, an RSS feed, a humans.txt file, and a sitemap. The homepage and every case page advertise the relevant Markdown, full-text, profile, and identity references. Its Content-Signal header permits search, AI input, and AI training.
Delivery and access
Functional CSS and JavaScript are inlined by route. The homepage preloads only its self-hosted interface font, and responsive image sets let the browser choose an appropriate file for the viewport. The HTML uses landmarks, live regions, visible keyboard focus, and reduced-motion handling. Cloudflare Pages serves the static output, Functions provide content negotiation and public agent endpoints, and _headers, _redirects, and _routes.json define delivery policy.
The page above is generated by the same system it describes.