npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hivemagnus/ardent

v3.0.0

Published

Headless TypeScript agent runtime with A2A, scheduled jobs, skills, chat channels, and a plugin API

Readme

Ardent

Ardent is a headless TypeScript agent runtime. One process serves one profile: a directory with a YAML config, a persona (SOUL.md, IDENTITY.md), skills, and private SQLite state. Ardent runs model turns against an OpenAI-compatible chat server (llama.cpp, vLLM, ...) or the Codex App Server, calls typed tools, and keeps a durable journal of every task.

What you get:

  • Durable tasks with idempotency keys, cancellation, deadlines, and a per-task event journal in SQLite. Interrupted work is marked unknown, never silently replayed.
  • Management API (Hono, bearer-token protected) for tasks, sessions, config, identity, skills, peers, channels, and live event streams, including the reply text as the model writes it. The OpenAPI document is served at /openapi.json and pinned in openapi/ardent.json.
  • A2A protocol 1.0 server and client over JSON-RPC, HTTP+JSON, and gRPC, with authenticated peers, push notifications, and bounded "team missions" that scope which tools a peer may reach.
  • Scheduled jobs (cron with IANA timezones, or fixed intervals) with exactly-once window receipts.
  • Skills: Markdown working methods the model can read on demand.
  • Context compaction for long sessions, driven by a model policy file.
  • Optional integrations: news research (SearXNG + Firecrawl), HiveMem long-term memory, Telegram and Mattermost chat channels, read-only Magnus Control tools, and Hermes Agent team-card compatibility. Each one is off until you configure it.
  • Plugins: add tools, API routes, CLI commands, and job handlers from a separate npm package.

Context fitting and checkpoint compression use the pinned @hivemagnus/emberfold library directly; the optional Emberfold HTTP service is not required. Ardent retains authoritative SQLite history, transactional publication, model-policy fences, task eligibility, and journal events. Existing checkpoints stay readable by the previous runtime; richer portable metadata is stored in an additive emberfold_checkpoint_metadata sidecar table. The initial integration retains existing budgets and uses the general continuity profile.

For existing installations that must retain their runtime extensions, the node manager offers context-only adoption. It stages the pinned adapter and engine without replacing the runtime, runs isolated provider acceptance, and verifies an actual old-runtime rollback.

Install

Ardent needs Node.js 24.22.1+ or 26.13.0+. Its pinned @photostructure/sqlite driver bundles SQLite 3.54.1 and stable Node-API binaries for supported x64/arm64 platforms. Existing profile databases keep their format and journal history. Node.js 26 is recommended for its patched built-in libraries.

Version 3 replaces the low-level TaskStore.db driver surface with the application's storage boundary. Code that used better-sqlite3-specific methods must migrate; the management API, CLI and plugin hooks retain their existing contracts.

npm i -g @hivemagnus/ardent
ardent init --non-interactive            # creates ~/.ardent/profiles/assistant
ardent doctor                            # lists unresolved configuration

init copies the bundled assistant template, sets the profile timezone to the host's zone, and writes a random management token to the profile's .env. Use --profile NAME (or --profile-path /abs/dir) to create and select other profiles, and --api-port N to choose the management port. ARDENT_HOME overrides the ~/.ardent root.

Point the profile at a model, then talk to it:

cat >> ~/.ardent/profiles/assistant/.env <<'EOF'
ARDENT_LLAMA_BASE_URL=http://127.0.0.1:8080/
ARDENT_LLAMA_MODEL=your-model-name
EOF
ardent chat                              # interactive session
ardent run --prompt "Summarize RFC 9110 in three bullets"
ardent mission submit --mission-file mission.json   # a team mission, see below
ardent gateway run                       # management API, A2A, channels, jobs

ardent help lists every command.

Native suite artifact

Build this repository's Dockerfile from the repository root, then select that reviewed immutable image ID or digest in a Magnus checkout:

npm run build:native-agent -- --product ardent --image PINNED_IMAGE --output NEW_DIRECTORY

The archive includes the exact Node binary, the public Ardent package, locked production dependencies and their licenses. It contains no inference runtime or models. The descriptor supplies literal launch arguments, an exact version preflight and a read-only acceptance command for Hive's signed release matrix. Local export does not publish or certify an artifact.

Select an existing profile explicitly with --profile-path and keep credentials in an owned, mode 0600 environment file selected with --env-file. The native launcher loads that file before Node starts, including NODE_EXTRA_CA_CERTS when a private CA is required. It never evaluates the file as shell code. Profile initialization is a separate operator action and refuses an existing profile. Provider sign-in remains explicit.

ardent --env-file FILE --profile-path DIR acceptance checks the running gateway's authenticated status, exact version, profile, backend and dependency readiness. It returns bounded JSON without credentials or upstream diagnostics. The default target is the profile's loopback management listener; an optional ARDENT_ACCEPTANCE_URL must be an HTTPS origin, with an optional protected ARDENT_ACCEPTANCE_CA_FILE. Redirects and nonlocal cleartext are refused. Acceptance executes no model generation or tools. Verify scoped inference, memory, skills and channel workflows separately before enabling consumers.

Configuration

config.yaml (schema 1) is validated strictly; unknown keys are rejected. A string of the form ${NAME} is resolved from the environment (the profile .env is loaded automatically). Secrets are never written to the config, only the names of the environment variables that hold them.

| Key | Default | Notes | |---|---|---| | profile, name | required | Profile ID and display name. | | product | Ardent | Shown in the profile summary. | | timezone | host zone, else UTC | IANA zone used for dates shown to the model. Job schedules carry their own zone. | | identity.soul_file, identity.identity_file | SOUL.md, IDENTITY.md | Persona files in the profile. | | execution | required | backend: llamacpp \| codex, step, tool-call, parallelism, and deadline limits. | | providers.llamacpp | required | base_url, model, api_key_env, request_timeout_seconds, optional context_window_tokens, max_output_tokens, reasoning_effort. | | providers.codex | required | command, model, auth_mode: chatgpt \| api_key (default chatgpt); approval_policy: never and sandbox: read-only are fixed. | | memory | { provider: none } | See HiveMem. | | research | disabled | enabled, searxng_url, firecrawl_url, firecrawl_token_env. Enables the news_search, news_article, news_collect, and news_capabilities tools. | | skills | directories: [skills], enabled: [] | Only enabled skills are shown to the model. | | a2a | disabled | See A2A. | | channels.telegram, channels.mattermost | disabled | See Chat channels. | | integrations.magnus_control | disabled | See Magnus Control tools. | | artifacts.max_bytes | 2097152 (2 MiB) | Largest artifact a task may store, up to 64 MiB. See Team missions. | | plugins | [] | See Plugins. | | api | 127.0.0.1:9910, ARDENT_API_TOKEN | Management listener and the env var holding its bearer token. A non-loopback host is refused; put a TLS proxy in front instead. |

ardent config schema prints the full JSON Schema, and ardent config validate checks a profile without starting it.

HiveMem memory

Memory is off by default. To use a HiveMem server:

memory:
  provider: hivemem
  base_url: ${HIVEMEM_API_URL}
  token_env: HIVEMEM_TOKEN
  agent_id: assistant
  workspace_id: ${HIVEMEM_WORKSPACE}
  default_scope: private

Direct conversations then get hivemem_status, hivemem_search, hivemem_browse, and explicit write tools. Peers never read private memory; a validated team mission may grant read access to one shared project:<id> scope.

To rotate the HiveMem credential without a restart, write the new token into the profile environment file (.env, or the --env-file the gateway started with) and call POST /api/v1/memory/credential/reload with no body or {}. The gateway re-reads only the token_env variable, checks that it is a well-formed JWT, and uses it for every later HiveMem request; requests in flight finish with the previous token. It answers {"reloaded": true, "token_env": "...", "changed": true|false}, 409 when memory is not hivemem, and 422 (the old token stays in use) when the new value is missing or malformed.

Chat channels

channels:
  telegram:
    enabled: true
    token_env: TELEGRAM_BOT_TOKEN
    allowed_user_ids: [123456789]     # only these users are answered
  mattermost:
    enabled: true
    base_url: ${MATTERMOST_URL}       # e.g. https://chat.example.com
    token_env: MATTERMOST_TOKEN
    allowed_user_ids: [user-id]
    require_mention: true
    reply_mode: thread                # thread | flat

Telegram uses long polling; Mattermost uses its v4 REST and WebSocket APIs. Both support /help, /new, /status, /jobs, /model, /stop, /tasks, and /sources. Each inbound message and outbound reply is journaled, so a restart never sends the same reply twice.

A2A

