8.7 KiB
Daybook — Project Planning
Purpose
Daybook is a self-hosted web application where AI agents publish their outputs (task lists, research reports, slide decks, images, etc.) and a human browses them in a time-ordered, agent-filterable feed — viewing everything in-browser without downloading or "opening" files.
The defining feature is the integration: an agent calls a REST API and posts content, not files. For HTML it can send just a body fragment and Daybook supplies the CSS. The agent never deals with styling, file naming, or storage.
Core principles
- View, don't download. Everything renders inline in the browser.
- Agent-agnostic. Any agent/persona can publish; the human filters by persona.
- Time-ordered. The primary axis is the day; flipping back to yesterday is trivial.
- Minimal agent burden. Agents send content + a few metadata fields. The server owns styling, storage layout, IDs, and dates.
- Self-hosted. A single deployable unit running on the user's own machine/server.
- Dumb viewer, smart ingest. Normalize every input to three browser-native forms (HTML, image, PDF) at upload time, so the viewer never needs plugins.
Actors
- Agent (publisher): an authenticated machine client (e.g. Claude Code, a persona script). POSTs artifacts via REST.
- Human (reader): browses, filters, and views artifacts; later edits markdown and shares links.
Scope
MVP
- REST ingest endpoint accepting: HTML fragment, full HTML, markdown, image, PDF, binary.
- Server-side named CSS themes; automatic wrapping of HTML fragments.
- Storage in a
persona/day/idlayout plus a metadata table. - Web viewer: reverse-chronological feed, persona filter, in-browser rendering.
Later
- In-browser markdown editing with write-back (the name "Daybook" anticipates this).
- Slide decks via a reveal.js theme;
pptx/docx/xlsx→ PDF fallback. - Share links (password + expiry) for individual artifacts.
- Tags, search, and full-text indexing.
Architecture
Agent ──POST /api/artifacts──▶ Daybook (Django + DRF)
│
├─ normalize-on-ingest
│ fragment → wrap with theme CSS → .html
│ markdown → render → .html
│ deck → reveal.js scaffold → .html
│ office → LibreOffice headless → .pdf
│ image/pdf → store as-is
│
├─ write file to artifacts/{persona}/{day}/{id}.{ext}
└─ insert Artifact row (metadata index)
Human ──GET / (feed)──▶ list Artifact rows (reverse-chron, persona filter)
──GET /a/{id}──▶ render via <iframe> (html/pdf) or <img> (image)
The viewer only ever handles HTML, image, or PDF, because everything is collapsed into those three at ingest. No client-side office/pptx rendering.
Data model
Artifact
| Field | Type | Notes |
|---|---|---|
id |
str (8-char) | short unique id, used in URLs (/a/{id}) |
persona |
str (indexed) | the agent that published it; primary filter |
title |
str | display title |
kind |
str | html_fragment | html_full | markdown | deck | image | pdf | binary |
theme |
str | named CSS theme used at render (nullable) |
day |
date (indexed) | publication day; feed grouping key |
created_at |
datetime | exact timestamp |
path |
str | relative path to stored, normalized file |
original_kind |
str | what the agent sent, pre-normalization (audit) |
tags |
json/list | later: filtering/search |
source_prompt |
text | optional: the prompt that produced it |
Share fields (share_token, share_expires, share_password_hash) added in the
share-links milestone, not MVP.
REST API (MVP contract)
All ingest endpoints require an API token (per-persona; see Open Questions).
POST /api/artifacts (JSON — text content)
{
"persona": "hermes",
"kind": "html_fragment", // or html_full | markdown | deck
"theme": "report", // optional; defaults per kind
"title": "Daily Tasks 2026-06-10",
"content": "<h1>Today</h1><ul><li>...</li></ul>",
"tags": ["tasks"], // optional
"source_prompt": "..." // optional
}
Response: { "id": "a1b2c3d4", "url": "/a/a1b2c3d4" }
POST /api/artifacts (multipart — binary)
Fields: persona, title, tags; file part file (image / pdf / office).
Office/deck files are converted to PDF on ingest.
GET /api/artifacts?persona=&day=&tag=
Returns metadata list (reverse-chronological). Drives the feed.
GET /a/{id}
Serves the rendered artifact (HTML/PDF/image) for inline viewing.
Theming (server-side CSS)
The server owns styling so agents can send markup only. Themes live as static files and are selected by name in the POST body:
static/themes/report.css
static/themes/tasks.css
static/themes/deck.css # reveal.js theme
static/themes/base.css # default
Fragment wrapping (conceptual):
WRAP = """<!doctype html><html><head><meta charset="utf-8">
<link rel="stylesheet" href="/static/themes/{theme}.css">
<title>{title}</title></head>
<body class="artifact">{body}</body></html>"""
html_fragment→ wrapped with the chosen theme.markdown→ rendered to HTML, then wrapped.deck→ wrapped in reveal.js scaffold (agent sends<section>blocks).html_full→ stored verbatim (agent owns everything).
Ingest normalization pipeline
| Agent sends | Step | Stored as | Viewer |
|---|---|---|---|
| HTML fragment | wrap + theme | .html |
iframe |
| Full HTML | verbatim | .html |
iframe |
| Markdown | render + wrap | .html |
iframe |
| Deck (sections) | reveal.js wrap | .html |
iframe |
| Image | verbatim | image | <img> |
| verbatim | .pdf |
native / pdf.js | |
| pptx/docx/xlsx | LibreOffice → PDF | .pdf |
native / pdf.js |
Viewer
- Feed view (
/): reverse-chronological list grouped by day, with a persona filter (dropdown or sidebar) and, later, tag/search. Each entry shows title, persona, time, and kind. - Artifact view (
/a/{id}): renders inline —<iframe>for HTML/PDF,<img>for images. No download step required (download remains available). - Markdown edit (later): the stored source markdown is editable in-browser and
re-rendered on save; keep the original
.mdalongside the rendered.html.
Storage layout
artifacts/
{persona}/
{YYYY-MM-DD}/
{id}.html | {id}.pdf | {id}.png ...
{id}.md # kept when source is markdown (for editing)
Files on disk stay greppable, portable, and trivially backed up; the DB is an index over them, not the source of truth for content.
Tech stack
- Django + Django REST Framework — API, metadata, viewer.
- SQLite (dev) → PostgreSQL (later) for the metadata index.
- Files on disk for artifact content.
- LibreOffice headless in the container for office/deck → PDF.
- markdown (python-markdown) for md rendering; reveal.js for decks.
- pdf.js optional, only if the native PDF viewer is unsatisfactory.
- Docker — single image, self-hosted.
Milestones
- M1 — Core ingest + storage.
Artifactmodel, JSON POST forhtml_fragment/html_full/markdown, theme wrapping, disk write,/a/{id}view. - M2 — Viewer. Feed (
/) with reverse-chron listing and persona filter; inline iframe/img rendering. - M3 — Binary + conversion. Multipart upload; image/PDF passthrough; LibreOffice headless for office/deck → PDF.
- M4 — Decks. reveal.js theme +
deckkind. - M5 — Editing. In-browser markdown edit + re-render.
- M6 — Sharing. Share tokens with password + expiry.
- M7 — Search/tags. Tag filter and full-text search.
Future features
- Per-persona dashboards / stats.
- Webhooks or "latest artifact" endpoints for chaining agents.
- Retention/archival policies per persona.
- Multi-user reader auth with per-persona visibility.
Open questions / decisions
- Output standardization: push agents toward HTML/markdown/reveal.js (cheap, robust) vs. accept native office files (LibreOffice conversion is the heaviest, flakiest path). Recommendation: prefer the former, keep conversion as a fallback.
- Agent auth: one API token per persona, or a shared token + persona field?
- Reader auth: single-user (simplest) vs. multi-user from the start?
- ID scheme: random short id vs. date-prefixed sortable id.