Context
Deploy autonomous Moderador de Redes (LAFH/GC-Red, per docs/research-lafh-knowledge-management.md) for a Seed Hypermedia community. Skill already authored under seed-knowledge-manager/.
Stack:
Local Seed daemon on oc.hyper.media (Go binary seed-daemon, default HTTP API on :55001, gRPC on :55002, P2P on :55000). seed-cli talks to it via -s http://127.0.0.1:55001. Means content is fetched from the network into a local store, then queried locally — no round-trip-per-call to remote hyper.media.
HKUDS/nanobot as agent runtime (Python pip, JSON config, MCP stdio, optional bwrap sandbox).
DeepSeek as LLM (OpenAI-compatible).
Custom stdio MCP wrapper around seed-cli for security envelope, rate limits, audit logging.
Telegram channel as secondary surface for ops chat with the operator (primary surface: Seed comments).
Headline goal: prove an agent can be governed by Seed documents, not local markdown.
Decisions (locked in)
Seed daemon: runs locally on oc.hyper.media (Docker image ghcr.io/seed-hypermedia/seed-daemon from backend/cmd/seed-daemon/Dockerfile, ports 55000/1/2).
Identity: agent has its own Ed25519 key (knowledge-manager, account KM_AID) stored in the same OS keyring the local daemon uses (libsecret on Linux). Owner grants WRITER on --path / of the site.
Write scope: agent may edit any document under the site. Rules doc still defines deny_write_paths (governance docs themselves, plus optional opt-out areas).
Telegram: secondary channel, operator-only (channels.telegram.allowFrom = [<OPS_TELEGRAM_ID>]). Used to chat about the agent and the server, not for community member queries.
All other prior decisions stand: HKUDS/nanobot, DeepSeek, mention-driven via Seed comments AND document mentions, 60s polling, en default locale, governance via Seed docs, bootstrap-on-first-run, full audit trail in km-logs/.
Why local daemon helps
Activity polling, citation lookups, document fetches all hit 127.0.0.1:55001 → low latency, no rate limit risk, works during hyper.media outages.
The daemon also pins / replicates the site content locally, giving us a real P2P peer rather than a thin client.
seed-cli keys live in the same OS keyring the daemon uses (per frontend/apps/cli/docs/KEYS.md), so signing operations don't require key duplication.
Cost: ~one Docker container, a few hundred MB on disk, plus the daemon's P2P traffic.
Architecture
oc.hyper.media (Ubuntu, ssh ubuntu@)
└── /home/km/ # system user `km`, linger enabled
├── seed-daemon/ # docker compose: data + config volumes
│ ├── compose.yaml
│ └── data/ # persistent state (DB, IPFS blocks, etc.)
├── .nanobot/
│ ├── config.json # providers, agents, tools.mcpServers, channels.telegram
│ └── workspace/skill/ # rsynced from repo's seed-knowledge-manager/
├── km-agent/
│ └── mcp/seed-cli-mcp/ # custom stdio MCP server wrapping seed-cli
├── km-state/ # ephemeral runtime state
│ ├── activity-cursor.json
│ ├── inbox.jsonl
│ ├── processed.jsonl
│ ├── rate-counters.json
│ └── rules.cache.json
└── km-logs/ # full audit log of every agent action
├── runs/2026-05-05T14-02Z__poll-mentions__<ulid>/
│ ├── meta.json # trigger, KM_AID, env hash, start, end, wall_ms
│ ├── trace.jsonl # ordered events with timestamps
│ ├── llm.jsonl # prompts, completions, reasoning, tokens
│ ├── tools.jsonl # MCP tool calls + latencies
│ ├── seed-cli.jsonl # argv + stdout + stderr + exit + ms
│ ├── stdout.log # raw nanobot stdout
│ └── stderr.log # raw nanobot stderr
├── current -> runs/<latest> # symlink to most recent
└── index.jsonl # one summary line per run
systemd --user units (linger keeps them alive after logout):
seed-daemon.service # `docker compose up` for the daemon
nanobot-gateway.service # `nanobot gateway`
km-bootstrap.service # one-shot, ensures governance docs exist
km-poll.timer/.service # `nanobot agent -m "/poll-mentions"` every 60s
km-boletin.timer/.service # Mon 09:00 → /run-capability boletin --period last-7d
km-health.timer/.service # 1st of month 09:00 → /run-capability network-health
km-gap.timer/.service # Wed 10:00 → /run-capability gap-detection
External: api.deepseek.com, telegram bot API, the Seed P2P network (via daemon).Governance via Seed docs (recap)
Same as previous version. Owner-editable Seed documents at fixed paths drive policy:
Rules YAML — now allow_write_paths defaults to [/] (whole site):
---
type: agent-rules
schema_version: 1
allow_write_paths: ["/"]
deny_write_paths:
- /agents/knowledge-manager/charter
- /agents/knowledge-manager/rules
- /agents/knowledge-manager/runbook
- /agents/knowledge-manager/allowlist
caps:
max_documents_per_run: 1
max_comments_per_run: 5
max_comments_per_day: 30
poll_interval_seconds: 60
mentions:
trigger: "@knowledge-manager"
invoker_source: "writer-capabilities"
moderation:
blocked_authors: []
draft_only: false
language: en
---Wrapper enforces: deny_paths > allow_paths > caps. Governance docs are always denied so the agent can't rewrite its own constraints.
Mentions, reactions, audit logging
Same as previous plan version (mention syntax @[Name](hm://aid) in comments and document blocks; replies always via comment, block-anchored where possible; full per-run logs under km-logs/runs/<id>/ with meta, trace, llm (incl. DeepSeek reasoning_content), tools, seed-cli, raw stdout/stderr; secrets redacted; km-log Bash helper for SSH browsing; logrotate 30d / 5GB).
Phased execution
Each phase ships a verifiable artifact. Run them one at a time. After each phase: smoke-test, commit code, then move on.
Phase 0 — Repo scaffolding (local, ~30min)
Outcome: directory seed-knowledge-manager/agent/ with empty placeholders + the operator README skeleton. No deploy yet.
Tasks:
Create seed-knowledge-manager/agent/{config,mcp/seed-cli-mcp,scripts,systemd,templates,logrotate} with stub files.
Stub seed-knowledge-manager/agent/README.md with the operator runbook outline.
Stub templates/agent-charter.md, agent-rules.md, agent-runbook.md, agent-allowlist.md (empty frontmatter + placeholder bodies).
Verify: tree seed-knowledge-manager/agent/ shows the layout; pnpm typecheck still passes (no real code yet).
Phase 1 — Server bootstrap (oc.hyper.media)
Outcome: server has all OS dependencies, the km user, and Docker-based Seed daemon running and synced.
Tasks:
SSH ubuntu@oc.hyper.media. Install: python3.12 python3.12-venv pipx libsecret-1-0 libsecret-tools dbus-user-session bubblewrap nodejs npm jq curl docker.io docker-compose-plugin logrotate rsync.
Create user km, loginctl enable-linger km. Add km to docker group.
As km: mkdir -p ~/seed-daemon/data && cd ~/seed-daemon. Add compose.yaml:
services:
seed-daemon:
image: ghcr.io/seed-hypermedia/seed-daemon:latest
restart: unless-stopped
ports:
- "127.0.0.1:55001:55001" # HTTP
- "127.0.0.1:55002:55002" # gRPC
- "55000:55000/tcp" # P2P
- "55000:55000/udp"
volumes:
- ./data:/data
environment:
- SEED_DAEMON_FLAGS=--data.dir=/datadocker compose up -d. Tail logs docker compose logs -f until daemon reports healthy on 127.0.0.1:55001.
Create user-systemd unit seed-daemon.service that runs docker compose up so it survives reboots.
Verify (must pass before phase 2):
curl -s http://127.0.0.1:55001/healthz (or /status) returns 200.
npx -y @seed-hypermedia/cli -s http://127.0.0.1:55001 account list works.
Daemon survives reboot.
Phase 2 — Agent identity + capability grant
Outcome: agent's Ed25519 key in keyring, owner has granted WRITER on the site, contact published.
Tasks (as km):
npx -y @seed-hypermedia/cli -s http://127.0.0.1:55001 key generate --name knowledge-manager --show-mnemonic. Capture mnemonic OFFLINE. Capture KM_AID.
On owner's machine (with owner key in their keyring):
seed-cli capability create --delegate <KM_AID> --role WRITER --path / --label "knowledge-manager" --key <owner>
seed-cli contact create --subject <KM_AID> --name "Knowledge Manager" --key <owner>From km: seed-cli account capabilities <SITE_AID> confirms KM_AID is listed as WRITER on /.
Verify:
From km: create + delete a throwaway document under any path → succeeds.
Owner sees "Knowledge Manager" appear with avatar in the site UI.
Phase 3 — seed-cli MCP wrapper + tests (local repo)
Outcome: seed-knowledge-manager/agent/mcp/seed-cli-mcp/ is a working Node MCP server with unit tests, runnable on macOS without server access.
Tasks:
Set up Node package: TS + Vitest + @modelcontextprotocol/sdk. package.json name @km/seed-cli-mcp, bin: ./dist/index.js. Workspace member of pnpm root.
Implement modules:
src/governance.ts — fetches the four governance docs via seed_get_document, parses YAML frontmatter, caches 60s, returns Rules.
src/limits.ts — path allow/deny matcher (globstar), per-run / per-day counter persistence in KM_STATE_DIR/rate-counters.json.
src/seedcli.ts — typed wrapper: runSeedCli(args), captures argv/exit/stdout/stderr/ms, redacts env values, returns structured result. Force-injects --key knowledge-manager and -s ${SEED_SERVER} for write commands.
src/audit.ts — current-run dir resolution (created at process start), append-only writers for trace, llm, tools, seed-cli JSONL streams, secret redaction.
src/mentions.ts — parses Seed activity / citations responses, classifies into mention { kind: comment|doc-block, doc, blockId?, commentId?, author, text }.
src/state.ts — cursor + inbox + processed-set helpers.
src/index.ts — registers MCP tools (seed_search, seed_query_space, seed_get_document, seed_list_comments, seed_get_activity, seed_get_citations, seed_list_capabilities, seed_get_governance, seed_create_document, seed_update_document, seed_create_comment, seed_reply_comment, inbox_pop, inbox_mark_done, cursor_get, cursor_set).
Hardcoded denylist: never accept key *, capability *, account create, document delete <governance-path>.
Tests (*.test.ts, Vitest): governance YAML parsing, deny-path beats allow-path, rate caps, denylist refusal, draft-only mode forces comment-only, mention parsing for both surfaces, redaction of env values.
Build: pnpm --filter @km/seed-cli-mcp build produces dist/index.js.
Verify:
pnpm --filter @km/seed-cli-mcp test green.
Smoke run locally: MCP_DEBUG=1 node dist/index.js then send tools/list JSON-RPC over stdin → returns the expected toolset.
Phase 4 — nanobot install + governance bootstrap
Outcome: nanobot gateway running on oc.hyper.media; agent has created the four governance docs in the site on first run.
Tasks (as km):
pipx install nanobot-ai. nanobot --version.
nanobot onboard (creates ~/.nanobot/{config.json,workspace/}).
rsync seed-knowledge-manager/ → ~/.nanobot/workspace/skill/ (read-only).
Deploy ~/.nanobot/secrets.env (mode 600):
DEEPSEEK_API_KEY=...
SEED_SERVER=http://127.0.0.1:55001
SEED_SITE=hm://...Write ~/.nanobot/config.json:
{
"providers": { "deepseek": { "apiKey": "${DEEPSEEK_API_KEY}" } },
"agents": {
"defaults": {
"provider": "deepseek",
"model": "deepseek-chat",
"systemPrompt": "You are the Knowledge Manager for ${SEED_SITE}. Always call seed_get_governance first; obey it strictly. Methodology: workspace/skill/SKILL.md and templates under workspace/skill/templates/. Default language: en (override only if charter sets language).",
"temperature": 0.2,
"timezone": "UTC",
"idleCompactAfterMinutes": 15
}
},
"tools": {
"restrictToWorkspace": true,
"exec": { "enable": true, "sandbox": "bwrap", "pathAppend": "/usr/bin:/usr/local/bin" },
"web": { "enable": true, "search": { "provider": "duckduckgo", "maxResults": 5 } },
"mcpServers": {
"seed": {
"command": "node",
"args": ["/home/km/km-agent/mcp/seed-cli-mcp/dist/index.js"],
"toolTimeout": 60,
"env": {
"SEED_SERVER": "${SEED_SERVER}",
"SEED_SITE": "${SEED_SITE}",
"KM_KEY_NAME": "knowledge-manager",
"KM_STATE_DIR": "/home/km/km-state",
"KM_LOGS_DIR": "/home/km/km-logs"
}
}
}
},
"channels": {}
}Deploy nanobot-gateway.service and km-bootstrap.service (oneshot, runs nanobot agent -m "/bootstrap-governance").
systemctl --user daemon-reload && systemctl --user enable --now nanobot-gateway.
systemctl --user start km-bootstrap.service.
Verify:
The four governance docs exist in the site at /agents/knowledge-manager/{charter,rules,runbook,allowlist}.
A run dir exists under km-logs/runs/...__bootstrap__<ulid>/ with meta.json, trace.jsonl, seed-cli.jsonl showing the four document create calls.
Phase 5 — Mention polling + reaction
Outcome: writer mentions in comments and documents trigger an agent reply within 2 minutes.
Tasks:
Deploy km-poll.timer/.service (60s cadence).
System prompt addendum (committed in repo): the /poll-mentions flow:
seed_get_governance → rules.
seed_list_capabilities ${SEED_SITE} → writer set.
cursor_get → token.
seed_get_activity --token <cursor> → events. Filter mentions to KM_AID.
For each mention, if author ∈ writers and not in processed.jsonl → inbox_pop queue.
For each queued mention: classify against capabilities 1–7 in SKILL.md; respond via seed_reply_comment (comment-mention) or seed_create_comment <doc>#<blockId> (doc-mention).
cursor_set newer.
Deploy km-log Bash helper to /home/km/.local/bin/km-log.
Deploy logrotate user config.
Verify:
From a writer account, comment @[Knowledge Manager](hm://<KM_AID>) what does this community know about X? → reply within 60s.
From the same account, edit a doc and add inline mention → block-anchored comment within 60s.
From a non-writer account, mention → ignored, processed.jsonl records reason: not-allowed.
km-log tail shows full event sequence with timestamps and DeepSeek reasoning_content.
Phase 6 — Scheduled LAFH cadences
Outcome: weekly bulletin, weekly gap report, monthly health report run on schedule.
Tasks:
Deploy km-boletin.{timer,service} (Mon 09:00 UTC, message /run-capability boletin --period last-7d).
Deploy km-gap.{timer,service} (Wed 10:00 UTC, message /run-capability gap-detection --period last-7d).
Deploy km-health.{timer,service} (1st of month 09:00 UTC, message /run-capability network-health --period last-30d).
Add system prompt support for /run-capability <name> --period <range> (maps capability name → SKILL section).
Verify:
systemctl --user start km-boletin.service produces a doc at /agents/knowledge-manager/state/boletin/<YYYY-WW>.
systemctl --user start km-health.service rewrites /agents/knowledge-manager/state/network-health.
Each run has its own dir under km-logs/runs/.
Phase 7 — Telegram secondary channel
Outcome: operator can chat with the agent over Telegram about agent status, force a poll, query logs, toggle kill-switch.
Tasks:
Create Telegram bot via @BotFather → token. Get operator's Telegram user ID.
Add to secrets.env: TELEGRAM_TOKEN=..., OPS_TELEGRAM_ID=<numeric>.
Edit ~/.nanobot/config.json:
"channels": {
"sendProgress": true,
"telegram": {
"enabled": true,
"token": "${TELEGRAM_TOKEN}",
"allowFrom": ["${OPS_TELEGRAM_ID}"]
}
}
systemctl --user restart nanobot-gateway.
Extend the agent's system prompt with operator-only verbs (/status, /poll-now, /show-rules, /last-runs, /draft-only on|off). These never write to Seed; they only read state and emit human-formatted Telegram replies.
Document in operator README that allowFrom is locked to ops only — community members must use Seed comments, not Telegram.
Verify:
/start on Telegram returns a welcome.
Send /status from operator's account → bot responds with daemon health, last run time, comments-today count.
Send /status from a different Telegram account → silently ignored (allowFrom enforced).
Send /draft-only on → check next mention reply is a draft comment.
Phase 8 — Audit-log polish + verification suite
Outcome: full smoke test of every flow; logs make sense from SSH.
Tasks:
Run all 19 verification steps end-to-end (combined from earlier plan plus Telegram).
Capture screenshots / log excerpts in seed-knowledge-manager/agent/README.md.
Phase 9 — Future / deferred (not in v1)
Broader "members can invoke" mode (mentions.invoker_source: allowlist-doc).
Seed-native skill files (currently on disk).
Heartbeat-style autonomous to-dos in Seed (no checkbox primitive yet).
Multi-site moderation.
Web UI for browsing logs.
Files to create / modify (in this repo, across phases)
seed-knowledge-manager/ — already exists; no changes.
seed-knowledge-manager/agent/config/config.json — nanobot config template (Phase 4).
seed-knowledge-manager/agent/seed-daemon/compose.yaml — local daemon compose file (Phase 1).
seed-knowledge-manager/agent/mcp/seed-cli-mcp/ — Node MCP server + tests (Phase 3).
seed-knowledge-manager/agent/systemd/{seed-daemon,nanobot-gateway,km-bootstrap,km-poll{.service,.timer},km-boletin{.service,.timer},km-health{.service,.timer},km-gap{.service,.timer}} — all systemd units.
seed-knowledge-manager/agent/scripts/install.sh — idempotent provisioning script.
seed-knowledge-manager/agent/scripts/km-log — Bash log helper.
seed-knowledge-manager/agent/templates/{agent-charter,agent-rules,agent-runbook,agent-allowlist}.md — bootstrap seeds.
seed-knowledge-manager/agent/logrotate/km-logs.conf — user logrotate (30d / 5GB).
seed-knowledge-manager/agent/README.md — operator runbook (key gen, capability grant, env vars, deploy, kill-switch, log paths, Telegram setup, daemon volume backup).
Reuses (no changes):
backend/cmd/seed-daemon/Dockerfile and the published image — local daemon.
frontend/apps/cli/docs/CLI-REFERENCE.md, KEYS.md, README.md — seed-cli surface, mention syntax, key storage.
seed-knowledge-manager/SKILL.md, templates/, references/ — methodology.
docs/research-lafh-knowledge-management.md — theory.
Verification (full v1 suite)
1–2. Daemon up locally; seed-cli account list works against http://127.0.0.1:55001.
3. Agent key generated; capability granted; contact published; throwaway doc round-trip.
4. MCP wrapper unit tests green; manual tools/list smoke run works.
5. Bootstrap creates four governance docs; run dir under km-logs/.
6. Comment-mention from writer → reply ≤60s, block-anchored where applicable.
7. Document-mention from writer → block-anchored comment reply ≤60s.
8. Mention from non-writer → ignored, recorded.
9. draft_only: true → reply is draft comment, no document writes.
10. deny_write_paths add → write attempt declines with explanatory comment.
11. Weekly bulletin manual trigger creates state/boletin/<YYYY-W
12. Health report manual trigger rewrites state/network-health.
13. Capability revoke → next-tick writes start failing with logged errors.
14. Wrapper unit tests green in CI.
15. km-log tail works; meta.json.wall_ms, per-tool latency_ms, DeepSeek reasoning_content all present.
16. Grep km-logs/ for DEEPSEEK_API_KEY value → zero matches.
17. logrotate -d dry-run on user config exits clean.
18. Telegram /status from ops → returns; from non-ops → ignored.
19. /draft-only on from ops → next mention reply is a draft.
Sources:
backend/cmd/seed-daemon/Dockerfile
backend/config/config.go (default ports 55000/1/2)
frontend/apps/cli/docs/CLI-REFERENCE.md
frontend/apps/cli/docs/KEYS.md
frontend/apps/cli/README.md (mention syntax + block-level comments)
seed-knowledge-manager/SKILL.md
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime