Contents

Giving AI Agents a Second Brain: Declarative APIs, Secret Vaults, and a Trilium Memory Bridge in Pure Coni Lisp

We all know the tragedy of modern AI agents: they have the analytical reasoning of a senior principal engineer, but the long-term memory of a goldfish.

You sit down, have a brilliant 45-minute architectural brainstorming session with your local agent, build out a killer feature, shut down your laptop, and the next morning… poof. Amnesia. The terminal buffer is gone. The agent has no idea what you decided yesterday, why you chose that particular database schema, or which server holds your benchmark logs.

The tech industry’s standard “solution” to this problem is usually a grotesque contraption: spin up a 3GB Docker container with an opaque vector database, install 400MB of Python LangChain wrappers, chunk your personal notes into arbitrary 500-token embeddings, and pray that cosine similarity doesn’t hallucinate your grocery list into a production deployment script.

We wanted something dramatically simpler, more tactile, and fundamentally human:

What if our AI agents could just open our self-hosted personal knowledge base (Trilium Notes), read our notes, take their own notes, organize them into our hierarchical knowledge trees, and fetch historical context whenever they need it?

Today, we are releasing three new, razor-sharp libraries for the Coni language ecosystem that make this reality:

  1. libs/api: A data-driven, declarative HTTP API client engine.
  2. libs/secret: A multi-tier, zero-leak credential resolver with transparent NPKM Vault encryption.
  3. libs/trilium: A full-featured Trilium Notes ETAPI client with native AST markdown-to-HTML rendering.

Here is the story of how they work, how they fit together, and how you can use them to turn a self-hosted note server into a living, bi-directional memory bridge for autonomous AI agents.


Act I: Stop Writing HTTP Boilerplate in 2026 (libs/api)

If you look at how most programming languages interact with modern REST APIs, it is an endless wasteland of boilerplate:

# The depressing reality of standard REST code
def get_note(base_url, token, note_id):
    url = f"{base_url}/etapi/notes/{note_id}"
    headers = {"Authorization": token, "Content-Type": "application/json"}
    resp = requests.get(url, headers=headers)
    if resp.status_code != 200:
        raise Exception(...)
    return resp.json()

Multiply that by 30 endpoints, and you have hundreds of lines of repetitive string templating, error unpacking, and header management. If an endpoint changes from GET to POST or moves under /v2/, you have to rewrite function signatures everywhere.

In Coni, we took inspiration from data-driven Lisp philosophy: an API is not code; an API is pure data.

With libs/api, you define an entire web client as a single declarative dictionary of endpoint specs:

(require "libs/api/src/api.coni" :as api)

(def client
  (api/client
    {:base-url "https://notes.hellonico.info"
     :auth     {:token "my-secret-etapi-token"}
     :endpoints
     {:get-note    {:method :get  :path "/etapi/notes/:note-id"}
      :create-note {:method :post :path "/etapi/create-note"}
      :update-body {:method :put  :path "/etapi/notes/:note-id/content"
                    :content-type "text/plain"}}}))

To execute a request, you simply call (api/call client endpoint-key params body):

;; Fetch note metadata
(def note (api/call client :get-note {:note-id "n9Yb7kGsCPJz"}))

;; Update note content directly
(api/call client :update-body {:note-id "n9Yb7kGsCPJz"} "Updated text from Coni!")

The engine takes care of everything behind the scenes:

  • Automatically interpolates path parameters like :note-id into the URL.
  • Serializes leftover map keys into URL-encoded query strings (/etapi/notes?search=clojure).
  • Injects authentication headers (Authorization: <token>).
  • Auto-marshals JSON request and response bodies into native immutable Coni maps.
  • Supports custom :content-type overrides when endpoints require raw text or binary streams.

Want to add 15 more endpoints? You don’t write 15 functions—you just append 15 lines of data to the map.


Act II: Don’t Let Your Agent Leak Your Keys (libs/secret)

Every developer has experienced that cold sweat when accidentally running git status and realizing a raw API token was sitting in an automation script. When building autonomous AI agents that run scripts, edit files, and self-heal code, hardcoding credentials in files is practically begging for a disaster.