a2a:
  enabled: true
  bindings: [jsonrpc, http-json, grpc]
  public_url: ${ARDENT_A2A_PUBLIC_URL}   # advertised origin, e.g. https://agent.example.com:9900
  listen_host: 127.0.0.1
  listen_port: 9900                      # gRPC listens on listen_port + 1
  peers_file: peers.yaml
  skill: { id: research, name: Research, description: Sourced answers, tags: [research] }
  hermes_compat: false
  rate_limit:                            # all optional; these are the defaults
    enabled: true
    ip_requests_per_minute: 600          # every HTTP request, before authentication
    ip_burst: 120
    peer_requests_per_minute: 300        # after authentication, per peer (also gRPC)
    peer_burst: 60
    max_concurrent_tasks_per_peer: 16    # queued and running tasks
    trusted_proxies: []                  # proxies whose X-Forwarded-For is believed

A limited request is answered with 429 and Retry-After. The client address is the socket address unless the connecting address is listed in trusted_proxies (addresses or CIDR ranges). Behind a reverse proxy, all clients otherwise share the proxy's address and one bucket, so list the proxy there. gRPC requests count against the peer limit and the task cap; the address limit covers HTTP only. Limits are kept in memory, per process.

Peers are listed in peers.yaml with the environment variables holding their inbound and outbound bearer tokens. An ordinary peer task can reach only the public news tools. A peer that sends a validated team mission envelope gets the tools its resources allow (public:news, magnus:fleet, magnus:node:<id>, or tool:<name> for any registered tool) and must return a structured, evidence-checked team result.

hermes_compat: true additionally serves the authenticated /.well-known/hermes-team-card.json discovery document and accepts Hermes Agent's slash-style JSON-RPC method aliases (message/send, tasks/get, ...).

ardent a2a discover <peer> and ardent a2a call <peer> --prompt TEXT talk to configured peers from the command line.

Magnus Control tools

Read-only fleet tools for a Magnus Control API (fleet inventory, readiness, node health, dependency impact, anomalies, command status, and more). Enable them with integrations.magnus_control.enabled: true or ARDENT_MAGNUS_CONTROL_TOOLS=true. The base URL comes only from integrations.magnus_control.base_url (default ${ARDENT_MAGNUS_API_URL}) and the read token from the env var named by token_env (default ARDENT_MAGNUS_READ_TOKEN). Bearer tokens are sent only over HTTPS or to loopback.

Scheduled jobs

Put job definitions in jobs/active.json inside the profile:

{
  "schema_version": 1, "profile": "assistant", "revision": 1,
  "jobs": [{
    "id": "morning-news", "owner": "ardent", "enabled": true, "handler": "news.collect",
    "schedule": { "cron": "30 6 * * *", "timezone": "Europe/Berlin", "missed_window": "skip", "catch_up_minutes": 0 },
    "input": { "query": "open source AI agents", "max_articles": 3 },
    "delivery": { "kind": "none" },
    "policy": { "max_attempts": 2, "retry_backoff_seconds": 300 }
  }]
}

ardent jobs definitions validate --source /abs/path.json checks a file before you install it; ardent jobs definitions status and GET /api/v1/jobs/definitions/status report the installed revision and recent windows. The built-in handler is news.collect (requires research); plugins add more.

Model policy

An optional, owner-only $ARDENT_HOME/model-policy/active.json selects the Codex model and reasoning effort and configures context compaction for all profiles under that home. model-policy.default.json is a starting point; compaction is disabled until you enable it.

Live task streams

GET /api/v1/tasks/{id}/stream is a server-sent event stream. It sends:

| Event | Data | Notes | |---|---|---| | task.event | A journal event | The event id is the journal sequence. Resume with Last-Event-ID or ?after= | | task.text | {step, text}: the visible reply text of the current model step | Sent on connect and when a new step starts showing text | | task.delta | {step, text}: text to append | | | task.done | The finished task | Its result is the authoritative answer |

The stream closes after 60 seconds; reconnect to continue.

Live text works the same for every backend and caller:

  • It holds only what a user would see. Structured output, such as tool or research JSON, is never streamed.
  • It is not journaled and has no event id. A reconnect receives a task.text snapshot instead of a replay.
  • Codex sends its answer as one piece when the turn completes.

ardent chat uses the same rule to print replies as they arrive.

Team missions

POST /api/v1/missions runs a team mission locally, for a coordinator such as a project studio. It is accepted like a task (202 {task_id, state}) and streams on /api/v1/tasks/{id}/stream like any task.

