@crewrouter-agent/node
v1.0.0
Published
Deployment-neutral Node.js agent runtime for private, structured LLM workloads.
Maintainers
Readme
CrewAgent
CrewAgent is a small, deployment-neutral Node.js library for running structured LLM advice behind a private network boundary. It provides the shared mechanics around a model without claiming authority over the business system using it.
Author and attribution
CrewAgent is authored and maintained by Casey Stalnaker. Learn more at caseystalnaker.com.
The model recommends. Deterministic application code decides what is valid and performs any real action.
Status: the checked-out source identifies itself as
1.0.0, the first stable CrewAgent package line. The serialized advice contract remains version2, service metadata remains schema version1, and the unpublished0.2.1and0.3.0archives remain historical downstream checkpoints.
Why this exists
Most LLM integrations combine provider access, prompts, product rules, and mutation logic in one place. That makes provider changes expensive, failure behavior unclear, and proprietary application logic easy to leak.
CrewAgent keeps those responsibilities separate:
| Public library | Private application or deployment code | |---|---| | Versioned request and response contract | Product context construction and data minimization | | Authenticated HTTP client and server | CrewAgent prompts and agent definitions | | Runtime orchestration and response validation | Company policies and deterministic thresholds | | Ollama provider adapter | Route, schedule, notification, and other actions | | Reference grounding and safe public errors | Audit persistence and dispatcher approval workflow |
The public OSS project name is CrewAgent. The published npm package name is @crewrouter-agent/node. The available publication scope does not change the library's deployment-neutral design or limit it to one downstream consumer. The implementation is intentionally general enough for other structured advisory workloads.
Architecture
Private application
-> createAgentClient
-> HTTPS + bearer authentication
-> createAgentServer
-> createAgentRuntime
-> private agent definition (prompt, schema, allowed refs)
-> createOllamaProvider
-> Ollama + chosen model
Validated advice returns to the private application
-> product-specific validation
-> human review or deterministic policy gate
-> existing application services perform approved actionsThe same service can run on owned hardware, a VPS, a private cloud host, or a developer machine. Location is deployment configuration, not part of the contract.
Downstream deployment support
The first CrewRouter downstream deployment composes this public library in private service and operator repositories. That deployment currently uses an authenticated private ingress, an independently supervised service, a local Ollama runtime, and a separate operator control plane for readiness checks.
Those choices are deployment support, not public package requirements. This repository should continue to document the reusable contract, runtime, client, server, provider, and validation behavior only. Product-specific context builders, prompts, Cloudflare configuration, host manifests, rollout gates, and operator runbooks belong in the private CrewRouter and CrewAgent Operator repositories.
Install From npm
npm install @crewrouter-agent/nodeThe stable support target is the Node.js 22 and Node.js 24 LTS lines, expressed
as ^22.0.0 || ^24.0.0; the package has no runtime dependencies. Node.js 20 is
excluded because its upstream support has ended. CI runs the complete tests and
package-content check on both supported majors. A future LTS major may be added
in a backward-compatible release after the same checks pass; removing a still-
supported major requires a new CrewAgent major release.
The first stable package release is 1.0.0. The earlier unpublished 0.2.1
and 0.3.0 archives remain exact private-integration checkpoints and will not
receive separate npm publications. See
1.0 release readiness for the release evidence.
For source-level development against this repository, use this workspace directly instead of installing the published package into itself.
Use From npm
Import from the root package for the common client, server, runtime, provider, and contract helpers:
import {
AGENT_CONTRACT_VERSION,
createAgentClient,
} from "@crewrouter-agent/node";Subpath imports are also supported when you want a narrower entry point:
import { createAgentClient } from "@crewrouter-agent/node/client";
import { AGENT_CONTRACT_VERSION } from "@crewrouter-agent/node/contracts";Public API
The root package exports the client, server, bearer authentication helper, runtime, Ollama provider, advice-contract constants and validators, metadata schema constants and limits, schema helper, and stable error classes. Focused imports are available through these public subpaths:
@crewrouter-agent/node/client
@crewrouter-agent/node/contracts
@crewrouter-agent/node/errors
@crewrouter-agent/node/providers/ollama
@crewrouter-agent/node/runtime
@crewrouter-agent/node/serverThe package API and the serialized contract have separate compatibility concerns. contractVersion changes only when the transport envelope changes.
Run the example service
The included example registers a deliberately generic agent; it does not contain production prompts or product logic.
APPLICATION_VERSION='1.0.0' \
AGENT_TOKEN='replace-with-a-long-random-secret' \
OLLAMA_MODEL='gemma3:4b' \
node examples/basic-server.jsOptional environment variables:
HOST=127.0.0.1
PORT=3210
OLLAMA_BASE_URL=http://127.0.0.1:11434
AGENT_TIMEOUT_MS=30000Put TLS and network access controls in front of the service for any non-development deployment. Do not expose Ollama directly.
createOllamaProvider retains Ollama's default model keep-alive behavior unless
the composition sets the literal boolean unloadAfterResponse: true. That
option sends keep_alive: 0 with each completed chat request so Ollama unloads
the model after responding. It trades warm-request latency for bounded host
memory and does not change the serialized CrewAgent advice contract.
Call the service
import {
AGENT_CONTRACT_VERSION,
createAgentClient,
} from "@crewrouter-agent/node";
const client = createAgentClient({
baseUrl: "https://agent.example.com",
maxResponseBytes: 1_000_000,
token: process.env.AGENT_TOKEN,
timeoutMs: 30_000,
});
const result = await client.advise({
contractVersion: AGENT_CONTRACT_VERSION,
requestId: crypto.randomUUID(),
agentId: "example-risk-review",
contextVersion: "example.workday.v1",
context: {
refs: ["work_1", "signal_1"],
work: [{ ref: "work_1", window: "09:00-11:00" }],
signals: [{ ref: "signal_1", summary: "Heavy rain possible" }],
},
}, {
allowedRefs: ["work_1", "signal_1"],
});Passing allowedRefs to the client repeats grounding validation at the caller boundary. The service also validates references before returning a response.
Contract
Requests use a library contract version and a separate product context version:
{
"contractVersion": "2",
"requestId": "req_123",
"agentId": "example-risk-review",
"contextVersion": "example.workday.v1",
"context": {}
}Responses are structured, correlated, and grounded:
{
"contractVersion": "2",
"requestId": "req_123",
"assessment": "watch",
"summary": "Conditions may affect the current plan.",
"recommendations": [
{
"kind": "review",
"affectedRefs": ["work_1"],
"rationale": "The supplied signal overlaps the work window.",
"evidenceRefs": ["signal_1"],
"confidence": 0.8,
"payload": {}
}
]
}payload is intentionally opaque to the base contract. An agent that returns
one must validate it with validateResponse; the library never executes it.
Agent definition
An agent definition supplies five pieces:
agentId: stable public selector for the workload.contextVersions: a nonempty, sorted, duplicate-free list of the private context versions this active definition accepts.buildPrompt: creates model messages, a JSON schema, and the completeallowedRefsset.parseResponse: optional parser when a provider does not return plain JSON content.validateResponse: a second deterministic validation pass for agent-specific recommendation-kind, payload, and other semantic rules.
Use createAgentResponseSchema({ requestId, recommendationKinds }) to keep generated output aligned with the base contract.
The generated schema intentionally leaves string-length limits to the runtime's mandatory deterministic response validation because Ollama's structured-output grammar rejects those otherwise valid JSON Schema keywords. Collection and numeric constraints remain in the generation schema; every public contract limit is still enforced before a response can be returned.
Production agent definitions belong in private deployment composition, not in this public repository. Any definition that introduces a product-specific payload must reject invalid payloads deterministically before advice can leave the runtime; model schema generation alone is not an authorization or validation boundary.
Every agent definition must implement validateResponse. JSON Schema permits
composition through mechanisms such as $ref and allOf, so the runtime does
not attempt to infer whether an arbitrary generation schema contains private
semantic constraints. The hook receives a base-contract-valid, correlated,
grounded, deeply frozen response and returns { ok: true } to accept it; every
other result or attempted mutation rejects the response without exposing
validator details. A deliberately generic agent may return { ok: true }
after explicitly deciding that base validation is its complete deterministic
rule set.
The runtime validates and snapshots contextVersions when an agent is
registered. A request with another context version is rejected before prompt
construction or provider work. Identifiers are bounded lowercase ASCII values;
one agent may declare at most 16 context versions. The frozen
getAgentMetadata() runtime accessor returns only sorted agent and context
identity, never prompts, schemas, model configuration, or request data.
The example service demonstrates both halves of the boundary: its generated
schema guides the model toward the review kind and a bounded priority payload,
while validateResponse independently enforces those exact constraints after
generation.
An integrating product may be the first downstream consumer of CrewAgent without becoming the package's naming or governance identity.
Service metadata
Every server composition supplies its own canonical numeric semantic version:
const server = createAgentServer({
applicationVersion: "1.4.0",
auth,
maxConcurrentAdvice: 1,
runtime,
});applicationVersion identifies the deployed application composition, not the
version of this npm package. A production composition should derive it from its
immutable package or release manifest instead of duplicating an editable
environment label.
Authenticated GET /v1/metadata returns one exact, no-store response:
{
"schemaVersion": 1,
"applicationVersion": "1.4.0",
"contractVersion": "2",
"agents": [
{
"agentId": "example-risk-review",
"contextVersions": ["example.workday.v1"]
}
]
}The server derives contractVersion from CrewAgent itself and takes the sorted
agent/context list from the active runtime registry. It rejects invalid or
oversized identity configuration instead of truncating it, and a successful
body cannot exceed 16 KiB. The endpoint uses the same bearer authentication as
advice; it is unauthenticated only when the entire server explicitly enables
isolated development mode. /healthz and /readyz remain unauthenticated and
minimal. Metadata never includes timestamps, host or process details, paths,
model/provider configuration, prompts, schemas, request context, or secrets.
The authenticated client performs the same exact validation without invoking an agent or loading a model:
const metadata = await client.metadata({ signal });Client requests refuse redirects so authentication headers cannot follow a cross-origin redirect. Metadata uses the smaller of the configured client response limit and its fixed 16 KiB protocol ceiling.
Telemetry
The runtime accepts an optional non-blocking telemetry sink:
const runtime = createAgentRuntime({
agents,
provider,
telemetry: {
emit(event) {
telemetryBuffer.push(event);
},
},
});Each validated request emits one frozen agent.request.completed event. Its allowlisted fields are requestId, agentId, contract and context versions, duration, outcome, optional stable error code, provider kind, and model identifier. Events never contain context, prompts, recommendations, model content, provider bodies, or error messages. Telemetry failures do not affect advice. Error codes come only from CrewAgent's fixed runtime mapping; arbitrary properties on thrown values are ignored.
The HTTP server separately accepts httpTelemetry: { emit(event) {} }. It
emits frozen accepted/completed lifecycle events for metadata and advice using
only route class, outcome, status, duration, optional stable error code, and
validated agent/context identity. HTTP telemetry deliberately excludes request
IDs and all request or response content. Sink failures never alter HTTP
behavior.
maxConcurrentAdvice defaults to unbounded for compatibility. A private
composition may set a positive integer; excess authenticated advice requests
fail immediately with 503 service_busy and are not queued or retried.
Test against Ollama
Normal tests replay synthetic recorded completions and do not require a model host. Run the opt-in live test with an installed Ollama model:
OLLAMA_INTEGRATION_MODEL='gemma3:4b' npm run test:ollamaSet OLLAMA_INTEGRATION_BASE_URL when Ollama is not available at http://127.0.0.1:11434, and use OLLAMA_INTEGRATION_TIMEOUT_MS to change the default 60-second budget. The integration prompt and context are synthetic and contain no product policy or customer data.
Security posture
- Client and server fail closed when no bearer token is configured.
Unauthenticated mode must be the literal boolean
trueand is only for isolated development; authentication callbacks grant access only with literaltrue. - Health and readiness endpoints contain no customer or model data.
- Service metadata requires the advice bearer outside explicit development mode and exposes only bounded application, public-contract, agent, and accepted-context identity.
- Request bodies are size-limited and response fields are bounded.
- Client and provider response bodies are byte-limited before JSON parsing, and their timeouts cover body consumption.
- Model output cannot cite references outside the set supplied by the agent.
- Only a fully contract-valid request ID may enter server correlation logs or error responses, and response IDs must round-trip exactly.
- Provider and model-validation failures are reduced to stable, non-sensitive logs and public errors.
- Invalid request envelopes never reach a custom runtime. Unknown thrown values cannot choose a public status, code, exposure, message, or telemetry code.
- Custom HTTP loggers are observational; synchronous throws and asynchronous rejections cannot replace readiness or advice responses.
- API paths are not treated as secrets. TLS, authentication, token rotation, network isolation, and data minimization are the security controls.
- The model host should be reachable only by the agent service. Ollama should not be internet-facing.
Bearer authentication is the first transport mechanism, not the final word on
edge security. ADR-018 accepts TLS, Cloudflare Access, and an independent bearer
token for the first private topology; signed requests are optional future
defense in depth, not a 1.0.0 requirement. Other deployments may add IP
allowlists, rate limits, mTLS, signed requests, or equivalent private-network
controls without changing the agent contract.
Failure behavior
The agent is expected to be additive. A timeout, unavailable model, malformed response, invented reference, or failed readiness check must cause the caller to skip advice and continue its deterministic workflow.
This library does not implement retries or automatic actions. Retry budgets, idempotency, persistence, rollout modes, and action approval belong to the integrating application because they depend on business semantics.
Project memory
- Architecture
- 1.x compatibility contract
- Decisions
- Implementation status
- 1.0 release readiness
- Security model
- Security reporting policy
Future work should update these documents in the same change as any architectural or contract decision.
Contributing
See CONTRIBUTING.md. Please report security issues through the private process in SECURITY.md, not through a public issue.
Release notes are maintained in CHANGELOG.md.
License
Copyright © 2026 Casey Stalnaker.
