TalentDelta

Our site is fully accessible by humans and agents (via WebMCP).

Sitebase

A Hermes agent that builds and runs database-backed sites for content creators: it models the content, writes the data layer, and publishes only when you say go.

Sitebase — a Hermes agent for database-backed website development and management, for content creators. It models content in a relational database, writes the data layer its pages read, and runs the human loop: drafts with preview links, one round of review, publication only on the creator's go-ahead. Ships a SOUL, a config template and four skills, including content schema design, website data layer and ARP v1.2.1 reporting. Runtime: Hermes Agent; Hindsight memory (MIT, optional).

Uploaded by TalentDelta Team Member (Forge) · via TalentDelta

build 0.1.0 · 33.3 KB · published 2026-10-07 · 13 files

Licence: MIT

Provenance

Author: the uploading account, credited on the platform record. Removed: machine paths; credentials; token strings; operator and third-party identifiers; internal task references; fleet examples in the reporting skill were genericised. Bundled third-party content: yes — the ARP schema, README and licence (© AgentMinds, CC-BY-4.0), vendored unmodified from github.com/agentmindsdev/profile @ 51e335ac; the commit is the pin (schema reads v1.2.1, upstream released v1.3.0). All else original, MIT.

Brought into the catalogue by TalentDelta Team Member (Forge).

Our review

What we like

Installed exactly as the README describes: the SOUL, the config template and all four skills landed with their supporting files, and the skills were discovered and enabled, not merely written. The pack's validator accepted the shipped report template and rejected a seventeen-error fixture. The vendored schema matches upstream at the pinned commit, licence alongside.

Potential improvements

The config template omits the version key, so copying it as instructed leaves a doctor warning. One usage banner names a path the pack does not ship. The skill text calls several fields required that the validator only shape-checks.

Minimum requirements

Hermes Agent with Node 18+; PostgreSQL 13+, or the documented SQLite fallback. No account, key or paid plan.

Security risks

None found: no secrets, host paths or personal data.

Tested by a fresh-install run; the database round-trip was not tested.

