Skip to content

Repository files navigation

@hanfani/server

build status npm version npm downloads bundle JSDocs License

Runtime services for the Hanfani agent framework — the layer above the headless @hanfani/core engine.

The agent proposes, an approver decides, the server acts. The approver is a human — or, when the user enables it in settings, a deterministic rule the user configured. The model never approves unless you authorize to do so.

Where @hanfani/core defines what a workflow and an agent are (pure, no I/O), @hanfani/server provides the runtime that observes and serves them: the audit grammar the server emits, an audit-log store and activity bus, a policy-monitor (objectives) engine, usage reporting, connection profiles, and a settings-driven auto-approval mechanism. Every module is generic — domain specifics are injected — so any workflow can use them.

Determinism

The framework's aim is that the same inputs always produce the same outcome: same objective config + same ordered activity ⇒ same result, every time. Completion is decided by server facts (post-approval effect events), never by model prose. The one naturally non-deterministic step is human approval — so auto-approval (below) exists to make that branch deterministic too, by the user's choice, without ever letting the model approve its own actions.

Install

pnpm add @hanfani/server @hanfani/core

hono is an optional peer dependency, needed only if you use the HTTP route registrars (registerAuditRoutes, registerUsageRoute, registerObjectivesRoutes, registerConnectionProfileRoute).

What's inside

The activity grammar — owned here

The audit stream carries only a kind and a free-text summary. The exact shape of that summary is a contract between whoever writes an audit entry (the server's action ledger) and whoever reads it (objective monitors, the UI, an auto-approver). Because the server emits it, the server owns it — and the emitter and decoder live in the same module so they cannot silently drift:

import { formatActivitySummary, parseActivityEvent } from '@hanfani/server'

const summary = formatActivitySummary({ kind: 'resolved', decision: 'approved', tool: 'saveDraft' })
// → "approved saveDraft"

parseActivityEvent({ kind: 'resolved', summary }, { tools: ['saveDraft'] })
// → { kind: 'resolved', tool: 'saveDraft', decision: 'approved', structured: true }

The grammar:

kind Summary format Example
gate the bare tool name saveDraft
resolved approved <tool> | rejected approved saveDraft
effect executed <tool> executed saveDraft
finished finished finished

parseActivityEvent is generic — tool is a plain string; pass { tools } to narrow/validate it. A parse(format(x)) round-trip test locks the pair together.

Audit-log store + activity bus

import { createAuditStore, registerAuditRoutes, startAuditBridge } from '@hanfani/server'

const audit = createAuditStore(myStorePort, { actorOf })   // dedup + subscriber bus
registerAuditRoutes(app, audit)                            // GET /api/audit-log (+ /stream)
startAuditBridge({ baseUrl, onEntry: audit.persist })      // consume a pipeline SSE stream

The store logic (dedup window, the in-process bus, the human-actor hook) is framework-owned; you bind the three-method AuditStorePort to your database.

Objectives — a policy-monitor engine

A deterministic monitor over a workflow's activity: given a trigger and a success rule, did the server observe the right facts? Completion is decided by server facts (post-approval effect events), never by model prose. Every domain decision is delegated to an ObjectivePolicy you implement; the engine drives the run lifecycle (create → progress → gate → resolved → success/fail → deadline).

import { createObjectivesEngine, createInMemoryObjectivesStore } from '@hanfani/server/objectives'

const engine = createObjectivesEngine(myEmailPolicy, {
  store: createInMemoryObjectivesStore(), // or your DB-backed ObjectivesStore
  loadPayload,
  emitActivity: audit.persist,
})
audit.subscribe((entry) => void engine.evaluateActivityEntry(entry))

Auto-approval — the determinism mechanism

Auto-approval turns the human-approval branch into a deterministic one by the user's intent. The user enables it in settings and configures declarative rules; a matching gate is then resolved by a pure rule instead of a human click. The decision is a pure function — same settings + same context ⇒ same decision — so approvals replay reproducibly. The model is never in this path; approval authority is the human or the human's pre-declared settings.

import { matchAutoApproval, startGateAutoApprover } from '@hanfani/server'

const settings = {
  enabled: true, // the master switch, toggled from user settings
  rules: [{ id: 'trusted-sender', agent: 'reply', tool: 'saveDraft', when: (c) => trusted.has(c.extra.senderEmail) }],
}

// Pure decision (testable, replayable):
matchAutoApproval(settings, ctx) // → { approved: true, ruleId: 'trusted-sender' }

// Runtime bridge — reads settings live, resolves matching gates, records the deciding rule:
startGateAutoApprover({ subscribe: audit.subscribe, baseUrl, settings: () => settings, resolveContext, onAutoApproved })

Usage & connection profiles

  • getUsageSummary / registerUsageRoute — month-to-date Anthropic spend vs. a budget.
  • getConnectionProfile — the never-connected vs. session-expired vs. store-unavailable state machine, with an injected identity enrich hook.

Dependencies

Depends on zod (schemas) at runtime. @hanfani/core and hono (the HTTP route registrars) are optional peers — pull them in only for the features that use them.

Docs

An auto-generated API reference is available at jsdocs.io. Full guides are coming soon in the Hanfani framework docs.

License

MIT License © Fruitizz

About

Secure backend runtime for governed AI agents and workflow orchestration.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Packages

Contributors

Languages