1
Architecture
Hermes Agent edited this page 2026-10-21 10:56:58 +00:00

Architecture

Module layout

dsh-vk-group/
├── package.json              # dsh.bundle manifest, engines.dsh, peerDependencies
├── cordis.patch.yml          # the profile layer this bundle contributes
├── src/
│   ├── index.ts              # apply(ctx, config) — the only entry point
│   ├── config.ts             # Config interface + Schemastery schema
│   ├── vk/
│   │   ├── client.ts         # token bucket, retry, error mapping, execute-batching
│   │   ├── methods.ts        # typed wrappers, one per VK method used
│   │   ├── errors.ts         # VkError hierarchy + code → typed error
│   │   └── longpoll.ts       # session loop, ts recovery, batch assembly
│   ├── tools/
│   │   ├── index.ts          # registerAll(ctx, deps) — single registration point
│   │   ├── community.ts
│   │   ├── members.ts
│   │   ├── moderation.ts
│   │   ├── managers.ts
│   │   ├── wall.ts
│   │   ├── comments.ts
│   │   ├── media.ts
│   │   ├── board.ts
│   │   ├── market.ts
│   │   ├── messages.ts
│   │   ├── analytics.ts
│   │   ├── infrastructure.ts
│   │   └── raw.ts
│   ├── events/
│   │   ├── inbound.ts        # VK update → agent.followup()
│   │   └── mapping.ts        # update-type table
│   ├── policy/
│   │   ├── gate.ts           # tools/pre-execute waterfall listener
│   │   ├── guard.ts          # ctx.tools.guard() monotonic denial
│   │   └── rules.ts          # rule engine
│   ├── audit/
│   │   └── log.ts            # append-only audit record on ctx.storage
│   ├── prompt/
│   │   └── section.ts        # ctx.systemPrompt.section()
│   ├── skill/
│   │   └── vk-group-ops/SKILL.md
│   └── ui/
│       ├── settings.tsx      # settings panel contribution
│       └── cards.tsx         # ConversationNodeDefinition for post/moderation cards
└── tests/

DSH extension points used, and why

Point Purpose in this plugin
ctx.tools.register(defineTool(...)) every tool; registration is an effect, so HMR and unload clean up automatically
ctx.jobs.start({kind, label, owner, run}) the LongPoll loop and long batch moderation runs
ctx.effect(fn) timers, the HTTP dispatcher, the LongPoll abort controller
tools/pre-execute returns ask for destructive calls, deny when the token lacks the required scope
ctx.tools.guard() monotonic final denial — cannot be undone by a listener registered later
tools/execute wraps dispatch to collect latency/error metrics without touching tool code
tools/result append-only audit of every mutating call
ctx.agents + agent.followup() deliver inbound VK events into the conversation as durable context
ctx.systemPrompt.section() short live summary: community name, member count, current moderation mode
ctx.storage rules, audit log, LongPoll ts, warn counters
Config (Schemastery) all tunables; validated at load, hot-replaced on edit
Skill (skill/) procedural playbooks loaded only when the agent works on the community
Client plugin (ConversationNodeDefinition) rich cards for posts and the moderation queue

Dependency direction

tools/*  →  vk/methods.ts  →  vk/client.ts  →  fetch
   ↓              ↓
policy/*      vk/errors.ts
   ↓
audit/log.ts  →  ctx.storage

Rules:

  • No tool imports fetch. All HTTP lives in vk/client.ts.
  • No tool builds a URL or a query string. vk/methods.ts owns the method names and parameter names.
  • No tool reads Config directly — apply resolves config once and passes a frozen VkContext down.
  • policy/* never calls VK. It returns decisions; the caller acts.

Lifecycle

  1. apply(ctx, config) runs after every injected service is ready.
  2. Config is validated by Schemastery; invalid config fails the load with an actionable error.
  3. The client is constructed with the resolved token bucket.
  4. Tools register (each returns a disposer that ctx owns).
  5. Policy listeners and the guard register.
  6. The prompt section registers.
  7. If longpoll.enabled, a job starts; its abort controller is owned by ctx.effect.
  8. On unload/HMR every registration is disposed in reverse order; the LongPoll job is aborted and awaited.

Why one vk_api_raw tool exists

VK ships methods faster than a plugin can wrap them, and a community manager sometimes needs one call the plugin does not model. The escape hatch is a single tool gated by safety.rawApiEnabled (default false), restricted to an allowlist of method prefixes, always audited, and never able to reach a token-scope it does not already have. It is a deliberate hole with a lock, not an oversight.