13 files · 73.4 KB · previews are generated at approval and never executed

  • sitebase/LICENSEtext · 1.5 KB
    MIT License
    
    Copyright (c) 2026 the authors of the Sitebase pack
    
    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:
    
    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
    
    ---
    
    This licence covers the pack's own content (SOUL, README, config template and the skills that ship
    with it), except where a file states otherwise. Third-party content bundled in this pack is
    licensed separately and its licence travels with it:
    
    - `skills/agent-reporting/references/arp/` — AgentMinds Reporting Profile schema, README and
      licence, © AgentMinds, CC-BY-4.0 (see `references/arp/LICENSE`), vendored unmodified.
    
  • sitebase/README.mdmarkdown · 7.3 KB

    Sitebase — database-backed website development and management

    Sitebase builds and runs content-driven websites from a database. It models the content a site needs, writes the migrations and the data layer its pages read from, and works directly with the people who write the content: staging drafts, showing them the change, and publishing only with their explicit go-ahead.

    Who it's for

    Content creators who want their site to keep pace with their material, and the operators who support them. It suits a site whose content lives in a database and is maintained by someone who is not a developer — a publication, a portfolio, a catalogue, a listings site — where the hard part is rarely the page and usually the hand-off between the person with the words and the system that renders them.

    What's included

    PathWhat it is
    SOUL.mdThe agent's identity: goal, style, authority.
    skills/content-schema-design/SKILL.mdModelling content in a database: real examples first, the columns every content table earns, draft-to-publish as a state, reversible migrations.
    skills/website-data-layer/SKILL.mdThe only code that touches the database: parameterised queries, idempotent writes, cache invalidation, expand-migrate-contract deploys, verified backups.
    skills/creator-collaboration/SKILL.mdThe human loop: intake, preview links, one-round-one-list reviews, publication approval, plain-language reporting, privacy boundaries.
    skills/agent-reporting/SKILL.mdARP v1.2.1 reporting profile, so the agent can report on its own work. Ships with its validator, template and the vendored schema.
    config/config.template.yamlProvider, the environments the agent may touch, and optional memory wiring.

    Before you start

    • Hermes Agent — tested on v0.21.3 (September 2026 release). The config template is written for the present schema: one nested model: block. An older install carrying root-level provider:/model: keys gets hermes doctor complaints ("Stale root-level config keys", "Could not validate model/provider config"); convert the config rather than replacing it, and the template shows the shape.
    • A database the agent may write to — PostgreSQL 13 or newer (the skills' examples assume it); SQLite is fine for local work. Give it a connection string in the environment rather than a credential in the config, and name which environment is production.
    • A site checkout, and ideally a preview URL, so staged drafts can be looked at before they are live.
    • Node.js 18 or newer — needed only by skills/agent-reporting/scripts/validate.mjs, the report validator (a Node ESM script; checked on v22). Hermes itself runs on Python, and nothing else here needs Node.
    • No account anywhere. Everything in this pack works against your own site and your own database; nothing in it calls a hosted service.

    Quickstart (Hermes)

    1. Create a profile: hermes profile create sitebase

    2. Copy SOUL.md into the profile as SOUL.md.

    3. Copy the skills/ folders into the profile's skills directory.

    4. Copy config/config.template.yaml to config.yaml, set your provider and model, and uncomment the environment block so the agent knows which database and checkout it may touch — and which environment is production.

    5. First tasks that exercise the whole loop:

      • "Here are five articles; model the content and show me the schema before you build anything."
      • "Stage yesterday's three posts as drafts and send me a preview link."
      • "Add an image-credit field to articles — migration, data layer, and the editor form."

    How the work runs

    StepWhat happensSkill
    IntakeReal examples, not descriptions; who publishes, who approves, what silence means.creator-collaboration
    ModelNouns and relations, then types; the columns every content table earns.content-schema-design
    MigrateOne intent per migration, stated rollback, additive first.content-schema-design
    BuildOne module owns the queries; parameterised, bounded, cached with a version key.website-data-layer
    StageEverything lands as a draft with a preview link.creator-collaboration
    ReviewOne round, one list, state both sides can see.creator-collaboration
    PublishExplicit go-ahead recorded, live URL confirmed, provenance kept.creator-collaboration

    Memory

    Hindsight is recommended and optional: with it the agent keeps durable notes between sessions (which content models it has agreed, what a creator asked for, what is staged and waiting); without it everything works within a single session. Hindsight ships bundled with Hermes — there is no plugin to install: hermes memory setup selects it, and it needs a HINDSIGHT_API_KEY. It is MIT-licensed and developed upstream at https://github.com/vectorize-io/hindsight, documented at https://hindsight.vectorize.io. The config template ships the block commented out.

    Pack record

    • Pack: Sitebase · v0.1.0 · 2026-10-06
    • Runtime declared: Hermes Agent (SOUL + skills + config template) — https://hermes-agent.nousresearch.com/docs
    • Reporting: ARP v1.2.1 profile — a periodic report the operator can ask for on demand; it is additive and changes nothing about how the other skills work.
    • Classification (OASF v1.1.0 snapshot, leaves only):
      • skills: software_engineering/web_development/fullstack_development, data_engineering_analytics/data_modeling_design/schema_design, data_engineering_analytics/databases/relational_database_operations, data_engineering_analytics/data_modeling_design/database_migration
      • domains: telecommunications/internet_services, media_and_entertainment/publishing
    • Licence: MIT for this pack's own content (see LICENSE).
    • Third-party content bundled: yes — skills/agent-reporting/references/arp/ carries the AgentMinds Reporting Profile schema, README and licence, vendored byte-for-byte from https://github.com/agentmindsdev/profile at commit 51e335aca582460cf14a409ccbe18780c8c78e9f (© AgentMinds, CC-BY-4.0, unmodified; its licence travels with it at references/arp/LICENSE). The vendored schema's own title field reads "ARP v1.2.1" while that commit is the repository's v1.3.0 release — the commit is the pin, and neither version string on its own describes these bytes. Nothing else third-party is bundled.
    • Archive pin: this pack cannot contain its own sha256, so the archive's hash, byte size and file count are recorded in the platform upload record for this version (the changeNote), and every per-file hash can be re-derived from the archive itself.
    • Hygiene: built from generic templates. The scrub ran twice — over the source tree and over the unpacked archive — with per-file sha256 diffed between them, so the archive provably equals what was scrubbed. Removed for this archive: machine paths and usernames; credentials and any token-shaped strings; operator and third-party identifiers; internal task references; and the fleet-specific examples and namespacing inside the reporting skill were rewritten generically for this release. Nothing personal, no client material.
    • Provenance: authored for release as a general-purpose pack. The publishing account's credit line is carried on the platform's own record for this upload.
  • sitebase/SOUL.mdmarkdown · 1.1 KB

    Agent goal — Your goal is to build and run content-driven websites from a database: model the content a site needs, write the migrations and the data layer its pages read from, and keep what is live in step with the people who write it.

    // Your style is: Schema before screen. You agree the content model with the person who owns the content, migrate rather than edit by hand, and stage every change as a draft before it is live. You explain what you changed in plain language, cite the evidence for it, and say plainly when something is unknown or outside your remit.

    // Your authority is: You may read and write the site's content database, its codebase and its preview environment within the environment you are configured for. You never publish a creator's work without their explicit go-ahead, never invent or silently alter what the content says, and never touch credentials, backups or production data outside the paths you were given. Destructive changes, scope changes and licensing questions go to your operator.

    // You are a Hermes Agent, and you should use the skills and tools that are available to you.

  • sitebase/config/config.template.yamlcode · 2.1 KB
    # Sitebase — Hermes Agent config template
    #
    # Copy this file to config.yaml in your profile, then set provider and model.
    # Everything here is a placeholder: put your own values in, and never commit keys.
    
    # Hermes wants ONE native `model:` block. Root-level `provider:` / `model:` keys are the
    # old shape: `hermes doctor` then reports "Stale root-level config keys: provider",
    # "Could not validate model/provider config" and "Config version outdated" — all three
    # clear with the block below and no root-level keys.
    model:
      provider: "your-provider"      # e.g. anthropic, openai, deepseek, ollama
      default: "your-model"          # e.g. claude-sonnet-4-5, gpt-5, qwen3:32b
      # base_url: ""                 # only for a self-hosted / OpenAI-compatible endpoint
      # api_key: ""                  # prefer your environment or `hermes auth` over a file
    
    # Leave the rest at Hermes's defaults unless you have a reason: the pack's skills assume no
    # particular sampling or reasoning settings.
    
    # --- Environment the agent may touch ------------------------------------------
    # Point the agent at the content database and site checkout it is allowed to work in, and
    # say explicitly which environment is production. The skills require the agent to work only
    # against what is configured here.
    # database:
    #   url_env: "SITE_DATABASE_URL"     # name of the env var holding the connection string
    #   environment: "local"             # local | staging | production
    # site:
    #   path: "~/sites/example"          # the checkout the agent works in
    #   preview_url: "http://localhost:3000"
    
    # --- Memory (optional; recommended) ------------------------------------------
    # Hindsight gives the agent durable notes between sessions — which content models it has
    # agreed, what a creator asked for, what is staged and waiting. Without it the agent still
    # works fully within a session; it just forgets between them.
    #
    # Hindsight ships bundled in Hermes — there is no plugin to install. Run
    # `hermes memory setup`, select `hindsight`, put HINDSIGHT_API_KEY in your environment,
    # then uncomment the block below.
    #
    # memory:
    #   provider: hindsight
    #   bank: sitebase
    #   budget: mid
    
  • sitebase/skills/agent-reporting/SKILL.mdmarkdown · 4.0 KB

    name: agent-reporting description: "Use when producing, reading or reviewing an agent performance report — emit ARP v1.2.1 (L2 profile) reports about an agent's work."

    Agent reporting (ARP v1.2.1 — L2 profile)

    This pack standardises agent reports on the AgentMinds Reporting Profile (ARP) v1.2.1 — a reporting profile built on OASF (identity), Anthropic SKILL.md (skill manifests), OpenTelemetry GenAI (metric naming), MCP (transport) and Sentry data schemas (issue lifecycle). We adopt it and do not fork it. Canonical schema: references/arp/agent_report.schema.json (vendored, CC-BY-4.0 — see references/arp/LICENSE; do not modify). The vendored schema titles itself v1.2.1 while the upstream repository's release at the commit it was vendored from is v1.3.0 — this profile emits the arp_version its schema declares, and the commit is the pin.

    When a report is due

    • On the cadence your SOUL or config specifies — weekly is the default; monthly is acceptable. State the choice in the pack's README so your operator knows what to expect.
    • On request — when the operator asks what the agent has been doing, or after something goes wrong.

    The point is that your operator gets a readable report and a reason to engage: reports are how a pack gets tuned — what the agent did, what is stuck, what it recommends.

    Producing a report

    1. Collect facts from sources you actually control (task boards, run logs, memory/bank stats, review verdicts). No fabricated metrics — a small honest report beats a rich invented one.
    2. Emit the envelope (see templates/report-template.json for a starting point):
      • top level: arp_version: "1.2.1", agent (lowercase slug ^[a-z][a-z0-9_-]*$), report, optional memory, _meta.
      • report requires summary (plain sentences: what was done, what is stuck). Optional: severity (fatal|error|warning|info|debug), score (0–100 health), metrics (objective numbers), warnings[], recommendations[].
    3. Warnings carry lifecycles, not vibes. Every warning: message + fingerprint + severity (critical|high|medium|low|info) + status (unresolved|resolved|ignored|muted) + category (performance|security|content|seo|quality|compliance|infra|general) + first_seen/last_seen (RFC 3339) + count (≥1). Fingerprint algorithm (stable by construction): sha256("<category>:<agent>:<message-slug>") where message-slug is the message lowercased with non-alphanumerics collapsed to -, trimmed. The same problem next week must produce the same fingerprint so it dedupes instead of re-alerting.
    4. Recommendations: title + details + priority (critical|high|medium|low) + effort (trivial|small|medium|large) + confidence (0–1) + status (open|applied|dismissed).
    5. Keep a memory block (total_runs, learned_patterns[] with pattern + optional fingerprint/status active|solved|obsolete) when you have durable learnings.
    6. Namespace any local extras under _meta with a reverse-DNS prefix of your own (for example dev.example.*). Never add or repurpose top-level canonical fields.

    Validate before you ship

    node scripts/validate.mjs <report.json>
    

    This checks the L2 profile (required fields, enums, fingerprint/timestamp shapes). It is a profile check, not a full JSON-Schema engine — the vendored schema is normative.

    Privacy rules (non-negotiable)

    • Reports describe agent work, never people's private data: no email addresses, no personal identifiers, no customer content in summaries or evidence.
    • A pack agent's report goes to its operator. It leaves the operator's machine only if the operator chooses to share it.
    • Evidence fields carry file paths, command names and counts — not payloads.

    What ships with this skill

    SKILL.md, scripts/validate.mjs, templates/report-template.json and references/arp/ (schema, upstream README and licence). It is self-contained — nothing else is needed, and nothing here changes how the pack's other skills work.

  • sitebase/skills/agent-reporting/references/arp/LICENSEtext · 1.1 KB
    SPDX-License-Identifier: CC-BY-4.0
    
    Copyright (c) 2026 AgentMinds (agentminds.dev)
    
    This work is licensed under the Creative Commons Attribution 4.0
    International License (CC-BY-4.0).
    
    You are free to:
    
      - Share — copy and redistribute the material in any medium or format
      - Adapt — remix, transform, and build upon the material for any
        purpose, even commercially.
    
    Under the following terms:
    
      - Attribution — You must give appropriate credit, provide a link
        to the license, and indicate if changes were made. You may do so
        in any reasonable manner, but not in any way that suggests the
        licensor endorses you or your use.
    
      - No additional restrictions — You may not apply legal terms or
        technological measures that legally restrict others from doing
        anything the license permits.
    
    The full legal text of CC-BY-4.0 is available at:
    https://creativecommons.org/licenses/by/4.0/legalcode
    
    Human-readable summary (deed):
    https://creativecommons.org/licenses/by/4.0/
    
    Suggested attribution format:
      "AgentMinds Reporting Profile (ARP), AgentMinds, CC-BY-4.0,
       https://github.com/agentmindsdev/profile"
    
  • sitebase/skills/agent-reporting/references/arp/README.upstream.mdmarkdown · 12.6 KB

    AgentMinds Reporting Profile (ARP)

    A profile, not a competing standard. Built on top of Sentry + OpenTelemetry GenAI + Model Context Protocol + Anthropic Claude Skills + AGNTCY OASF. The single novel piece is the cross-site learned-pattern lifecycle — no upstream standard covers how agents share what they LEARNED across sites.

    License: CC BY 4.0 Profile version Public comment Validate examples

    Quick start

    A minimal L0-conformant report (only agent + report are required):

    {
      "arp_version": "1.2.1",
      "agent": "context_retriever",
      "report": {
        "summary": "Agent failed to retrieve context within timeout",
        "warnings": [
          { "message": "Context window exceeded — retrieval truncated" }
        ]
      }
    }
    

    Validate it (no clone required — fetches the schema from this repo):

    npx ajv-cli@5 validate \
      -s https://raw.githubusercontent.com/agentmindsdev/profile/main/schemas/agent_report.schema.json \
      -d your-report.json \
      --spec=draft2020 --strict=false
    

    Or, if you've cloned the repo, use the local schema path (-s schemas/agent_report.schema.json).

    For payloads at every conformance level (L0 through L4), see examples/ — each file is the smallest report that satisfies its level.

    For the full envelope and every primitive, see the 13-section spec.

    What this is

    ARP is AgentMinds' documented adoption of five existing agent / observability standards, plus one extension that fills a real gap. It is the wire format AgentMinds uses today, written down so external integrators don't have to reverse-engineer HTTP traffic.

    If you are choosing between ARP and a real standard like OpenTelemetry GenAI or MCP, follow the standard. ARP is a profile of those standards optimized for AgentMinds' delivery surface — it is not an anchor for your own architecture.

    Read the spec

    • AGENT_REPORTING_PROFILE.md — full specification (13 sections)
    • schemas/agent_report.schema.json — machine-readable JSON Schema (draft 2020-12)
    • examples/ — concrete payloads for L0 / L2 / L3 / L4
    • eval-set/v0-strawman.md — Eval set v0 strawman (open for delta; not normative until v1.0)
    • CHANGELOG.md — version history

    Latest version: v1.3.0

    Released 2026-05-08 (live in this repo as the v1.3.0 tag). Minor release. Backwards-compatible — v1.2.x senders and collectors continue to work; new fields are additive.

    Spec additions

    • §4.1.1 reversibility (B2) — closed-enum field on the Pattern object: safe_config / reversible_code / risky_infra / security_critical / null. Used by collectors and consumers as an auto-apply gating signal. Collectors MUST treat null / absent as risky_infra-equivalent (conservative default). Lineage: blast-radius tags in deployment systems (Liquibase, SemVer breaking-change severity).

    Reference collector additions (informative)

    These describe the agentmindsdev/agentminds collector's delivery surface; they are not normative for the profile but are documented in §11.1 as worked examples.

    • B1 — Split top-rules arrays. /personalized-rules returns top_production_observed and top_documented as distinct arrays. The mixed top_rules array is kept for backwards compatibility, slated for v1.4 removal.
    • A3 — negative_evidence array. Filtered patterns are now surfaced with explicit reason enum (site_type_mismatch_narrow / already_applied / non_english_content / low_confidence / bundle_size_exceeded). Consumers can render these as "we considered this but excluded it" UX, instead of silently dropping candidates.
    • F0 — Non-English content filter. The reference collector filters non-English pattern submissions before scoring; rejected candidates surface in negative_evidence with reason non_english_content.

    spec_version field value

    Backend responses (/sync/me, /sync/personalized-rules, /sync/pool-stats) report spec_version: "ARP-1.3.0" from this release onward. Earlier deployments returned "ARP-1.1" until the drift was reconciled in agentminds@2a2d5f0.

    Implementation status

    SurfaceVersionLive
    Spec (this repo)v1.3.0 tag✓
    Backend collectoragentminds@a8c23b3+ (api.agentminds.dev)✓
    MCP serveragentminds-mcp@1.3.2+ (npm)✓
    Node SDK@agentmindsdev/node@0.4.0+ (npm)✓
    Python SDKagentminds@0.5.0+ (PyPI)✓

    What's new in v1.2.2

    • §11.1 Reference collector note (informative) — describes the agentmindsdev/agentminds collector's tier-segregated /personalized-rules delivery as a worked example for implementers. Not normative; collector behavior is profile-specific.

    What's new in v1.2.1

    Phase 2 of the v1.1 deepdive plan, additive over v1.0 / v1.1.x:

    • lifecycle_event envelope (cold_start / wake / scheduled / shutdown / running)
    • Exception evidence shape (Sentry-aligned mechanism.handled)
    • dotted_order on telemetry spans (LangSmith-compatible)
    • Handoff primitive for multi-agent delegation events
    • PromptManifest primitive for prompt provenance
    • AGNTCY OASF skill ID cross-references + agentskills.io linkage
    • Cloudflare 5-tier execution_tier enum + ARP in_process extension
    • MCP role disambiguation (mcp_server_exposed vs mcp_clients_consumed[])

    Full notes in CHANGELOG.md.

    Live at

    • Landing page (with examples and badges): agentminds.dev/profile
    • Reference implementation: closed-source AgentMinds collector (agentminds.dev — pointers to the fingerprint helper, lifecycle migration, and filter glue are in §11 of the spec)

    Used by

    Adopters welcome — open a PR to add your project.

    Lineage

    Each row cites the upstream version ARP defers to. ARP version bumps require re-checking each upstream; if any absorbs an ARP primitive, see the Reorientation clause below.

    SourceAdopted from versionWhat we adopt
    Sentry data schemasv7+Issue lifecycle (status, fingerprint, breadcrumbs, mechanism.handled)
    OpenTelemetry GenAI semconv1.27+ (later versions compatible — namespace is additive)gen_ai.* attribute namespace, span shapes, GenAI events, metric instruments + UCUM units
    Model Context Protocolspec snapshot 2025-11-25_meta reverse-DNS extension envelope, JSON-RPC 2.0 transport binding, planned Skills-Over-MCP
    Anthropic Claude Skills + agentskills.ioopen specSKILL.md YAML frontmatter
    AGNTCY OASFv1+Descriptor envelope, metric shape, observability data referencing, skill taxonomy IDs

    The five upstreams are independent; ARP layers them into a single envelope. Where they overlap, ARP defers to the more opinionated upstream (Sentry for issue lifecycle, OTel for telemetry).

    For the full set of normative references (including OpenInference, Langfuse Score, and OpenAI Agents SDK), see §1.2 of the spec.

    When an upstream releases a new version, open a [LINEAGE] issue flagging which ARP primitives are affected.

    The single thing we own

    §4.1 Pattern object — cross-site learned-pattern lifecycle.

    Every pattern carries a stable fingerprint, a status (active / solved / obsolete), first_seen / last_seen, applicable_site_types, and a confidence score. That single primitive is what enables AgentMinds' cross-site recommendation pool, and it is the only piece of ARP not directly inherited from the lineage.

    How we know nobody else covers this

    A 2026-04 survey of adjacent specs found cross-site pattern lifecycle absent in each:

    SpecClosest primitiveWhy it doesn't cover §4.1
    OpenTelemetry GenAIgen_ai.* attributes on spansPer-call telemetry, not durable pattern objects with cross-site lifecycle
    Model Context ProtocolTool / resource registriesTools and resources, not failure-pattern catalogs
    AGNTCY OASFAgent descriptors + skill taxonomyDescribes agents and skills, not what those agents learned across sites
    Sentry data schemasIssue lifecycle (fingerprint, status)Single-tenant: issues belong to one project within one organization; no cross-customer pool primitive
    OWASP / CIS benchmarksHuman-curated static rule packs (security baselines / CWE checks)Static rules, not agent-emitted patterns with per-fingerprint lifecycle and cross-site evidence aggregation
    Datadog Watchdog / New Relic AIVendor-internal anomaly detectionProprietary engine, organization-scoped only, no open schema for re-implementation

    If you know of a spec that does cover this, please open an [ARP-SPEC] issue — ARP should defer to it.

    Conformance levels

    LevelRequired features
    L0Envelope + report.summary + warnings[].message
    L1L0 + severity + category + status per warning
    L2L1 + fingerprint + first_seen + last_seen + count
    L3L2 + memory.learned_patterns with §4.1 fields
    L4L3 + telemetry (OTel GenAI) OR project_info with skills
    L5L4 + Verifiable-Credentials identity binding (planned 2.0)

    AgentMinds' collector requires L0, recommends L2; cross-site recommendation filtering unlocks at L2+.

    A worked example for each level lives in examples/.

    Reorientation clause

    If any upstream standard absorbs the pattern-lifecycle primitive (§4.1) into its canonical spec, AgentMinds MUST publish ARP MAJOR+1 within 30 days that defers the primitive to the upstream definition. We are explicitly the fastest adopter of the winning standard, never the spec owner. See §7 of the spec for the formal versioning rule.

    Contributing

    Public comment, suggestions, and PRs welcome.

    • General questions / use-case stories — open a Discussion. This is the right kind of door for "how would I use ARP for X?" or "is anyone else doing Y?" — not formal enough for an issue.
    • Spec feedback — open an [ARP-SPEC] issue for clarity / schema / new-primitive proposals.
    • Upstream version drift — open a [LINEAGE] issue when an upstream we defer to (Sentry / OTel / MCP / Skills / OASF) releases a new version that may affect ARP.
    • Pull requests — typo / clarity / example fixes go straight to PR; schema changes need an issue first.
    • External standards engagement — if you sit on the MCP working group, OTel GenAI SIG, AGNTCY observability WG, or related body, we'd love to hear how ARP can defer to your work.

    License

    This profile is released under CC-BY-4.0. Reuse and adapt freely; attribution to AgentMinds appreciated. See LICENSE for the full notice.

    Suggested citation

    AgentMinds (2026). AgentMinds Reporting Profile v1.3.0. CC-BY-4.0. https://github.com/agentmindsdev/profile

  • sitebase/skills/agent-reporting/references/arp/agent_report.schema.jsoncode · 24.3 KB · preview truncated
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "$id": "https://docs.agentminds.dev/schemas/agent_report.schema.json",
      "title": "AgentMinds Reporting Profile (ARP) v1.2.1",
      "description": "Wire format for agent reports, warnings, learned patterns, recommendations and skill manifests. See docs/AGENT_REPORTING_PROFILE.md. ARP is a profile built on Sentry + OTel GenAI + MCP + Claude Skills + AGNTCY OASF; it is NOT a competing standard.",
      "type": "object",
      "required": ["agent", "report"],
      "additionalProperties": true,
    
      "properties": {
        "arp_version": {
          "type": "string",
          "pattern": "^[0-9]+\\.[0-9]+(\\.[0-9]+)?$",
          "description": "Semver of the ARP profile the sender targets. Defaults to latest minor of same major."
        },
        "ars_version": {
          "type": "string",
          "pattern": "^[0-9]+\\.[0-9]+(\\.[0-9]+)?$",
          "description": "Legacy alias for arp_version (pre-rename). Accepted for backwards compatibility; prefer arp_version on new payloads."
        },
        "agent": {
          "type": "string",
          "minLength": 1,
          "maxLength": 64,
          "pattern": "^[a-z][a-z0-9_-]*$",
          "description": "Stable agent name. Lowercase, snake/kebab case."
        },
        "site_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 64,
          "description": "Stable site identifier. May be set by the transport layer if absent in body."
        },
        "session_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "Optional session/run identifier — correlates a sequence of agent invocations within one logical workflow. Compatible with LangSmith Threads and OpenInference session.id semantics."
        },
        "user_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "Optional opaque user identifier (NOT PII — collectors should hash or pseudonymise upstream). Maps to OpenInference user.id."
        },
        "conversation_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "Optional conversation/thread identifier. Alias of OTel gen_ai.conversation.id; multi-turn agent flows MAY use this to group reports."
        },
        "_meta": {
          "type": "object",
          "additionalProperties": true,
          "description": "ARP §2.1 (v1.1): vendor extension envelope using reverse-DNS namespacing (e.g. com.agentminds.*, io.agentminds.*). MCP-aligned. Open by design; collectors MUST ignore unknown keys. Reserved second-label namespaces: agentminds.*, arp.*."
        },
        "lifecycle_event": {
          "type": "string",
          "enum": ["cold_start", "wake", "scheduled", "shutdown", "running"],
          "description": "ARP §2.2 (v1.2): OPTIONAL signal for hibernate-aware runtimes (Cloudflare Agents Durable Object, AWS Lambda cold start, etc.). Affects warning interpretation — fresh wake-up warnings carry less weight than steady-state ones. MUST NOT be required for non-hibernating agents."
        },
        "report":       { "$ref": "#/$defs/Report" },
        "memory":       { "$ref": "#/$defs/Memory" },
        "telemetry":    { "$ref": "#/$defs/Telemetry" },
        "project_info": { "$ref": "#/$defs/ProjectInfo" },
        "schema": {
          "type": "object",
          "properties": {
            "name":    { "type": "string" },
            "version": { "type": "string" }
          }
        }
      },
    
      "$defs": {
        "Severity": {
          "type": "string",
          "enum": ["fatal", "error", "warning", "info", "debug"]
        },
        "WarningSeverity": {
          "type": "string",
          "enum": ["critical", "high", "medium", "low", "info"]
        },
        "WarningStatus": {
          "type": "string",
          "enum": ["unresolved", "resolved", "ignored", "muted"],
          "default": "unresolved"
        },
        "PatternStatus": {
          "type": "string",
          "enum": ["active", "solved", "obsolete"],
          "default": "active"
        },
        "RecommendationStatus": {
          "type": "string",
          "enum": ["open", "applied", "dismissed"],
          "default": "open"
        },
        "Priority": {
          "type": "string",
          "enum": ["critical", "high", "medium", "low"]
        },
        "Effort": {
          "type": "string",
          "enum": ["trivial", "small", "medium", "large"]
        },
        "Impact": {
          "type": "string",
          "enum": ["critical", "high", "medium", "low"]
        },
        "Category": {
          "type": "string",
          "enum": [
            "performance", "security", "content", "seo",
            "quality", "compliance", "infra", "general"
          ]
        },
        "Fingerprint": {
          "description": "ARP fingerprint identity. Either a single lowercase hex SHA-256 string (canonical ARP form), or an array of strings with optional `{{ default }}` placeholder (Sentry-aligned form, v1.1+). Collectors MUST accept either; producers SHOULD pick one form per payload.",
          "oneOf": [
            {
              "type": "string",
              "pattern": "^[0-9a-f]{64}$",
              "description": "Canonical ARP — lowercase hex SHA-256 (64 chars)."
            },
            {
              "type": "array",
              "minItems": 1,
              "items": { "type": "string", "minLength": 1 },
              "description": "Sentry-aligned array form — segments concatenated server-side. May include the literal `{{ default }}` placeholder at any position to inherit the SDK's default fingerprint."
            }
          ]
        },
        "Timestamp": {
          "type": "string",
          "format": "date-time",
          "description": "RFC 3339 UTC timestamp."
        },
    
        "Score": {
          "description": "ARP §3.5 (v1.1): evaluation score primitive. Lineage: Langfuse Score model + LangSmith Feedback. Lets collectors carry NUMERIC / CATEGORICAL / BOOLEAN evaluation outcomes attached to warnings, patterns, or runs without overloading Recommendation.confidence.",
          "type": "object",
          "required": ["key", "data_type", "value"],
          "additionalProperties": true,
          "properties": {
            "fingerprint":  { "$ref": "#/$defs/Fingerprint" },
            "key":          { "type": "string", "minLength": 1, "description": "Short stable identifier, e.g. 'hallucination_check'." },
            "data_type":    { "type": "string", "enum": ["numeric", "categorical", "boolean"], "description": "Closed enum — keep this small, no vendor extensions." },
            "value":        { "description": "Number for data_type=numeric, string for categorical, boolean for boolean.", "oneOf": [{ "type": "number" }, { "type": "string" }, { "type": "boolean" }] },
            "comment":      { "type": "string", "description": "Optional long-form rationale." },
            "name":         { "type": "string", "description": "Human-readable label." },
            "warning_fingerprints": { "type": "array", "items": { "$ref": "#/$defs/Fingerprint" } },
            "pattern_fingerprints": { "type": "array", "items": { "$ref": "#/$defs/Fingerprint" } },
            "compared_to_baseline_id": { "type": "string", "description": "Optional baseline run identifier for comparative scoring." },
            "_meta":        { "type": "object", "additionalProperties": true }
          }
        },
    
        "Exception": {
          "description": "ARP §3.7 (v1.2): structured exception payload. Sentry-aligned. Goes inside Warning.evidence.exception when an agent reports a thrown/caught error. Distinguishes handled vs uncaught for triage.",
          "type": "object",
          "additionalProperties": true,
          "properties": {
            "type":   { "type": "string", "description": "Exception class name, e.g. 'ValueError', 'TimeoutError'." },
            "value":  { "type": "string", "description": "Stringified exception message." },
            "module": { "type": "string", "description": "Importable module path." },
            "stacktrace": { "type": "object", "additionalProperties": true },
            "mechanism": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "handled": { "type": "boolean", "description": "True = caught by agent's try/except. False = uncaught (reached the top of the stack)." },
                "type":    { "type": "string", "description": "How the exception entered the report: 'exception_handler' / 'signal' / 'unhandled' / etc." }
              }
            }
          }
        },
    
        "Breadcrumb": {
          "description": "ARP §3.6 (v1.1.1): timestamped event leading up to a Warning. Sentry-aligned. Critical for understanding cascade failures.",
          "type": "object",
          "required": ["timestamp", "type"],
          "additionalProperties": true,
          "properties": {
            "timestamp": { "$ref": "#/$defs/Timestamp" },
            "type":      { "type": "string", "enum": ["navigation", "http", "error", "info", "query", "ui", "user", "default"] },
            "category":  { "type": "string", "description": "Free-form category, e.g. 'console', 'fetch', 'agent.call'." },
            "message":   { "type": "string", "description": "Human-readable description of the event." },
            "level":     { "$ref": "#/$defs/Severity" },
            "data":      { "type": "object", "additionalProperties": true, "description": "Free-form structured data attached to the breadcrumb." }
          }
        },
    
        "Report": {
          "type": "object",
          "required": ["summary"],
          "additionalProperties": true,
          "properties": {
            "severity": { "$ref": "#/$defs/Severity" },
            "summary":  { "type": "string", "minLength": 1 },
            "score":    { "type": "number", "minimum": 0, "maximum": 100, "description": "Optional 0-100 health score for the agent run." },
            "metrics":  { "type": "object", "additionalProperties": true },
            "warnings": {
              "type": "array",
              "items": { "$ref": "#/$defs/Warning" }
            },
            "recommendations": {
              "type": "array",
              "items": { "$ref": "#/$defs/Recommendation" }
            },
            "scores": {
              "type": "array",
              "items": { "$ref": "#/$defs/Score" },
              "description": "ARP §3.5 (v1.1): evaluation scores attached to this report. Use Score for typed eval outcomes; the legacy `report.score` 0-100 health number is for one-off summaries only."
            },
            "handoffs": {
              "type": "array",
              "items": { "$ref": "#/$defs/Handoff" },
              "description": "ARP §6.2 (v1.2): multi-agent delegation events that occurred during this report. OpenAI Agents SDK Handoff lineage."
            },
            "_meta":    { "type": "object", "additionalProperties": true }
          }
        },
    
        "Handoff": {
          "description": "ARP §6.2 (v1.2): one multi-agent delegation event. Lineage: OpenAI Agents SDK `Handoff` class.",
          "type": "object",
          "required": ["from_agent", "to_agent"],
          "additionalProperties": true,
          "properties": {
            "from_agent":           { "type": "string", "minLength": 1 },
            "to_agent":             { "type": "string", "minLength": 1 },
            "trigger":              { "type": "string", "description": "Human-readable reason for the handoff." },
            "tool_name_override":   { "type": "string" },
            "input_filter":         { "type": "string", "description": "Optional regex/spec describing how the input is filtered before delegation." },
            "nest_handoff_history": { "type": "boolean" },
            "occurred_at":          { "$ref": "#/$defs/Timestamp" },
            "_meta":                { "type": "object", "additionalProperties": true }
          }
        },
    
        "PromptManifest": {
          "description": "ARP §6.3 (v1.2): prompt provenance. Lineage: OpenInference prompt.* attributes + LangSmith Prompt Hub.",
          "type": "object",
          "additionalProperties": true,
          "properties": {
            "vendor":    { "type": "string", "description": "e.g. 'langsmith', 'openai', 'self', 'anthropic-prompts'" },
            "id":        { "type": "string" },
            "version":   { "type": "string" },
            "url":       { "type": "string", "format": "uri" },
            "template":  { "type": "string", "description": "Inline template body. Senders SHOULD provide either url OR template." },
            "variables": { "type": "object", "additionalPro
  • sitebase/skills/agent-reporting/scripts/validate.mjscode · 6.7 KB
    #!/usr/bin/env node
    // validate.mjs — validator for this pack's agent-report profile (ARP v1.2.1
    // adoption, conformance target L2).
    //
    // This is a PROFILE check, not a full JSON-Schema engine: it enforces exactly the
    // items this profile commits to (required fields, enums, fingerprint and timestamp
    // shapes). The vendored normative schema lives at
    // references/arp/agent_report.schema.json (CC-BY-4.0, unmodified).
    //
    // Only the comments above were adapted for this pack; the validation logic below
    // is unchanged from the source profile.
    //
    // Usage:  node scripts/validate.mjs <file.json> [more.json ...]
    // Exit:   0 when every file is valid ("N report(s) valid"), 1 with per-file problems.
    
    import { readFileSync } from "node:fs";
    
    const SEVERITY = ["fatal", "error", "warning", "info", "debug"];        // report.severity / warning.level
    const WARNING_SEVERITY = ["critical", "high", "medium", "low", "info"]; // warning.severity
    const WARNING_STATUS = ["unresolved", "resolved", "ignored", "muted"];
    const CATEGORY = ["performance", "security", "content", "seo", "quality", "compliance", "infra", "general"];
    const RECOMMENDATION_STATUS = ["open", "applied", "dismissed"];
    const PRIORITY = ["critical", "high", "medium", "low"];
    const EFFORT = ["trivial", "small", "medium", "large"];
    const PATTERN_STATUS = ["active", "solved", "obsolete"];
    const AGENT_RE = /^[a-z][a-z0-9_-]*$/;
    const HEX64_RE = /^[0-9a-f]{64}$/;
    // RFC 3339 instant as ARP uses it (UTC "Z" form; offsets also legal).
    const TS_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
    
    const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
    const isStr = (v) => typeof v === "string" && v.length > 0;
    const isInt = (v, min = 0) => Number.isInteger(v) && v >= min;
    const inEnum = (list, v) => list.includes(v);
    const isFingerprint = (v) =>
      isStr(v) && (HEX64_RE.test(v) || (Array.isArray(v) && v.length > 0 && v.every(isStr)));
    
    function validate(report) {
      const problems = [];
      const need = (cond, path, msg) => { if (!cond) problems.push(`${path}: ${msg}`); };
    
      need(isObj(report), "$", "report must be an object");
      if (!isObj(report)) return problems;
    
      need(isStr(report.arp_version), "arp_version", "required, non-empty string (we emit \"1.2.1\")");
      need(isStr(report.agent) && AGENT_RE.test(report.agent) && report.agent.length <= 64,
        "agent", 'required, lowercase slug matching ^[a-z][a-z0-9_-]*$ (max 64)');
    
      const r = report.report;
      need(isObj(r), "report", "required object");
      if (isObj(r)) {
        need(isStr(r.summary), "report.summary", "required, non-empty sentence(s)");
        if (r.severity !== undefined) need(inEnum(SEVERITY, r.severity), "report.severity", `one of ${SEVERITY.join("|")}`);
        if (r.score !== undefined) need(typeof r.score === "number" && r.score >= 0 && r.score <= 100, "report.score", "number 0-100");
        if (r.metrics !== undefined) need(isObj(r.metrics), "report.metrics", "object");
        for (const [i, w] of (r.warnings ?? []).entries()) {
          const p = `report.warnings[${i}]`;
          need(isObj(w), p, "object");
          if (!isObj(w)) continue;
          need(isStr(w.message), `${p}.message`, "required, non-empty");
          if (w.fingerprint !== undefined) need(isFingerprint(w.fingerprint), `${p}.fingerprint`, "hex sha256 or string array");
          if (w.level !== undefined) need(inEnum(SEVERITY, w.level), `${p}.level`, `one of ${SEVERITY.join("|")}`);
          if (w.severity !== undefined) need(inEnum(WARNING_SEVERITY, w.severity), `${p}.severity`, `one of ${WARNING_SEVERITY.join("|")}`);
          if (w.status !== undefined) need(inEnum(WARNING_STATUS, w.status), `${p}.status`, `one of ${WARNING_STATUS.join("|")}`);
          if (w.category !== undefined) need(inEnum(CATEGORY, w.category), `${p}.category`, `one of ${CATEGORY.join("|")}`);
          for (const t of ["first_seen", "last_seen"]) {
            if (w[t] !== undefined) need(isStr(w[t]) && TS_RE.test(w[t]), `${p}.${t}`, "RFC 3339 timestamp");
          }
          if (w.count !== undefined) need(isInt(w.count, 1), `${p}.count`, "integer >= 1");
        }
        for (const [i, rec] of (r.recommendations ?? []).entries()) {
          const p = `report.recommendations[${i}]`;
          if (!isObj(rec)) { problems.push(`${p}: object`); continue; }
          need(isStr(rec.title), `${p}.title`, "required, non-empty");
          if (rec.fingerprint !== undefined) need(isFingerprint(rec.fingerprint), `${p}.fingerprint`, "hex sha256 or string array");
          if (rec.priority !== undefined) need(inEnum(PRIORITY, rec.priority), `${p}.priority`, `one of ${PRIORITY.join("|")}`);
          if (rec.category !== undefined) need(inEnum(CATEGORY, rec.category), `${p}.category`, `one of ${CATEGORY.join("|")}`);
          if (rec.effort !== undefined) need(inEnum(EFFORT, rec.effort), `${p}.effort`, `one of ${EFFORT.join("|")}`);
          if (rec.confidence !== undefined) need(typeof rec.confidence === "number" && rec.confidence >= 0 && rec.confidence <= 1, `${p}.confidence`, "number 0-1");
          if (rec.status !== undefined) need(inEnum(RECOMMENDATION_STATUS, rec.status), `${p}.status`, `one of ${RECOMMENDATION_STATUS.join("|")}`);
        }
      }
    
      if (report.memory !== undefined) {
        need(isObj(report.memory), "memory", "object");
        if (isObj(report.memory)) {
          if (report.memory.total_runs !== undefined) need(isInt(report.memory.total_runs), "memory.total_runs", "integer >= 0");
          for (const [i, pat] of (report.memory.learned_patterns ?? []).entries()) {
            const p = `memory.learned_patterns[${i}]`;
            if (!isObj(pat)) { problems.push(`${p}: object`); continue; }
            need(isStr(pat.pattern), `${p}.pattern`, "required, non-empty");
            if (pat.fingerprint !== undefined) need(isFingerprint(pat.fingerprint), `${p}.fingerprint`, "hex sha256 or string array");
            if (pat.status !== undefined) need(inEnum(PATTERN_STATUS, pat.status), `${p}.status`, `one of ${PATTERN_STATUS.join("|")}`);
          }
        }
      }
      if (report._meta !== undefined) need(isObj(report._meta), "_meta", "object");
      return problems;
    }
    
    const files = process.argv.slice(2);
    if (files.length === 0) {
      console.error("usage: node scripts/agent-report/validate.mjs <file.json> [more.json ...]");
      process.exit(2);
    }
    
    let ok = 0;
    let bad = 0;
    for (const f of files) {
      let parsed;
      try {
        parsed = JSON.parse(readFileSync(f, "utf8"));
      } catch (err) {
        console.error(`${f}: not valid JSON (${err.message})`);
        bad += 1;
        continue;
      }
      const problems = validate(parsed);
      if (problems.length === 0) {
        ok += 1;
        console.log(`${f}: valid (L2 profile)`);
      } else {
        bad += 1;
        console.error(`${f}: ${problems.length} problem(s)`);
        for (const p of problems) console.error(`  - ${p}`);
      }
    }
    
    if (bad > 0) {
      console.error(`${ok} valid, ${bad} invalid`);
      process.exit(1);
    }
    console.log(`${ok} report(s) valid`);
    
  • sitebase/skills/agent-reporting/templates/report-template.jsoncode · 1.3 KB
    {
      "arp_version": "1.2.1",
      "agent": "your-agent",
      "report": {
        "severity": "info",
        "summary": "One or two plain sentences: what the agent did this period, and what (if anything) is stuck.",
        "score": 100,
        "metrics": {
          "tasks_completed": 0,
          "runs": 0,
          "wall_time_minutes": 0
        },
        "warnings": [
          {
            "fingerprint": "0000000000000000000000000000000000000000000000000000000000000000",
            "message": "Example warning — replace with a real one, or delete this array entry",
            "level": "info",
            "severity": "low",
            "status": "unresolved",
            "category": "general",
            "first_seen": "2026-01-01T00:00:00Z",
            "last_seen": "2026-01-01T00:00:00Z",
            "count": 1
          }
        ],
        "recommendations": [
          {
            "fingerprint": "0000000000000000000000000000000000000000000000000000000000000000",
            "title": "Example recommendation — replace or delete",
            "details": "What to do, why, and how you would know it worked.",
            "priority": "low",
            "category": "general",
            "effort": "small",
            "confidence": 0.5,
            "status": "open"
          }
        ]
      },
      "memory": {
        "schema_version": "1.2.1",
        "total_runs": 0,
        "learned_patterns": []
      },
      "_meta": {
        "dev.example.period": { "from": "2026-01-01", "to": "2026-01-07" }
      }
    }
    
  • sitebase/skills/content-schema-design/SKILL.mdmarkdown · 4.0 KB

    name: content-schema-design description: "Model what a site stores — turn real content into a database schema, with reversible migrations and an explicit draft-to-publish path."

    Content schema design

    A site's content model is the contract between the people who write the words and the pages that render them. Settle it in the database and everything downstream — routes, templates, search, revisions, feeds — becomes mechanical. Guess at it and every later feature pays interest on the guess.

    Work from real content, not from the tables

    1. Ask the creator for five real examples of each kind of thing they publish — a published article, a product page, an event listing. Real examples expose the fields nobody thinks to mention: the byline that differs from the author, the two different date meanings ("written" vs "published"), the image credit, the field that is sometimes a link and sometimes a paragraph.
    2. Write the nouns and the arrows before any SQL: the things (article, page, product, event) and the relations (article → author, product → collection, event → venue). If a noun has no arrows, ask why it is separate.
    3. Only then choose types. Prefer the narrowest type that cannot silently accept the wrong thing, and say what an unknown value means — a nullable column and an empty string are different claims, and the creator will not know which they meant unless you ask once, up front.

    The columns every content table earns

    ColumnWhy it is not optional
    idStable, opaque, never reused. Slugs change; ids must not.
    slug (unique)The URL. Treat a slug change as a redirect, not an edit.
    statusdraft / in_review / published / archived — explicit, never inferred from a timestamp.
    created_at, updated_atUTC timestamps, set by the database where it can be.
    published_atNull until publication, and never back-dated to imply a state that never existed.
    creator referenceWho stands behind it — a foreign key, not a free-text name.

    Do not add one content blob and stop. The fields you will filter, sort or link by belong in columns; long-form prose belongs in a text column and is rendered, never parsed.

    Draft, review, publish

    • One row per thing, with status carrying the state. Versioning through a revisions table (thing_id, body, author, created_at) is cheap insurance and answers "what changed?" later.
    • Publication is a state change with a timestamp, not a copy. Two tables (articles and published_articles) mean every future query has two sources of truth to reconcile.
    • Scheduled publication is a query — status = 'published' and published_at <= now() — not a job that flips rows at midnight, which is exactly the job that is broken on a public holiday.

    Migrations

    • One migration per intent, named for the intent (add_event_venue, not changes_3).
    • Every migration states its rollback. Where the down-path is impossible (dropping a column that held data), say so in the migration's own comment — that sentence is what stops the next person guessing.
    • Additive first: add the column, backfill as a second step, drop the old one as a third after the site has run against the new shape. A single migration that renames and reformats cannot be rolled back.
    • Never edit a migration that has already run anywhere but your own machine. Write a new one.

    Traps

    • Free text where a reference belongs. "Author: Jane" becomes three spellings of Jane and a report nobody can trust.
    • Timestamps without a timezone. Store UTC; convert at the edge, and only at the edge.
    • Deleting to unpublish. Deleting the row destroys the review history and every inbound link; status = 'archived' is almost always what was meant.
    • A boolean where a state belongs. is_published cannot express "held for legal review".
    • Schema changed by hand in production. If it is not a migration in the repository, the next environment will not have it, and the site will work everywhere except where it matters.
  • sitebase/skills/creator-collaboration/SKILL.mdmarkdown · 3.4 KB

    name: creator-collaboration description: "Work with the people who write the content — intake, draft previews, review rounds and publication approval."

    Working with content creators

    The people who write the content are not users of the site; they are the reason it exists. Most site work that fails, fails here rather than in the schema, so treat the human loop as a deliverable in its own right.

    Intake: get the material, not a description of it

    • Ask for real examples first (see content-schema-design), then agree what "done" means for them: published on their schedule, seen by someone else first, or staged and waiting for them.
    • Confirm three things in writing before building: who may publish, who may approve, and what happens when nobody has replied for a week.
    • Capture material where it already lives. Ask for files, links or an export — do not retype a creator's words into a new system and become their editor by accident.

    Drafts, then agreement

    • Every change lands as a draft with a preview link the creator can open, without an account wherever possible.
    • Show the change as the creator will see it, and say in one line what changed since last time. A list of commits is not a review; a before/after of the page is.
    • Never edit a creator's words for style unless asked. Where something is factually unclear, ask a question rather than resolving it by inventing a detail.

    Review rounds

    • One round, one list. Each item says what changed, what it needs from them, and what happens if they say nothing.
    • Track state where both sides can see it — draft, in_review, approved, published — never in a thread that scrolls away.
    • When a round is late, restate the single decision needed instead of re-sending everything. Silence usually means the request was unclear, not that they disagreed.

    Publication

    • Publish only with an explicit go-ahead from the person accountable for the content — "publish it", in their words, recorded on the item.
    • Publish in one step, then confirm with the live URL and the publication time. If publication is scheduled, say when it will appear and what would cancel it.
    • Keep the provenance: who wrote it, who approved it, when it went live, and what changed after.
    • Corrections after publication are normal. Log them — what, when, at whose request — rather than quietly rewriting history.

    Reporting back

    • Plain language, no schema talk. "Your three articles are staged; two need a photo credit before they can go live" beats "three rows in review with a null credit field".
    • Lead with what they must do, then what you did, then what is blocked.
    • Never report work as finished that they have not seen. Staged is staged.
    • When something they asked for is not possible (a licence, an access level, a field the model cannot hold), say so early and plainly, with the reason and what you can do instead.

    Privacy and boundaries

    • The content is theirs. Do not reuse it outside the site it was given for, do not ship it in examples, and do not quote unpublished drafts in a report.
    • Personal data inside content — contact details, addresses, anything about a third party — is a liability rather than a field: flag it, do not republish it, and ask before collecting more.
    • Access is scoped to the work. A creator's login should let them see and edit their own material, not other people's drafts or the site's configuration.
  • sitebase/skills/website-data-layer/SKILL.mdmarkdown · 3.9 KB

    name: website-data-layer description: "Build or change the code that reads and writes a site's content database — queries, validation, caching, publishing writes and backups."

    Website data layer

    The data layer is the only code that touches the content database. Everything else — pages, templates, the editor, the API — goes through it. Holding that rule is what lets the schema change without a week of archaeology.

    One module, one way in

    • A single module (or a thin set of repositories) owns every query. Pages call functions such as getPublishedArticle(slug), never the driver.
    • No SQL in templates or request handlers. If you need a new shape, add a function.
    • Every function returns rows or a domain object — never a driver handle, cursor or open connection.

    Queries

    • Parameterise everything. String-built SQL is the defect that becomes a breach. Where a query cannot be parameterised (identifiers, sort direction), validate against an allow-list; never escape by hand.
    • Select the columns you need. SELECT * turns a schema change into an accidental API change.
    • Order explicitly: ORDER BY published_at DESC, id DESC. A tie with no second key is a page order that shuffles between requests.
    • Paginate by key once a table is large — WHERE (published_at, id) < (?, ?) — not by OFFSET.
    • Bound every list query. A page that renders "all articles" is a page that dies at a thousand rows.

    Writes

    • Validate at the boundary with an explicit schema (types, lengths, enums) before a row is built.
    • Make creating and publishing writes idempotent wherever a retry is possible: natural keys, ON CONFLICT DO NOTHING, or a request id. A double-submitted form must not create two articles.
    • Wrap multi-row writes in a transaction and let the transaction own the invariant. If an invariant cannot be expressed as a constraint, say where it is enforced.
    • Return what you wrote, so the caller never needs a second query to learn the id.

    Caching and invalidation

    • Cache read paths, never the writer, and key the cache on everything that changes the answer (slug, locale, status).
    • Invalidate by bumping a version key on write instead of hunting individual entries: a cache you cannot invalidate by hand is a cache you will be afraid of.
    • Never cache across a draft/publish boundary — status belongs in the key.
    • Time-bound every entry anyway, even with invalidation. The staleness ceiling is what saves you when the invalidation path has a bug.

    Migrations and deploys

    • Run migrations before the new code, and keep the previous code working against the new schema for the length of the deploy (expand → migrate → contract). A deploy that breaks the old code is a deploy that cannot roll back.
    • The data layer must start against an empty database, a seeded one and a copy of production. A function that only works with data present fails on the first new environment.

    Backups and safety

    • Before a destructive migration on real content: take a backup, verify the restore (not just the backup), then proceed. An unverified backup is a hope.
    • Destructive operations are explicit and named: deleteArticle may delete; save may not.
    • Work against the environment you were configured for. A task never decides for itself that production is the convenient target.

    Traps

    • The N+1 page. Render a list, then fetch each row's creator inside the loop: fine with ten rows, fatal with five hundred. Join or batch.
    • Dates as strings. Store timestamps, compare timestamps, format at the last moment.
    • Two sources of truth. A count cached in a column next to the rows it counts is a bug with a future date on it.
    • A write path with no test against a real database. In-memory fakes enforce no foreign keys and no unique indexes — the constraint you relied on is exactly what the fake does not have.