{
  "objective": "Draft the opening scene",
  "mission": { "id": "scene-1", "revision": 1, "parent_task": null, "constraints": [],
    "allowed_actions": ["observe", "analyze", "create_artifact"],
    "resources": ["project:adventure", "tool:artifact_write", "tool:team_handoff"],
    "deadline": 1893456000, "acceptance_criteria": ["The scene is saved"], "expected_artifacts": [] },
  "context_items": [{ "title": "Brief", "mime": "text/markdown", "text": "..." }],
  "labels": { "project": "adventure" },
  "session_id": "<a session opened with context: mission>",
  "idempotency_key": "scene-1-turn-1"
}
  • mission is the A2A team mission shape. The task runs in context mission, and its tools follow the peer mission rule: resources grant tools, never the model.
  • The objective is at most 16,000 characters; context_items together at most 64 KiB. Context items reach the model as tool data, so the context fitter can shorten them; the full text stays in the task journal.
  • Memory reads use the agent's private scope plus one project:<id> grant. Missions never write memory.
  • A session for a mission is opened with POST /api/v1/sessions {key, context: "mission"}.
  • The task result is a TeamResult JSON document. A prose reply becomes a partial result with the reply as summary; a reply carrying a team_result is checked against the mission and the observed artifacts.

Mission tools are granted with tool:<name> resources. The coordination tools only record a journal event; Ardent never acts on them itself:

| Tool | Journal event | Notes | |---|---|---| | team_handoff {to_role, objective, deliverables?, note?} | team_handoff | Asks the coordinator to give work to another role | | team_ask_user {question, options?} | team_ask_user | Ends the turn; the result is input_required with a clarification | | team_propose_decision {title, statement, rationale?} | team_propose_decision | A proposal; only the user decides | | artifact_write {name, mime, text \| base64 \| workspace_path, revises_sha256?} | artifact_registered | Markdown, text, JSON, CSV, SVG, PNG, JPEG, WebP, PDF or DOCX, up to artifacts.max_bytes. workspace_path reads a file inside the profile's workspace | | document_render {source_markdown, format: pdf \| docx, name, revises_sha256?} | artifact_registered | Markdown to PDF or DOCX, up to artifacts.max_bytes; see below | | development_note {kind, title, body, sources?} | development_note | Also available in direct tasks; see below |

document_render reads GitHub-flavoured Markdown: headings, paragraphs, bold, italic, strikethrough, inline code, links, bulleted, numbered and task lists, block quotes, code blocks, tables and horizontal rules. It never fetches a URL or reads a file. PDF output draws ```svg blocks as vector figures; images keep only their alternative text. DOCX output is written with @hivemagnus/canvas-word-codec:

  • Headings use Word's Heading 1-6 styles, so they appear in the navigation pane; links to http(s) URLs become hyperlinks and other link targets are kept as text after the label.
  • Bulleted lists and numbered lists that start at 1 use Word numbering. Word numbering here has one level, so nested lists are indented, and a numbered list that starts elsewhere or has nested content gets its numbers as text.
  • Tables keep their header row, column alignment and inline formatting; a table longer than 100 rows continues in another table.
  • PNG and JPEG images given as data: URIs are embedded. Other images become an [image: alt] placeholder and ```svg figures a short note, since DOCX output does not embed vector figures.
  • Raw HTML is kept as literal text. A document beyond the DOCX exchange limits (for example more than 100 table columns) fails with DOCX_RENDER_FAILED and no effect.

Every registered artifact has an immutable manifest (digest, size, media type, revision, provenance and the artifact it supersedes). revises_sha256 makes a new artifact a revision of an earlier one. GET /api/v1/tasks/{id}/artifacts lists a task's manifests and GET /api/v1/artifacts/{sha256}/manifest returns the newest manifest for a digest; the bytes stay at GET /api/v1/artifacts/{id}.

development_note stores an append-only development record: the agent's own note about how it works (epistemic: agent_reported).

Pause, preview and development history

Pause and resume. POST /api/v1/tasks/{id}/pause holds a queued or running task: a queued task does not start (later tasks run instead), and a running task stops at its next step boundary, before a model call, before a tool call is journaled, or before it settles. The journal records pause_requested, paused (with the boundary) and resumed. Both routes are idempotent, and the deadline keeps running while a task is paused. POST /api/v1/tasks/{id}/resume releases it. Locally, ardent tasks pause <id> holds a queued task and ardent tasks resume <id> releases and runs it.

Preview. POST /api/v1/preview/tasks {prompt, overrides: {soul?, identity?, skills_enabled?, model?}, session_id?} runs one task in context preview with unsaved changes. A preview gets the read-only direct tools, no mission tools and no memory tools. It never writes memory, development records, identity or configuration, and any artifact it produces expires after 24 hours. It streams like any task; a session for it is opened with context: "preview". ardent preview --prompt TEXT [--soul-file FILE] [--identity-file FILE] [--skills a,b | --no-skills] [--model NAME] does the same locally.

