260 lines
12 KiB
Markdown
260 lines
12 KiB
Markdown
# Daybook — LLM Posting Skill: Design & Next Steps
|
|
|
|
**Status:** planning / pre-implementation
|
|
**Date:** 2026-06-10
|
|
**Author:** drafted by Claude (Opus 4.8) after a live smoke test of the deployed instance
|
|
|
|
## What This Document Is
|
|
|
|
A consolidated design recommendation for making **publishing to Daybook trivially
|
|
easy for LLM agents**, plus notes on missing functionality surfaced during testing.
|
|
Goal: an agent should be able to publish a correct artifact out of the box via a
|
|
single command, without hand-crafting HTTP, JSON, or multipart requests.
|
|
|
|
## What Was Validated (Live Smoke Test)
|
|
|
|
Tested against the prod instance `http://192.168.1.36:8000` via desktop-commander
|
|
(the sandbox cannot reach the `192.168.1.x` LAN, so all calls were driven on-host).
|
|
|
|
All seven artifact kinds were posted and verified under persona `claude`:
|
|
|
|
| Kind | Path | Result |
|
|
|---|---|---|
|
|
| markdown | JSON | OK (`2db087a7`) |
|
|
| html_full | JSON | OK (`8cd79771`) |
|
|
| deck (markdown — WRONG) | JSON | stored but malformed (`5df27410`) |
|
|
| image (svg) | multipart | OK (`83ab938c`) |
|
|
| pdf | multipart | OK (`8c82b15c`) |
|
|
| binary (csv) | multipart | OK, download-only (`2cceceaf`) |
|
|
| deck (`<section>` blocks) | JSON | OK (`60d26e5a`) |
|
|
| html_fragment | JSON | OK (`fd42816f`) |
|
|
|
|
Confirmed working: capabilities endpoint, bearer auth, content-type inference
|
|
(svg→image, pdf→pdf, csv→binary), persona/tag/kind filtering, read-back by id.
|
|
|
|
## The Core Problem To Design Around
|
|
|
|
Every failure during testing was the same class of problem: **an LLM
|
|
hand-assembling a request.** Three concrete failure modes, all observed:
|
|
|
|
1. **Inline content in shell commands breaks repeatedly.** `powershell -Command`
|
|
mangled `$`-expansion, nested quotes, and backtick newlines on three separate
|
|
attempts. This is not incidental — every model re-derives the shell incantation
|
|
and gets it wrong. It is the single biggest source of friction.
|
|
2. **Multipart is environment-dependent.** Windows PowerShell 5.1 has no
|
|
`Invoke-RestMethod -Form`; it needs `curl.exe` or PowerShell 7+. An agent cannot
|
|
reliably know which host it is running on.
|
|
3. **Per-kind structure rules are easy to violate.** Markdown was sent to `deck`
|
|
and rendered malformed — precisely because request-building is where judgment
|
|
leaks in. The docs' stance is "Daybook does not fix malformed content," so the
|
|
client must guarantee structure instead.
|
|
|
|
**Design conclusion:** the skill must NOT teach the model how to build the request.
|
|
It must give the model ONE command to run. All fiddliness (auth, JSON-vs-multipart,
|
|
content-type, escaping, kind validation) belongs inside a helper, not in the prompt.
|
|
|
|
## Recommended Approach
|
|
|
|
A **bundled CLI helper + a thin SKILL.md**.
|
|
|
|
- The skill folder ships a single **zero-dependency Python script**
|
|
(`scripts/daybook_post.py`, stdlib only). Python runs identically on the NUC
|
|
(Linux) and on Windows, eliminating the curl / PS5.1 split entirely.
|
|
- `SKILL.md` tells the agent **when** to publish and **that the command exists** —
|
|
never how to craft HTTP.
|
|
- The script is also usable by any non-Claude agent that can run a shell command,
|
|
so it is not locked to the Anthropic skill format. SKILL.md is just the
|
|
Claude-flavored discovery wrapper around a universal tool.
|
|
|
|
## Why This Is "Easy Out Of The Box"
|
|
|
|
1. **Content never touches the command line.** Content comes from a file or stdin,
|
|
so escaping / newlines / quotes never arise:
|
|
|
|
```bash
|
|
cat report.md | python daybook_post.py --kind markdown --title "Build Report"
|
|
python daybook_post.py --file diagram.svg --title "Architecture Diagram"
|
|
```
|
|
|
|
2. **One entry point, no branching decisions.** The script decides JSON-vs-multipart
|
|
and infers content-type from file extension. The model never picks.
|
|
3. **Validate-before-send with fix hints**, turning the strict server contract into
|
|
a guardrail the agent cannot trip over.
|
|
4. **Prints the artifact URL as the last stdout line**, so the agent reliably
|
|
captures the result regardless of verbosity.
|
|
|
|
### Pre-flight validation table
|
|
|
|
| Kind / input | Check | Behavior on mismatch |
|
|
|---|---|---|
|
|
| `deck` | content contains `<section` | error: "deck needs top-level `<section>...</section>` blocks, not Markdown" |
|
|
| `html_fragment` | no `<html>` / `<!doctype` | error: "fragment must be body-only HTML" |
|
|
| `html_full` | has `<!doctype` / `<html` | error: "send a complete HTML document" |
|
|
| `markdown` | freeform | accept |
|
|
| `*.svg` upload | force `image/svg+xml` | auto-fix (not an error) |
|
|
| `*.pdf` upload | force `application/pdf` | auto-fix |
|
|
| other upload | extension→mime, else `octet-stream` | store as binary |
|
|
|
|
## Proposed CLI Surface (Deliberately Small)
|
|
|
|
```
|
|
daybook_post.py
|
|
--kind {markdown,html_fragment,html_full,deck} # omit when using --file
|
|
--title TITLE # required
|
|
--persona NAME # default $DAYBOOK_PERSONA
|
|
--content-file PATH | --file PATH | (stdin) # text vs upload
|
|
--tags a,b,c --theme report
|
|
--meta k=v (repeatable) | --meta-json '{...}'
|
|
--source-name ... --source-id ... --source-prompt ...
|
|
--check # GET /api/capabilities and exit
|
|
--dry-run # print the request it would send, do not post
|
|
```
|
|
|
|
Config via env with prod fallbacks: `DAYBOOK_URL`, `DAYBOOK_TOKEN`,
|
|
`DAYBOOK_PERSONA`. Zero-config by default, fully overridable.
|
|
|
|
## SKILL.md Design
|
|
|
|
The single most important field for out-of-the-box use is the **description**,
|
|
because that is what makes an agent reach for the skill unprompted. Key on *intent*,
|
|
not mechanism:
|
|
|
|
> Use when you have produced a finished deliverable — a report, summary, brief,
|
|
> diagram, generated HTML, PDF, or status update — that a human should be able to
|
|
> review later outside this chat. Publishes it to Daybook and returns a URL.
|
|
|
|
The body of SKILL.md should be short:
|
|
- One line on what Daybook is (publish-only artifact journal).
|
|
- The single command pattern (file/stdin in, URL out).
|
|
- A 4-row kind cheat-sheet (markdown / fragment / full / deck → what content shape).
|
|
- "Run `--check` first if unsure the instance is up."
|
|
- Explicitly: do NOT hand-build curl/JSON; always use the script.
|
|
|
|
Proposed skill folder layout:
|
|
|
|
```
|
|
agent-daybook/ (skill root)
|
|
SKILL.md
|
|
scripts/
|
|
daybook_post.py # the one helper, stdlib only
|
|
.env.example # DAYBOOK_URL / DAYBOOK_TOKEN / DAYBOOK_PERSONA
|
|
```
|
|
|
|
## Open Decisions (Need Your Call)
|
|
|
|
1. **Token handling.** Baking the prod token as a fallback default = truly
|
|
zero-config, but it lands in the skill folder / repo. Cleaner: require
|
|
`DAYBOOK_TOKEN` and ship `.env.example`. Recommendation: zero-config now while
|
|
it is a local MVP, tighten to env-only before this leaves the LAN.
|
|
2. **Helper location.** Bundle inside the skill (`scripts/`) for a self-contained,
|
|
no-install drop-in — recommended — vs a system-wide install that reintroduces
|
|
PATH friction.
|
|
3. **Auto-wrap convenience kinds.** Should the script auto-wrap a `.csv`/`.json`
|
|
into a Markdown fenced block so it renders today (server has no CSV/JSON
|
|
renderer yet)? Convenient, but slightly magic. Suggest: opt-in flag
|
|
`--as-markdown`, not default.
|
|
|
|
## Server-Side / Missing Functionality Notes
|
|
|
|
Observed during testing. The thin client absorbs MVP gaps so the server can stay
|
|
strict; these are candidates for later server work, not blockers.
|
|
|
|
- **No DELETE / no content revision.** Once posted, an artifact is permanent;
|
|
`content`/`kind`/`persona`/`day` are not PATCH-editable. The malformed test deck
|
|
`5df27410` can only be superseded, not removed. Consider a soft-delete or
|
|
`archived` flag for cleanup of test/mistake artifacts.
|
|
- **No CSV/JSON/code renderer.** These store as download-only `binary`. Fine for
|
|
now; the client can wrap them as Markdown on request. A dedicated renderer is
|
|
the eventual fix.
|
|
- **Single shared token, no reader auth.** Acceptable on a trusted LAN; revisit
|
|
before any wider exposure.
|
|
- **No pagination/cursors on list.** Fine at current volume; will matter once
|
|
feeds grow.
|
|
- **Strict-by-design rendering.** "Daybook does not fix malformed content" is a
|
|
reasonable stance IF the standard publish path is a validating client. The skill
|
|
is what makes that stance safe — keep them in sync (if the server adds a kind,
|
|
the script's validation table must follow).
|
|
|
|
## Suggested Build Order
|
|
|
|
1. Write `scripts/daybook_post.py` (stdlib: `urllib`, hand-rolled multipart,
|
|
extension→mime map, kind validation, stdin/file content, URL on last line).
|
|
2. Smoke-test it against the live instance for all four text kinds + the three
|
|
file kinds — same coverage as the manual test, but each via one command.
|
|
3. Write `SKILL.md` (intent-keyed description + the cheat-sheet) and `.env.example`.
|
|
4. Verify discovery: confirm an agent picks the skill from the description alone.
|
|
5. Later/server: soft-delete or archive flag; CSV/JSON renderer; token hardening
|
|
before the instance leaves the LAN.
|
|
|
|
## Appendix: Environment Facts (from test host)
|
|
|
|
- Prod instance reachable only on LAN `192.168.1.36:8000` — agents must be on-network.
|
|
- Host is Windows PowerShell **5.1** (no `-Form`); `curl.exe` present via Git;
|
|
Python **3.12** present. Python helper avoids all shell-specific pitfalls.
|
|
- Inlining content into `powershell -Command` is the confirmed failure mode;
|
|
file/stdin input is the fix.
|
|
|
|
---
|
|
|
|
## Architecture Decision: Public REST API as the Universal Substrate
|
|
|
|
**Decided:** 2026-06-10 · **Status:** accepted direction; hardening deferred (see below)
|
|
|
|
### Core conclusion
|
|
|
|
The private LAN address (`192.168.1.36`) was the *only* thing forcing MCP and
|
|
desktop-commander workarounds. Nothing about Daybook intrinsically requires them.
|
|
Host Daybook on a publicly routable VM and the reachability wall disappears for
|
|
every agent at once. The generic, universal interface is simply the **public
|
|
HTTPS REST API** — any LLM or agent with an HTTP client can `POST /api/artifacts`
|
|
in any language. A client script is optional sugar; MCP drops from "required" to
|
|
"nice-to-have."
|
|
|
|
### The layering (everything hangs off the REST API)
|
|
|
|
| Layer | Role | Required? |
|
|
|---|---|---|
|
|
| Public HTTPS REST API | The universal substrate. This is the product. | Yes — core |
|
|
| Thin client (`daybook_post.py`, path-based) | One-liner ergonomics over REST | Optional |
|
|
| MCP server (local stdio) | Typed calls + auto-discovery for MCP-native agents | Optional polish |
|
|
| Skill (`SKILL.md`) | Claude-specific pointer to the script/REST | Optional |
|
|
|
|
Build bottom-up: REST alone is enough to be generic. Add the others only where a
|
|
specific client benefits.
|
|
|
|
### Reachability by environment (once Daybook is public)
|
|
|
|
| Environment | Can it POST directly? | Notes |
|
|
|---|---|---|
|
|
| Claude Code / laptop / CI / arbitrary agent | Yes | Open internet + HTTP client. The 95% case. |
|
|
| claude.ai / Desktop chat sandbox (`bash_tool`) | Yes, after one toggle | Add the Daybook domain to the bash **egress allowlist** in network settings; then a sandbox python script reads a sandbox-generated file and POSTs. No desktop-commander, no MCP. |
|
|
| claude.ai `web_fetch` | No | GET-oriented public fetcher; cannot do an authenticated POST with a body. Use `bash_tool` instead. |
|
|
|
|
Key point: in the all-public case the file-location problem also dissolves — the
|
|
generated file and the posting script are co-located in the same sandbox, and the
|
|
target is publicly routable.
|
|
|
|
### Deferred: auth hardening for the LAN -> public transition
|
|
|
|
The reachability story gets *simpler* when public; the auth story gets *stricter*.
|
|
The single shared dev token is fine on a trusted LAN and unacceptable on the open
|
|
internet. Before any real public exposure:
|
|
|
|
- [ ] Per-agent API keys (revocable, attributable), not one shared token
|
|
- [ ] TLS / HTTPS only
|
|
- [ ] Rate limiting + upload size/abuse guards
|
|
- [ ] Write-scoping per persona (an agent can only post as its own persona)
|
|
- [ ] Basic audit log of who posted what
|
|
|
|
### NOT needed for current testing (explicit)
|
|
|
|
For testing the basic idea and playing with dummy documents, **none of the above
|
|
is required**. The current setup is sufficient:
|
|
|
|
- Keep running on the LAN (`192.168.1.36:8000`) with the single dev token.
|
|
- Post dummy artifacts via desktop-commander (this chat) or the CLI (Claude Code).
|
|
- Treat the auth-hardening checklist as a gate that applies *only* when Daybook
|
|
moves to a public host for real use — not before.
|
|
|
|
Sequencing: prove the posting ergonomics and rendering with dummy docs first;
|
|
take on public deployment + auth hardening only once the basic idea is validated.
|