Implements the HF side of Plexum DESIGN-DYNAMIC-BLOCK.md Phase 2 — the
HarborForge knowledge-base block agents cache facts into per session.
Cross-runtime aligned with the ClawSkills workflow text (same tool
names + input schemas + return shapes will land on openclaw side later).
manifest.json:
- 5 new dynamic-kb-* tool contracts (list-kbs / list-topics /
list-facts / cache / evict)
- dynamicSubblocks contract entry declaring this plugin owns
the "kb-block" <dynamic-block> subblock
internal/kbblock/ (new):
- Per-session storage at <PLEXUM_PROFILE_ROOT>/agents/<id>/sessions/
<sid>/plugins/harbor-forge/kb-block.json
- Entry carries ID (HF backend DB primary key) + KBCode + SourceTopic
+ Content + InsertSeq for §9 #4 cache-insertion-order rendering
- Render emits <kb-fact id=N kb=<code> source=topic:<slug>>...
</kb-fact> (no title/description per §9 #8; source attr omitted
when empty)
- Fade NOT applied in v1 — §9 #3 lock has fade params shared with
memory but implementation deferred until prod data informs whether
KB needs it; agent dynamic-kb-evict is the only eviction path
- 11 unit tests
internal/kbclient/ (new):
- Typed HTTP client for HarborForge.Backend KB routes verified
against app/api/routers/knowledge.py
- GET /knowledge-bases[?project=<code>] (list KBs)
- GET /knowledge-bases/{kb_code}/topics (list topics)
- GET /knowledge-bases/{kb_code}/tree (full hierarchy — ListFacts
flattens this client-side filtered by topic ids; backend has no
flat list-facts-in-topic route)
- GET /knowledge-facts/{id} per fact (GetFacts batch loop)
- Auth: plugin-level Bearer APIKey. Per-agent hf-token resolution
is a TODO when SDK exposes secret-mgr access.
internal/tools/kb.go (new) + tools.go:
- 5 tool functions hooked into Dispatch
- KBDeps struct bundles Client + ProfileRoot + SessionFor + Turn
- Cache/evict use SessionFor lookup populated by main.go's
RenderDynamicSubblock (called per turn by host; carries sessionID)
cmd/plexum-harborforge-plugin/main.go:
- kbClient field initialized when BackendURL + APIKey present
- profileRoot cached for kbblock path resolution
- agentSession sync.Map tracks agentID → sessionID; populated by
RenderDynamicSubblock so subsequent tool calls in the same turn
can resolve the per-session kb-block.json path
- Implements sdkplugin.DynamicBlockProvider.RenderDynamicSubblock:
opens kbblock for (agentID, sessionID) and returns its Render()
body; host wraps in <kb-block>...</kb-block>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HarborForge.PlexumPlugin
Plexum-side equivalent of HarborForge.OpenclawPlugin: exposes Plexum-side telemetry to the HarborForge Monitor bridge, drives the HarborForge Calendar scheduler, and gives agents a tool surface for the same calendar lifecycle actions OpenClaw agents had.
Part of the HarborForge platform; tracked as a git submodule of the HarborForge umbrella repo.
- Plugin id:
harbor-forge(matches the OpenClaw counterpart so the backend's per-plugin schemas don't fork) - Plugin version:
0.1.0 - Activation:
eager— Monitor bridge + Calendar scheduler must be running before any agent turn fires - Plexum SDK version: requires
Plexum-sdk-gowithHostAPI.WakeAgent(commit 216cf21 or later)
What it does
- Monitor push loop — when
monitor_push_enabled: true, posts a flat telemetry payload (cpu/mem/disk/swap/load + per-agent state) to<backendUrl>/monitor/server/heartbeateverymonitor_push_interval_seconds. This replaces the standaloneharborforge-monitordaemon — the plugin's lifecycle (gateway start/stop) bounds the loop, so a separate supervisor isn't needed. Use the sameapiKeyvalue the standalone monitor'sHF_MONITER_API_KEYcarried. - Monitor bridge (optional) — HTTP server on
127.0.0.1:<monitor_port>that responds to/telemetrywith a Snapshot. Useful when the standalone monitor is still present and you want it to enrich its push payload from the plugin's view of agents. Disable by settingmonitor_port: 0. - Calendar scheduler — heartbeats
<backendUrl>/calendar/agent/ heartbeatevery interval, receives any TimeSlots due to fire, and dispatches them throughHostAPI.WakeAgent(state-aware queue with depth-1 replace-newest) - 9 harborforge_ tools* mirroring the OpenClaw plugin's surface
| Tool | Use |
|---|---|
harborforge_status |
resolved config + Monitor bridge health + Calendar status + telemetry snapshot |
harborforge_telemetry |
fresh system + agent metrics |
harborforge_monitor_telemetry |
last bridge query timing + last snapshot served |
harborforge_calendar_status |
active slot(s) + history + heartbeat clock |
harborforge_calendar_complete |
mark active slot completed (+optional summary) |
harborforge_calendar_abort |
mark active slot aborted (+optional reason) |
harborforge_calendar_pause |
pause active slot (non-terminal) |
harborforge_calendar_resume |
resume a paused slot |
harborforge_restart_status |
backend restart-pending flag + last poll time |
Install
git clone --recurse-submodules https://git.hangman-lab.top/zhi/HarborForge.PlexumPlugin
cd HarborForge.PlexumPlugin
bash scripts/install.sh # or: make install
Then in ~/.plexum/plexum.json:
{
"plugins": {
"allow": [
".",
"harbor-forge"
]
}
}
And configure at ~/.plexum/plugins/harbor-forge/config.json:
{
"backendUrl": "https://hf-api.hangman-lab.top",
"identifier": "server-t3",
"apiKey": "g1_xxx",
"monitor_push_enabled": true,
"monitor_push_interval_seconds": 30,
"monitor_port": 0,
"calendar_enabled": true,
"calendar_heartbeat_interval_seconds": 30
}
Replacing the standalone harborforge-monitor container: lift the
container's HF_MONITER_API_KEY into apiKey, set
monitor_push_enabled: true, then docker rm -f harborforge-monitor
once you've confirmed the plugin's pushes are landing (the backend's
server_states.last_seen_at should keep advancing without the
container running).
Restart the host (systemctl --user restart plexum) and verify:
plexum plugin-list | grep harbor
curl -s http://127.0.0.1:9100/health
curl -s http://127.0.0.1:9100/telemetry | jq .agents
How calendar wake works
When the backend returns a slot_to_fire in a heartbeat response:
- Scheduler builds the message from
slot.wake_options.override_messageor falls back toslot.prompt host.WakeAgent({agent_id, message, source: "calendar:slot-<id>"})- Plexum host-side
wake.Manager:- if agent's sm-state is
idle→ runs the turn synchronously in a goroutine against the agent'swakesession - else → enqueues (depth 1; new wake replaces any pending one)
- drains automatically when the running turn returns
- if agent's sm-state is
- The
sourcetag lands on the turn's faithful event so retros can tell which slot caused which turn
The agent uses harborforge_calendar_complete / _abort / _pause /
_resume mid-turn to push status back to the backend.
Layout
HarborForge.PlexumPlugin/
├── manifest.json # plugin manifest (eager, 9 tools)
├── go.mod # → Plexum-sdk-go (replace ../)
├── cmd/plexum-harborforge-plugin/ # main entry (Serve + Init)
├── internal/config/ # config.json schema + Resolve
├── internal/telemetry/ # /proc-based snapshot collector
├── internal/monitor/ # HTTP bridge for HF.Monitor
├── internal/calendar/ # types + backend client + scheduler
├── internal/tools/ # 9 tool implementations
└── scripts/install.sh # build + drop into ~/.plexum/plugins
Differences vs OpenClaw equivalent
| OpenClaw plugin | Plexum plugin |
|---|---|
api.registerTool(factory) runtime |
ToolPlugin.CallTool + manifest contract |
api.spawn({agentId, task}) |
HostAPI.WakeAgent({agent_id, message, source}) (state-aware queue) |
api.getAgentStatus() |
HostAPI.ReadAgentState(ctx, agent_id) |
--install-monitor / --install-cli flags |
n/a — Monitor + hf CLI deploy separately (e.g. via HangmanLab.Server.T3 docker compose) |
TS source compiled by tsc |
static Go binary built per-platform |