Enter libs/secret—a polite, zero-leak credential resolution tier for Coni applications.

Instead of hardcoding a token or relying on fragile one-off sys-getenv calls, secret/resolve checks a clean hierarchy of trust:

In your scripts and agents, you simply write:

(require "libs/secret/src/secret.coni" :as secret)

;; Resolves safely across Vault, dotfiles, and env vars
(def token (secret/resolve :trilium-token {:required true}))

If you store your token in ~/.trilium_token with chmod 600, or inside an encrypted NPKM Vault (libs/vault) protected with AES-256-GCM, your code runs seamlessly anywhere without exposing a single character of sensitive data in version control.


Act III: The Second Brain Bridge (libs/trilium)

Now comes the fun part: connecting Coni directly to Trilium Notes, a powerful, open-source hierarchical note-taking application designed for building personal knowledge bases.

Trilium provides an External API (ETAPI) that lets external programs interact with its entire database tree. We wrapped this into libs/trilium/src/trilium.coni, delivering a complete, idiomatic Lisp interface:

  • Notes: create-note, get-note, update-note, delete-note, get-note-content, update-note-content, search-notes
  • Branches & Structure: get-branch, create-branch, delete-branch, refresh-note-ordering
  • Attributes & Labels: get-attribute, create-attribute, set-label, set-relation
  • Attachments & Revisions: save-revision, create-attachment, get-attachment-content
  • Calendar & Inbox: calendar-day, calendar-week, calendar-month, inbox

Rich Markdown Conversion

Trilium’s rich-text notes expect HTML markup (<h1>, <p>, <blockquote>, <code>). But humans (and LLMs) think and write in Markdown.

To bridge this without pulling in bulky external converters, we built libs/trilium/src/markdown.coni—a lightweight AST-based markdown parser in pure Coni that transforms raw markdown headers, blockquotes, horizontal rules, lists, and inline styles into clean, compliant HTML:

(require "libs/trilium/src/trilium.coni" :as trilium)
(require "libs/secret/src/secret.coni" :as secret)

(def t (trilium/client "https://notes.hellonico.info" (secret/resolve :trilium-token)))

;; Upload a note with automatic title extraction & markdown conversion
(trilium/upload-markdown t "root" "Deep Work Philosophy" 
  "# Deep Work Philosophy\n\n> Focus is the new superpower.\n\nHere are the core rules...")

The Nightly Harvester in Action: Remote SSH to Knowledge Tree

Let’s look at a real-world scenario we ran today.

On a remote workstation named niko-s5, there was an essay sitting in the home directory: yepicurus_essay.md (a brilliant reflection titled “You Must Break The Curse of Becoming Your Job”).

We wanted our local system to:

  1. Connect to niko-s5 over SSH.
  2. Read the markdown file.
  3. Automatically parse its top-level header as the note title.
  4. Convert the markdown to rich HTML.
  5. Push it as a new note under Trilium’s root tree.
  6. Tag it with structured labels (category=essay).

We wrote a reusable CLI utility: libs/trilium/bin/upload_md.coni.

Running it takes a single, joyful command:

$ ./coni libs/trilium/bin/upload_md.coni yepicurus_essay.md --ssh niko-s5 --label category=essay

Uploading to: https://notes.hellonico.info (parent: root)
Title: You Must Break The Curse of Becoming Your Job
✓ Note created successfully!
  Note ID: n9Yb7kGsCPJz
  Title:   You Must Break The Curse of Becoming Your Job
  URL:     https://notes.hellonico.info/#root/n9Yb7kGsCPJz
✓ Attached label: category=essay

Within 300 milliseconds, the remote file was fetched, converted, uploaded, tagged, and live on our server at https://notes.hellonico.info/#root/n9Yb7kGsCPJz.

You can also use standard scp-style syntax:

./coni libs/trilium/bin/upload_md.coni niko-s5:~/drafts/neural-networks.md --parent myProjectsFolderId

Bi-Directional Memory: How AI Agents Push and Pull Knowledge at Will

Now step back and consider what this means for autonomous AI agents (like our Coni multi-agent swarms or conigravity).

Instead of an agent operating in an isolated sandbox, we now have a bi-directional knowledge pipeline:

1. Pushing Information (Agent ➔ Second Brain)

When an agent finishes an autonomous coding sprint, runs an intensive benchmark across GPU clusters, or synthesizes a long research topic, it doesn’t just spew 500 lines of raw text into a terminal that scrolls out of view.

It can call:

(defn save-agent-report [agent-id task-summary markdown-body]
  (let [client   (trilium/client (secret/resolve :trilium-server) (secret/resolve :trilium-token))
        res      (trilium/upload-markdown client "aiReportsFolder" task-summary markdown-body)
        note-id  (get-in res [:note :noteId])]
    (trilium/set-label client note-id "source" "conigravity")
    (trilium/set-label client note-id "agent" agent-id)
    (trilium/set-label client note-id "date" (date-now-iso))))

Instantly, the agent’s work is organized, searchable, formatted, and permanently archived in your personal knowledge base.

2. Pulling Information (Second Brain ➔ Agent)

Conversely, when an agent wakes up to tackle a task, it doesn’t need to ask you 10 questions about your project conventions. It can simply introspect your notes:

(defn get-project-context [project-name]
  (let [client  (trilium/client (secret/resolve :trilium-server) (secret/resolve :trilium-token))
        matches (trilium/search-notes client (str "#project=" project-name))]
    (map (fn [n] 
           {:title   (:title n)
            :content (trilium/get-note-content client (:noteId n))})
         matches)))

The agent fetches your actual architecture specs, coding style guides, and design decisions directly from your notes, injecting relevant context into its prompt dynamically.

3. Human-in-the-Loop Collaboration

Because Trilium has slick desktop and mobile web interfaces, this creates a wonderfully natural collaboration loop:

  • On your phone while having morning coffee: You type three bullet points into Trilium’s daily journal under today’s date:
    • Benchmark FP8 precision on Apple Metal vs ROCm
    • Fix under-arity bug in evaluator variadics
    • Deploy test build to staging server
  • In the background: A Coni background watcher reads (trilium/calendar-day client today). It picks up the tasks, kicks off the jobs on the remote machines, collects the metrics, and appends the results directly back to the note with child sub-notes.
  • When you open your laptop: Everything is done, organized, and waiting for you.

Why Coni?

Could you do this in Python? Sure—after installing 45 pip packages, debugging a virtual environment conflict, and configuring 15 environment variables.

Could you do this in Bash? Sure—if you enjoy parsing nested JSON using horrific jq pipelines and escaping single-quotes inside SSH commands until your eyes bleed.

In Coni, the entire stack—the HTTP API framework, the secret resolver, the ETAPI client, the AST markdown parser, and the CLI tool—is written in clean, pure, functional Clojure-like syntax, running on a statically compiled Go runtime with zero external dependencies.

Here is what the unit test suite looks like:

$ ./coni test libs/api/tests/api_test.coni libs/trilium/tests/trilium_test.coni libs/secret/tests/secret_test.coni

=========================================================================================
                              ⬡ CONI TEST RESULTS                              
=========================================================================================
File                                 | Tests  | Pass   | Fail   | Time (ms)
-----------------------------------------------------------------------------------------
libs/api/tests/api_test.coni         | 6      | 23     | 0      | 3        
libs/trilium/tests/trilium_test.coni | 3      | 60     | 0      | 288      
libs/secret/tests/secret_test.coni   | 4      | 9      | 0      | 70       
-----------------------------------------------------------------------------------------
  Total Executed : 3 files, 13 tests
  Assertions     : 92 passes, 0 failures
  Duration       : 361ms
=========================================================================================
 ✓ ALL TESTS PASSED

92 assertions executed in 361 milliseconds. Clean. Fast. Dependable.


Try It Out

All three libraries and the upload CLI tool are part of the Coni open-source repository:

Stop letting your AI agents forget everything the moment you leave your desk. Give them a second brain, wire them up to your notes, and let them think alongside you.