Development history. GET /api/v1/development?after=&limit=&kind= returns the agent's development in one portable record shape, oldest first, with a next cursor:

{"id": "dev_...", "agent_ref": "assistant", "kind": "self_understanding", "title": "...", "body": "...",
 "author": "agent", "epistemic": "agent_reported", "before_sha256": null, "after_sha256": null,
 "sources": [{"kind": "task", "ref": "..."}], "created_at": "2026-11-08T12:00:00.000Z"}

It merges:

  • the agent's development_note records (agent_reported);
  • operator notes from POST /api/v1/development (operator_reported);
  • skill selection changes, with the configuration revisions before and after (observed);
  • soul and identity edits from the retained identity history (observed);
  • research method reflections (observed).

agent_ref is the profile name. ardent development list [--after CURSOR] [--limit N] [--kind KIND] prints the same pages.

Plugins

A plugin is an ES module whose default export (or named plugin export) is an ArdentPlugin object, or an array of them:

import { definePlugin } from '@hivemagnus/ardent';
import { z } from 'zod';

export default definePlugin({
  name: 'weather',
  registerTools(ctx) {
    ctx.tools.register(ctx.defineTool({
      name: 'weather_now', description: 'Current weather for a city.',
      input: z.object({ city: z.string().min(1) }).strict(),
      output: z.object({ summary: z.string() }),
      effect: 'read', contexts: ['direct'], timeoutMs: 10_000,
      handler: async ({ city }, signal) => ({ summary: await lookUp(city, signal) }),
    }));
  },
  routes(app, ctx) {                       // served under /api/v1/plugins/weather/
    app.get('/health', c => c.json({ ok: true, profile: ctx.config.profile }));
  },
  cliCommands: {                           // ardent weather-check
    'weather-check': { description: 'Probe the provider', run: async ({ print }) => print({ ok: true }) },
  },
  jobHandlers: ctx => ({ /* 'weather.daily': JobHandler */ }),
  jobOwnership: ctx => async (job, now) => { /* throw to refuse a job window */ },
  peerTool: (tool, task, envelope) => false, // allow a peer to call one of your tools
});

Load it from config, as an npm package name (resolved from the profile directory first, then from Ardent's own install), an absolute path, or a file:// URL:

plugins: ['@example/ardent-weather', /opt/ardent/plugins/local.mjs]

Embedders that build the runtime themselves can call registerPlugin() before loading plugins. Plugin tools join the profile's shared tool registry; a tool's contexts decides whether direct, peer, or scheduled tasks may call it, and a peer still needs an explicit mission grant (tool:<name>) or peerTool. Plugin routes sit behind the same bearer-token check as the rest of the management API.

Security model

  • The model never gets a shell, deployment, or credential tool from Ardent itself. Retrieved text, memories, and peer messages are treated as data.
  • Every tool declares its effect (read, artifact-write, memory-write, external-write). A cancelled task whose tool call may have had an effect is marked unknown, not cancelled, and is never retried automatically.
  • Profile state, .env, and config are created owner-only (0600/0700).
  • News URLs are restricted to public HTTP(S) addresses; private and loopback ranges are refused.

Development

npm ci
npm run hooks:install   # privacy and secret checks on commit and push
npm run typecheck
npm run lint
npm test
npm run build
npm run openapi:check
npm run pack:check      # packs, installs, and smoke-tests the tarball
npm run verify          # every gate CI runs, in order

See AGENTS.md for contributor guidance.

License

Apache-2.0. The vendored A2A protocol definition in protocol/ carries its own license, protocol/LICENSE-A2A.txt.

Interoperability checks

Run npm run interop from this standalone checkout. The runner installs pinned Python A2A SDKs and checks out an exact public Hermes revision in an ignored cache; Python is used only for these tests. Both supported Node versions run this suite in CI.

The suite exercises the real Python SDK and Hermes wire adapter in both client/server directions. Team missions, lifecycle and child-budget transactions use independent Python reference peers in test/fixtures/team_reference.py. They implement the published Ardent wire contract; they do not load an external Hermes team plugin or require a sibling Magnus checkout. The compatibility probe explicitly enables hermes_compat and allows its combined functional workload through the fixture's rate buckets. Default throttling remains covered by the separate rate-limit tests.